From 28cb4db17326dbbe3ee3c43a74a0cab613b4c8a1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 10 May 2014 23:07:43 -0700 Subject: [PATCH 001/791] Add initial simply menu --- src/js/simply.pebble.js | 14 ++++++- src/simply_menu.c | 84 +++++++++++++++++++++++++++++++++++++++++ src/simply_menu.h | 17 +++++++++ src/simply_msg.c | 17 +++++++++ src/simply_ui.c | 8 ++-- src/simplyjs.c | 2 + src/simplyjs.h | 1 + 7 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 src/simply_menu.c create mode 100644 src/simply_menu.h diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index 923b7cb7..9588a6f6 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -84,6 +84,10 @@ var commands = [{ }, { name: 'down', }], +}, { + name: 'showUi' +}, { + name: 'showMenu' }]; var commandMap = {}; @@ -271,6 +275,14 @@ SimplyPebble.sendPacket = function(packet) { send(); }; +SimplyPebble.showUi = function() { + SimplyPebble.sendPacket(makePacket(commandMap.showUi)); +}; + +SimplyPebble.showMenu = function() { + SimplyPebble.sendPacket(makePacket(commandMap.showMenu)); +}; + SimplyPebble.buttonConfig = function(buttonConf) { var command = commandMap.configButtons; var packet = makePacket(command, buttonConf); @@ -360,7 +372,7 @@ SimplyPebble.accelPeek = function(callback) { SimplyPebble.sendPacket(packet); }; -readInt = function(packet, width, pos, signed) { +var readInt = function(packet, width, pos, signed) { var value = 0; pos = pos || 0; for (var i = 0; i < width; ++i) { diff --git a/src/simply_menu.c b/src/simply_menu.c new file mode 100644 index 00000000..ea759d3b --- /dev/null +++ b/src/simply_menu.c @@ -0,0 +1,84 @@ +#include "simply_menu.h" + +SimplyMenu *s_menu = NULL; + +static uint16_t menu_get_num_sections_callback(MenuLayer *menu_layer, void *data) { + return 1; +} + +static uint16_t menu_get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *data) { + return 1; +} + +static int16_t menu_get_header_height_callback(MenuLayer *menu_layer, uint16_t section_index, void *data) { + return MENU_CELL_BASIC_HEADER_HEIGHT; +} + +static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer, uint16_t section_index, void *data) { + +} + +static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index, void *data) { + +} + +static void menu_select_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { + +} + +static void window_load(Window *window) { + SimplyMenu *self = window_get_user_data(window); + + Layer *window_layer = window_get_root_layer(window); + GRect frame = layer_get_frame(window_layer); + frame.origin = GPointZero; + + MenuLayer *menu_layer = self->menu_layer = menu_layer_create(frame); + Layer *menu_base_layer = menu_layer_get_layer(menu_layer); + layer_add_child(window_layer, menu_base_layer); + + menu_layer_set_callbacks(menu_layer, NULL, (MenuLayerCallbacks){ + .get_num_sections = menu_get_num_sections_callback, + .get_num_rows = menu_get_num_rows_callback, + .get_header_height = menu_get_header_height_callback, + .draw_header = menu_draw_header_callback, + .draw_row = menu_draw_row_callback, + .select_click = menu_select_callback, + }); +} + +static void window_unload(Window *window) { + SimplyMenu *self = window_get_user_data(window); + + menu_layer_destroy(self->menu_layer); + window_destroy(window); +} + +void simply_menu_show(SimplyMenu *self) { + if (!window_stack_contains_window(self->window)) { + bool animated = true; + window_stack_push(self->window, animated); + } +} + +SimplyMenu *simply_menu_create(void) { + if (s_menu) { + return s_menu; + } + + SimplyMenu *self = malloc(sizeof(*self)); + *self = (SimplyMenu) { .window = NULL }; + s_menu = self; + + Window *window = self->window = window_create(); + window_set_user_data(window, self); + window_set_background_color(window, GColorWhite); + window_set_window_handlers(window, (WindowHandlers) { + .unload = window_unload, + }); + + window_load(self->window); + + return self; +} + diff --git a/src/simply_menu.h b/src/simply_menu.h new file mode 100644 index 00000000..3378141b --- /dev/null +++ b/src/simply_menu.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +typedef struct SimplyMenu SimplyMenu; + +struct SimplyMenu { + Window *window; + MenuLayer *menu_layer; +}; + +SimplyMenu *simply_menu_create(void); + +void simply_menu_destroy(SimplyMenu *self); + +void simply_menu_show(SimplyMenu *self); + diff --git a/src/simply_msg.c b/src/simply_msg.c index 2bb01d79..7027b611 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -1,6 +1,7 @@ #include "simply_msg.h" #include "simply_accel.h" +#include "simply_menu.h" #include "simply_ui.h" #include "simplyjs.h" @@ -22,6 +23,8 @@ enum SimplyACmd { SimplyACmd_getAccelData, SimplyACmd_configAccelData, SimplyACmd_configButtons, + SimplyACmd_showUi, + SimplyACmd_showMenu, }; typedef enum VibeType VibeType; @@ -126,6 +129,14 @@ static void handle_set_accel_config(DictionaryIterator *iter, Simply *simply) { } } +static void handle_show_ui(DictionaryIterator *iter, Simply *simply) { + simply_ui_show(simply->ui); +} + +static void handle_show_menu(DictionaryIterator *iter, Simply *simply) { + simply_menu_show(simply->menu); +} + static void received_callback(DictionaryIterator *iter, void *context) { Tuple *tuple = dict_find(iter, 0); if (!tuple) { @@ -157,6 +168,12 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_configButtons: handle_config_buttons(iter, context); break; + case SimplyACmd_showUi: + handle_show_ui(iter, context); + break; + case SimplyACmd_showMenu: + handle_show_menu(iter, context); + break; } } diff --git a/src/simply_ui.c b/src/simply_ui.c index 84c386bb..43862691 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -238,14 +238,14 @@ static void window_load(Window *window) { SimplyUi *self = window_get_user_data(window); Layer *window_layer = window_get_root_layer(window); - GRect bounds = layer_get_bounds(window_layer); - bounds.origin = GPointZero; + GRect frame = layer_get_frame(window_layer); + frame.origin = GPointZero; - ScrollLayer *scroll_layer = self->scroll_layer = scroll_layer_create(bounds); + ScrollLayer *scroll_layer = self->scroll_layer = scroll_layer_create(frame); Layer *scroll_base_layer = scroll_layer_get_layer(scroll_layer); layer_add_child(window_layer, scroll_base_layer); - Layer *display_layer = self->display_layer = layer_create(bounds); + Layer *display_layer = self->display_layer = layer_create(frame); layer_set_update_proc(display_layer, display_layer_update_callback); scroll_layer_add_child(scroll_layer, display_layer); diff --git a/src/simplyjs.c b/src/simplyjs.c index e6cb27f4..2add5a3a 100644 --- a/src/simplyjs.c +++ b/src/simplyjs.c @@ -2,6 +2,7 @@ #include "simply_accel.h" #include "simply_splash.h" +#include "simply_menu.h" #include "simply_ui.h" #include "simply_msg.h" @@ -11,6 +12,7 @@ static Simply *init(void) { Simply *simply = malloc(sizeof(*simply)); simply->accel = simply_accel_create(); simply->splash = simply_splash_create(simply); + simply->menu = simply_menu_create(); simply->ui = simply_ui_create(); bool animated = true; diff --git a/src/simplyjs.h b/src/simplyjs.h index 0e405821..e4c0d273 100644 --- a/src/simplyjs.h +++ b/src/simplyjs.h @@ -7,6 +7,7 @@ typedef struct Simply Simply; struct Simply { struct SimplyAccel *accel; struct SimplySplash *splash; + struct SimplyMenu *menu; struct SimplyUi *ui; }; From 370d9a10413930be23cf981db4e1526abb2c4535 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 10 May 2014 23:45:51 -0700 Subject: [PATCH 002/791] Add simply menu section and menu item --- src/simply_menu.c | 35 ++++++++++++++++++++++++++++++++--- src/simply_menu.h | 22 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index ea759d3b..c44479c5 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -2,12 +2,33 @@ SimplyMenu *s_menu = NULL; +static SimplyMenuSection *get_menu_section(SimplyMenu *self, int index) { + for (SimplyMenuSection *section = self->sections; section; section = section->next) { + if (section->index == index) { + return section; + } + } + return NULL; +} + +static SimplyMenuItem *get_menu_item(SimplyMenu *self, int section, int index) { + for (SimplyMenuItem *item = self->items; item; item = item->next) { + if (item->section == section && item->index == index) { + return item; + } + } + return NULL; +} + static uint16_t menu_get_num_sections_callback(MenuLayer *menu_layer, void *data) { - return 1; + SimplyMenu *self = data; + return self->num_sections; } static uint16_t menu_get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *data) { - return 1; + SimplyMenu *self = data; + SimplyMenuSection *section = get_menu_section(self, section_index); + return section ? section->num_items : 1; } static int16_t menu_get_header_height_callback(MenuLayer *menu_layer, uint16_t section_index, void *data) { @@ -15,11 +36,19 @@ static int16_t menu_get_header_height_callback(MenuLayer *menu_layer, uint16_t s } static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer, uint16_t section_index, void *data) { + SimplyMenu *self = data; + SimplyMenuSection *section = get_menu_section(self, section_index); + if (!section) { return; } + menu_cell_basic_header_draw(ctx, cell_layer, section->title); } static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index, void *data) { + SimplyMenu *self = data; + SimplyMenuItem *item = get_menu_item(self, cell_index->section, cell_index->row); + if (!item) { return; } + menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, NULL); } static void menu_select_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { @@ -37,7 +66,7 @@ static void window_load(Window *window) { Layer *menu_base_layer = menu_layer_get_layer(menu_layer); layer_add_child(window_layer, menu_base_layer); - menu_layer_set_callbacks(menu_layer, NULL, (MenuLayerCallbacks){ + menu_layer_set_callbacks(menu_layer, self, (MenuLayerCallbacks){ .get_num_sections = menu_get_num_sections_callback, .get_num_rows = menu_get_num_rows_callback, .get_header_height = menu_get_header_height_callback, diff --git a/src/simply_menu.h b/src/simply_menu.h index 3378141b..31ce680f 100644 --- a/src/simply_menu.h +++ b/src/simply_menu.h @@ -4,9 +4,31 @@ typedef struct SimplyMenu SimplyMenu; +typedef struct SimplyMenuSection SimplyMenuSection; + +typedef struct SimplyMenuItem SimplyMenuItem; + struct SimplyMenu { Window *window; MenuLayer *menu_layer; + SimplyMenuSection *sections; + SimplyMenuItem *items; + int num_sections; +}; + +struct SimplyMenuSection { + SimplyMenuSection *next; + char *title; + int16_t index; + int16_t num_items; +}; + +struct SimplyMenuItem { + SimplyMenuItem *next; + char *title; + char *subtitle; + int16_t section; + int16_t index; }; SimplyMenu *simply_menu_create(void); From 9e6b8459e2bd62e04dc14d3d565e7544d68f3a39 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 11 May 2014 05:23:57 -0700 Subject: [PATCH 003/791] Add util string with strdup2 --- src/util/string.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/util/string.h diff --git a/src/util/string.h b/src/util/string.h new file mode 100644 index 00000000..df40fc89 --- /dev/null +++ b/src/util/string.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +static inline char *strdup2(const char *str) { + if (!str) { + return NULL; + } + + char *buffer = malloc(strlen(str) + 1); + strcpy(buffer, str); + return buffer; +} From dcdddae3c399b951348ee99a70c9b412fae3a30a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 11 May 2014 05:25:35 -0700 Subject: [PATCH 004/791] Change simply ui set text to use strdup2 --- src/simply_ui.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/simply_ui.c b/src/simply_ui.c index 43862691..d21d9e41 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -4,6 +4,8 @@ #include "simplyjs.h" +#include "util/string.h" + #include struct SimplyStyle { @@ -90,12 +92,7 @@ static void set_text(char **str_field, const char *str) { return; } - size_t size = strlen(str) + 1; - char *buffer = malloc(size); - strncpy(buffer, str, size); - buffer[size - 1] = '\0'; - - *str_field = buffer; + *str_field = strdup2(str); } void simply_ui_set_text(SimplyUi *self, char **str_field, const char *str) { From b53d4273ac2c1e22190d7f7b98676ac8515a70cc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 11 May 2014 05:27:23 -0700 Subject: [PATCH 005/791] Add util list1 for singly linked lists --- src/util/list1.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/util/list1.h diff --git a/src/util/list1.h b/src/util/list1.h new file mode 100644 index 00000000..ff29acfd --- /dev/null +++ b/src/util/list1.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +typedef struct List1Node List1Node; + +struct List1Node { + List1Node *next; +}; + +typedef bool (*List1FilterCallback)(List1Node *node, void *data); + +static inline List1Node *list1_find_prev(List1Node *node, + List1FilterCallback callback, void *data, List1Node **prev_out) { + for (List1Node *prev = NULL; node; node = node->next) { + if (callback(node, data)) { + if (prev_out) { + *prev_out = prev; + } + return node; + } + prev = node; + } + return NULL; +} + +static inline List1Node *list1_find(List1Node *node, List1FilterCallback callback, void *data) { + return list1_find_prev(node, callback, data, NULL); +} + +static inline List1Node *list1_remove_one(List1Node **head, List1FilterCallback callback, void *data) { + List1Node *prev = NULL; + List1Node *node = *head; + node = list1_find_prev(node, callback, data, &prev); + if (node) { + if (head && *head == node) { + *head = node->next; + } + if (prev) { + prev->next = node->next; + } + } + return node; +} From ed852b350bc2aba7d9bb638f57762107bc8466d4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 11 May 2014 05:27:45 -0700 Subject: [PATCH 006/791] Add simply menu section and item display functionality --- src/js/simply.js | 25 +++++++ src/js/simply.pebble.js | 71 +++++++++++++++++-- src/simply_menu.c | 148 ++++++++++++++++++++++++++++++++++++---- src/simply_menu.h | 49 ++++++++++--- src/simply_msg.c | 85 +++++++++++++++++++++++ src/simply_msg.h | 3 + 6 files changed, 350 insertions(+), 31 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 95b38d3f..26ae2047 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -20,6 +20,8 @@ var eventTypes = [ 'longClick', 'accelTap', 'accelData', + 'menuSection', + 'menuItem', ]; simply.state = {}; @@ -630,6 +632,14 @@ simply.accelPeek = function(callback) { return simply.impl.accelPeek.apply(this, arguments); }; +simply.menuSection = function(sectionDef) { + return simply.impl.menuSection.apply(this, arguments); +}; + +simply.menuItem = function(itemDef) { + return simply.impl.menuItem.apply(this, arguments); +}; + /** * Simply.js event. See all the possible event types. Subscribe to events using {@link simply.on}. * @typedef simply.event @@ -698,6 +708,21 @@ simply.emitAccelData = function(accels, callback) { simply.emit('accelData', e); }; +simply.emitMenuSection = function(section) { + var e = { + section: section, + }; + simply.emit('menuSection', e); +}; + +simply.emitMenuItem = function(section, row) { + var e = { + section: section, + row: row, + }; + simply.emit('menuItem', e); +}; + return simply; })(); diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index 9588a6f6..186d1e4f 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -85,9 +85,41 @@ var commands = [{ name: 'down', }], }, { - name: 'showUi' + name: 'showUi', }, { - name: 'showMenu' + name: 'showMenu', +}, { + name: 'setMenuSection', + params: [{ + name: 'section', + }, { + name: 'count', + }, { + name: 'title', + }], +}, { + name: 'getMenuSection', + params: [{ + name: 'section', + }], +}, { + name: 'setMenuItem', + params: [{ + name: 'section', + }, { + name: 'row', + }, { + name: 'title', + }, { + name: 'subtitle', + }], +}, { + name: 'getMenuItem', + params: [{ + name: 'section', + }, { + name: 'row', + }], }]; var commandMap = {}; @@ -357,11 +389,7 @@ SimplyPebble.style = function(type) { SimplyPebble.accelConfig = function(configDef) { var command = commandMap.configAccelData; - var packetDef = {}; - for (var k in configDef) { - packetDef[k] = configDef[k]; - } - var packet = makePacket(command, packetDef); + var packet = makePacket(command, configDef); SimplyPebble.sendPacket(packet); }; @@ -372,6 +400,29 @@ SimplyPebble.accelPeek = function(callback) { SimplyPebble.sendPacket(packet); }; +SimplyPebble.menuSection = function(sectionDef) { + var command = commandMap.setMenuSection; + var packetDef = util2.copy(sectionDef); + if ('title' in packetDef) { + packetDef.title = packetDef.title.toString(); + } + var packet = makePacket(command, packetDef); + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.menuItem = function(itemDef) { + var command = commandMap.setMenuItem; + var packetDef = util2.copy(itemDef); + if ('title' in packetDef) { + packetDef.title = packetDef.title.toString(); + } + if ('subtitle' in packetDef) { + packetDef.subtitle = packetDef.subtitle.toString(); + } + var packet = makePacket(command, packetDef); + SimplyPebble.sendPacket(packet); +}; + var readInt = function(packet, width, pos, signed) { var value = 0; pos = pos || 0; @@ -428,6 +479,12 @@ SimplyPebble.onAppMessage = function(e) { } } break; + case 'getMenuSection': + simply.emitMenuSection(payload[1]); + break; + case 'getMenuItem': + simply.emitMenuItem(payload[1], payload[2]); + break; } }; diff --git a/src/simply_menu.c b/src/simply_menu.c index c44479c5..04e405c2 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -1,23 +1,138 @@ #include "simply_menu.h" +#include "simply_msg.h" + SimplyMenu *s_menu = NULL; +static bool section_filter(List1Node *node, void *data) { + SimplyMenuSection *section = (SimplyMenuSection*) node; + uint16_t section_index = (uint16_t)(uintptr_t) data; + return (section->index == section_index); +} + +static bool item_filter(List1Node *node, void *data) { + SimplyMenuItem *item = (SimplyMenuItem*) node; + uint32_t cell_index = (uint32_t)(uintptr_t) data; + uint16_t section_index = cell_index; + uint16_t row = cell_index >> 16; + return (item->section == section_index && item->index == row); +} + +static bool empty_filter(List1Node *node, void *data) { + SimplyMenuCommon *item = (SimplyMenuCommon*) node; + return (item->title == NULL); +} + static SimplyMenuSection *get_menu_section(SimplyMenu *self, int index) { - for (SimplyMenuSection *section = self->sections; section; section = section->next) { - if (section->index == index) { - return section; - } + return (SimplyMenuSection*) list1_find(self->sections, section_filter, (void*)(uintptr_t) index); +} + +static void free_section(SimplyMenuSection *section) { + if (!section) { return; } + if (section->title) { + free(section->title); + section->title = NULL; } - return NULL; + free(section); +} + +static void remove_menu_section(List1Node **head, int index) { + free_section((SimplyMenuSection*) list1_remove_one(head, section_filter, (void*)(uintptr_t) index)); } static SimplyMenuItem *get_menu_item(SimplyMenu *self, int section, int index) { - for (SimplyMenuItem *item = self->items; item; item = item->next) { - if (item->section == section && item->index == index) { - return item; - } + uint32_t cell_index = section | (index << 16); + return (SimplyMenuItem*) list1_find(self->items, item_filter, (void*)(uintptr_t) cell_index); +} + +static void free_item(SimplyMenuItem *item) { + if (!item) { return; } + if (item->title) { + free(item->title); + item->title = NULL; } - return NULL; + if (item->subtitle) { + free(item->subtitle); + item->subtitle = NULL; + } + free(item); +} + +static void remove_menu_item(List1Node **head, int section, int index) { + uint32_t cell_index = section | (index << 16); + free_item((SimplyMenuItem*) list1_remove_one(head, item_filter, (void*)(uintptr_t) cell_index)); +} + +static void schedule_get_timer(SimplyMenu *self); + +static void request_menu_node(void *data) { + SimplyMenu *self = data; + self->get_timer = NULL; + SimplyMenuSection *section = (SimplyMenuSection*) list1_find(self->sections, empty_filter, NULL); + bool found = false; + if (section) { + simply_msg_menu_get_section(section->index); + found = true; + } + SimplyMenuItem *item = (SimplyMenuItem*) list1_find(self->items, empty_filter, NULL); + if (item) { + simply_msg_menu_get_item(item->section, item->index); + found = true; + } + if (found) { + schedule_get_timer(self); + } +} + +static void schedule_get_timer(SimplyMenu *self) { + if (self->get_timer) { return; } + self->get_timer = app_timer_register(10, request_menu_node, self); +} + +static void add_section(SimplyMenu *self, SimplyMenuSection *section) { + remove_menu_section(&self->sections, section->index); + section->node.next = self->sections; + self->sections = §ion->node; +} + +static void add_item(SimplyMenu *self, SimplyMenuItem *item) { + remove_menu_item(&self->items, item->section, item->index); + item->node.next = self->items; + self->items = &item->node; +} + +static void request_menu_section(SimplyMenu *self, uint16_t section_index) { + SimplyMenuSection *section = malloc(sizeof(*section)); + *section = (SimplyMenuSection) { + .index = section_index, + }; + add_section(self, section); + schedule_get_timer(self); +} + +static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t item_index) { + SimplyMenuItem *item = malloc(sizeof(*item)); + *item = (SimplyMenuItem) { + .section = section_index, + .index = item_index, + }; + add_item(self, item); + schedule_get_timer(self); +} + +static void mark_dirty(SimplyMenu *self) { + layer_mark_dirty(window_get_root_layer(self->window)); + request_menu_node(self); +} + +void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section) { + add_section(self, section); + mark_dirty(self); +} + +void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item) { + add_item(self, item); + mark_dirty(self); } static uint16_t menu_get_num_sections_callback(MenuLayer *menu_layer, void *data) { @@ -38,7 +153,10 @@ static int16_t menu_get_header_height_callback(MenuLayer *menu_layer, uint16_t s static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer, uint16_t section_index, void *data) { SimplyMenu *self = data; SimplyMenuSection *section = get_menu_section(self, section_index); - if (!section) { return; } + if (!section) { + request_menu_section(self, section_index); + return; + } menu_cell_basic_header_draw(ctx, cell_layer, section->title); } @@ -46,13 +164,15 @@ static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer, ui static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index, void *data) { SimplyMenu *self = data; SimplyMenuItem *item = get_menu_item(self, cell_index->section, cell_index->row); - if (!item) { return; } + if (!item) { + request_menu_item(self, cell_index->section, cell_index->row); + return; + } menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, NULL); } static void menu_select_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { - } static void window_load(Window *window) { @@ -74,6 +194,8 @@ static void window_load(Window *window) { .draw_row = menu_draw_row_callback, .select_click = menu_select_callback, }); + + menu_layer_set_click_config_onto_window(menu_layer, window); } static void window_unload(Window *window) { diff --git a/src/simply_menu.h b/src/simply_menu.h index 31ce680f..61f6404c 100644 --- a/src/simply_menu.h +++ b/src/simply_menu.h @@ -1,5 +1,7 @@ #pragma once +#include "util/list1.h" + #include typedef struct SimplyMenu SimplyMenu; @@ -8,27 +10,49 @@ typedef struct SimplyMenuSection SimplyMenuSection; typedef struct SimplyMenuItem SimplyMenuItem; +typedef enum SimplyMenuType SimplyMenuType; + +enum SimplyMenuType { + SimplyMenuTypeNone = 0, + SimplyMenuTypeSection, + SimplyMenuTypeItem, +}; + struct SimplyMenu { Window *window; MenuLayer *menu_layer; - SimplyMenuSection *sections; - SimplyMenuItem *items; - int num_sections; + List1Node *sections; + List1Node *items; + AppTimer *get_timer; + uint32_t num_sections; }; +typedef struct SimplyMenuCommon SimplyMenuCommon; + +#define SimplyMenuCommonDef { \ + List1Node node; \ + char *title; \ +} + +struct SimplyMenuCommon SimplyMenuCommonDef; + +#define SimplyMenuCommonMember \ + union { \ + struct SimplyMenuCommon common; \ + struct SimplyMenuCommonDef; \ + } + struct SimplyMenuSection { - SimplyMenuSection *next; - char *title; - int16_t index; - int16_t num_items; + SimplyMenuCommonMember; + uint16_t index; + uint16_t num_items; }; struct SimplyMenuItem { - SimplyMenuItem *next; - char *title; + SimplyMenuCommonMember; char *subtitle; - int16_t section; - int16_t index; + uint16_t section; + uint16_t index; }; SimplyMenu *simply_menu_create(void); @@ -37,3 +61,6 @@ void simply_menu_destroy(SimplyMenu *self); void simply_menu_show(SimplyMenu *self); +void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section); + +void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item); diff --git a/src/simply_msg.c b/src/simply_msg.c index 7027b611..13fe04c4 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -6,6 +6,8 @@ #include "simplyjs.h" +#include "util/string.h" + #include typedef enum SimplyACmd SimplyACmd; @@ -25,6 +27,10 @@ enum SimplyACmd { SimplyACmd_configButtons, SimplyACmd_showUi, SimplyACmd_showMenu, + SimplyACmd_setMenuSection, + SimplyACmd_getMenuSection, + SimplyACmd_setMenuItem, + SimplyACmd_getMenuItem, }; typedef enum VibeType VibeType; @@ -137,6 +143,57 @@ static void handle_show_menu(DictionaryIterator *iter, Simply *simply) { simply_menu_show(simply->menu); } +static void handle_set_menu_section(DictionaryIterator *iter, Simply *simply) { + Tuple *tuple; + uint16_t section_index = 0; + uint16_t num_items = 1; + char *title = "Section"; + if ((tuple = dict_find(iter, 1))) { + section_index = tuple->value->uint16; + } + if ((tuple = dict_find(iter, 2))) { + num_items = tuple->value->uint16; + } + if ((tuple = dict_find(iter, 3))) { + title = tuple->value->cstring; + } + SimplyMenuSection *section = malloc(sizeof(*section)); + *section = (SimplyMenuSection) { + .index = section_index, + .num_items = num_items, + .title = strdup2(title), + }; + simply_menu_add_section(simply->menu, section); +} + +static void handle_set_menu_item(DictionaryIterator *iter, Simply *simply) { + Tuple *tuple; + uint16_t section_index = 0; + uint16_t row = 0; + char *title = NULL; + char *subtitle = NULL; + if ((tuple = dict_find(iter, 1))) { + section_index = tuple->value->uint16; + } + if ((tuple = dict_find(iter, 2))) { + row = tuple->value->uint16; + } + if ((tuple = dict_find(iter, 3))) { + title = tuple->value->cstring; + } + if ((tuple = dict_find(iter, 4))) { + title = tuple->value->cstring; + } + SimplyMenuItem *item = malloc(sizeof(*item)); + *item = (SimplyMenuItem) { + .section = section_index, + .index = row, + .title = strdup2(title), + .subtitle = strdup2(subtitle), + }; + simply_menu_add_item(simply->menu, item); +} + static void received_callback(DictionaryIterator *iter, void *context) { Tuple *tuple = dict_find(iter, 0); if (!tuple) { @@ -174,6 +231,12 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_showMenu: handle_show_menu(iter, context); break; + case SimplyACmd_setMenuSection: + handle_set_menu_section(iter, context); + break; + case SimplyACmd_setMenuItem: + handle_set_menu_item(iter, context); + break; } } @@ -254,3 +317,25 @@ bool simply_msg_accel_data(AccelData *data, uint32_t num_samples, int32_t transa dict_write_data(iter, 3, (uint8_t*) data, sizeof(*data) * num_samples); return (app_message_outbox_send() == APP_MSG_OK); } + +bool simply_msg_menu_get_section(uint16_t index) { + DictionaryIterator *iter = NULL; + if (app_message_outbox_begin(&iter) != APP_MSG_OK) { + return false; + } + dict_write_uint8(iter, 0, SimplyACmd_getMenuSection); + dict_write_uint16(iter, 1, index); + return (app_message_outbox_send() == APP_MSG_OK); +} + +bool simply_msg_menu_get_item(uint16_t section, uint16_t index) { + DictionaryIterator *iter = NULL; + if (app_message_outbox_begin(&iter) != APP_MSG_OK) { + return false; + } + dict_write_uint8(iter, 0, SimplyACmd_getMenuItem); + dict_write_uint16(iter, 1, section); + dict_write_uint16(iter, 2, index); + return (app_message_outbox_send() == APP_MSG_OK); +} + diff --git a/src/simply_msg.h b/src/simply_msg.h index 50a5e53c..b0872197 100644 --- a/src/simply_msg.h +++ b/src/simply_msg.h @@ -18,3 +18,6 @@ bool simply_msg_accel_tap(AccelAxisType axis, int32_t direction); bool simply_msg_accel_data(AccelData *accel, uint32_t num_samples, int32_t transaction_id); +bool simply_msg_menu_get_section(uint16_t index); + +bool simply_msg_menu_get_item(uint16_t section, uint16_t index); From 0b92a938c8c7ab499f0994a6973b2a8a4366a67a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 11 May 2014 05:55:03 -0700 Subject: [PATCH 007/791] Add menu select click events --- src/js/simply.js | 21 ++++++++++++++------- src/js/simply.pebble.js | 18 ++++++++++++++++++ src/simply_menu.c | 10 ++++++++-- src/simply_msg.c | 33 ++++++++++++++++++++++----------- src/simply_msg.h | 4 ++++ 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 26ae2047..978ecb7e 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -22,6 +22,8 @@ var eventTypes = [ 'accelData', 'menuSection', 'menuItem', + 'menuSelectClick', + 'menuSelectLongClick', ]; simply.state = {}; @@ -709,18 +711,23 @@ simply.emitAccelData = function(accels, callback) { }; simply.emitMenuSection = function(section) { - var e = { - section: section, - }; - simply.emit('menuSection', e); + simply.emit('menuSection', { + section: section + }); }; simply.emitMenuItem = function(section, row) { - var e = { + simply.emit('menuItem', { section: section, row: row, - }; - simply.emit('menuItem', e); + }); +}; + +simply.emitMenuSelect = function(type, section, row) { + simply.emit(type, { + section: section, + row: row, + }); }; return simply; diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index 186d1e4f..77afa17b 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -120,6 +120,20 @@ var commands = [{ }, { name: 'row', }], +}, { + name: 'menuSelectClick', + params: [{ + name: 'section', + }, { + name: 'row', + }], +}, { + name: 'menuSelectLongClick', + params: [{ + name: 'section', + }, { + name: 'row', + }], }]; var commandMap = {}; @@ -485,6 +499,10 @@ SimplyPebble.onAppMessage = function(e) { case 'getMenuItem': simply.emitMenuItem(payload[1], payload[2]); break; + case 'menuSelectClick': + case 'menuSelectLongClick': + simply.emitMenuSelect(command.name, payload[1], payload[2]); + break; } }; diff --git a/src/simply_menu.c b/src/simply_menu.c index 04e405c2..55865c51 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -172,7 +172,12 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, NULL); } -static void menu_select_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { +static void menu_select_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { + simply_msg_menu_select_click(cell_index->section, cell_index->row); +} + +static void menu_select_long_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { + simply_msg_menu_select_long_click(cell_index->section, cell_index->row); } static void window_load(Window *window) { @@ -192,7 +197,8 @@ static void window_load(Window *window) { .get_header_height = menu_get_header_height_callback, .draw_header = menu_draw_header_callback, .draw_row = menu_draw_row_callback, - .select_click = menu_select_callback, + .select_click = menu_select_click_callback, + .select_long_click = menu_select_long_click_callback, }); menu_layer_set_click_config_onto_window(menu_layer, window); diff --git a/src/simply_msg.c b/src/simply_msg.c index 13fe04c4..13d89bd7 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -31,6 +31,8 @@ enum SimplyACmd { SimplyACmd_getMenuSection, SimplyACmd_setMenuItem, SimplyACmd_getMenuItem, + SimplyACmd_menuSelectClick, + SimplyACmd_menuSelectLongClick, }; typedef enum VibeType VibeType; @@ -273,24 +275,22 @@ void simply_msg_deinit() { app_message_deregister_callbacks(); } -bool simply_msg_single_click(ButtonId button) { +static bool send_click(SimplyACmd type, ButtonId button) { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { return false; } - dict_write_uint8(iter, 0, SimplyACmd_singleClick); + dict_write_uint8(iter, 0, type); dict_write_uint8(iter, 1, button); return (app_message_outbox_send() == APP_MSG_OK); } +bool simply_msg_single_click(ButtonId button) { + return send_click(SimplyACmd_singleClick, button); +} + bool simply_msg_long_click(ButtonId button) { - DictionaryIterator *iter = NULL; - if (app_message_outbox_begin(&iter) != APP_MSG_OK) { - return false; - } - dict_write_uint8(iter, 0, SimplyACmd_longClick); - dict_write_uint8(iter, 1, button); - return (app_message_outbox_send() == APP_MSG_OK); + return send_click(SimplyACmd_longClick, button); } bool simply_msg_accel_tap(AccelAxisType axis, int32_t direction) { @@ -328,14 +328,25 @@ bool simply_msg_menu_get_section(uint16_t index) { return (app_message_outbox_send() == APP_MSG_OK); } -bool simply_msg_menu_get_item(uint16_t section, uint16_t index) { +static bool send_menu_item(SimplyACmd type, uint16_t section, uint16_t index) { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { return false; } - dict_write_uint8(iter, 0, SimplyACmd_getMenuItem); + dict_write_uint8(iter, 0, type); dict_write_uint16(iter, 1, section); dict_write_uint16(iter, 2, index); return (app_message_outbox_send() == APP_MSG_OK); } +bool simply_msg_menu_get_item(uint16_t section, uint16_t index) { + return send_menu_item(SimplyACmd_getMenuItem, section, index); +} + +bool simply_msg_menu_select_click(uint16_t section, uint16_t index) { + return send_menu_item(SimplyACmd_menuSelectClick, section, index); +} + +bool simply_msg_menu_select_long_click(uint16_t section, uint16_t index) { + return send_menu_item(SimplyACmd_menuSelectLongClick, section, index); +} diff --git a/src/simply_msg.h b/src/simply_msg.h index b0872197..70f75501 100644 --- a/src/simply_msg.h +++ b/src/simply_msg.h @@ -21,3 +21,7 @@ bool simply_msg_accel_data(AccelData *accel, uint32_t num_samples, int32_t trans bool simply_msg_menu_get_section(uint16_t index); bool simply_msg_menu_get_item(uint16_t section, uint16_t index); + +bool simply_msg_menu_select_click(uint16_t section, uint16_t index); + +bool simply_msg_menu_select_long_click(uint16_t section, uint16_t index); From 4b020edeee61e2364e12b7f81f8a14459f27d3c7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 11 May 2014 06:02:16 -0700 Subject: [PATCH 008/791] Add menu request exponential backoff --- src/simply_menu.c | 10 ++++++++-- src/simply_menu.h | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index 55865c51..c11c5ae8 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -2,6 +2,8 @@ #include "simply_msg.h" +#define REQUEST_DELAY_MS 10 + SimplyMenu *s_menu = NULL; static bool section_filter(List1Node *node, void *data) { @@ -86,7 +88,8 @@ static void request_menu_node(void *data) { static void schedule_get_timer(SimplyMenu *self) { if (self->get_timer) { return; } - self->get_timer = app_timer_register(10, request_menu_node, self); + self->get_timer = app_timer_register(self->request_delay_ms, request_menu_node, self); + self->request_delay_ms *= 2; } static void add_section(SimplyMenu *self, SimplyMenuSection *section) { @@ -123,6 +126,7 @@ static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t static void mark_dirty(SimplyMenu *self) { layer_mark_dirty(window_get_root_layer(self->window)); request_menu_node(self); + self->request_delay_ms = REQUEST_DELAY_MS; } void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section) { @@ -224,7 +228,9 @@ SimplyMenu *simply_menu_create(void) { } SimplyMenu *self = malloc(sizeof(*self)); - *self = (SimplyMenu) { .window = NULL }; + *self = (SimplyMenu) { + .request_delay_ms = REQUEST_DELAY_MS, + }; s_menu = self; Window *window = self->window = window_create(); diff --git a/src/simply_menu.h b/src/simply_menu.h index 61f6404c..f1297e98 100644 --- a/src/simply_menu.h +++ b/src/simply_menu.h @@ -24,6 +24,7 @@ struct SimplyMenu { List1Node *sections; List1Node *items; AppTimer *get_timer; + uint32_t request_delay_ms; uint32_t num_sections; }; From 8dd7370446fb041a5f8c7091f783d14356791923 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 11 May 2014 06:05:05 -0700 Subject: [PATCH 009/791] Change simply menu window to not destroy on unload --- src/simply_menu.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index c11c5ae8..a3132d47 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -212,7 +212,6 @@ static void window_unload(Window *window) { SimplyMenu *self = window_get_user_data(window); menu_layer_destroy(self->menu_layer); - window_destroy(window); } void simply_menu_show(SimplyMenu *self) { @@ -237,11 +236,10 @@ SimplyMenu *simply_menu_create(void) { window_set_user_data(window, self); window_set_background_color(window, GColorWhite); window_set_window_handlers(window, (WindowHandlers) { + .load = window_load, .unload = window_unload, }); - window_load(self->window); - return self; } From 978737358356fc2d9372b52654f5a76a2b0fd40f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 11 May 2014 17:15:04 -0700 Subject: [PATCH 010/791] Add max cache menu nodes enforcement --- src/simply_menu.c | 15 ++++++++++++++- src/util/list1.h | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index a3132d47..1c537308 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -2,6 +2,10 @@ #include "simply_msg.h" +#define MAX_CACHED_SECTIONS 10 + +#define MAX_CACHED_ITEMS 6 + #define REQUEST_DELAY_MS 10 SimplyMenu *s_menu = NULL; @@ -93,12 +97,20 @@ static void schedule_get_timer(SimplyMenu *self) { } static void add_section(SimplyMenu *self, SimplyMenuSection *section) { + if (list1_size(self->sections) >= MAX_CACHED_SECTIONS) { + SimplyMenuSection *old_section = (SimplyMenuSection*) list1_last(self->sections); + remove_menu_section(&self->sections, old_section->index); + } remove_menu_section(&self->sections, section->index); section->node.next = self->sections; self->sections = §ion->node; } static void add_item(SimplyMenu *self, SimplyMenuItem *item) { + if (list1_size(self->items) >= MAX_CACHED_ITEMS) { + SimplyMenuItem *old_item = (SimplyMenuItem*) list1_last(self->items); + remove_menu_item(&self->items, old_item->section, old_item->index); + } remove_menu_item(&self->items, item->section, item->index); item->node.next = self->items; self->items = &item->node; @@ -124,7 +136,7 @@ static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t } static void mark_dirty(SimplyMenu *self) { - layer_mark_dirty(window_get_root_layer(self->window)); + menu_layer_reload_data(self->menu_layer); request_menu_node(self); self->request_delay_ms = REQUEST_DELAY_MS; } @@ -228,6 +240,7 @@ SimplyMenu *simply_menu_create(void) { SimplyMenu *self = malloc(sizeof(*self)); *self = (SimplyMenu) { + .num_sections = 1, .request_delay_ms = REQUEST_DELAY_MS, }; s_menu = self; diff --git a/src/util/list1.h b/src/util/list1.h index ff29acfd..8a3e88e8 100644 --- a/src/util/list1.h +++ b/src/util/list1.h @@ -11,6 +11,23 @@ struct List1Node { typedef bool (*List1FilterCallback)(List1Node *node, void *data); +static inline size_t list1_size(List1Node *node) { + size_t size = 0; + for (; node; node = node->next) { + size++; + } + return size; +} + +static inline List1Node *list1_last(List1Node *node) { + for (; node; node = node->next) { + if (!node->next) { + return node; + } + } + return NULL; +} + static inline List1Node *list1_find_prev(List1Node *node, List1FilterCallback callback, void *data, List1Node **prev_out) { for (List1Node *prev = NULL; node; node = node->next) { From 58f4935365895b5357a35aa3a27d0109a7b4d1f8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 01:42:55 -0700 Subject: [PATCH 011/791] Add util list1 prepend, append, remove --- src/util/list1.h | 54 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/src/util/list1.h b/src/util/list1.h index 8a3e88e8..678d35bd 100644 --- a/src/util/list1.h +++ b/src/util/list1.h @@ -28,6 +28,31 @@ static inline List1Node *list1_last(List1Node *node) { return NULL; } +static inline List1Node *list1_prev(List1Node *head, List1Node *node) { + for (List1Node *walk = head, *prev = NULL; walk; walk = walk->next) { + if (walk == node) { + return prev; + } + prev = walk; + } + return NULL; +} + +static inline List1Node *list1_prepend(List1Node **head, List1Node *node) { + node->next = *head; + *head = node; + return node; +} + +static inline List1Node *list1_append(List1Node **head, List1Node *node) { + if (*head) { + list1_last(*head)->next = node; + } else { + *head = node; + } + return node; +} + static inline List1Node *list1_find_prev(List1Node *node, List1FilterCallback callback, void *data, List1Node **prev_out) { for (List1Node *prev = NULL; node; node = node->next) { @@ -46,17 +71,24 @@ static inline List1Node *list1_find(List1Node *node, List1FilterCallback callbac return list1_find_prev(node, callback, data, NULL); } -static inline List1Node *list1_remove_one(List1Node **head, List1FilterCallback callback, void *data) { - List1Node *prev = NULL; - List1Node *node = *head; - node = list1_find_prev(node, callback, data, &prev); - if (node) { - if (head && *head == node) { - *head = node->next; - } - if (prev) { - prev->next = node->next; - } +static inline List1Node *list1_remove_prev(List1Node **head, List1Node *node, List1Node *prev) { + if (!node) { return NULL; } + if (*head == node) { + *head = node->next; + } + if (prev) { + prev->next = node->next; } + node->next = NULL; return node; } + +static inline List1Node *list1_remove(List1Node **head, List1Node *node) { + return list1_remove_prev(head, node, list1_prev(*head, node)); +} + +static inline List1Node *list1_remove_one(List1Node **head, List1FilterCallback callback, void *data) { + List1Node *prev = NULL; + List1Node *node = list1_find_prev(*head, callback, data, &prev); + return list1_remove_prev(head, node, prev); +} From de77a3cffae125212792bc28eeba9b6ae0464d5d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 01:43:40 -0700 Subject: [PATCH 012/791] Change simply menu sections and items cache to LRU --- src/simply_menu.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index 1c537308..fb911c62 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -102,8 +102,7 @@ static void add_section(SimplyMenu *self, SimplyMenuSection *section) { remove_menu_section(&self->sections, old_section->index); } remove_menu_section(&self->sections, section->index); - section->node.next = self->sections; - self->sections = §ion->node; + list1_prepend(&self->sections, §ion->node); } static void add_item(SimplyMenu *self, SimplyMenuItem *item) { @@ -112,8 +111,7 @@ static void add_item(SimplyMenu *self, SimplyMenuItem *item) { remove_menu_item(&self->items, old_item->section, old_item->index); } remove_menu_item(&self->items, item->section, item->index); - item->node.next = self->items; - self->items = &item->node; + list1_prepend(&self->items, &item->node); } static void request_menu_section(SimplyMenu *self, uint16_t section_index) { @@ -174,6 +172,9 @@ static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer, ui return; } + list1_remove(&self->sections, §ion->node); + list1_prepend(&self->sections, §ion->node); + menu_cell_basic_header_draw(ctx, cell_layer, section->title); } @@ -185,6 +186,9 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI return; } + list1_remove(&self->items, &item->node); + list1_prepend(&self->items, &item->node); + menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, NULL); } From f1cf239650cdc1587424e1a0119056e80238253f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 02:09:59 -0700 Subject: [PATCH 013/791] Add simply menu to show the menu, change menu api to use index params --- src/js/simply.js | 16 ++++++++++------ src/js/simply.pebble.js | 27 +++++++++++++++++++++++---- src/simply_menu.c | 7 +++++++ src/simply_menu.h | 4 +++- src/simply_msg.c | 6 +++++- 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 978ecb7e..78767822 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -634,11 +634,15 @@ simply.accelPeek = function(callback) { return simply.impl.accelPeek.apply(this, arguments); }; -simply.menuSection = function(sectionDef) { +simply.menu = function(menuDef) { + return simply.impl.menu.apply(this, arguments); +}; + +simply.menuSection = function(sectionIndex, sectionDef) { return simply.impl.menuSection.apply(this, arguments); }; -simply.menuItem = function(itemDef) { +simply.menuItem = function(sectionIndex, itemIndex, itemDef) { return simply.impl.menuItem.apply(this, arguments); }; @@ -716,17 +720,17 @@ simply.emitMenuSection = function(section) { }); }; -simply.emitMenuItem = function(section, row) { +simply.emitMenuItem = function(section, item) { simply.emit('menuItem', { section: section, - row: row, + item: item, }); }; -simply.emitMenuSelect = function(type, section, row) { +simply.emitMenuSelect = function(type, section, item) { simply.emit(type, { section: section, - row: row, + item: item, }); }; diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index 77afa17b..5a52ac55 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -88,12 +88,15 @@ var commands = [{ name: 'showUi', }, { name: 'showMenu', + params: [{ + name: 'sections', + }], }, { name: 'setMenuSection', params: [{ name: 'section', }, { - name: 'count', + name: 'items', }, { name: 'title', }], @@ -107,7 +110,7 @@ var commands = [{ params: [{ name: 'section', }, { - name: 'row', + name: 'item', }, { name: 'title', }, { @@ -414,9 +417,23 @@ SimplyPebble.accelPeek = function(callback) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.menuSection = function(sectionDef) { +SimplyPebble.menu = function(menuDef) { + var command = commandMap.showMenu; + var packetDef = util2.copy(menuDef); + if (packetDef.sections instanceof Array) { + packetDef.sections = packetDef.sections.length; + } + var packet = makePacket(command, packetDef); + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.menuSection = function(sectionIndex, sectionDef) { var command = commandMap.setMenuSection; var packetDef = util2.copy(sectionDef); + packetDef.section = sectionIndex; + if (packetDef.items instanceof Array) { + packetDef.items = packetDef.items.length; + } if ('title' in packetDef) { packetDef.title = packetDef.title.toString(); } @@ -424,9 +441,11 @@ SimplyPebble.menuSection = function(sectionDef) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.menuItem = function(itemDef) { +SimplyPebble.menuItem = function(sectionIndex, itemIndex, itemDef) { var command = commandMap.setMenuItem; var packetDef = util2.copy(itemDef); + packetDef.section = sectionIndex; + packetDef.item = itemIndex; if ('title' in packetDef) { packetDef.title = packetDef.title.toString(); } diff --git a/src/simply_menu.c b/src/simply_menu.c index fb911c62..4264e0bf 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -139,6 +139,13 @@ static void mark_dirty(SimplyMenu *self) { self->request_delay_ms = REQUEST_DELAY_MS; } +void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections) { + if (num_sections == 0) { + num_sections = 1; + } + self->num_sections = num_sections; +} + void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section) { add_section(self, section); mark_dirty(self); diff --git a/src/simply_menu.h b/src/simply_menu.h index f1297e98..be612e7e 100644 --- a/src/simply_menu.h +++ b/src/simply_menu.h @@ -25,7 +25,7 @@ struct SimplyMenu { List1Node *items; AppTimer *get_timer; uint32_t request_delay_ms; - uint32_t num_sections; + uint16_t num_sections; }; typedef struct SimplyMenuCommon SimplyMenuCommon; @@ -62,6 +62,8 @@ void simply_menu_destroy(SimplyMenu *self); void simply_menu_show(SimplyMenu *self); +void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections); + void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section); void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item); diff --git a/src/simply_msg.c b/src/simply_msg.c index 13d89bd7..cbde0da7 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -142,6 +142,10 @@ static void handle_show_ui(DictionaryIterator *iter, Simply *simply) { } static void handle_show_menu(DictionaryIterator *iter, Simply *simply) { + Tuple *tuple; + if ((tuple = dict_find(iter, 1))) { + simply_menu_set_num_sections(simply->menu, tuple->value->int32); + } simply_menu_show(simply->menu); } @@ -184,7 +188,7 @@ static void handle_set_menu_item(DictionaryIterator *iter, Simply *simply) { title = tuple->value->cstring; } if ((tuple = dict_find(iter, 4))) { - title = tuple->value->cstring; + subtitle = tuple->value->cstring; } SimplyMenuItem *item = malloc(sizeof(*item)); *item = (SimplyMenuItem) { From 3589235b88cdbd2b25bee28c31796863c1d570ff Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 02:46:24 -0700 Subject: [PATCH 014/791] Rename menu select click to menu select --- src/js/simply.js | 4 ++-- src/js/simply.pebble.js | 14 +++++++------- src/simply_msg.c | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 78767822..c4759e0c 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -22,8 +22,8 @@ var eventTypes = [ 'accelData', 'menuSection', 'menuItem', - 'menuSelectClick', - 'menuSelectLongClick', + 'menuSelect', + 'menuLongSelect', ]; simply.state = {}; diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index 5a52ac55..9ba83753 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -121,21 +121,21 @@ var commands = [{ params: [{ name: 'section', }, { - name: 'row', + name: 'item', }], }, { - name: 'menuSelectClick', + name: 'menuSelect', params: [{ name: 'section', }, { - name: 'row', + name: 'item', }], }, { - name: 'menuSelectLongClick', + name: 'menuLongSelect', params: [{ name: 'section', }, { - name: 'row', + name: 'item', }], }]; @@ -518,8 +518,8 @@ SimplyPebble.onAppMessage = function(e) { case 'getMenuItem': simply.emitMenuItem(payload[1], payload[2]); break; - case 'menuSelectClick': - case 'menuSelectLongClick': + case 'menuSelect': + case 'menuLongSelect': simply.emitMenuSelect(command.name, payload[1], payload[2]); break; } diff --git a/src/simply_msg.c b/src/simply_msg.c index cbde0da7..6c88a09c 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -31,8 +31,8 @@ enum SimplyACmd { SimplyACmd_getMenuSection, SimplyACmd_setMenuItem, SimplyACmd_getMenuItem, - SimplyACmd_menuSelectClick, - SimplyACmd_menuSelectLongClick, + SimplyACmd_menuSelect, + SimplyACmd_menuLongSelect, }; typedef enum VibeType VibeType; @@ -348,9 +348,9 @@ bool simply_msg_menu_get_item(uint16_t section, uint16_t index) { } bool simply_msg_menu_select_click(uint16_t section, uint16_t index) { - return send_menu_item(SimplyACmd_menuSelectClick, section, index); + return send_menu_item(SimplyACmd_menuSelect, section, index); } bool simply_msg_menu_select_long_click(uint16_t section, uint16_t index) { - return send_menu_item(SimplyACmd_menuSelectLongClick, section, index); + return send_menu_item(SimplyACmd_menuLongSelect, section, index); } From 17f1c18502712d169743c35bdb955b6d37abb90e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 03:05:42 -0700 Subject: [PATCH 015/791] Add type and subtype to events --- src/js/simply.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/simply.js b/src/js/simply.js index c4759e0c..6202414b 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -233,6 +233,8 @@ simply.emit = function(type, subtype, e) { e = subtype; subtype = null; } + e.type = type; + e.subtype = subtype; var typeMap = simply.listeners; var subtypeMap = typeMap[type]; if (!subtypeMap) { From 9b0ecdd9cd8e80d5b2feb37cf423470572bab807 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 03:06:03 -0700 Subject: [PATCH 016/791] Add simply stateful menu api --- src/js/simply.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/js/simply.js b/src/js/simply.js index 6202414b..45a596a6 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -75,6 +75,8 @@ simply.reset = function() { } simply.accelInit(); + + simply.menuInit(); }; /** @@ -636,7 +638,58 @@ simply.accelPeek = function(callback) { return simply.impl.accelPeek.apply(this, arguments); }; +simply.menuInit = function() { + simply.on('menuSection', simply.onMenuSection); + simply.on('menuItem', simply.onMenuItem); + simply.on('menuSelect', simply.onMenuSelect); + simply.on('menuLongSelect', simply.onMenuSelect); +}; + +var getMenuSection = function(e) { + var menu = e.menu; + if (!menu) { return; } + if (!(menu.sections instanceof Array)) { return; } + return menu.sections[e.section]; +}; + +var getMenuItem = function(e) { + var section = getMenuSection(e); + if (!section) { return; } + if (!(section.items instanceof Array)) { return; } + return section.items[e.item]; +}; + +simply.onMenuSection = function(e) { + var section = getMenuSection(e); + if (!section) { return; } + simply.menuSection(e.section, section); +}; + +simply.onMenuItem = function(e) { + var item = getMenuItem(e); + if (!item) { return; } + simply.menuItem(e.section, e.item, item); +}; + +simply.onMenuSelect = function(e) { + var item = getMenuItem(e); + if (!item) { return; } + switch (e.type) { + case 'menuSelect': + if (typeof item.select === 'function') { + return item.select(e); + } + break; + case 'menuLongSelect': + if (typeof item.longSelect === 'function') { + return item.longSelect(e); + } + break; + } +}; + simply.menu = function(menuDef) { + simply.state.menu = menuDef; return simply.impl.menu.apply(this, arguments); }; @@ -718,12 +771,14 @@ simply.emitAccelData = function(accels, callback) { simply.emitMenuSection = function(section) { simply.emit('menuSection', { + menu: simply.state.menu, section: section }); }; simply.emitMenuItem = function(section, item) { simply.emit('menuItem', { + menu: simply.state.menu, section: section, item: item, }); @@ -731,6 +786,7 @@ simply.emitMenuItem = function(section, item) { simply.emitMenuSelect = function(type, section, item) { simply.emit(type, { + menu: simply.state.menu, section: section, item: item, }); From e627c864e9dc262e23712d96f127ba67991114ea Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 03:21:47 -0700 Subject: [PATCH 017/791] Change simply ui set text to show the main ui --- src/simply_menu.c | 1 + src/simply_ui.c | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index 4264e0bf..c2109bd7 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -235,6 +235,7 @@ static void window_unload(Window *window) { SimplyMenu *self = window_get_user_data(window); menu_layer_destroy(self->menu_layer); + self->menu_layer = NULL; } void simply_menu_show(SimplyMenu *self) { diff --git a/src/simply_ui.c b/src/simply_ui.c index d21d9e41..2d1dae4d 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -84,10 +84,14 @@ void simply_ui_set_button(SimplyUi *self, ButtonId button, bool enable) { } } +static bool is_string(const char *str) { + return str && str[0]; +} + static void set_text(char **str_field, const char *str) { free(*str_field); - if (!str || !str[0]) { + if (!is_string(str)) { *str_field = NULL; return; } @@ -97,11 +101,10 @@ static void set_text(char **str_field, const char *str) { void simply_ui_set_text(SimplyUi *self, char **str_field, const char *str) { set_text(str_field, str); - layer_mark_dirty(self->display_layer); -} - -static bool is_string(const char *str) { - return str && str[0]; + if (self->display_layer) { + layer_mark_dirty(self->display_layer); + } + simply_ui_show(self); } void display_layer_update_callback(Layer *layer, GContext *ctx) { @@ -253,16 +256,15 @@ static void window_load(Window *window) { scroll_layer_set_click_config_onto_window(scroll_layer, window); simply_ui_set_style(self, 1); - - app_timer_register(10000, (AppTimerCallback) show_welcome_text, self); } static void window_unload(Window *window) { SimplyUi *self = window_get_user_data(window); layer_destroy(self->display_layer); + self->display_layer = NULL; scroll_layer_destroy(self->scroll_layer); - window_destroy(window); + self->scroll_layer = NULL; } void simply_ui_show(SimplyUi *self) { @@ -299,10 +301,11 @@ SimplyUi *simply_ui_create(void) { window_set_background_color(window, GColorBlack); window_set_click_config_provider(window, (ClickConfigProvider) click_config_provider); window_set_window_handlers(window, (WindowHandlers) { + .load = window_load, .unload = window_unload, }); - window_load(self->window); + app_timer_register(10000, (AppTimerCallback) show_welcome_text, self); return self; } From a1f598ed917d095c719c82dc92026eb51e012fb3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 03:38:14 -0700 Subject: [PATCH 018/791] Add simply menu exit event --- src/js/simply.js | 5 +++++ src/js/simply.pebble.js | 8 ++++++++ src/simply_menu.c | 8 ++++++++ src/simply_msg.c | 6 ++++++ src/simply_msg.h | 3 +++ 5 files changed, 30 insertions(+) diff --git a/src/js/simply.js b/src/js/simply.js index 45a596a6..94fdfc7e 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -685,6 +685,11 @@ simply.onMenuSelect = function(e) { return item.longSelect(e); } break; + case 'menuExit': + if (typeof item.exit === 'function') { + return item.exit(e); + } + break; } }; diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index 9ba83753..a346272c 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -137,6 +137,13 @@ var commands = [{ }, { name: 'item', }], +}, { + name: 'menuExit', + params: [{ + name: 'section', + }, { + name: 'item', + }], }]; var commandMap = {}; @@ -520,6 +527,7 @@ SimplyPebble.onAppMessage = function(e) { break; case 'menuSelect': case 'menuLongSelect': + case 'menuExit': simply.emitMenuSelect(command.name, payload[1], payload[2]); break; } diff --git a/src/simply_menu.c b/src/simply_menu.c index c2109bd7..8230891a 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -231,6 +231,13 @@ static void window_load(Window *window) { menu_layer_set_click_config_onto_window(menu_layer, window); } +static void window_disappear(Window *window) { + SimplyMenu *self = window_get_user_data(window); + + MenuIndex cell_index = menu_layer_get_selected_index(self->menu_layer); + simply_msg_menu_exit(cell_index.section, cell_index.row); +} + static void window_unload(Window *window) { SimplyMenu *self = window_get_user_data(window); @@ -262,6 +269,7 @@ SimplyMenu *simply_menu_create(void) { window_set_background_color(window, GColorWhite); window_set_window_handlers(window, (WindowHandlers) { .load = window_load, + .disappear = window_disappear, .unload = window_unload, }); diff --git a/src/simply_msg.c b/src/simply_msg.c index 6c88a09c..4ea51862 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -33,6 +33,7 @@ enum SimplyACmd { SimplyACmd_getMenuItem, SimplyACmd_menuSelect, SimplyACmd_menuLongSelect, + SimplyACmd_menuExit, }; typedef enum VibeType VibeType; @@ -354,3 +355,8 @@ bool simply_msg_menu_select_click(uint16_t section, uint16_t index) { bool simply_msg_menu_select_long_click(uint16_t section, uint16_t index) { return send_menu_item(SimplyACmd_menuLongSelect, section, index); } + +bool simply_msg_menu_exit(uint16_t section, uint16_t index) { + return send_menu_item(SimplyACmd_menuExit, section, index); +} + diff --git a/src/simply_msg.h b/src/simply_msg.h index 70f75501..d95f1d36 100644 --- a/src/simply_msg.h +++ b/src/simply_msg.h @@ -25,3 +25,6 @@ bool simply_msg_menu_get_item(uint16_t section, uint16_t index); bool simply_msg_menu_select_click(uint16_t section, uint16_t index); bool simply_msg_menu_select_long_click(uint16_t section, uint16_t index); + +bool simply_msg_menu_exit(uint16_t section, uint16_t index); + From c071d636b56b15afa284152bcfa7f291c616a346 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 03:41:35 -0700 Subject: [PATCH 019/791] Add simply stateful menu exit callback --- src/js/simply.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 94fdfc7e..da70a7c6 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -672,22 +672,34 @@ simply.onMenuItem = function(e) { }; simply.onMenuSelect = function(e) { + var menu = e.menu; var item = getMenuItem(e); if (!item) { return; } switch (e.type) { case 'menuSelect': if (typeof item.select === 'function') { - return item.select(e); + if (item.select(e) === false) { + return false; + } } break; case 'menuLongSelect': if (typeof item.longSelect === 'function') { - return item.longSelect(e); + if (item.longSelect(e) === false) { + return false; + } } break; case 'menuExit': if (typeof item.exit === 'function') { - return item.exit(e); + if (item.exit(e) === false) { + return false; + } + } + if (typeof menu.exit === 'function') { + if (menu.exit(e) === false) { + return false; + } } break; } From bab2ac1c9f567fbb4cc81e9c14aa54d7607a9d4c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 04:04:45 -0700 Subject: [PATCH 020/791] Add simply menu destroy --- src/simply_accel.c | 2 +- src/simply_menu.c | 47 ++++++++++++++++++++++++++++++++++------------ src/simply_ui.c | 5 ++++- src/simplyjs.c | 2 ++ 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/simply_accel.c b/src/simply_accel.c index 9446135b..01bf43c3 100644 --- a/src/simply_accel.c +++ b/src/simply_accel.c @@ -69,7 +69,7 @@ SimplyAccel *simply_accel_create(void) { } void simply_accel_destroy(SimplyAccel *self) { - if (!s_accel) { + if (!self) { return; } diff --git a/src/simply_menu.c b/src/simply_menu.c index 8230891a..61d65eee 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -33,8 +33,9 @@ static SimplyMenuSection *get_menu_section(SimplyMenu *self, int index) { return (SimplyMenuSection*) list1_find(self->sections, section_filter, (void*)(uintptr_t) index); } -static void free_section(SimplyMenuSection *section) { +static void destroy_section(SimplyMenu *self, SimplyMenuSection *section) { if (!section) { return; } + list1_remove(&self->sections, §ion->node); if (section->title) { free(section->title); section->title = NULL; @@ -42,8 +43,9 @@ static void free_section(SimplyMenuSection *section) { free(section); } -static void remove_menu_section(List1Node **head, int index) { - free_section((SimplyMenuSection*) list1_remove_one(head, section_filter, (void*)(uintptr_t) index)); +static void destroy_section_by_index(SimplyMenu *self, int section) { + destroy_section(self, (SimplyMenuSection*) list1_find( + self->sections, section_filter, (void*)(uintptr_t) section)); } static SimplyMenuItem *get_menu_item(SimplyMenu *self, int section, int index) { @@ -51,8 +53,9 @@ static SimplyMenuItem *get_menu_item(SimplyMenu *self, int section, int index) { return (SimplyMenuItem*) list1_find(self->items, item_filter, (void*)(uintptr_t) cell_index); } -static void free_item(SimplyMenuItem *item) { +static void destroy_item(SimplyMenu *self, SimplyMenuItem *item) { if (!item) { return; } + list1_remove(&self->items, &item->node); if (item->title) { free(item->title); item->title = NULL; @@ -64,9 +67,10 @@ static void free_item(SimplyMenuItem *item) { free(item); } -static void remove_menu_item(List1Node **head, int section, int index) { +static void destroy_item_by_index(SimplyMenu *self, int section, int index) { uint32_t cell_index = section | (index << 16); - free_item((SimplyMenuItem*) list1_remove_one(head, item_filter, (void*)(uintptr_t) cell_index)); + destroy_item(self, (SimplyMenuItem*) list1_find( + self->items, item_filter, (void*)(uintptr_t) cell_index)); } static void schedule_get_timer(SimplyMenu *self); @@ -98,19 +102,17 @@ static void schedule_get_timer(SimplyMenu *self) { static void add_section(SimplyMenu *self, SimplyMenuSection *section) { if (list1_size(self->sections) >= MAX_CACHED_SECTIONS) { - SimplyMenuSection *old_section = (SimplyMenuSection*) list1_last(self->sections); - remove_menu_section(&self->sections, old_section->index); + destroy_section(self, (SimplyMenuSection*) list1_last(self->sections)); } - remove_menu_section(&self->sections, section->index); + destroy_section_by_index(self, section->index); list1_prepend(&self->sections, §ion->node); } static void add_item(SimplyMenu *self, SimplyMenuItem *item) { if (list1_size(self->items) >= MAX_CACHED_ITEMS) { - SimplyMenuItem *old_item = (SimplyMenuItem*) list1_last(self->items); - remove_menu_item(&self->items, old_item->section, old_item->index); + destroy_item(self, (SimplyMenuItem*) list1_last(self->items)); } - remove_menu_item(&self->items, item->section, item->index); + destroy_item_by_index(self, item->section, item->index); list1_prepend(&self->items, &item->node); } @@ -134,6 +136,7 @@ static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t } static void mark_dirty(SimplyMenu *self) { + if (!self->menu_layer) { return; } menu_layer_reload_data(self->menu_layer); request_menu_node(self); self->request_delay_ms = REQUEST_DELAY_MS; @@ -276,3 +279,23 @@ SimplyMenu *simply_menu_create(void) { return self; } +void simply_menu_destroy(SimplyMenu *self) { + if (!self) { + return; + } + + while (self->sections) { + destroy_section(self, (SimplyMenuSection*) self->sections); + } + + while (self->items) { + destroy_item(self, (SimplyMenuItem*) self->items); + } + + window_destroy(self->window); + self->window = NULL; + + free(self); + + s_menu = NULL; +} diff --git a/src/simply_ui.c b/src/simply_ui.c index 2d1dae4d..05d53712 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -311,7 +311,7 @@ SimplyUi *simply_ui_create(void) { } void simply_ui_destroy(SimplyUi *self) { - if (!s_ui) { + if (!self) { return; } @@ -319,6 +319,9 @@ void simply_ui_destroy(SimplyUi *self) { simply_ui_set_text(self, &self->subtitle_text, NULL); simply_ui_set_text(self, &self->body_text, NULL); + window_destroy(self->window); + self->window = NULL; + free(self); s_ui = NULL; diff --git a/src/simplyjs.c b/src/simplyjs.c index 2add5a3a..d04e2a4e 100644 --- a/src/simplyjs.c +++ b/src/simplyjs.c @@ -25,6 +25,7 @@ static Simply *init(void) { static void deinit(Simply *simply) { simply_msg_deinit(); simply_ui_destroy(simply->ui); + simply_menu_destroy(simply->menu); simply_accel_destroy(simply->accel); } @@ -32,4 +33,5 @@ int main(void) { Simply *simply = init(); app_event_loop(); deinit(simply); + free(simply); } From 98d60813e3a2328404b49284f7415c2732af10ec Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 04:20:44 -0700 Subject: [PATCH 021/791] Move is string to util string --- src/simply_ui.c | 4 ---- src/util/string.h | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/simply_ui.c b/src/simply_ui.c index 05d53712..489f0c8e 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -84,10 +84,6 @@ void simply_ui_set_button(SimplyUi *self, ButtonId button, bool enable) { } } -static bool is_string(const char *str) { - return str && str[0]; -} - static void set_text(char **str_field, const char *str) { free(*str_field); diff --git a/src/util/string.h b/src/util/string.h index df40fc89..2b31ada2 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -1,8 +1,13 @@ #pragma once #include +#include #include +static inline bool is_string(const char *str) { + return str && str[0]; +} + static inline char *strdup2(const char *str) { if (!str) { return NULL; From a5de8022f9d6a36a7e93e75707bbcdef4ad324ac Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 04:21:44 -0700 Subject: [PATCH 022/791] Don't show the welcome text if the menu is used --- src/simply_menu.c | 3 +++ src/simply_ui.c | 11 +++++++++-- src/simply_ui.h | 5 ++++- src/simplyjs.c | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index 61d65eee..839ffecc 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -249,6 +249,9 @@ static void window_unload(Window *window) { } void simply_menu_show(SimplyMenu *self) { + if (!self->window) { + return; + } if (!window_stack_contains_window(self->window)) { bool animated = true; window_stack_push(self->window, animated); diff --git a/src/simply_ui.c b/src/simply_ui.c index 489f0c8e..173d8efd 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -1,6 +1,7 @@ #include "simply_ui.h" #include "simply_msg.h" +#include "simply_menu.h" #include "simplyjs.h" @@ -222,6 +223,9 @@ static void show_welcome_text(SimplyUi *self) { if (self->title_text || self->subtitle_text || self->body_text) { return; } + if (self->simply->menu->menu_layer) { + return; + } simply_ui_set_text(self, &self->title_text, "Simply.js"); simply_ui_set_text(self, &self->subtitle_text, "Write apps with JS!"); @@ -264,13 +268,16 @@ static void window_unload(Window *window) { } void simply_ui_show(SimplyUi *self) { + if (!self->window) { + return; + } if (!window_stack_contains_window(self->window)) { bool animated = true; window_stack_push(self->window, animated); } } -SimplyUi *simply_ui_create(void) { +SimplyUi *simply_ui_create(Simply *simply) { if (s_ui) { return s_ui; } @@ -283,7 +290,7 @@ SimplyUi *simply_ui_create(void) { } SimplyUi *self = malloc(sizeof(*self)); - *self = (SimplyUi) { .window = NULL }; + *self = (SimplyUi) { .simply = simply }; s_ui = self; for (int i = 0; i < NUM_BUTTONS; ++i) { diff --git a/src/simply_ui.h b/src/simply_ui.h index e0bada6e..b3d3d6f6 100644 --- a/src/simply_ui.h +++ b/src/simply_ui.h @@ -1,5 +1,7 @@ #pragma once +#include "simplyjs.h" + #include typedef struct SimplyStyle SimplyStyle; @@ -7,6 +9,7 @@ typedef struct SimplyStyle SimplyStyle; typedef struct SimplyUi SimplyUi; struct SimplyUi { + Simply *simply; Window *window; const SimplyStyle *style; char *title_text; @@ -18,7 +21,7 @@ struct SimplyUi { uint32_t button_mask; }; -SimplyUi *simply_ui_create(void); +SimplyUi *simply_ui_create(Simply *simply); void simply_ui_destroy(SimplyUi *self); diff --git a/src/simplyjs.c b/src/simplyjs.c index d04e2a4e..583d6fe9 100644 --- a/src/simplyjs.c +++ b/src/simplyjs.c @@ -13,7 +13,7 @@ static Simply *init(void) { simply->accel = simply_accel_create(); simply->splash = simply_splash_create(simply); simply->menu = simply_menu_create(); - simply->ui = simply_ui_create(); + simply->ui = simply_ui_create(simply); bool animated = true; window_stack_push(simply->splash->window, animated); From 20c6506bba81d7635040064610768c1645cd11e1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 04:22:08 -0700 Subject: [PATCH 023/791] Unload custom fonts on simply ui destroy --- src/simply_ui.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/simply_ui.c b/src/simply_ui.c index 173d8efd..06270cb2 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -318,12 +318,19 @@ void simply_ui_destroy(SimplyUi *self) { return; } + window_destroy(self->window); + self->window = NULL; + simply_ui_set_text(self, &self->title_text, NULL); simply_ui_set_text(self, &self->subtitle_text, NULL); simply_ui_set_text(self, &self->body_text, NULL); - window_destroy(self->window); - self->window = NULL; + for (unsigned int i = 0; i < ARRAY_LENGTH(STYLES); ++i) { + SimplyStyle *style = &STYLES[i]; + if (style->custom_body_font_id) { + fonts_unload_custom_font(style->custom_body_font); + } + } free(self); From 603d1ba5f9bedd1e60bb9e3806d6bbbec6da9c83 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 04:29:10 -0700 Subject: [PATCH 024/791] Change simply ui to only load the current custom body font --- src/simply_ui.c | 27 +++++++++++---------------- src/simply_ui.h | 3 ++- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/simply_ui.c b/src/simply_ui.c index 06270cb2..95b4b947 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -14,7 +14,6 @@ struct SimplyStyle { const char* subtitle_font; const char* body_font; int custom_body_font_id; - GFont custom_body_font; }; static SimplyStyle STYLES[] = { @@ -40,7 +39,14 @@ SimplyUi *s_ui = NULL; static void click_config_provider(SimplyUi *self); void simply_ui_set_style(SimplyUi *self, int style_index) { + if (self->custom_body_font) { + fonts_unload_custom_font(self->custom_body_font); + self->custom_body_font = NULL; + } self->style = &STYLES[style_index]; + if (self->style->custom_body_font_id) { + self->custom_body_font = fonts_load_custom_font(self->custom_body_font); + } layer_mark_dirty(self->display_layer); } @@ -113,7 +119,7 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { const SimplyStyle *style = self->style; GFont title_font = fonts_get_system_font(style->title_font); GFont subtitle_font = fonts_get_system_font(style->subtitle_font); - GFont body_font = style->custom_body_font ? style->custom_body_font : fonts_get_system_font(style->body_font); + GFont body_font = self->custom_body_font ? self->custom_body_font : fonts_get_system_font(style->body_font); const int16_t x_margin = 5; const int16_t y_margin = 2; @@ -162,7 +168,7 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { bounds.size.h = window_bounds.size.h > new_height ? window_bounds.size.h : new_height; layer_set_frame(layer, bounds); scroll_layer_set_content_size(self->scroll_layer, bounds.size); - } else if (!style->custom_body_font && body_size.h > body_rect.size.h) { + } else if (!self->custom_body_font && body_size.h > body_rect.size.h) { body_font = fonts_get_system_font(FONT_KEY_GOTHIC_18); } } @@ -282,13 +288,6 @@ SimplyUi *simply_ui_create(Simply *simply) { return s_ui; } - for (unsigned int i = 0; i < ARRAY_LENGTH(STYLES); ++i) { - SimplyStyle *style = &STYLES[i]; - if (style->custom_body_font_id) { - style->custom_body_font = fonts_load_custom_font(resource_get_handle(style->custom_body_font_id)); - } - } - SimplyUi *self = malloc(sizeof(*self)); *self = (SimplyUi) { .simply = simply }; s_ui = self; @@ -325,12 +324,8 @@ void simply_ui_destroy(SimplyUi *self) { simply_ui_set_text(self, &self->subtitle_text, NULL); simply_ui_set_text(self, &self->body_text, NULL); - for (unsigned int i = 0; i < ARRAY_LENGTH(STYLES); ++i) { - SimplyStyle *style = &STYLES[i]; - if (style->custom_body_font_id) { - fonts_unload_custom_font(style->custom_body_font); - } - } + fonts_unload_custom_font(self->custom_body_font); + self->custom_body_font = NULL; free(self); diff --git a/src/simply_ui.h b/src/simply_ui.h index b3d3d6f6..0de16588 100644 --- a/src/simply_ui.h +++ b/src/simply_ui.h @@ -17,8 +17,9 @@ struct SimplyUi { char *body_text; ScrollLayer *scroll_layer; Layer *display_layer; - bool is_scrollable; + GFont custom_body_font; uint32_t button_mask; + bool is_scrollable; }; SimplyUi *simply_ui_create(Simply *simply); From 55265f9c7dd73f2d55bafc8ace83390d94245eb1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 05:43:47 -0700 Subject: [PATCH 025/791] Change simply menu to clear on disappear --- src/simply_menu.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index 839ffecc..dcd54228 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -239,6 +239,14 @@ static void window_disappear(Window *window) { MenuIndex cell_index = menu_layer_get_selected_index(self->menu_layer); simply_msg_menu_exit(cell_index.section, cell_index.row); + + while (self->sections) { + destroy_section(self, (SimplyMenuSection*) self->sections); + } + + while (self->items) { + destroy_item(self, (SimplyMenuItem*) self->items); + } } static void window_unload(Window *window) { @@ -287,14 +295,6 @@ void simply_menu_destroy(SimplyMenu *self) { return; } - while (self->sections) { - destroy_section(self, (SimplyMenuSection*) self->sections); - } - - while (self->items) { - destroy_item(self, (SimplyMenuItem*) self->items); - } - window_destroy(self->window); self->window = NULL; From f8a0f996949c9d4de51ba6b0bf2527f18dd9d6ff Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 06:42:04 -0700 Subject: [PATCH 026/791] Add simply text exit event --- src/js/simply.js | 10 +++++++++- src/js/simply.pebble.js | 10 +++++++--- src/simply_msg.c | 10 ++++++++++ src/simply_msg.h | 2 ++ src/simply_ui.c | 5 +++++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index da70a7c6..2e4aed6b 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -18,12 +18,14 @@ var buttons = [ var eventTypes = [ 'singleClick', 'longClick', + 'textExit', 'accelTap', 'accelData', 'menuSection', 'menuItem', 'menuSelect', 'menuLongSelect', + 'menuExit', ]; simply.state = {}; @@ -236,7 +238,9 @@ simply.emit = function(type, subtype, e) { subtype = null; } e.type = type; - e.subtype = subtype; + if (subtype) { + e.subtype = subtype; + } var typeMap = simply.listeners; var subtypeMap = typeMap[type]; if (!subtypeMap) { @@ -739,6 +743,10 @@ simply.emitClick = function(type, button) { }); }; +simply.emitTextExit = function() { + simply.emit('textExit', {}); +}; + /** * Simply.js accel tap event. * Use the event type 'accelTap' to subscribe to these events. diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index a346272c..feb894be 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -85,7 +85,9 @@ var commands = [{ name: 'down', }], }, { - name: 'showUi', + name: 'showText', +}, { + name: 'textExit', }, { name: 'showMenu', params: [{ @@ -331,8 +333,8 @@ SimplyPebble.sendPacket = function(packet) { send(); }; -SimplyPebble.showUi = function() { - SimplyPebble.sendPacket(makePacket(commandMap.showUi)); +SimplyPebble.showText = function() { + SimplyPebble.sendPacket(makePacket(commandMap.showText)); }; SimplyPebble.showMenu = function() { @@ -489,6 +491,8 @@ SimplyPebble.onAppMessage = function(e) { var button = buttons[payload[1]]; simply.emitClick(command.name, button); break; + case 'textExit': + simply.emitTextExit(); case 'accelTap': var axis = accelAxes[payload[1]]; simply.emitAccelTap(axis, payload[2]); diff --git a/src/simply_msg.c b/src/simply_msg.c index 4ea51862..980f0d48 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -26,6 +26,7 @@ enum SimplyACmd { SimplyACmd_configAccelData, SimplyACmd_configButtons, SimplyACmd_showUi, + SimplyACmd_uiExit, SimplyACmd_showMenu, SimplyACmd_setMenuSection, SimplyACmd_getMenuSection, @@ -298,6 +299,15 @@ bool simply_msg_long_click(ButtonId button) { return send_click(SimplyACmd_longClick, button); } +bool simply_msg_ui_exit() { + DictionaryIterator *iter = NULL; + if (app_message_outbox_begin(&iter) != APP_MSG_OK) { + return false; + } + dict_write_uint8(iter, 0, SimplyACmd_uiExit); + return (app_message_outbox_send() == APP_MSG_OK); +} + bool simply_msg_accel_tap(AccelAxisType axis, int32_t direction) { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { diff --git a/src/simply_msg.h b/src/simply_msg.h index d95f1d36..368a3576 100644 --- a/src/simply_msg.h +++ b/src/simply_msg.h @@ -14,6 +14,8 @@ bool simply_msg_single_click(ButtonId button); bool simply_msg_long_click(ButtonId button); +bool simply_msg_ui_exit(); + bool simply_msg_accel_tap(AccelAxisType axis, int32_t direction); bool simply_msg_accel_data(AccelData *accel, uint32_t num_samples, int32_t transaction_id); diff --git a/src/simply_ui.c b/src/simply_ui.c index 95b4b947..f90dfd6d 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -273,6 +273,10 @@ static void window_unload(Window *window) { self->scroll_layer = NULL; } +static void window_disappear(Window *window) { + simply_msg_ui_exit(); +} + void simply_ui_show(SimplyUi *self) { if (!self->window) { return; @@ -304,6 +308,7 @@ SimplyUi *simply_ui_create(Simply *simply) { window_set_click_config_provider(window, (ClickConfigProvider) click_config_provider); window_set_window_handlers(window, (WindowHandlers) { .load = window_load, + .disappear = window_disappear, .unload = window_unload, }); From e1c70198f43a9944421363f71327cc3908f32bb7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 06:52:38 -0700 Subject: [PATCH 027/791] Change simply text api to keep text state --- src/js/simply.js | 40 ++++++++++++++++++++++++++++++++++++---- src/js/simply.pebble.js | 10 ---------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 2e4aed6b..69cb6248 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -65,6 +65,8 @@ simply.reset = function() { simply.state.run = true; simply.state.numPackages = 0; + simply.state.text = {}; + simply.state.button = { config: {}, configMode: 'auto', @@ -491,11 +493,30 @@ simply.buttonAutoConfig = function() { * @param {boolean} [clear] - If true, all other text fields will be cleared. */ simply.text = function(textDef, clear) { + if (!textDef) { + return simply.state.text; + } + if (clear) { + simply.state.text = textDef; + } else { + util2.copy(textDef, simply.state.text); + } return simply.impl.text.apply(this, arguments); }; simply.setText = simply.text; +var textfield = function(field, text, clear) { + if (!text) { + return simply.state.text[field]; + } + if (clear) { + simply.state.text = {}; + } + simply.state.text[field] = text; + return simply.impl.textfield(field, text, clear); +}; + /** * Sets the title field. The title field is the first and largest text field available. * @memberOf simply @@ -503,7 +524,7 @@ simply.setText = simply.text; * @param {boolean} [clear] - If true, all other text fields will be cleared. */ simply.title = function(text, clear) { - return simply.impl.textfield('title', text, clear); + return textfield('title', text, clear); }; /** @@ -513,7 +534,7 @@ simply.title = function(text, clear) { * @param {boolean} [clear] - If true, all other text fields will be cleared. */ simply.subtitle = function(text, clear) { - return simply.impl.textfield('subtitle', text, clear); + return textfield('subtitle', text, clear); }; /** @@ -525,7 +546,7 @@ simply.subtitle = function(text, clear) { * @param {boolean} [clear] - If true, all other text fields will be cleared. */ simply.body = function(text, clear) { - return simply.impl.textfield('body', text, clear); + return textfield('body', text, clear); }; /** @@ -547,6 +568,10 @@ simply.vibe = function() { */ simply.scrollable = function(scrollable) { + if (scrollable === null) { + return simply.state.scrollable === true; + } + simply.state.scrollable = scrollable; return simply.impl.scrollable.apply(this, arguments); }; @@ -558,6 +583,10 @@ simply.scrollable = function(scrollable) { */ simply.fullscreen = function(fullscreen) { + if (fullscreen === null) { + return simply.state.fullscreen === true; + } + simply.state.fullscreen = fullscreen; return simply.impl.fullscreen.apply(this, arguments); }; @@ -744,7 +773,10 @@ simply.emitClick = function(type, button) { }; simply.emitTextExit = function() { - simply.emit('textExit', {}); + var textDef = simply.state.text; + simply.emit('textExit', util2.copy(textDef, { + text: textDef + })); }; /** diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index feb894be..2bad3d14 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -382,11 +382,6 @@ SimplyPebble.vibe = function(type) { }; SimplyPebble.scrollable = function(scrollable) { - if (scrollable === null) { - return simply.state.scrollable === true; - } - simply.state.scrollable = scrollable; - var command = commandMap.setScrollable; var packet = makePacket(command); packet[command.paramMap.scrollable.id] = scrollable ? 1 : 0; @@ -394,11 +389,6 @@ SimplyPebble.scrollable = function(scrollable) { }; SimplyPebble.fullscreen = function(fullscreen) { - if (fullscreen === null) { - return simply.state.fullscreen === true; - } - simply.state.fullscreen = fullscreen; - var command = commandMap.setFullscreen; var packet = makePacket(command); packet[command.paramMap.fullscreen.id] = fullscreen ? 1 : 0; From fdc3445a74026ed02c5ab4afefd9585720517df8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 06:55:09 -0700 Subject: [PATCH 028/791] Change simply menu api to return the stateful menu on no params --- src/js/simply.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/js/simply.js b/src/js/simply.js index 69cb6248..d141699c 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -679,7 +679,7 @@ simply.menuInit = function() { }; var getMenuSection = function(e) { - var menu = e.menu; + var menu = e.menu || simply.state.menu; if (!menu) { return; } if (!(menu.sections instanceof Array)) { return; } return menu.sections[e.section]; @@ -739,15 +739,24 @@ simply.onMenuSelect = function(e) { }; simply.menu = function(menuDef) { + if (!menuDef) { + return simply.state.menu; + } simply.state.menu = menuDef; return simply.impl.menu.apply(this, arguments); }; simply.menuSection = function(sectionIndex, sectionDef) { + if (typeof sectionIndex === 'undefined') { + return getMenuSection({ section: sectionIndex }); + } return simply.impl.menuSection.apply(this, arguments); }; simply.menuItem = function(sectionIndex, itemIndex, itemDef) { + if (typeof sectionIndex === 'undefined') { + return getMenuItem({ section: sectionIndex, item: itemIndex }); + } return simply.impl.menuItem.apply(this, arguments); }; From d75b796715a8757228a008edaf6cda0eaf798c55 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 07:02:02 -0700 Subject: [PATCH 029/791] Change simply stateful menu to be overridable by event handlers --- src/js/simply.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index d141699c..68adcbb3 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -79,8 +79,6 @@ simply.reset = function() { } simply.accelInit(); - - simply.menuInit(); }; /** @@ -671,13 +669,6 @@ simply.accelPeek = function(callback) { return simply.impl.accelPeek.apply(this, arguments); }; -simply.menuInit = function() { - simply.on('menuSection', simply.onMenuSection); - simply.on('menuItem', simply.onMenuItem); - simply.on('menuSelect', simply.onMenuSelect); - simply.on('menuLongSelect', simply.onMenuSelect); -}; - var getMenuSection = function(e) { var menu = e.menu || simply.state.menu; if (!menu) { return; } @@ -836,26 +827,32 @@ simply.emitAccelData = function(accels, callback) { }; simply.emitMenuSection = function(section) { - simply.emit('menuSection', { + var e = { menu: simply.state.menu, section: section - }); + }; + if (simply.emit('menuSection', e)) { return; } + simply.onMenuSection(e); }; simply.emitMenuItem = function(section, item) { - simply.emit('menuItem', { + var e = { menu: simply.state.menu, section: section, item: item, - }); + }; + if (simply.emit('menuItem', e)) { return; } + simply.onMenuItem(e); }; simply.emitMenuSelect = function(type, section, item) { - simply.emit(type, { + var e = { menu: simply.state.menu, section: section, item: item, - }); + }; + if (simply.emit(type, e)) { return; } + simply.onMenuSelect(e); }; return simply; From 28dc1e7f7e273174458f15ef820b309e072b153d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 07:17:12 -0700 Subject: [PATCH 030/791] Check simply text arguments --- src/js/simply.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 68adcbb3..82a2314a 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -123,7 +123,7 @@ simply.countHandlers = function(type, subtype) { var checkEventType = function(type) { if (eventTypes.indexOf(type) === -1) { - throw Error('Invalid event type: ' + type); + throw new Error('Invalid event type: ' + type); } }; @@ -491,13 +491,16 @@ simply.buttonAutoConfig = function() { * @param {boolean} [clear] - If true, all other text fields will be cleared. */ simply.text = function(textDef, clear) { - if (!textDef) { + if (typeof textDef === 'undefined') { return simply.state.text; - } - if (clear) { - simply.state.text = textDef; + } else if (typeof textDef === 'object') { + if (clear) { + simply.state.text = textDef; + } else { + util2.copy(textDef, simply.state.text); + } } else { - util2.copy(textDef, simply.state.text); + throw new Error('simply.text takes a textDef object'); } return simply.impl.text.apply(this, arguments); }; @@ -505,7 +508,7 @@ simply.text = function(textDef, clear) { simply.setText = simply.text; var textfield = function(field, text, clear) { - if (!text) { + if (typeof text === 'undefined') { return simply.state.text[field]; } if (clear) { @@ -566,7 +569,7 @@ simply.vibe = function() { */ simply.scrollable = function(scrollable) { - if (scrollable === null) { + if (typeof scrollable === 'undefined') { return simply.state.scrollable === true; } simply.state.scrollable = scrollable; @@ -581,7 +584,7 @@ simply.scrollable = function(scrollable) { */ simply.fullscreen = function(fullscreen) { - if (fullscreen === null) { + if (typeof fullscreen === 'undefined') { return simply.state.fullscreen === true; } simply.state.fullscreen = fullscreen; @@ -664,7 +667,7 @@ simply.accelConfig = function(opt, auto) { */ simply.accelPeek = function(callback) { if (simply.state.accel.subscribe) { - throw Error('Cannot use accelPeek when listening to accelData events'); + throw new Error('Cannot use accelPeek when listening to accelData events'); } return simply.impl.accelPeek.apply(this, arguments); }; From 97ce811e4b9a8e2cfd93db15684758bb55b75396 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 07:33:32 -0700 Subject: [PATCH 031/791] Add util2 toString --- src/js/util2.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/js/util2.js b/src/js/util2.js index 019d4dd4..d970070a 100644 --- a/src/js/util2.js +++ b/src/js/util2.js @@ -20,6 +20,10 @@ util2.toNumber = function (x) { if (!isNaN(x = parseFloat(x))) { return x; } }; +util2.toString = function (x) { + return typeof x === 'object' ? JSON.stringify.apply(this, arguments) : '' + x; +}; + util2.toArray = function (x) { if (x instanceof Array) { return x; } return [x]; From 26d73c510e250ba308883d88bf369bf0f79f67b7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 07:33:41 -0700 Subject: [PATCH 032/791] Improve console log shim to stringify objects --- src/js/simply.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 82a2314a..4b6a08d2 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -291,6 +291,14 @@ simply.defun = function(fn, fargs, fbody) { return new Function('return function ' + fn + '(' + fargs.join(', ') + ') {' + fbody + '}')(); }; +var slog = function() { + var args = []; + for (var i = 0, ii = arguments.length; i < ii; ++i) { + args[i] = util2.toString(arguments[i]); + } + return args.join(' '); +}; + simply.fexecPackage = function(script, pkg) { // console shim for Android var console2 = {}; @@ -299,11 +307,7 @@ simply.fexecPackage = function(script, pkg) { } console2.log = function() { - var args = []; - for (var i = 0, ii = arguments.length; i < ii; ++i) { - args[i] = arguments[i].toString(); - } - var msg = pkg.name + ': ' + args.join(' '); + var msg = pkg.name + ': ' + slog.apply(this, arguments); var width = 45; var prefix = (new Array(width + 1)).join('\b'); // erase Simply.js source line var suffix = msg.length < width ? (new Array(width - msg.length + 1)).join(' ') : 0; From 44334c9c994994e7e1036780876a58c6cc2281e8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 12:04:41 -0700 Subject: [PATCH 033/791] Add zlib.js --- src/js/zlib.js | 464 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 src/js/zlib.js diff --git a/src/js/zlib.js b/src/js/zlib.js new file mode 100644 index 00000000..2d0f40b2 --- /dev/null +++ b/src/js/zlib.js @@ -0,0 +1,464 @@ +/* + * Extracted from pdf.js + * https://github.com/andreasgal/pdf.js + * + * Copyright (c) 2011 Mozilla Foundation + * + * Contributors: Andreas Gal + * Chris G Jones + * Shaon Barman + * Vivien Nicolas <21@vingtetun.org> + * Justin D'Arcangelo + * Yury Delendik + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +var DecodeStream = (function() { + function constructor() { + this.pos = 0; + this.bufferLength = 0; + this.eof = false; + this.buffer = null; + } + + constructor.prototype = { + ensureBuffer: function decodestream_ensureBuffer(requested) { + var buffer = this.buffer; + var current = buffer ? buffer.byteLength : 0; + if (requested < current) + return buffer; + var size = 512; + while (size < requested) + size <<= 1; + var buffer2 = new Uint8Array(size); + for (var i = 0; i < current; ++i) + buffer2[i] = buffer[i]; + return this.buffer = buffer2; + }, + getByte: function decodestream_getByte() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return null; + this.readBlock(); + } + return this.buffer[this.pos++]; + }, + getBytes: function decodestream_getBytes(length) { + var pos = this.pos; + + if (length) { + this.ensureBuffer(pos + length); + var end = pos + length; + + while (!this.eof && this.bufferLength < end) + this.readBlock(); + + var bufEnd = this.bufferLength; + if (end > bufEnd) + end = bufEnd; + } else { + while (!this.eof) + this.readBlock(); + + var end = this.bufferLength; + } + + this.pos = end; + return this.buffer.subarray(pos, end); + }, + lookChar: function decodestream_lookChar() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return null; + this.readBlock(); + } + return String.fromCharCode(this.buffer[this.pos]); + }, + getChar: function decodestream_getChar() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return null; + this.readBlock(); + } + return String.fromCharCode(this.buffer[this.pos++]); + }, + makeSubStream: function decodestream_makeSubstream(start, length, dict) { + var end = start + length; + while (this.bufferLength <= end && !this.eof) + this.readBlock(); + return new Stream(this.buffer, start, length, dict); + }, + skip: function decodestream_skip(n) { + if (!n) + n = 1; + this.pos += n; + }, + reset: function decodestream_reset() { + this.pos = 0; + } + }; + + return constructor; +})(); + +var FlateStream = (function() { + var codeLenCodeMap = new Uint32Array([ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + ]); + + var lengthDecode = new Uint32Array([ + 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, + 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, + 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, + 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102 + ]); + + var distDecode = new Uint32Array([ + 0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, + 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, + 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, + 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001 + ]); + + var fixedLitCodeTab = [new Uint32Array([ + 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, + 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, + 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, + 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, + 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, + 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, + 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, + 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, + 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, + 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, + 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, + 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, + 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, + 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, + 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, + 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, + 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, + 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, + 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, + 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, + 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, + 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, + 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, + 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, + 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, + 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, + 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, + 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, + 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, + 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, + 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, + 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, + 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, + 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, + 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, + 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, + 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, + 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, + 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, + 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, + 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, + 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, + 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, + 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, + 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, + 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, + 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, + 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, + 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, + 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, + 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, + 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, + 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, + 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, + 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, + 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, + 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, + 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, + 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, + 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, + 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, + 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, + 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, + 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff + ]), 9]; + + var fixedDistCodeTab = [new Uint32Array([ + 0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, + 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, + 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, + 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000 + ]), 5]; + + function error(e) { + throw new Error(e) + } + + function constructor(bytes) { + //var bytes = stream.getBytes(); + var bytesPos = 0; + + var cmf = bytes[bytesPos++]; + var flg = bytes[bytesPos++]; + if (cmf == -1 || flg == -1) + error('Invalid header in flate stream'); + if ((cmf & 0x0f) != 0x08) + error('Unknown compression method in flate stream'); + if ((((cmf << 8) + flg) % 31) != 0) + error('Bad FCHECK in flate stream'); + if (flg & 0x20) + error('FDICT bit set in flate stream'); + + this.bytes = bytes; + this.bytesPos = bytesPos; + + this.codeSize = 0; + this.codeBuf = 0; + + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.getBits = function(bits) { + var codeSize = this.codeSize; + var codeBuf = this.codeBuf; + var bytes = this.bytes; + var bytesPos = this.bytesPos; + + var b; + while (codeSize < bits) { + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad encoding in flate stream'); + codeBuf |= b << codeSize; + codeSize += 8; + } + b = codeBuf & ((1 << bits) - 1); + this.codeBuf = codeBuf >> bits; + this.codeSize = codeSize -= bits; + this.bytesPos = bytesPos; + return b; + }; + + constructor.prototype.getCode = function(table) { + var codes = table[0]; + var maxLen = table[1]; + var codeSize = this.codeSize; + var codeBuf = this.codeBuf; + var bytes = this.bytes; + var bytesPos = this.bytesPos; + + while (codeSize < maxLen) { + var b; + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad encoding in flate stream'); + codeBuf |= (b << codeSize); + codeSize += 8; + } + var code = codes[codeBuf & ((1 << maxLen) - 1)]; + var codeLen = code >> 16; + var codeVal = code & 0xffff; + if (codeSize == 0 || codeSize < codeLen || codeLen == 0) + error('Bad encoding in flate stream'); + this.codeBuf = (codeBuf >> codeLen); + this.codeSize = (codeSize - codeLen); + this.bytesPos = bytesPos; + return codeVal; + }; + + constructor.prototype.generateHuffmanTable = function(lengths) { + var n = lengths.length; + + // find max code length + var maxLen = 0; + for (var i = 0; i < n; ++i) { + if (lengths[i] > maxLen) + maxLen = lengths[i]; + } + + // build the table + var size = 1 << maxLen; + var codes = new Uint32Array(size); + for (var len = 1, code = 0, skip = 2; + len <= maxLen; + ++len, code <<= 1, skip <<= 1) { + for (var val = 0; val < n; ++val) { + if (lengths[val] == len) { + // bit-reverse the code + var code2 = 0; + var t = code; + for (var i = 0; i < len; ++i) { + code2 = (code2 << 1) | (t & 1); + t >>= 1; + } + + // fill the table entries + for (var i = code2; i < size; i += skip) + codes[i] = (len << 16) | val; + + ++code; + } + } + } + + return [codes, maxLen]; + }; + + constructor.prototype.readBlock = function() { + function repeat(stream, array, len, offset, what) { + var repeat = stream.getBits(len) + offset; + while (repeat-- > 0) + array[i++] = what; + } + + // read block header + var hdr = this.getBits(3); + if (hdr & 1) + this.eof = true; + hdr >>= 1; + + if (hdr == 0) { // uncompressed block + var bytes = this.bytes; + var bytesPos = this.bytesPos; + var b; + + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + var blockLen = b; + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + blockLen |= (b << 8); + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + var check = b; + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + check |= (b << 8); + if (check != (~blockLen & 0xffff)) + error('Bad uncompressed block length in flate stream'); + + this.codeBuf = 0; + this.codeSize = 0; + + var bufferLength = this.bufferLength; + var buffer = this.ensureBuffer(bufferLength + blockLen); + var end = bufferLength + blockLen; + this.bufferLength = end; + for (var n = bufferLength; n < end; ++n) { + if (typeof (b = bytes[bytesPos++]) == 'undefined') { + this.eof = true; + break; + } + buffer[n] = b; + } + this.bytesPos = bytesPos; + return; + } + + var litCodeTable; + var distCodeTable; + if (hdr == 1) { // compressed block, fixed codes + litCodeTable = fixedLitCodeTab; + distCodeTable = fixedDistCodeTab; + } else if (hdr == 2) { // compressed block, dynamic codes + var numLitCodes = this.getBits(5) + 257; + var numDistCodes = this.getBits(5) + 1; + var numCodeLenCodes = this.getBits(4) + 4; + + // build the code lengths code table + var codeLenCodeLengths = Array(codeLenCodeMap.length); + var i = 0; + while (i < numCodeLenCodes) + codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3); + var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); + + // build the literal and distance code tables + var len = 0; + var i = 0; + var codes = numLitCodes + numDistCodes; + var codeLengths = new Array(codes); + while (i < codes) { + var code = this.getCode(codeLenCodeTab); + if (code == 16) { + repeat(this, codeLengths, 2, 3, len); + } else if (code == 17) { + repeat(this, codeLengths, 3, 3, len = 0); + } else if (code == 18) { + repeat(this, codeLengths, 7, 11, len = 0); + } else { + codeLengths[i++] = len = code; + } + } + + litCodeTable = + this.generateHuffmanTable(codeLengths.slice(0, numLitCodes)); + distCodeTable = + this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes)); + } else { + error('Unknown block type in flate stream'); + } + + var buffer = this.buffer; + var limit = buffer ? buffer.length : 0; + var pos = this.bufferLength; + while (true) { + var code1 = this.getCode(litCodeTable); + if (code1 < 256) { + if (pos + 1 >= limit) { + buffer = this.ensureBuffer(pos + 1); + limit = buffer.length; + } + buffer[pos++] = code1; + continue; + } + if (code1 == 256) { + this.bufferLength = pos; + return; + } + code1 -= 257; + code1 = lengthDecode[code1]; + var code2 = code1 >> 16; + if (code2 > 0) + code2 = this.getBits(code2); + var len = (code1 & 0xffff) + code2; + code1 = this.getCode(distCodeTable); + code1 = distDecode[code1]; + code2 = code1 >> 16; + if (code2 > 0) + code2 = this.getBits(code2); + var dist = (code1 & 0xffff) + code2; + if (pos + len >= limit) { + buffer = this.ensureBuffer(pos + len); + limit = buffer.length; + } + for (var k = 0; k < len; ++k, ++pos) + buffer[pos] = buffer[pos - dist]; + } + }; + + return constructor; +})(); \ No newline at end of file From badc1e2a76ed035bbd8008bcfc0d6db9d3ed3e08 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 12:04:56 -0700 Subject: [PATCH 034/791] Add png.js browser version --- src/js/png.js | 458 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 src/js/png.js diff --git a/src/js/png.js b/src/js/png.js new file mode 100644 index 00000000..442bba54 --- /dev/null +++ b/src/js/png.js @@ -0,0 +1,458 @@ +// Generated by CoffeeScript 1.4.0 + +/* +# MIT LICENSE +# Copyright (c) 2011 Devon Govett +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this +# software and associated documentation files (the "Software"), to deal in the Software +# without restriction, including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +# to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or +# substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + + +(function() { + var PNG; + + PNG = (function() { + var APNG_BLEND_OP_OVER, APNG_BLEND_OP_SOURCE, APNG_DISPOSE_OP_BACKGROUND, APNG_DISPOSE_OP_NONE, APNG_DISPOSE_OP_PREVIOUS, makeImage, scratchCanvas, scratchCtx; + + PNG.load = function(url, canvas, callback) { + var xhr, + _this = this; + if (typeof canvas === 'function') { + callback = canvas; + } + xhr = new XMLHttpRequest; + xhr.open("GET", url, true); + xhr.responseType = "arraybuffer"; + xhr.onload = function() { + var data, png; + data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer); + png = new PNG(data); + if (typeof (canvas != null ? canvas.getContext : void 0) === 'function') { + png.render(canvas); + } + return typeof callback === "function" ? callback(png) : void 0; + }; + return xhr.send(null); + }; + + APNG_DISPOSE_OP_NONE = 0; + + APNG_DISPOSE_OP_BACKGROUND = 1; + + APNG_DISPOSE_OP_PREVIOUS = 2; + + APNG_BLEND_OP_SOURCE = 0; + + APNG_BLEND_OP_OVER = 1; + + function PNG(data) { + var chunkSize, colors, delayDen, delayNum, frame, i, index, key, section, short, text, _i, _j, _ref; + this.data = data; + this.pos = 8; + this.palette = []; + this.imgData = []; + this.transparency = {}; + this.animation = null; + this.text = {}; + frame = null; + while (true) { + chunkSize = this.readUInt32(); + section = ((function() { + var _i, _results; + _results = []; + for (i = _i = 0; _i < 4; i = ++_i) { + _results.push(String.fromCharCode(this.data[this.pos++])); + } + return _results; + }).call(this)).join(''); + switch (section) { + case 'IHDR': + this.width = this.readUInt32(); + this.height = this.readUInt32(); + this.bits = this.data[this.pos++]; + this.colorType = this.data[this.pos++]; + this.compressionMethod = this.data[this.pos++]; + this.filterMethod = this.data[this.pos++]; + this.interlaceMethod = this.data[this.pos++]; + break; + case 'acTL': + this.animation = { + numFrames: this.readUInt32(), + numPlays: this.readUInt32() || Infinity, + frames: [] + }; + break; + case 'PLTE': + this.palette = this.read(chunkSize); + break; + case 'fcTL': + if (frame) { + this.animation.frames.push(frame); + } + this.pos += 4; + frame = { + width: this.readUInt32(), + height: this.readUInt32(), + xOffset: this.readUInt32(), + yOffset: this.readUInt32() + }; + delayNum = this.readUInt16(); + delayDen = this.readUInt16() || 100; + frame.delay = 1000 * delayNum / delayDen; + frame.disposeOp = this.data[this.pos++]; + frame.blendOp = this.data[this.pos++]; + frame.data = []; + break; + case 'IDAT': + case 'fdAT': + if (section === 'fdAT') { + this.pos += 4; + chunkSize -= 4; + } + data = (frame != null ? frame.data : void 0) || this.imgData; + for (i = _i = 0; 0 <= chunkSize ? _i < chunkSize : _i > chunkSize; i = 0 <= chunkSize ? ++_i : --_i) { + data.push(this.data[this.pos++]); + } + break; + case 'tRNS': + this.transparency = {}; + switch (this.colorType) { + case 3: + this.transparency.indexed = this.read(chunkSize); + short = 255 - this.transparency.indexed.length; + if (short > 0) { + for (i = _j = 0; 0 <= short ? _j < short : _j > short; i = 0 <= short ? ++_j : --_j) { + this.transparency.indexed.push(255); + } + } + break; + case 0: + this.transparency.grayscale = this.read(chunkSize)[0]; + break; + case 2: + this.transparency.rgb = this.read(chunkSize); + } + break; + case 'tEXt': + text = this.read(chunkSize); + index = text.indexOf(0); + key = String.fromCharCode.apply(String, text.slice(0, index)); + this.text[key] = String.fromCharCode.apply(String, text.slice(index + 1)); + break; + case 'IEND': + if (frame) { + this.animation.frames.push(frame); + } + this.colors = (function() { + switch (this.colorType) { + case 0: + case 3: + case 4: + return 1; + case 2: + case 6: + return 3; + } + }).call(this); + this.hasAlphaChannel = (_ref = this.colorType) === 4 || _ref === 6; + colors = this.colors + (this.hasAlphaChannel ? 1 : 0); + this.pixelBitlength = this.bits * colors; + this.colorSpace = (function() { + switch (this.colors) { + case 1: + return 'DeviceGray'; + case 3: + return 'DeviceRGB'; + } + }).call(this); + this.imgData = new Uint8Array(this.imgData); + return; + default: + this.pos += chunkSize; + } + this.pos += 4; + if (this.pos > this.data.length) { + throw new Error("Incomplete or corrupt PNG file"); + } + } + return; + } + + PNG.prototype.read = function(bytes) { + var i, _i, _results; + _results = []; + for (i = _i = 0; 0 <= bytes ? _i < bytes : _i > bytes; i = 0 <= bytes ? ++_i : --_i) { + _results.push(this.data[this.pos++]); + } + return _results; + }; + + PNG.prototype.readUInt32 = function() { + var b1, b2, b3, b4; + b1 = this.data[this.pos++] << 24; + b2 = this.data[this.pos++] << 16; + b3 = this.data[this.pos++] << 8; + b4 = this.data[this.pos++]; + return b1 | b2 | b3 | b4; + }; + + PNG.prototype.readUInt16 = function() { + var b1, b2; + b1 = this.data[this.pos++] << 8; + b2 = this.data[this.pos++]; + return b1 | b2; + }; + + PNG.prototype.decodePixels = function(data) { + var byte, c, col, i, left, length, p, pa, paeth, pb, pc, pixelBytes, pixels, pos, row, scanlineLength, upper, upperLeft, _i, _j, _k, _l, _m; + if (data == null) { + data = this.imgData; + } + if (data.length === 0) { + return new Uint8Array(0); + } + data = new FlateStream(data); + data = data.getBytes(); + pixelBytes = this.pixelBitlength / 8; + scanlineLength = pixelBytes * this.width; + pixels = new Uint8Array(scanlineLength * this.height); + length = data.length; + row = 0; + pos = 0; + c = 0; + while (pos < length) { + switch (data[pos++]) { + case 0: + for (i = _i = 0; _i < scanlineLength; i = _i += 1) { + pixels[c++] = data[pos++]; + } + break; + case 1: + for (i = _j = 0; _j < scanlineLength; i = _j += 1) { + byte = data[pos++]; + left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; + pixels[c++] = (byte + left) % 256; + } + break; + case 2: + for (i = _k = 0; _k < scanlineLength; i = _k += 1) { + byte = data[pos++]; + col = (i - (i % pixelBytes)) / pixelBytes; + upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; + pixels[c++] = (upper + byte) % 256; + } + break; + case 3: + for (i = _l = 0; _l < scanlineLength; i = _l += 1) { + byte = data[pos++]; + col = (i - (i % pixelBytes)) / pixelBytes; + left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; + upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; + pixels[c++] = (byte + Math.floor((left + upper) / 2)) % 256; + } + break; + case 4: + for (i = _m = 0; _m < scanlineLength; i = _m += 1) { + byte = data[pos++]; + col = (i - (i % pixelBytes)) / pixelBytes; + left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; + if (row === 0) { + upper = upperLeft = 0; + } else { + upper = pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; + upperLeft = col && pixels[(row - 1) * scanlineLength + (col - 1) * pixelBytes + (i % pixelBytes)]; + } + p = left + upper - upperLeft; + pa = Math.abs(p - left); + pb = Math.abs(p - upper); + pc = Math.abs(p - upperLeft); + if (pa <= pb && pa <= pc) { + paeth = left; + } else if (pb <= pc) { + paeth = upper; + } else { + paeth = upperLeft; + } + pixels[c++] = (byte + paeth) % 256; + } + break; + default: + throw new Error("Invalid filter algorithm: " + data[pos - 1]); + } + row++; + } + return pixels; + }; + + PNG.prototype.decodePalette = function() { + var c, i, length, palette, pos, ret, transparency, _i, _ref, _ref1; + palette = this.palette; + transparency = this.transparency.indexed || []; + ret = new Uint8Array((transparency.length || 0) + palette.length); + pos = 0; + length = palette.length; + c = 0; + for (i = _i = 0, _ref = palette.length; _i < _ref; i = _i += 3) { + ret[pos++] = palette[i]; + ret[pos++] = palette[i + 1]; + ret[pos++] = palette[i + 2]; + ret[pos++] = (_ref1 = transparency[c++]) != null ? _ref1 : 255; + } + return ret; + }; + + PNG.prototype.copyToImageData = function(imageData, pixels) { + var alpha, colors, data, i, input, j, k, length, palette, v, _ref; + colors = this.colors; + palette = null; + alpha = this.hasAlphaChannel; + if (this.palette.length) { + palette = (_ref = this._decodedPalette) != null ? _ref : this._decodedPalette = this.decodePalette(); + colors = 4; + alpha = true; + } + data = imageData.data || imageData; + length = data.length; + input = palette || pixels; + i = j = 0; + if (colors === 1) { + while (i < length) { + k = palette ? pixels[i / 4] * 4 : j; + v = input[k++]; + data[i++] = v; + data[i++] = v; + data[i++] = v; + data[i++] = alpha ? input[k++] : 255; + j = k; + } + } else { + while (i < length) { + k = palette ? pixels[i / 4] * 4 : j; + data[i++] = input[k++]; + data[i++] = input[k++]; + data[i++] = input[k++]; + data[i++] = alpha ? input[k++] : 255; + j = k; + } + } + }; + + PNG.prototype.decode = function() { + var ret; + ret = new Uint8Array(this.width * this.height * 4); + this.copyToImageData(ret, this.decodePixels()); + return ret; + }; + + scratchCanvas = document.createElement('canvas'); + + scratchCtx = scratchCanvas.getContext('2d'); + + makeImage = function(imageData) { + var img; + scratchCtx.width = imageData.width; + scratchCtx.height = imageData.height; + scratchCtx.clearRect(0, 0, imageData.width, imageData.height); + scratchCtx.putImageData(imageData, 0, 0); + img = new Image; + img.src = scratchCanvas.toDataURL(); + return img; + }; + + PNG.prototype.decodeFrames = function(ctx) { + var frame, i, imageData, pixels, _i, _len, _ref, _results; + if (!this.animation) { + return; + } + _ref = this.animation.frames; + _results = []; + for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { + frame = _ref[i]; + imageData = ctx.createImageData(frame.width, frame.height); + pixels = this.decodePixels(new Uint8Array(frame.data)); + this.copyToImageData(imageData, pixels); + frame.imageData = imageData; + _results.push(frame.image = makeImage(imageData)); + } + return _results; + }; + + PNG.prototype.renderFrame = function(ctx, number) { + var frame, frames, prev; + frames = this.animation.frames; + frame = frames[number]; + prev = frames[number - 1]; + if (number === 0) { + ctx.clearRect(0, 0, this.width, this.height); + } + if ((prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_BACKGROUND) { + ctx.clearRect(prev.xOffset, prev.yOffset, prev.width, prev.height); + } else if ((prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_PREVIOUS) { + ctx.putImageData(prev.imageData, prev.xOffset, prev.yOffset); + } + if (frame.blendOp === APNG_BLEND_OP_SOURCE) { + ctx.clearRect(frame.xOffset, frame.yOffset, frame.width, frame.height); + } + return ctx.drawImage(frame.image, frame.xOffset, frame.yOffset); + }; + + PNG.prototype.animate = function(ctx) { + var doFrame, frameNumber, frames, numFrames, numPlays, _ref, + _this = this; + frameNumber = 0; + _ref = this.animation, numFrames = _ref.numFrames, frames = _ref.frames, numPlays = _ref.numPlays; + return (doFrame = function() { + var f, frame; + f = frameNumber++ % numFrames; + frame = frames[f]; + _this.renderFrame(ctx, f); + if (numFrames > 1 && frameNumber / numFrames < numPlays) { + return _this.animation._timeout = setTimeout(doFrame, frame.delay); + } + })(); + }; + + PNG.prototype.stopAnimation = function() { + var _ref; + return clearTimeout((_ref = this.animation) != null ? _ref._timeout : void 0); + }; + + PNG.prototype.render = function(canvas) { + var ctx, data; + if (canvas._png) { + canvas._png.stopAnimation(); + } + canvas._png = this; + canvas.width = this.width; + canvas.height = this.height; + ctx = canvas.getContext("2d"); + if (this.animation) { + this.decodeFrames(ctx); + return this.animate(ctx); + } else { + data = ctx.createImageData(this.width, this.height); + this.copyToImageData(data, this.decodePixels()); + return ctx.putImageData(data, 0, 0); + } + }; + + return PNG; + + })(); + + window.PNG = PNG; + +}).call(this); From 978b9ccd5b576bfe65d38048e4b68f51fbae645b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 12:11:13 -0700 Subject: [PATCH 035/791] Remove png.js scratchCtx - canvas is not supported --- src/js/png.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/js/png.js b/src/js/png.js index 442bba54..3cb7d72b 100644 --- a/src/js/png.js +++ b/src/js/png.js @@ -359,8 +359,6 @@ scratchCanvas = document.createElement('canvas'); - scratchCtx = scratchCanvas.getContext('2d'); - makeImage = function(imageData) { var img; scratchCtx.width = imageData.width; From e9f5988821ff882fb466e11571f407817b01f96f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 16:51:52 -0700 Subject: [PATCH 036/791] Add simply res for resource handling --- src/simply_res.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ src/simply_res.h | 32 ++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/simply_res.c create mode 100644 src/simply_res.h diff --git a/src/simply_res.c b/src/simply_res.c new file mode 100644 index 00000000..d2d8f5c3 --- /dev/null +++ b/src/simply_res.c @@ -0,0 +1,69 @@ +#include "simply_res.h" + +#include + +static bool id_filter(List1Node *node, void *data) { + return (((SimplyImage*) node)->id == (uint32_t)(uintptr_t) data); +} + +static void destroy_image(SimplyRes *self, SimplyImage *image) { + list1_remove(&self->images, &image->node); + free(image->bitmap.addr); + free(image); +} + +GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint32_t *pixels) { + SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); + if (image) { + free(image->bitmap.addr); + image->bitmap.addr = NULL; + } else { + image = malloc(sizeof(*image)); + *image = (SimplyImage) { .id = id }; + list1_prepend(&self->images, &image->node); + } + + uint16_t row_size_bytes = (1 + (width - 1) / 32) * 4; + size_t pixels_size = height * row_size_bytes; + image->bitmap = (GBitmap) { + .row_size_bytes = row_size_bytes, + .bounds.size = { width, height }, + }; + + image->bitmap.addr = malloc(pixels_size); + memcpy(image->bitmap.addr, pixels, pixels_size); + + Window *window = window_stack_get_top_window(); + layer_mark_dirty(window_get_root_layer(window)); + + return &image->bitmap; +} + +void simply_res_remove_image(SimplyRes *self, uint32_t id) { + SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); + if (image) { + destroy_image(self, image); + } +} + +GBitmap *simply_res_get_image(SimplyRes *self, uint32_t id) { + SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); + return image ? &image->bitmap : NULL; +} + +void simply_res_clear(SimplyRes *self) { + while (self->images) { + destroy_image(self, (SimplyImage*) self->images); + } +} + +SimplyRes *simply_res_create() { + SimplyRes *self = malloc(sizeof(*self)); + *self = (SimplyRes) { .images = NULL }; + return self; +} + +void simply_res_destroy(SimplyRes *self) { + simply_res_clear(self); + free(self); +} diff --git a/src/simply_res.h b/src/simply_res.h new file mode 100644 index 00000000..7787c2aa --- /dev/null +++ b/src/simply_res.h @@ -0,0 +1,32 @@ +#pragma once + +#include "util/list1.h" + +#include + +typedef struct SimplyRes SimplyRes; + +typedef struct SimplyImage SimplyImage; + +struct SimplyRes { + List1Node *images; +}; + +struct SimplyImage { + List1Node node; + uint32_t id; + GBitmap bitmap; +}; + +SimplyRes *simply_res_create(); + +void simply_res_destroy(SimplyRes *self); + +void simply_res_clear(SimplyRes *self); + +GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint32_t *pixels); + +void simply_res_remove_image(SimplyRes *self, uint32_t id); + +GBitmap *simply_res_get_image(SimplyRes *self, uint32_t id); + From 95a75492d66fd6becd3c7f7e48d45980712b2128 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 16:53:12 -0700 Subject: [PATCH 037/791] Add simply msg set image --- src/js/simply.pebble.js | 21 +++++++++++++++++++++ src/simply_msg.c | 30 ++++++++++++++++++++++++++++++ src/simplyjs.c | 3 +++ src/simplyjs.h | 1 + wscript | 5 +++-- 5 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index 2bad3d14..f2e792ca 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -117,6 +117,8 @@ var commands = [{ name: 'title', }, { name: 'subtitle', + }, { + name: 'image', }], }, { name: 'getMenuItem', @@ -146,6 +148,17 @@ var commands = [{ }, { name: 'item', }], +}, { + name: 'image', + params: [{ + name: 'id', + }, { + name: 'width', + }, { + name: 'height', + }, { + name: 'pixels', + }], }]; var commandMap = {}; @@ -455,6 +468,14 @@ SimplyPebble.menuItem = function(sectionIndex, itemIndex, itemDef) { SimplyPebble.sendPacket(packet); }; +SimplyPebble.image = function(id, gbitmap) { + var command = commandMap.image; + var packetDef = util2.copy(gbitmap); + packetDef.id = id; + var packet = makePacket(command, packetDef); + SimplyPebble.sendPacket(packet); +}; + var readInt = function(packet, width, pos, signed) { var value = 0; pos = pos || 0; diff --git a/src/simply_msg.c b/src/simply_msg.c index 980f0d48..9423a4fd 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -1,6 +1,7 @@ #include "simply_msg.h" #include "simply_accel.h" +#include "simply_res.h" #include "simply_menu.h" #include "simply_ui.h" @@ -35,6 +36,7 @@ enum SimplyACmd { SimplyACmd_menuSelect, SimplyACmd_menuLongSelect, SimplyACmd_menuExit, + SimplyACmd_image, }; typedef enum VibeType VibeType; @@ -192,16 +194,41 @@ static void handle_set_menu_item(DictionaryIterator *iter, Simply *simply) { if ((tuple = dict_find(iter, 4))) { subtitle = tuple->value->cstring; } + if ((tuple = dict_find(iter, 5))) { + image = tuple->value->uint32; + } SimplyMenuItem *item = malloc(sizeof(*item)); *item = (SimplyMenuItem) { .section = section_index, .index = row, .title = strdup2(title), .subtitle = strdup2(subtitle), + .image = image, }; simply_menu_add_item(simply->menu, item); } +static void handle_set_image(DictionaryIterator *iter, Simply *simply) { + Tuple *tuple; + uint32_t id = 0; + int16_t width = 0; + int16_t height = 0; + uint32_t *pixels = NULL; + if ((tuple = dict_find(iter, 1))) { + id = tuple->value->uint32; + } + if ((tuple = dict_find(iter, 2))) { + width = tuple->value->int16; + } + if ((tuple = dict_find(iter, 3))) { + height = tuple->value->int16; + } + if ((tuple = dict_find(iter, 4))) { + pixels = (uint32_t*) tuple->value->data; + } + simply_res_add_image(simply->res, id, width, height, pixels); +} + static void received_callback(DictionaryIterator *iter, void *context) { Tuple *tuple = dict_find(iter, 0); if (!tuple) { @@ -245,6 +272,9 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_setMenuItem: handle_set_menu_item(iter, context); break; + case SimplyACmd_image: + handle_set_image(iter, context); + break; } } diff --git a/src/simplyjs.c b/src/simplyjs.c index 583d6fe9..dd65fe82 100644 --- a/src/simplyjs.c +++ b/src/simplyjs.c @@ -1,6 +1,7 @@ #include "simplyjs.h" #include "simply_accel.h" +#include "simply_res.h" #include "simply_splash.h" #include "simply_menu.h" #include "simply_ui.h" @@ -11,6 +12,7 @@ static Simply *init(void) { Simply *simply = malloc(sizeof(*simply)); simply->accel = simply_accel_create(); + simply->res = simply_res_create(); simply->splash = simply_splash_create(simply); simply->menu = simply_menu_create(); simply->ui = simply_ui_create(simply); @@ -26,6 +28,7 @@ static void deinit(Simply *simply) { simply_msg_deinit(); simply_ui_destroy(simply->ui); simply_menu_destroy(simply->menu); + simply_res_destroy(simply->res); simply_accel_destroy(simply->accel); } diff --git a/src/simplyjs.h b/src/simplyjs.h index e4c0d273..1db7a792 100644 --- a/src/simplyjs.h +++ b/src/simplyjs.h @@ -6,6 +6,7 @@ typedef struct Simply Simply; struct Simply { struct SimplyAccel *accel; + struct SimplyRes *res; struct SimplySplash *splash; struct SimplyMenu *menu; struct SimplyUi *ui; diff --git a/wscript b/wscript index 35d540d7..8c2d32e5 100644 --- a/wscript +++ b/wscript @@ -13,8 +13,9 @@ def build(ctx): ctx.load('pebble_sdk') ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), - cflags=['-Wno-type-limits', - '-Wno-address'], + cflags=['-Wno-address', + '-Wno-type-limits', + '-Wno-missing-field-initializers'], target='pebble-app.elf') js_target = ctx.concat_javascript(js=ctx.path.ant_glob('src/js/**/*.js')) From 33f8a46f764134a7f199fe3cb945ca52a07d1d67 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 16:53:38 -0700 Subject: [PATCH 038/791] Add simply menu icon image --- src/simply_menu.c | 12 ++++++++++-- src/simply_menu.h | 6 +++++- src/simply_msg.c | 1 + src/simplyjs.c | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index dcd54228..b9f16bca 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -1,7 +1,13 @@ #include "simply_menu.h" +#include "simply_res.h" + #include "simply_msg.h" +#include "simplyjs.h" + +#include + #define MAX_CACHED_SECTIONS 10 #define MAX_CACHED_ITEMS 6 @@ -199,7 +205,8 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI list1_remove(&self->items, &item->node); list1_prepend(&self->items, &item->node); - menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, NULL); + GBitmap *bitmap = simply_res_get_image(self->simply->res, item->image); + menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, bitmap); } static void menu_select_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { @@ -266,13 +273,14 @@ void simply_menu_show(SimplyMenu *self) { } } -SimplyMenu *simply_menu_create(void) { +SimplyMenu *simply_menu_create(Simply *simply) { if (s_menu) { return s_menu; } SimplyMenu *self = malloc(sizeof(*self)); *self = (SimplyMenu) { + .simply = simply, .num_sections = 1, .request_delay_ms = REQUEST_DELAY_MS, }; diff --git a/src/simply_menu.h b/src/simply_menu.h index be612e7e..f75d623f 100644 --- a/src/simply_menu.h +++ b/src/simply_menu.h @@ -1,5 +1,7 @@ #pragma once +#include "simplyjs.h" + #include "util/list1.h" #include @@ -19,6 +21,7 @@ enum SimplyMenuType { }; struct SimplyMenu { + Simply *simply; Window *window; MenuLayer *menu_layer; List1Node *sections; @@ -52,11 +55,12 @@ struct SimplyMenuSection { struct SimplyMenuItem { SimplyMenuCommonMember; char *subtitle; + uint32_t image; uint16_t section; uint16_t index; }; -SimplyMenu *simply_menu_create(void); +SimplyMenu *simply_menu_create(Simply *simply); void simply_menu_destroy(SimplyMenu *self); diff --git a/src/simply_msg.c b/src/simply_msg.c index 9423a4fd..61345a08 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -180,6 +180,7 @@ static void handle_set_menu_item(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; uint16_t section_index = 0; uint16_t row = 0; + uint32_t image = 0; char *title = NULL; char *subtitle = NULL; if ((tuple = dict_find(iter, 1))) { diff --git a/src/simplyjs.c b/src/simplyjs.c index dd65fe82..684ede3b 100644 --- a/src/simplyjs.c +++ b/src/simplyjs.c @@ -14,7 +14,7 @@ static Simply *init(void) { simply->accel = simply_accel_create(); simply->res = simply_res_create(); simply->splash = simply_splash_create(simply); - simply->menu = simply_menu_create(); + simply->menu = simply_menu_create(simply); simply->ui = simply_ui_create(simply); bool animated = true; From bce91092b6f45610ccd9ee0703e8b297b87bdebb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 17:21:56 -0700 Subject: [PATCH 039/791] Add simply image for loading images --- src/js/simply.js | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/js/simply.js b/src/js/simply.js index 4b6a08d2..a6385bab 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -65,6 +65,9 @@ simply.reset = function() { simply.state.run = true; simply.state.numPackages = 0; + simply.state.images = {}; + simply.state.nextImageId = 1; + simply.state.text = {}; simply.state.button = { @@ -607,6 +610,76 @@ simply.style = function(type) { return simply.impl.style.apply(this, arguments); }; +var toGbitmap = function(png) { + var pixels = png.decodePixels(); + var pixelBytes = png.pixelBitlength / 8; + var rowBytes = png.width * pixelBytes; + + var gpixels = []; + var growBytes = Math.ceil(png.width / 32) * 4; + for (var i = 0, ii = png.height * growBytes; i < ii; ++i) { + gpixels[i] = 0; + } + + for (var x = 0, xx = png.width; x < xx; ++x) { + for (var y = 0, yy = png.height; y < yy; ++y) { + var pos = y * rowBytes + x * pixelBytes; + var gbytePos = y * growBytes + Math.floor(x / 8); + var grey = 0; + for (var i = 0; i < pixelBytes - 1; ++i) { + grey += pixels[pos + i]; + } + grey /= pixelBytes - 1; + if (grey >= 128) { + gpixels[gbytePos] += 1 << (x % 8); + } + } + } + + var gbitmap = { + width: png.width, + height: png.height, + pixels: gpixels, + }; + + return gbitmap; +}; + +simply.loadImage = function(image, callback) { + PNG.load(image.path, function(png) { + image.gbitmap = toGbitmap(png); + simply.impl.image(image.id, image.gbitmap); + if (callback) { + var e = { + type: 'image', + image: image.id, + path: image.path, + }; + callback(e); + } + }); + return image; +}; + +simply.image = function(path, reset, callback) { + if (typeof reset === 'function') { + callback = reset; + reset = null; + } + path = simply.basepath() + path; + var image = simply.state.images[path]; + if (image && reset !== true) { + return image.id; + } + var image = { + id: simply.state.nextImageId++, + path: path, + }; + simply.state.images[path] = image; + simply.loadImage(image, callback); + return image.id; +}; + simply.accelInit = function() { simply.state.accel = { rate: 100, From 68d62c27e08db2170be4e8fd97efbfd0d4943c81 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 19:49:41 -0700 Subject: [PATCH 040/791] Add support for certain low bit depth png images --- src/js/simply.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index a6385bab..a8e51e8f 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -612,8 +612,9 @@ simply.style = function(type) { var toGbitmap = function(png) { var pixels = png.decodePixels(); - var pixelBytes = png.pixelBitlength / 8; - var rowBytes = png.width * pixelBytes; + var bitDepth = png.pixelBitlength; + var byteDepth = bitDepth / 8; + var rowBytes = png.width * byteDepth; var gpixels = []; var growBytes = Math.ceil(png.width / 32) * 4; @@ -621,16 +622,26 @@ var toGbitmap = function(png) { gpixels[i] = 0; } - for (var x = 0, xx = png.width; x < xx; ++x) { - for (var y = 0, yy = png.height; y < yy; ++y) { - var pos = y * rowBytes + x * pixelBytes; - var gbytePos = y * growBytes + Math.floor(x / 8); + for (var y = 0, yy = png.height; y < yy; ++y) { + for (var x = 0, xx = png.width; x < xx; ++x) { var grey = 0; - for (var i = 0; i < pixelBytes - 1; ++i) { - grey += pixels[pos + i]; + if (bitDepth < 8) { + var bitPos = y * png.width * bitDepth + x * bitDepth; + var pos = parseInt(bitPos / 8); + var mask = (1 << bitDepth) - 1; + var j = bitPos % (8 / bitDepth); + grey = (pixels[pos] >> (7 - j * bitDepth)) & mask; + grey /= mask; + } else { + var pos = y * rowBytes + parseInt(x * byteDepth); + var numColors = byteDepth - (png.hasAlphaChannel ? 1 : 0); + for (var i = 0; i < numColors; ++i) { + grey += pixels[pos + i]; + } + grey /= numColors * 255; } - grey /= pixelBytes - 1; - if (grey >= 128) { + if (grey >= 0.5) { + var gbytePos = y * growBytes + parseInt(x / 8); gpixels[gbytePos] += 1 << (x % 8); } } From 13a82fad51404cacd197b4e9e2cc81955e30b7b9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 12 May 2014 19:50:13 -0700 Subject: [PATCH 041/791] Change util2 toArray to convert to an array if 0 is a key --- src/js/util2.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/util2.js b/src/js/util2.js index d970070a..45bf6f0b 100644 --- a/src/js/util2.js +++ b/src/js/util2.js @@ -26,6 +26,7 @@ util2.toString = function (x) { util2.toArray = function (x) { if (x instanceof Array) { return x; } + if (x[0]) { return util2.copy(x, []); } return [x]; }; From 8e51330c7484bd923521cae433c5c55d0aa04470 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 13 May 2014 22:17:39 -0700 Subject: [PATCH 042/791] Rename menu item image to icon --- src/simply_menu.c | 2 +- src/simply_menu.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index b9f16bca..e334d26b 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -205,7 +205,7 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI list1_remove(&self->items, &item->node); list1_prepend(&self->items, &item->node); - GBitmap *bitmap = simply_res_get_image(self->simply->res, item->image); + GBitmap *bitmap = simply_res_get_image(self->simply->res, item->icon); menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, bitmap); } diff --git a/src/simply_menu.h b/src/simply_menu.h index f75d623f..5c9f9c5d 100644 --- a/src/simply_menu.h +++ b/src/simply_menu.h @@ -55,7 +55,7 @@ struct SimplyMenuSection { struct SimplyMenuItem { SimplyMenuCommonMember; char *subtitle; - uint32_t image; + uint32_t icon; uint16_t section; uint16_t index; }; From c27aa26be68cbac9c36af9d7304cde4da10a2c14 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 13 May 2014 22:17:59 -0700 Subject: [PATCH 043/791] Add util graphics --- src/util/graphics.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/util/graphics.h diff --git a/src/util/graphics.h b/src/util/graphics.h new file mode 100644 index 00000000..86c1f09c --- /dev/null +++ b/src/util/graphics.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +static inline GRect grect_center_rect(const GRect *rect_a, const GRect *rect_b) { + return (GRect) { + .origin = { + .x = rect_a->origin.x + (rect_a->size.w - rect_b->size.w) / 2, + .y = rect_a->origin.y + (rect_a->size.h - rect_b->size.h) / 2, + }, + .size = rect_b->size, + }; +} + +static inline void graphics_draw_bitmap_centered(GContext *ctx, GBitmap *bitmap, const GRect frame) { + graphics_draw_bitmap_in_rect(ctx, bitmap, grect_center_rect(&frame, &bitmap->bounds)); +} From cd3276e3bc8f37cc33ccbcbcf4a5d462d54fadd4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 13 May 2014 22:16:36 -0700 Subject: [PATCH 044/791] Add simply ui title icon and subtitle icon --- src/simply_ui.c | 74 ++++++++++++++++++++++++++++++++++++------------- src/simply_ui.h | 3 ++ 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/simply_ui.c b/src/simply_ui.c index f90dfd6d..afc9e2a4 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -1,10 +1,12 @@ #include "simply_ui.h" #include "simply_msg.h" +#include "simply_res.h" #include "simply_menu.h" #include "simplyjs.h" +#include "util/graphics.h" #include "util/string.h" #include @@ -107,14 +109,13 @@ void simply_ui_set_text(SimplyUi *self, char **str_field, const char *str) { if (self->display_layer) { layer_mark_dirty(self->display_layer); } - simply_ui_show(self); } void display_layer_update_callback(Layer *layer, GContext *ctx) { SimplyUi *self = s_ui; - GRect window_bounds = layer_get_bounds(window_get_root_layer(self->window)); - GRect bounds = layer_get_bounds(layer); + GRect window_frame = layer_get_frame(window_get_root_layer(self->window)); + GRect frame = layer_get_frame(layer); const SimplyStyle *style = self->style; GFont title_font = fonts_get_system_font(style->title_font); @@ -124,9 +125,9 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { const int16_t x_margin = 5; const int16_t y_margin = 2; - GRect text_bounds = bounds; - text_bounds.size.w -= 2 * x_margin; - text_bounds.size.h += 1000; + GRect text_frame = frame; + text_frame.size.w -= 2 * x_margin; + text_frame.size.h += 1000; GPoint cursor = { x_margin, y_margin }; graphics_context_set_text_color(ctx, GColorBlack); @@ -139,49 +140,82 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { GPoint title_pos, subtitle_pos; GRect body_rect; + GBitmap *title_icon = simply_res_get_image(self->simply->res, self->title_icon); + GBitmap *subtitle_icon = simply_res_get_image(self->simply->res, self->subtitle_icon); + if (has_title) { - title_size = graphics_text_layout_get_content_size(self->title_text, title_font, text_bounds, - GTextOverflowModeWordWrap, GTextAlignmentLeft); - title_size.w = text_bounds.size.w; + GRect title_frame = text_frame; + if (title_icon) { + title_frame.origin.x += title_icon->bounds.size.w; + title_frame.size.w -= title_icon->bounds.size.w; + } + title_size = graphics_text_layout_get_content_size(self->title_text, + title_font, title_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); + title_size.w = title_frame.size.w; title_pos = cursor; + if (title_icon) { + title_pos.x += title_icon->bounds.size.w; + } cursor.y += title_size.h; } if (has_subtitle) { - subtitle_size = graphics_text_layout_get_content_size(self->subtitle_text, title_font, text_bounds, - GTextOverflowModeWordWrap, GTextAlignmentLeft); - subtitle_size.w = text_bounds.size.w; + GRect subtitle_frame = text_frame; + if (subtitle_icon) { + subtitle_frame.origin.x += subtitle_icon->bounds.size.w; + subtitle_frame.size.w -= subtitle_icon->bounds.size.w; + } + subtitle_size = graphics_text_layout_get_content_size(self->subtitle_text, + title_font, subtitle_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); + subtitle_size.w = subtitle_frame.size.w; subtitle_pos = cursor; + if (subtitle_icon) { + subtitle_pos.x += subtitle_icon->bounds.size.w; + } cursor.y += subtitle_size.h; } if (has_body) { - body_rect = bounds; + body_rect = frame; body_rect.origin = cursor; body_rect.size.w -= 2 * x_margin; body_rect.size.h -= 2 * y_margin + cursor.y; - GSize body_size = graphics_text_layout_get_content_size(self->body_text, body_font, text_bounds, - GTextOverflowModeWordWrap, GTextAlignmentLeft); + GSize body_size = graphics_text_layout_get_content_size(self->body_text, + body_font, text_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); if (self->is_scrollable) { body_rect.size = body_size; int16_t new_height = cursor.y + 2 * y_margin + body_size.h; - bounds.size.h = window_bounds.size.h > new_height ? window_bounds.size.h : new_height; - layer_set_frame(layer, bounds); - scroll_layer_set_content_size(self->scroll_layer, bounds.size); + frame.size.h = window_frame.size.h > new_height ? window_frame.size.h : new_height; + layer_set_frame(layer, frame); + scroll_layer_set_content_size(self->scroll_layer, frame.size); } else if (!self->custom_body_font && body_size.h > body_rect.size.h) { body_font = fonts_get_system_font(FONT_KEY_GOTHIC_18); } } graphics_context_set_fill_color(ctx, GColorWhite); - graphics_fill_rect(ctx, bounds, 4, GCornersAll); - + graphics_fill_rect(ctx, frame, 4, GCornersAll); + + if (title_icon) { + GRect icon_frame = (GRect) { + .origin = { x_margin, title_pos.y + 3 }, + .size = { title_icon->bounds.size.w, title_size.h } + }; + graphics_draw_bitmap_centered(ctx, title_icon, icon_frame); + } if (has_title) { graphics_draw_text(ctx, self->title_text, title_font, (GRect) { .origin = title_pos, .size = title_size }, GTextOverflowModeWordWrap, GTextAlignmentLeft, NULL); } + if (subtitle_icon) { + GRect subicon_frame = (GRect) { + .origin = { x_margin, subtitle_pos.y + 3 }, + .size = { subtitle_icon->bounds.size.w, subtitle_size.h } + }; + graphics_draw_bitmap_centered(ctx, subtitle_icon, subicon_frame); + } if (has_subtitle) { graphics_draw_text(ctx, self->subtitle_text, subtitle_font, (GRect) { .origin = subtitle_pos, .size = subtitle_size }, diff --git a/src/simply_ui.h b/src/simply_ui.h index 0de16588..c09915ef 100644 --- a/src/simply_ui.h +++ b/src/simply_ui.h @@ -15,6 +15,9 @@ struct SimplyUi { char *title_text; char *subtitle_text; char *body_text; + uint32_t title_icon; + uint32_t subtitle_icon; + uint32_t image; ScrollLayer *scroll_layer; Layer *display_layer; GFont custom_body_font; From 4dc2e1f1114b65a5b871b737122a876cedd2c4f4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 13 May 2014 22:17:08 -0700 Subject: [PATCH 045/791] Add simply msg ui image support --- src/simply_msg.c | 50 ++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/simply_msg.c b/src/simply_msg.c index 61345a08..4496a36c 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -14,7 +14,7 @@ typedef enum SimplyACmd SimplyACmd; enum SimplyACmd { - SimplyACmd_setText = 0, + SimplyACmd_setUi = 0, SimplyACmd_singleClick, SimplyACmd_longClick, SimplyACmd_accelTap, @@ -26,9 +26,8 @@ enum SimplyACmd { SimplyACmd_getAccelData, SimplyACmd_configAccelData, SimplyACmd_configButtons, - SimplyACmd_showUi, SimplyACmd_uiExit, - SimplyACmd_showMenu, + SimplyACmd_setMenu, SimplyACmd_setMenuSection, SimplyACmd_getMenuSection, SimplyACmd_setMenuItem, @@ -53,24 +52,32 @@ static void check_splash(Simply *simply) { } } -static void handle_set_text(DictionaryIterator *iter, Simply *simply) { +static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { SimplyUi *ui = simply->ui; Tuple *tuple; bool clear = false; - if ((tuple = dict_find(iter, 4))) { + if ((tuple = dict_find(iter, 1))) { clear = true; } - if ((tuple = dict_find(iter, 1)) || clear) { + if ((tuple = dict_find(iter, 2)) || clear) { simply_ui_set_text(ui, &ui->title_text, tuple ? tuple->value->cstring : NULL); } - if ((tuple = dict_find(iter, 2)) || clear) { + if ((tuple = dict_find(iter, 3)) || clear) { simply_ui_set_text(ui, &ui->subtitle_text, tuple ? tuple->value->cstring : NULL); } - if ((tuple = dict_find(iter, 3)) || clear) { + if ((tuple = dict_find(iter, 4)) || clear) { simply_ui_set_text(ui, &ui->body_text, tuple ? tuple->value->cstring : NULL); } - - check_splash(simply); + if ((tuple = dict_find(iter, 5)) || clear) { + ui->title_icon = tuple ? tuple->value->uint32 : 0; + } + if ((tuple = dict_find(iter, 6)) || clear) { + ui->subtitle_icon = tuple ? tuple->value->uint32 : 0; + } + if ((tuple = dict_find(iter, 7)) || clear) { + ui->image = tuple ? tuple->value->uint32 : 0; + } + simply_ui_show(simply->ui); } static void handle_vibe(DictionaryIterator *iter, Simply *simply) { @@ -141,11 +148,7 @@ static void handle_set_accel_config(DictionaryIterator *iter, Simply *simply) { } } -static void handle_show_ui(DictionaryIterator *iter, Simply *simply) { - simply_ui_show(simply->ui); -} - -static void handle_show_menu(DictionaryIterator *iter, Simply *simply) { +static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; if ((tuple = dict_find(iter, 1))) { simply_menu_set_num_sections(simply->menu, tuple->value->int32); @@ -180,7 +183,7 @@ static void handle_set_menu_item(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; uint16_t section_index = 0; uint16_t row = 0; - uint32_t image = 0; + uint32_t icon = 0; char *title = NULL; char *subtitle = NULL; if ((tuple = dict_find(iter, 1))) { @@ -196,7 +199,7 @@ static void handle_set_menu_item(DictionaryIterator *iter, Simply *simply) { subtitle = tuple->value->cstring; } if ((tuple = dict_find(iter, 5))) { - image = tuple->value->uint32; + icon = tuple->value->uint32; } SimplyMenuItem *item = malloc(sizeof(*item)); *item = (SimplyMenuItem) { @@ -204,7 +207,7 @@ static void handle_set_menu_item(DictionaryIterator *iter, Simply *simply) { .index = row, .title = strdup2(title), .subtitle = strdup2(subtitle), - .image = image, + .icon = icon, }; simply_menu_add_item(simply->menu, item); } @@ -237,8 +240,8 @@ static void received_callback(DictionaryIterator *iter, void *context) { } switch (tuple->value->uint8) { - case SimplyACmd_setText: - handle_set_text(iter, context); + case SimplyACmd_setUi: + handle_set_ui(iter, context); break; case SimplyACmd_vibe: handle_vibe(iter, context); @@ -261,11 +264,8 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_configButtons: handle_config_buttons(iter, context); break; - case SimplyACmd_showUi: - handle_show_ui(iter, context); - break; - case SimplyACmd_showMenu: - handle_show_menu(iter, context); + case SimplyACmd_setMenu: + handle_set_menu(iter, context); break; case SimplyACmd_setMenuSection: handle_set_menu_section(iter, context); From 4e01bf5b8e2251da6f8e0837ccb8ce24ac7f54ea Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 13 May 2014 22:20:59 -0700 Subject: [PATCH 046/791] Add simply ui image support under new card function --- src/js/simply.js | 38 ++++++++++++++++++---------------- src/js/simply.pebble.js | 46 +++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index a8e51e8f..9ee85ee9 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -68,7 +68,7 @@ simply.reset = function() { simply.state.images = {}; simply.state.nextImageId = 1; - simply.state.text = {}; + simply.state.card = {}; simply.state.button = { config: {}, @@ -480,6 +480,21 @@ simply.buttonAutoConfig = function() { } }; +simply.card = function(cardDef, clear) { + if (typeof cardDef === 'undefined') { + return simply.state.card; + } else if (typeof cardDef === 'object') { + if (clear) { + simply.state.card = cardDef; + } else { + util2.copy(cardDef, simply.state.card); + } + } else { + throw new Error('simply.text takes a cardDef object'); + } + return simply.impl.card.apply(this, arguments); +}; + /** * The text definition parameter for {@link simply.text}. * @typedef {object} simply.textDef @@ -497,31 +512,18 @@ simply.buttonAutoConfig = function() { * @param {simply.textDef} textDef - An object defining new text values. * @param {boolean} [clear] - If true, all other text fields will be cleared. */ -simply.text = function(textDef, clear) { - if (typeof textDef === 'undefined') { - return simply.state.text; - } else if (typeof textDef === 'object') { - if (clear) { - simply.state.text = textDef; - } else { - util2.copy(textDef, simply.state.text); - } - } else { - throw new Error('simply.text takes a textDef object'); - } - return simply.impl.text.apply(this, arguments); -}; +simply.text = simply.card; simply.setText = simply.text; var textfield = function(field, text, clear) { if (typeof text === 'undefined') { - return simply.state.text[field]; + return simply.state.card[field]; } if (clear) { - simply.state.text = {}; + simply.state.card = {}; } - simply.state.text[field] = text; + simply.state.card[field] = text; return simply.impl.textfield(field, text, clear); }; diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index f2e792ca..72bec7d3 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -1,17 +1,27 @@ -/* global simply */ +/* global simply, util2 */ var SimplyPebble = (function() { var commands = [{ - name: 'setText', + name: 'setCard', params: [{ + name: 'clear', + type: Boolean, + }, { name: 'title', + type: String, }, { name: 'subtitle', + type: String, }, { name: 'body', + type: String, }, { - name: 'clear', + name: 'icon', + }, { + name: 'subicon', + }, { + name: 'banner', }], }, { name: 'singleClick', @@ -84,8 +94,6 @@ var commands = [{ }, { name: 'down', }], -}, { - name: 'showText', }, { name: 'textExit', }, { @@ -101,6 +109,7 @@ var commands = [{ name: 'items', }, { name: 'title', + type: String, }], }, { name: 'getMenuSection', @@ -115,8 +124,10 @@ var commands = [{ name: 'item', }, { name: 'title', + type: String, }, { name: 'subtitle', + type: String, }, { name: 'image', }], @@ -328,7 +339,13 @@ function makePacket(command, def) { for (var k in def) { var param = paramMap[k]; if (param) { - packet[param.id] = def[k]; + var v = def[k]; + if (param.type === String) { + v = v.toString(); + } else if (param.type === Boolean) { + v = v ? 1 : 0; + } + packet[param.id] = v; } } } @@ -360,28 +377,21 @@ SimplyPebble.buttonConfig = function(buttonConf) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.text = function(textDef, clear) { - var command = commandMap.setText; - var packetDef = {}; - for (var k in textDef) { - packetDef[k] = textDef[k].toString(); - } - var packet = makePacket(command, packetDef); - if (clear) { - packet[command.paramMap.clear.id] = 1; - } +SimplyPebble.card = function(cardDef, clear) { + var command = commandMap.setCard; + var packet = makePacket(command, cardDef); SimplyPebble.sendPacket(packet); }; SimplyPebble.textfield = function(field, text, clear) { - var command = commandMap.setText; + var command = commandMap.setCard; var packet = makePacket(command); var param = command.paramMap[field]; if (param) { packet[param.id] = text.toString(); } if (clear) { - packet[command.paramMap.clear.id] = 1; + packet[command.paramMap.clear.id] = true; } SimplyPebble.sendPacket(packet); }; From e780f18fee32f31a118d937d5a17cefdf1585919 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 13 May 2014 22:21:23 -0700 Subject: [PATCH 047/791] Cleanup simply.js formatting --- src/js/simply.js | 32 +++++++++++++++++--------------- src/js/simply.pebble.js | 34 +++++++++++----------------------- 2 files changed, 28 insertions(+), 38 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 9ee85ee9..11141e7d 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -2,6 +2,7 @@ * Simply.js * @namespace simply */ +/* global util2, PNG */ var simply = (function() { var noop = typeof util2 !== 'undefined' ? util2.noop : function() {}; @@ -18,7 +19,7 @@ var buttons = [ var eventTypes = [ 'singleClick', 'longClick', - 'textExit', + 'cardExit', 'accelTap', 'accelData', 'menuSection', @@ -448,8 +449,8 @@ simply.buttonConfig = function(buttonConf, auto) { if (typeof buttonConf === 'undefined') { var config = {}; for (var i = 0, ii = buttons.length; i < ii; ++i) { - var k = buttons[i]; - config[k] = buttonConf.config[k]; + var name = buttons[i]; + config[name] = buttonConf.config[name]; } return config; } @@ -627,18 +628,19 @@ var toGbitmap = function(png) { for (var y = 0, yy = png.height; y < yy; ++y) { for (var x = 0, xx = png.width; x < xx; ++x) { var grey = 0; + var pos; if (bitDepth < 8) { var bitPos = y * png.width * bitDepth + x * bitDepth; - var pos = parseInt(bitPos / 8); + pos = parseInt(bitPos / 8); var mask = (1 << bitDepth) - 1; - var j = bitPos % (8 / bitDepth); - grey = (pixels[pos] >> (7 - j * bitDepth)) & mask; + var s = bitPos % (8 / bitDepth); + grey = (pixels[pos] >> (7 - s * bitDepth)) & mask; grey /= mask; } else { - var pos = y * rowBytes + parseInt(x * byteDepth); + pos = y * rowBytes + parseInt(x * byteDepth); var numColors = byteDepth - (png.hasAlphaChannel ? 1 : 0); - for (var i = 0; i < numColors; ++i) { - grey += pixels[pos + i]; + for (var j = 0; j < numColors; ++j) { + grey += pixels[pos + j]; } grey /= numColors * 255; } @@ -684,7 +686,7 @@ simply.image = function(path, reset, callback) { if (image && reset !== true) { return image.id; } - var image = { + image = { id: simply.state.nextImageId++, path: path, }; @@ -865,10 +867,10 @@ simply.emitClick = function(type, button) { }); }; -simply.emitTextExit = function() { - var textDef = simply.state.text; - simply.emit('textExit', util2.copy(textDef, { - text: textDef +simply.emitCardExit = function() { + var cardDef = simply.state.card; + simply.emit('cardExit', util2.copy(cardDef, { + text: cardDef })); }; @@ -953,4 +955,4 @@ return simply; })(); Pebble.require = require; -var require = simply.require; +window.require = simply.require; diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index 72bec7d3..dd73d8c5 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -95,9 +95,9 @@ var commands = [{ name: 'down', }], }, { - name: 'textExit', + name: 'cardExit', }, { - name: 'showMenu', + name: 'setMenu', params: [{ name: 'sections', }], @@ -363,12 +363,8 @@ SimplyPebble.sendPacket = function(packet) { send(); }; -SimplyPebble.showText = function() { - SimplyPebble.sendPacket(makePacket(commandMap.showText)); -}; - -SimplyPebble.showMenu = function() { - SimplyPebble.sendPacket(makePacket(commandMap.showMenu)); +SimplyPebble.setMenu = function() { + SimplyPebble.sendPacket(makePacket(commandMap.setMenu)); }; SimplyPebble.buttonConfig = function(buttonConf) { @@ -416,7 +412,7 @@ SimplyPebble.fullscreen = function(fullscreen) { var packet = makePacket(command); packet[command.paramMap.fullscreen.id] = fullscreen ? 1 : 0; SimplyPebble.sendPacket(packet); -} +}; SimplyPebble.style = function(type) { var command = commandMap.setStyle; @@ -440,7 +436,7 @@ SimplyPebble.accelPeek = function(callback) { }; SimplyPebble.menu = function(menuDef) { - var command = commandMap.showMenu; + var command = commandMap.setMenu; var packetDef = util2.copy(menuDef); if (packetDef.sections instanceof Array) { packetDef.sections = packetDef.sections.length; @@ -456,9 +452,6 @@ SimplyPebble.menuSection = function(sectionIndex, sectionDef) { if (packetDef.items instanceof Array) { packetDef.items = packetDef.items.length; } - if ('title' in packetDef) { - packetDef.title = packetDef.title.toString(); - } var packet = makePacket(command, packetDef); SimplyPebble.sendPacket(packet); }; @@ -468,12 +461,6 @@ SimplyPebble.menuItem = function(sectionIndex, itemIndex, itemDef) { var packetDef = util2.copy(itemDef); packetDef.section = sectionIndex; packetDef.item = itemIndex; - if ('title' in packetDef) { - packetDef.title = packetDef.title.toString(); - } - if ('subtitle' in packetDef) { - packetDef.subtitle = packetDef.subtitle.toString(); - } var packet = makePacket(command, packetDef); SimplyPebble.sendPacket(packet); }; @@ -512,8 +499,9 @@ SimplyPebble.onAppMessage = function(e) { var button = buttons[payload[1]]; simply.emitClick(command.name, button); break; - case 'textExit': - simply.emitTextExit(); + case 'cardExit': + simply.emitCardExit(); + break; case 'accelTap': var axis = accelAxes[payload[1]]; simply.emitAccelTap(axis, payload[2]); @@ -539,8 +527,8 @@ SimplyPebble.onAppMessage = function(e) { } else { var handlers = simply.state.accel.listeners; simply.state.accel.listeners = []; - for (var i = 0, ii = handlers.length; i < ii; ++i) { - simply.emitAccelData(accels, handlers[i]); + for (var j = 0, jj = handlers.length; j < jj; ++j) { + simply.emitAccelData(accels, handlers[j]); } } break; From 430d55c1e0f032c6d8e32881655eb45fb19bda22 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 14 May 2014 01:19:44 -0700 Subject: [PATCH 048/791] Add simply ui body image --- src/simply_res.c | 3 +++ src/simply_ui.c | 34 ++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/simply_res.c b/src/simply_res.c index d2d8f5c3..efa456d6 100644 --- a/src/simply_res.c +++ b/src/simply_res.c @@ -47,6 +47,9 @@ void simply_res_remove_image(SimplyRes *self, uint32_t id) { } GBitmap *simply_res_get_image(SimplyRes *self, uint32_t id) { + if (!id) { + return NULL; + } SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); return image ? &image->bitmap : NULL; } diff --git a/src/simply_ui.c b/src/simply_ui.c index afc9e2a4..800eca5c 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -122,13 +122,14 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { GFont subtitle_font = fonts_get_system_font(style->subtitle_font); GFont body_font = self->custom_body_font ? self->custom_body_font : fonts_get_system_font(style->body_font); - const int16_t x_margin = 5; - const int16_t y_margin = 2; + const int16_t margin_x = 5; + const int16_t margin_y = 2; + const int16_t image_offset_y = 3; GRect text_frame = frame; - text_frame.size.w -= 2 * x_margin; + text_frame.size.w -= 2 * margin_x; text_frame.size.h += 1000; - GPoint cursor = { x_margin, y_margin }; + GPoint cursor = { margin_x, margin_y }; graphics_context_set_text_color(ctx, GColorBlack); @@ -137,11 +138,12 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { bool has_body = is_string(self->body_text); GSize title_size, subtitle_size; - GPoint title_pos, subtitle_pos; + GPoint title_pos, subtitle_pos, image_pos = GPointZero; GRect body_rect; GBitmap *title_icon = simply_res_get_image(self->simply->res, self->title_icon); GBitmap *subtitle_icon = simply_res_get_image(self->simply->res, self->subtitle_icon); + GBitmap *body_image = simply_res_get_image(self->simply->res, self->image); if (has_title) { GRect title_frame = text_frame; @@ -175,16 +177,21 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { cursor.y += subtitle_size.h; } + if (body_image) { + image_pos = cursor; + cursor.y += body_image->bounds.size.h; + } + if (has_body) { body_rect = frame; body_rect.origin = cursor; - body_rect.size.w -= 2 * x_margin; - body_rect.size.h -= 2 * y_margin + cursor.y; + body_rect.size.w -= 2 * margin_x; + body_rect.size.h -= 2 * margin_y + cursor.y; GSize body_size = graphics_text_layout_get_content_size(self->body_text, body_font, text_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); if (self->is_scrollable) { body_rect.size = body_size; - int16_t new_height = cursor.y + 2 * y_margin + body_size.h; + int16_t new_height = cursor.y + 2 * margin_y + body_size.h; frame.size.h = window_frame.size.h > new_height ? window_frame.size.h : new_height; layer_set_frame(layer, frame); scroll_layer_set_content_size(self->scroll_layer, frame.size); @@ -198,7 +205,7 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { if (title_icon) { GRect icon_frame = (GRect) { - .origin = { x_margin, title_pos.y + 3 }, + .origin = { margin_x, title_pos.y + image_offset_y }, .size = { title_icon->bounds.size.w, title_size.h } }; graphics_draw_bitmap_centered(ctx, title_icon, icon_frame); @@ -211,7 +218,7 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { if (subtitle_icon) { GRect subicon_frame = (GRect) { - .origin = { x_margin, subtitle_pos.y + 3 }, + .origin = { margin_x, subtitle_pos.y + image_offset_y }, .size = { subtitle_icon->bounds.size.w, subtitle_size.h } }; graphics_draw_bitmap_centered(ctx, subtitle_icon, subicon_frame); @@ -222,6 +229,13 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { GTextOverflowModeWordWrap, GTextAlignmentLeft, NULL); } + if (body_image) { + GRect image_frame = (GRect) { + .origin = { 0, image_pos.y + image_offset_y }, + .size = { window_frame.size.w, body_image->bounds.size.h } + }; + graphics_draw_bitmap_centered(ctx, body_image, image_frame); + } if (has_body) { graphics_draw_text(ctx, self->body_text, body_font, body_rect, GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); From 453c4ba53e8209adc977cb708873d086104ea66e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 14 May 2014 01:24:06 -0700 Subject: [PATCH 049/791] Change api to accept path to images directly in image parameters --- src/js/simply.pebble.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/js/simply.pebble.js b/src/js/simply.pebble.js index dd73d8c5..0bf03035 100644 --- a/src/js/simply.pebble.js +++ b/src/js/simply.pebble.js @@ -2,6 +2,8 @@ var SimplyPebble = (function() { +var Image = 'Image'; + var commands = [{ name: 'setCard', params: [{ @@ -18,10 +20,13 @@ var commands = [{ type: String, }, { name: 'icon', + type: Image, }, { name: 'subicon', + type: Image, }, { name: 'banner', + type: Image, }], }, { name: 'singleClick', @@ -130,6 +135,7 @@ var commands = [{ type: String, }, { name: 'image', + type: Image, }], }, { name: 'getMenuItem', @@ -344,6 +350,8 @@ function makePacket(command, def) { v = v.toString(); } else if (param.type === Boolean) { v = v ? 1 : 0; + } else if (param.type === Image && typeof v === 'string') { + v = simply.image(v); } packet[param.id] = v; } From 74ac2e8ccd5308de5e6ac7f679104f0cf6bbf5c8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 14 May 2014 21:28:59 -0700 Subject: [PATCH 050/791] Move 3rd party scripts to vendor --- src/js/{ => vendor}/moment.min.js | 0 src/js/{ => vendor}/png.js | 0 src/js/{ => vendor}/zlib.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/js/{ => vendor}/moment.min.js (100%) rename src/js/{ => vendor}/png.js (100%) rename src/js/{ => vendor}/zlib.js (100%) diff --git a/src/js/moment.min.js b/src/js/vendor/moment.min.js similarity index 100% rename from src/js/moment.min.js rename to src/js/vendor/moment.min.js diff --git a/src/js/png.js b/src/js/vendor/png.js similarity index 100% rename from src/js/png.js rename to src/js/vendor/png.js diff --git a/src/js/zlib.js b/src/js/vendor/zlib.js similarity index 100% rename from src/js/zlib.js rename to src/js/vendor/zlib.js From 772afebb2f510805ab355985cf3fb92cbc1b4033 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 14 May 2014 21:29:17 -0700 Subject: [PATCH 051/791] Rename simply.pebble.js to simply-pebble.js --- src/js/{simply.pebble.js => simply-pebble.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/js/{simply.pebble.js => simply-pebble.js} (100%) diff --git a/src/js/simply.pebble.js b/src/js/simply-pebble.js similarity index 100% rename from src/js/simply.pebble.js rename to src/js/simply-pebble.js From 64d5090052ec3297042a536f4026ceaf57cea94e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 02:14:55 -0700 Subject: [PATCH 052/791] Move simply.js image logic to simply-image.js --- src/js/simply-image.js | 55 ++++++++++++++++++++++ src/js/simply.js | 101 ++++++++++++++--------------------------- 2 files changed, 88 insertions(+), 68 deletions(-) create mode 100644 src/js/simply-image.js diff --git a/src/js/simply-image.js b/src/js/simply-image.js new file mode 100644 index 00000000..c7befe3e --- /dev/null +++ b/src/js/simply-image.js @@ -0,0 +1,55 @@ +/* global simply, util2, PNG */ + +var SimplyImage = (function() { + +var SimplyImage = {}; + +SimplyImage.toGbitmap = function(png) { + var pixels = png.decode(); + var byteDepth = 4; + var rowBytes = png.width * byteDepth; + + var gpixels = []; + var growBytes = Math.ceil(png.width / 32) * 4; + for (var i = 0, ii = png.height * growBytes; i < ii; ++i) { + gpixels[i] = 0; + } + + for (var y = 0, yy = png.height; y < yy; ++y) { + for (var x = 0, xx = png.width; x < xx; ++x) { + var grey = 0; + var pos = y * rowBytes + parseInt(x * byteDepth); + var numColors = byteDepth - (png.hasAlphaChannel ? 1 : 0); + for (var j = 0; j < numColors; ++j) { + grey += pixels[pos + j]; + } + grey /= numColors * 255; + if (grey >= 0.5) { + var gbytePos = y * growBytes + parseInt(x / 8); + gpixels[gbytePos] += 1 << (x % 8); + } + } + } + + var gbitmap = { + width: png.width, + height: png.height, + pixels: gpixels, + }; + + return gbitmap; +}; + +SimplyImage.load = function(image, callback) { + PNG.load(image.path, function(png) { + image.gbitmap = SimplyImage.toGbitmap(png); + if (callback) { + callback(image); + } + }); + return image; +}; + +return SimplyImage; + +})(); diff --git a/src/js/simply.js b/src/js/simply.js index 11141e7d..4ec50361 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -2,7 +2,7 @@ * Simply.js * @namespace simply */ -/* global util2, PNG */ +/* global util2, PNG, SimplyImage */ var simply = (function() { var noop = typeof util2 !== 'undefined' ? util2.noop : function() {}; @@ -613,85 +613,50 @@ simply.style = function(type) { return simply.impl.style.apply(this, arguments); }; -var toGbitmap = function(png) { - var pixels = png.decodePixels(); - var bitDepth = png.pixelBitlength; - var byteDepth = bitDepth / 8; - var rowBytes = png.width * byteDepth; - - var gpixels = []; - var growBytes = Math.ceil(png.width / 32) * 4; - for (var i = 0, ii = png.height * growBytes; i < ii; ++i) { - gpixels[i] = 0; - } - - for (var y = 0, yy = png.height; y < yy; ++y) { - for (var x = 0, xx = png.width; x < xx; ++x) { - var grey = 0; - var pos; - if (bitDepth < 8) { - var bitPos = y * png.width * bitDepth + x * bitDepth; - pos = parseInt(bitPos / 8); - var mask = (1 << bitDepth) - 1; - var s = bitPos % (8 / bitDepth); - grey = (pixels[pos] >> (7 - s * bitDepth)) & mask; - grey /= mask; - } else { - pos = y * rowBytes + parseInt(x * byteDepth); - var numColors = byteDepth - (png.hasAlphaChannel ? 1 : 0); - for (var j = 0; j < numColors; ++j) { - grey += pixels[pos + j]; - } - grey /= numColors * 255; - } - if (grey >= 0.5) { - var gbytePos = y * growBytes + parseInt(x / 8); - gpixels[gbytePos] += 1 << (x % 8); - } +var getImageHash = function(image) { + return image.url + '#' + + '/w' + (image.width || 0) + + '/h' + (image.height || 0); +}; + +simply.image = function(opt, reset, callback) { + if (typeof opt === 'string') { + opt = { url: opt }; + } + if (typeof reset === 'function') { + callback = reset; + reset = null; + } + var url = simply.basepath() + opt.url; + var hash = getImageHash(opt); + var image = simply.state.images[hash]; + if (image) { + if ((opt.width && image.width !== opt.width) || + (opt.height && image.height !== opt.height)) { + reset = true; + } + if (reset !== true) { + return image.id; } } - - var gbitmap = { - width: png.width, - height: png.height, - pixels: gpixels, + image = { + id: simply.state.nextImageId++, + url: url, + width: opt.width, + height: opt.height, }; - - return gbitmap; -}; - -simply.loadImage = function(image, callback) { - PNG.load(image.path, function(png) { - image.gbitmap = toGbitmap(png); + simply.state.images[hash] = image; + SimplyImage.load(image, function() { simply.impl.image(image.id, image.gbitmap); if (callback) { var e = { type: 'image', image: image.id, - path: image.path, + url: image.url, }; callback(e); } }); - return image; -}; - -simply.image = function(path, reset, callback) { - if (typeof reset === 'function') { - callback = reset; - reset = null; - } - path = simply.basepath() + path; - var image = simply.state.images[path]; - if (image && reset !== true) { - return image.id; - } - image = { - id: simply.state.nextImageId++, - path: path, - }; - simply.state.images[path] = image; - simply.loadImage(image, callback); return image.id; }; From 6eb9c99bb3babef8024c599538af4ee7ffb9f32c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 02:15:17 -0700 Subject: [PATCH 053/791] Add simply image resize --- src/js/simply-image.js | 99 ++++++++++++++++++++++++++++++++++------- src/js/simply-pebble.js | 2 +- 2 files changed, 84 insertions(+), 17 deletions(-) diff --git a/src/js/simply-image.js b/src/js/simply-image.js index c7befe3e..24fbbd05 100644 --- a/src/js/simply-image.js +++ b/src/js/simply-image.js @@ -4,26 +4,73 @@ var SimplyImage = (function() { var SimplyImage = {}; -SimplyImage.toGbitmap = function(png) { - var pixels = png.decode(); - var byteDepth = 4; - var rowBytes = png.width * byteDepth; +var getPos = function(width, x, y) { + return y * width * 4 + x * 4; +}; + +SimplyImage.resizeNearest = function(pixels, width, height, newWidth, newHeight) { + var newPixels = new Array(newWidth * newHeight * 4); + var widthRatio = width / newWidth; + var heightRatio = height / newHeight; + for (var y = 0, yy = newHeight; y < yy; ++y) { + for (var x = 0, xx = newWidth; x < xx; ++x) { + var x2 = parseInt(x * widthRatio); + var y2 = parseInt(y * heightRatio); + var pos2 = getPos(width, x2, y2); + var pos = getPos(newWidth, x, y); + for (var i = 0; i < 4; ++i) { + newPixels[pos + i] = pixels[pos2 + i]; + } + } + } + return newPixels; +}; + +SimplyImage.resizeSample = function(pixels, width, height, newWidth, newHeight) { + var newPixels = new Array(newWidth * newHeight * 4); + var widthRatio = width / newWidth; + var heightRatio = height / newHeight; + for (var y = 0, yy = newHeight; y < yy; ++y) { + for (var x = 0, xx = newWidth; x < xx; ++x) { + var x2 = Math.min(parseInt(x * widthRatio), width - 1); + var y2 = Math.min(parseInt(y * heightRatio), height - 1); + var pos = getPos(newWidth, x, y); + for (var i = 0; i < 4; ++i) { + newPixels[pos + i] = ((pixels[getPos(width, x2 , y2 ) + i] + + pixels[getPos(width, x2+1, y2 ) + i] + + pixels[getPos(width, x2 , y2+1) + i] + + pixels[getPos(width, x2+1, y2+1) + i]) / 4) & 0xFF; + } + } + } + return newPixels; +}; + +SimplyImage.resize = function(pixels, width, height, newWidth, newHeight) { + if (newWidth < width || newHeight < height) { + return SimplyImage.resizeSample.apply(this, arguments); + } else { + return SimplyImage.resizeNearest.apply(this, arguments); + } +}; + +SimplyImage.toGbitmap = function(pixels, width, height) { + var rowBytes = width * 4; var gpixels = []; - var growBytes = Math.ceil(png.width / 32) * 4; - for (var i = 0, ii = png.height * growBytes; i < ii; ++i) { + var growBytes = Math.ceil(width / 32) * 4; + for (var i = 0, ii = height * growBytes; i < ii; ++i) { gpixels[i] = 0; } - for (var y = 0, yy = png.height; y < yy; ++y) { - for (var x = 0, xx = png.width; x < xx; ++x) { + for (var y = 0, yy = height; y < yy; ++y) { + for (var x = 0, xx = width; x < xx; ++x) { var grey = 0; - var pos = y * rowBytes + parseInt(x * byteDepth); - var numColors = byteDepth - (png.hasAlphaChannel ? 1 : 0); - for (var j = 0; j < numColors; ++j) { + var pos = y * rowBytes + parseInt(x * 4); + for (var j = 0; j < 3; ++j) { grey += pixels[pos + j]; } - grey /= numColors * 255; + grey /= 3 * 255; if (grey >= 0.5) { var gbytePos = y * growBytes + parseInt(x / 8); gpixels[gbytePos] += 1 << (x % 8); @@ -32,8 +79,8 @@ SimplyImage.toGbitmap = function(png) { } var gbitmap = { - width: png.width, - height: png.height, + width: width, + height: height, pixels: gpixels, }; @@ -41,8 +88,28 @@ SimplyImage.toGbitmap = function(png) { }; SimplyImage.load = function(image, callback) { - PNG.load(image.path, function(png) { - image.gbitmap = SimplyImage.toGbitmap(png); + PNG.load(image.url, function(png) { + var pixels = png.decode(); + var width = png.width; + var height = png.height; + if (image.width) { + if (!image.height) { + image.height = parseInt(height * (image.width / width)); + } + } else if (image.height) { + if (!image.width) { + image.width = parseInt(width * (image.height / height)); + } + } else { + image.width = width; + image.height = height; + } + if (image.width !== width || image.height !== height) { + pixels = SimplyImage.resize(pixels, width, height, image.width, image.height); + width = image.width; + height = image.height; + } + image.gbitmap = SimplyImage.toGbitmap(pixels, width, height); if (callback) { callback(image); } diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index 0bf03035..a767ac46 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -350,7 +350,7 @@ function makePacket(command, def) { v = v.toString(); } else if (param.type === Boolean) { v = v ? 1 : 0; - } else if (param.type === Image && typeof v === 'string') { + } else if (param.type === Image && typeof v !== 'number') { v = simply.image(v); } packet[param.id] = v; From ab5e007bd1ba49cf23bf69b54a4034b3ed8f5499 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 04:56:25 -0700 Subject: [PATCH 054/791] Add simply image dithering --- src/js/simply-image.js | 47 ++++++++++++++++++++++++++++++++++++++++++ src/js/simply.js | 7 +++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/js/simply-image.js b/src/js/simply-image.js index 24fbbd05..d8b97cf6 100644 --- a/src/js/simply-image.js +++ b/src/js/simply-image.js @@ -8,6 +8,49 @@ var getPos = function(width, x, y) { return y * width * 4 + x * 4; }; +var getPixelGrey = function(pixels, pos) { + return ((pixels[pos] + pixels[pos + 1] + pixels[pos + 2]) / 3) & 0xFF; +}; + +SimplyImage.greyscale = function(pixels, width, height) { + for (var y = 0, yy = height; y < yy; ++y) { + for (var x = 0, xx = width; x < xx; ++x) { + var pos = getPos(width, x, y); + var newColor = getPixelGrey(pixels, pos); + for (var i = 0; i < 3; ++i) { + pixels[pos + i] = newColor; + } + } + } +}; + +SimplyImage.dither = function(pixels, width, height) { + for (var y = 0, yy = height; y < yy; ++y) { + for (var x = 0, xx = width; x < xx; ++x) { + var pos = getPos(width, x, y); + var oldColor = pixels[pos]; + var newColor = oldColor >= 128 ? 255 : 0; + var error = oldColor - newColor; + pixels[pos] = newColor; + if (x + 1 < width) { + pixels[getPos(width, x+1, y )] += parseInt(error * 7/16); + } + if (x - 1 >= 0 && y + 1 < height) { + pixels[getPos(width, x-1, y+1)] += parseInt(error * 3/16); + } + if (y + 1 < height) { + pixels[getPos(width, x , y+1)] += parseInt(error * 5/16); + } + if (x + 1 < width && y + 1 < height) { + pixels[getPos(width, x+1, y+1)] += parseInt(error * 1/16); + } + for (var i = 1; i < 3; ++i) { + pixels[pos + i] = newColor; + } + } + } +}; + SimplyImage.resizeNearest = function(pixels, width, height, newWidth, newHeight) { var newPixels = new Array(newWidth * newHeight * 4); var widthRatio = width / newWidth; @@ -92,6 +135,7 @@ SimplyImage.load = function(image, callback) { var pixels = png.decode(); var width = png.width; var height = png.height; + SimplyImage.greyscale(pixels, width, height); if (image.width) { if (!image.height) { image.height = parseInt(height * (image.width / width)); @@ -109,6 +153,9 @@ SimplyImage.load = function(image, callback) { width = image.width; height = image.height; } + if (image.dither) { + SimplyImage.dither(pixels, width, height); + } image.gbitmap = SimplyImage.toGbitmap(pixels, width, height); if (callback) { callback(image); diff --git a/src/js/simply.js b/src/js/simply.js index 4ec50361..35dfdbe3 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -616,7 +616,8 @@ simply.style = function(type) { var getImageHash = function(image) { return image.url + '#' + '/w' + (image.width || 0) + - '/h' + (image.height || 0); + '/h' + (image.height || 0) + + '/d' + (image.dither || 0); }; simply.image = function(opt, reset, callback) { @@ -632,7 +633,8 @@ simply.image = function(opt, reset, callback) { var image = simply.state.images[hash]; if (image) { if ((opt.width && image.width !== opt.width) || - (opt.height && image.height !== opt.height)) { + (opt.height && image.height !== opt.height) || + (opt.dither && image.dither !== opt.dither)) { reset = true; } if (reset !== true) { @@ -644,6 +646,7 @@ simply.image = function(opt, reset, callback) { url: url, width: opt.width, height: opt.height, + dither: opt.dither, }; simply.state.images[hash] = image; SimplyImage.load(image, function() { From ca72f184238c53528efe63b3bd414738f04dfd7f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 05:16:39 -0700 Subject: [PATCH 055/791] Add simply image sierra dithering algorithm --- src/js/simply-image.js | 64 ++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/src/js/simply-image.js b/src/js/simply-image.js index d8b97cf6..26be04fb 100644 --- a/src/js/simply-image.js +++ b/src/js/simply-image.js @@ -24,7 +24,45 @@ SimplyImage.greyscale = function(pixels, width, height) { } }; -SimplyImage.dither = function(pixels, width, height) { +SimplyImage.Dithers = {}; + +SimplyImage.Dithers['floyd-steinberg'] = [ + [ 1, 0, 7/16], + [-1, 1, 3/16], + [ 0, 1, 5/16], + [ 1, 1, 1/16]]; + +SimplyImage.Dithers['jarvis-judice-ninke'] = [ + [ 1, 0, 7/48], + [ 2, 0, 5/48], + [-2, 1, 3/48], + [-1, 1, 5/48], + [ 0, 1, 7/48], + [ 1, 1, 5/48], + [ 2, 1, 3/48], + [-2, 2, 1/48], + [-1, 2, 3/48], + [ 0, 2, 5/48], + [ 1, 2, 3/48], + [ 2, 2, 1/48]]; + +SimplyImage.Dithers.sierra = [ + [ 1, 0, 5/32], + [ 2, 0, 3/32], + [-2, 1, 2/32], + [-1, 1, 4/32], + [ 0, 1, 5/32], + [ 1, 1, 4/32], + [ 2, 1, 2/32], + [-1, 2, 2/32], + [ 0, 2, 3/32], + [ 1, 2, 2/32]]; + +SimplyImage.Dithers['default'] = SimplyImage.Dithers.sierra; + +SimplyImage.dither = function(pixels, width, height, dithers) { + dithers = dithers || SimplyImage.Dithers['default']; + var numDithers = dithers.length; for (var y = 0, yy = height; y < yy; ++y) { for (var x = 0, xx = width; x < xx; ++x) { var pos = getPos(width, x, y); @@ -32,20 +70,15 @@ SimplyImage.dither = function(pixels, width, height) { var newColor = oldColor >= 128 ? 255 : 0; var error = oldColor - newColor; pixels[pos] = newColor; - if (x + 1 < width) { - pixels[getPos(width, x+1, y )] += parseInt(error * 7/16); + for (var i = 0; i < numDithers; ++i) { + var dither = dithers[i]; + var x2 = x + dither[0], y2 = y + dither[1]; + if (x2 >= 0 && x2 < width && y < height) { + pixels[getPos(width, x2, y2)] += parseInt(error * dither[2]); + } } - if (x - 1 >= 0 && y + 1 < height) { - pixels[getPos(width, x-1, y+1)] += parseInt(error * 3/16); - } - if (y + 1 < height) { - pixels[getPos(width, x , y+1)] += parseInt(error * 5/16); - } - if (x + 1 < width && y + 1 < height) { - pixels[getPos(width, x+1, y+1)] += parseInt(error * 1/16); - } - for (var i = 1; i < 3; ++i) { - pixels[pos + i] = newColor; + for (var j = 1; j < 3; ++j) { + pixels[pos + j] = newColor; } } } @@ -154,7 +187,8 @@ SimplyImage.load = function(image, callback) { height = image.height; } if (image.dither) { - SimplyImage.dither(pixels, width, height); + var dithers = SimplyImage.Dithers[image.dither]; + SimplyImage.dither(pixels, width, height, dithers); } image.gbitmap = SimplyImage.toGbitmap(pixels, width, height); if (callback) { From 31a80cbc22bfe77e8ae1a746e7b443da33dfc625 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 05:28:24 -0700 Subject: [PATCH 056/791] Change simply image hash format --- src/js/simply.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 35dfdbe3..421971fb 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -614,10 +614,21 @@ simply.style = function(type) { }; var getImageHash = function(image) { - return image.url + '#' + - '/w' + (image.width || 0) + - '/h' + (image.height || 0) + - '/d' + (image.dither || 0); + var url = image.url; + var hashPart = ''; + if (image.width) { + hashPart += ',width:' + image.width; + } + if (image.height) { + hashPart += ',height:' + image.height; + } + if (image.dither) { + hashPart += ',dither:' + image.dither; + } + if (hashPart) { + url += '#' + hashPart.substr(1); + } + return url; }; simply.image = function(opt, reset, callback) { From e3c486adfe05629f01150923fa27dbad443216e7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 05:39:07 -0700 Subject: [PATCH 057/791] Add simply image hash options support --- src/js/simply.js | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 421971fb..d2f4105d 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -613,7 +613,7 @@ simply.style = function(type) { return simply.impl.style.apply(this, arguments); }; -var getImageHash = function(image) { +var makeImageHash = function(image) { var url = image.url; var hashPart = ''; if (image.width) { @@ -631,16 +631,36 @@ var getImageHash = function(image) { return url; }; +var parseImageHash = function(hash) { + var image = {}; + hash = hash.split('#'); + image.url = hash[0]; + hash = hash[1]; + if (!hash) { return image; } + var args = hash.split(','); + for (var i = 0, ii = args.length; i < ii; ++i) { + var arg = args[i]; + if (arg.match(':')) { + arg = arg.split(':'); + var v = arg[1]; + image[arg[0]] = !isNaN(Number(v)) ? Number(v) : v; + } else { + image[arg] = true; + } + } + return image; +}; + simply.image = function(opt, reset, callback) { if (typeof opt === 'string') { - opt = { url: opt }; + opt = parseImageHash(opt); } if (typeof reset === 'function') { callback = reset; reset = null; } var url = simply.basepath() + opt.url; - var hash = getImageHash(opt); + var hash = makeImageHash(opt); var image = simply.state.images[hash]; if (image) { if ((opt.width && image.width !== opt.width) || From cbf278e2beba2d8172ab43a655c47fad5bcb5bb3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 06:12:18 -0700 Subject: [PATCH 058/791] Fix card exit event to export the card as card --- src/js/simply.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/simply.js b/src/js/simply.js index d2f4105d..db9553b7 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -869,7 +869,7 @@ simply.emitClick = function(type, button) { simply.emitCardExit = function() { var cardDef = simply.state.card; simply.emit('cardExit', util2.copy(cardDef, { - text: cardDef + card: cardDef })); }; From 527ce29fa77f8da21a5e9dcf2e300640db078a2b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 07:18:08 -0700 Subject: [PATCH 059/791] Add simply option for per-app options --- src/js/simply.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/js/simply.js b/src/js/simply.js index db9553b7..863f439c 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -71,6 +71,8 @@ simply.reset = function() { simply.state.card = {}; + simply.state.options = {}; + simply.state.button = { config: {}, configMode: 'auto', @@ -384,6 +386,7 @@ simply.loadMainScript = function(scriptUrl) { if (!scriptUrl) { return; } + simply.loadOptions(); try { simply.loadScript(scriptUrl, false); } catch (e) { @@ -694,6 +697,46 @@ simply.image = function(opt, reset, callback) { return image.id; }; +var getOptionsKey = function() { + return 'options:' + simply.basename(); +}; + +simply.saveOptions = function() { + var options = simply.state.options; + localStorage.setItem(getOptionsKey(), JSON.stringify(options)); +}; + +simply.loadOptions = function() { + simply.state.options = {}; + var options = localStorage.getItem(getOptionsKey()); + try { + simply.state.options = JSON.parse(options); + } catch (e) {} +}; + +simply.option = function(key, value) { + var options = simply.state.options; + if (arguments.length >= 2) { + if (typeof value === 'undefined') { + delete options[key]; + } else { + try { + value = JSON.stringify(value); + } catch (e) {} + options[key] = '' + value; + } + simply.saveOptions(); + } + value = options[key]; + if (!isNaN(Number(value))) { + return Number(value); + } + try { + value = JSON.parse(value); + } catch (e) {} + return value; +}; + simply.accelInit = function() { simply.state.accel = { rate: 100, From b3d32b4045b261701254bd040bab9921dbfc6b04 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 12:38:06 -0700 Subject: [PATCH 060/791] Add util2 last for getting the last element --- src/js/util2.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/js/util2.js b/src/js/util2.js index 45bf6f0b..0a2a38b3 100644 --- a/src/js/util2.js +++ b/src/js/util2.js @@ -6,34 +6,38 @@ var util2 = (function(util2){ util2.noop = function() {}; -util2.copy = function (a, b) { +util2.copy = function(a, b) { b = b || (a instanceof Array ? [] : {}); for (var k in a) { b[k] = a[k]; } return b; }; -util2.toInteger = function (x) { +util2.toInteger = function(x) { if (!isNaN(x = parseInt(x))) { return x; } }; -util2.toNumber = function (x) { +util2.toNumber = function(x) { if (!isNaN(x = parseFloat(x))) { return x; } }; -util2.toString = function (x) { +util2.toString = function(x) { return typeof x === 'object' ? JSON.stringify.apply(this, arguments) : '' + x; }; -util2.toArray = function (x) { +util2.toArray = function(x) { if (x instanceof Array) { return x; } if (x[0]) { return util2.copy(x, []); } return [x]; }; -util2.trim = function (s) { +util2.trim = function(s) { return s ? s.toString().trim() : s; }; +util2.last = function(a) { + return a[a.length-1]; +}; + var chunkSize = 128; var randomBytes = function(chunkSize) { @@ -44,7 +48,7 @@ var randomBytes = function(chunkSize) { return z.join(''); }; -util2.randomString = function (regex, size, acc) { +util2.randomString = function(regex, size, acc) { if (!size) { return ''; } @@ -65,7 +69,7 @@ util2.randomString = function (regex, size, acc) { var varpat = new RegExp("^([\\s\\S]*?)\\$([_a-zA-Z0-9]+)", "m"); -util2.format = function (text, table) { +util2.format = function(text, table) { var m, z = ''; while ((m = text.match(varpat))) { var subtext = m[0], value = table[m[2]]; From 80d69af60d1861105d71ee1fbd558713771e2de3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 12:38:48 -0700 Subject: [PATCH 061/791] Add simply settings for custom configurables --- src/js/simply-pebble.js | 20 ++-------- src/js/simply.js | 84 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 25 deletions(-) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index a767ac46..da8be59f 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -317,24 +317,12 @@ SimplyPebble.loadPackage = function(pkg, loader) { return SimplyPebble.papply(loader, null, pkg.name); }; -SimplyPebble.onWebViewClosed = function(e) { - if (!e.response) { - return; - } - - var options = JSON.parse(decodeURIComponent(e.response)); - simply.loadMainScript(options.scriptUrl); -}; - -SimplyPebble.getOptions = function() { - return { - scriptUrl: localStorage.getItem('mainJsUrl'), - }; +SimplyPebble.onShowConfiguration = function(e) { + simply.openSettings(e); }; -SimplyPebble.onShowConfiguration = function(e) { - var options = encodeURIComponent(JSON.stringify(SimplyPebble.getOptions())); - Pebble.openURL(simply.settingsUrl + '#' + options); +SimplyPebble.onWebViewClosed = function(e) { + simply.closeSettings(e); }; function makePacket(command, def) { diff --git a/src/js/simply.js b/src/js/simply.js index 863f439c..721d23b9 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -37,7 +37,7 @@ simply.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; simply.init = function() { if (!simply.inited) { - simply.inited = true; + simply.inited = new Date().getTime(); ajax.onHandler = function(type, handler) { return simply.wrapHandler(handler, 2); }; @@ -65,12 +65,7 @@ simply.reset = function() { simply.state = {}; simply.state.run = true; simply.state.numPackages = 0; - - simply.state.images = {}; - simply.state.nextImageId = 1; - simply.state.card = {}; - simply.state.options = {}; simply.state.button = { @@ -84,6 +79,15 @@ simply.reset = function() { } } + simply.state.image = { + cache: {}, + nextId: 1, + }; + + simply.state.webview = { + listeners: [], + }; + simply.accelInit(); }; @@ -664,7 +668,7 @@ simply.image = function(opt, reset, callback) { } var url = simply.basepath() + opt.url; var hash = makeImageHash(opt); - var image = simply.state.images[hash]; + var image = simply.state.image.cache[hash]; if (image) { if ((opt.width && image.width !== opt.width) || (opt.height && image.height !== opt.height) || @@ -676,13 +680,13 @@ simply.image = function(opt, reset, callback) { } } image = { - id: simply.state.nextImageId++, + id: simply.state.image.nextId++, url: url, width: opt.width, height: opt.height, dither: opt.dither, }; - simply.state.images[hash] = image; + simply.state.image.cache[hash] = image; SimplyImage.load(image, function() { simply.impl.image(image.id, image.gbitmap); if (callback) { @@ -737,6 +741,68 @@ simply.option = function(key, value) { return value; }; +simply.getBaseOptions = function() { + return { + scriptUrl: localStorage.getItem('mainJsUrl'), + }; +}; + +simply.settings = function(opt, open, close) { + if (typeof opt === 'string') { + opt = { url: opt }; + } + if (typeof close === 'undefined') { + close = open; + open = util2.noop; + } + var listener = { + params: opt, + open: open, + close: close, + }; + simply.state.webview.listeners.push(listener); +}; + +simply.openSettings = function(e) { + var options; + var url; + var listener = util2.last(simply.state.webview.listeners); + if (listener && (new Date().getTime() - simply.inited) > 2000) { + url = listener.params.url; + options = simply.state.options; + e = { + originalEvent: e, + options: options, + url: listener.params.url, + }; + listener.open(e); + } else { + url = simply.settingsUrl; + options = simply.getBaseOptions(); + } + var hash = encodeURIComponent(JSON.stringify(options)); + Pebble.openURL(url + '#' + hash); +}; + +simply.closeSettings = function(e) { + var listener = util2.last(simply.state.webview.listeners); + var options = {}; + if (e.response) { + options = JSON.parse(decodeURIComponent(e.response)); + } + if (listener) { + e = { + originalEvent: e, + options: options, + url: listener.params.url, + }; + return listener.close(e); + } + if (options.scriptUrl) { + simply.loadMainScript(options.scriptUrl); + } +}; + simply.accelInit = function() { simply.state.accel = { rate: 100, From 0db76afe0a9c6f60a3c6dc7e2682e14e7d17cec8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 22:40:56 -0700 Subject: [PATCH 062/791] Add simply res image placeholder creation --- src/simply_res.c | 10 ++++++++-- src/simply_res.h | 4 +++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/simply_res.c b/src/simply_res.c index efa456d6..ae8a22f4 100644 --- a/src/simply_res.c +++ b/src/simply_res.c @@ -46,12 +46,18 @@ void simply_res_remove_image(SimplyRes *self, uint32_t id) { } } -GBitmap *simply_res_get_image(SimplyRes *self, uint32_t id) { +GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder) { if (!id) { return NULL; } SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); - return image ? &image->bitmap : NULL; + if (image) { + return &image->bitmap; + } + if (!image && is_placeholder) { + return simply_res_add_image(self, id, 0, 0, NULL); + } + return NULL; } void simply_res_clear(SimplyRes *self) { diff --git a/src/simply_res.h b/src/simply_res.h index 7787c2aa..c602e6e9 100644 --- a/src/simply_res.h +++ b/src/simply_res.h @@ -4,6 +4,8 @@ #include +#define simply_res_get_image(self, id) simply_res_auto_image(self, id, false) + typedef struct SimplyRes SimplyRes; typedef struct SimplyImage SimplyImage; @@ -28,5 +30,5 @@ GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16 void simply_res_remove_image(SimplyRes *self, uint32_t id); -GBitmap *simply_res_get_image(SimplyRes *self, uint32_t id); +GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder); From a75d25bd967169a1090f9f15f261b94fe37d4529 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 22:42:20 -0700 Subject: [PATCH 063/791] Add simply ui action bar layer --- src/simply_ui.c | 59 +++++++++++++++++++++++++++++++++++++++++++++---- src/simply_ui.h | 8 +++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/simply_ui.c b/src/simply_ui.c index 800eca5c..543db84f 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -38,7 +38,26 @@ static SimplyStyle STYLES[] = { SimplyUi *s_ui = NULL; -static void click_config_provider(SimplyUi *self); +static void click_config_provider(void *data); + +void simply_ui_clear(SimplyUi *self, uint32_t clear_mask) { + if (clear_mask & (1 << 0)) { + simply_ui_set_text(self, &self->title_text, NULL); + simply_ui_set_text(self, &self->subtitle_text, NULL); + simply_ui_set_text(self, &self->body_text, NULL); + } + if (clear_mask & (1 << 1)) { + self->title_icon = 0; + self->subtitle_icon = 0; + self->image = 0; + } + if (clear_mask & (1 << 2)) { + simply_ui_set_action_bar(self, false); + for (ButtonId button = BUTTON_ID_UP; button <= BUTTON_ID_DOWN; ++button) { + action_bar_layer_clear_icon(self->action_bar_layer, button); + } + } +} void simply_ui_set_style(SimplyUi *self, int style_index) { if (self->custom_body_font) { @@ -85,6 +104,27 @@ void simply_ui_set_fullscreen(SimplyUi *self, bool is_fullscreen) { window_destroy(window); } +void simply_ui_set_action_bar(SimplyUi *self, bool is_action_bar) { + self->is_action_bar = is_action_bar; + action_bar_layer_remove_from_window(self->action_bar_layer); + if (is_action_bar) { + action_bar_layer_add_to_window(self->action_bar_layer, self->window); + action_bar_layer_set_click_config_provider(self->action_bar_layer, click_config_provider); + } else { + window_set_click_config_provider(self->window, click_config_provider); + } +} + +void simply_ui_set_action_bar_icon(SimplyUi *self, ButtonId button, uint32_t id) { + if (id) { + GBitmap *icon = simply_res_auto_image(self->simply->res, id, true); + action_bar_layer_set_icon(self->action_bar_layer, button, icon); + simply_ui_set_action_bar(self, true); + } else { + action_bar_layer_clear_icon(self->action_bar_layer, button); + } +} + void simply_ui_set_button(SimplyUi *self, ButtonId button, bool enable) { if (enable) { self->button_mask |= 1 << button; @@ -264,7 +304,8 @@ static void long_click_handler(ClickRecognizerRef recognizer, void *context) { } } -static void click_config_provider(SimplyUi *self) { +static void click_config_provider(void *data) { + SimplyUi *self = data; for (int i = 0; i < NUM_BUTTONS; ++i) { if (!self->is_scrollable || (i != BUTTON_ID_UP && i != BUTTON_ID_DOWN)) { window_single_click_subscribe(i, (ClickHandler) single_click_handler); @@ -305,10 +346,14 @@ static void window_load(Window *window) { scroll_layer_set_context(scroll_layer, self); scroll_layer_set_callbacks(scroll_layer, (ScrollLayerCallbacks) { - .click_config_provider = (ClickConfigProvider) click_config_provider, + .click_config_provider = click_config_provider, }); scroll_layer_set_click_config_onto_window(scroll_layer, window); + if (self->is_action_bar) { + simply_ui_set_action_bar(self, true); + } + simply_ui_set_style(self, 1); } @@ -353,13 +398,16 @@ SimplyUi *simply_ui_create(Simply *simply) { Window *window = self->window = window_create(); window_set_user_data(window, self); window_set_background_color(window, GColorBlack); - window_set_click_config_provider(window, (ClickConfigProvider) click_config_provider); + window_set_click_config_provider(window, click_config_provider); window_set_window_handlers(window, (WindowHandlers) { .load = window_load, .disappear = window_disappear, .unload = window_unload, }); + ActionBarLayer *action_bar_layer = self->action_bar_layer = action_bar_layer_create(); + action_bar_layer_set_context(action_bar_layer, self); + app_timer_register(10000, (AppTimerCallback) show_welcome_text, self); return self; @@ -370,6 +418,9 @@ void simply_ui_destroy(SimplyUi *self) { return; } + action_bar_layer_destroy(self->action_bar_layer); + self->action_bar_layer = NULL; + window_destroy(self->window); self->window = NULL; diff --git a/src/simply_ui.h b/src/simply_ui.h index c09915ef..e102c423 100644 --- a/src/simply_ui.h +++ b/src/simply_ui.h @@ -20,9 +20,11 @@ struct SimplyUi { uint32_t image; ScrollLayer *scroll_layer; Layer *display_layer; + ActionBarLayer *action_bar_layer; GFont custom_body_font; uint32_t button_mask; bool is_scrollable; + bool is_action_bar; }; SimplyUi *simply_ui_create(Simply *simply); @@ -31,6 +33,8 @@ void simply_ui_destroy(SimplyUi *self); void simply_ui_show(SimplyUi *self); +void simply_ui_clear(SimplyUi *self, uint32_t clear_mask); + void simply_ui_set_style(SimplyUi *self, int style_index); void simply_ui_set_text(SimplyUi *self, char **str_field, const char *str); @@ -39,5 +43,9 @@ void simply_ui_set_scrollable(SimplyUi *self, bool is_scrollable); void simply_ui_set_fullscreen(SimplyUi *self, bool is_fullscreen); +void simply_ui_set_action_bar(SimplyUi *self, bool is_action_bar); + +void simply_ui_set_action_bar_icon(SimplyUi *self, ButtonId button, uint32_t id); + void simply_ui_set_button(SimplyUi *self, ButtonId button, bool enable); From 33f0481e0b84380d98e69e3a1627de126882f595 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 23:27:36 -0700 Subject: [PATCH 064/791] Add simply msg card action and move card settings to card --- src/simply_msg.c | 114 +++++++++++++++++++++++++---------------------- 1 file changed, 60 insertions(+), 54 deletions(-) diff --git a/src/simply_msg.c b/src/simply_msg.c index 4496a36c..02c381cf 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -19,9 +19,6 @@ enum SimplyACmd { SimplyACmd_longClick, SimplyACmd_accelTap, SimplyACmd_vibe, - SimplyACmd_setScrollable, - SimplyACmd_setStyle, - SimplyACmd_setFullscreen, SimplyACmd_accelData, SimplyACmd_getAccelData, SimplyACmd_configAccelData, @@ -38,6 +35,25 @@ enum SimplyACmd { SimplyACmd_image, }; +typedef enum SimplySetUiParam SimplySetUiParam; + +enum SimplySetUiParam { + SetUi_clear = 1, + SetUi_title, + SetUi_subtitle, + SetUi_body, + SetUi_icon, + SetUi_subicon, + SetUi_banner, + SetUi_action, + SetUi_actionUp, + SetUi_actionSelect, + SetUi_actionDown, + SetUi_fullscreen, + SetUi_style, + SetUi_scrollable, +}; + typedef enum VibeType VibeType; enum VibeType { @@ -55,27 +71,47 @@ static void check_splash(Simply *simply) { static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { SimplyUi *ui = simply->ui; Tuple *tuple; - bool clear = false; - if ((tuple = dict_find(iter, 1))) { - clear = true; - } - if ((tuple = dict_find(iter, 2)) || clear) { - simply_ui_set_text(ui, &ui->title_text, tuple ? tuple->value->cstring : NULL); - } - if ((tuple = dict_find(iter, 3)) || clear) { - simply_ui_set_text(ui, &ui->subtitle_text, tuple ? tuple->value->cstring : NULL); - } - if ((tuple = dict_find(iter, 4)) || clear) { - simply_ui_set_text(ui, &ui->body_text, tuple ? tuple->value->cstring : NULL); - } - if ((tuple = dict_find(iter, 5)) || clear) { - ui->title_icon = tuple ? tuple->value->uint32 : 0; - } - if ((tuple = dict_find(iter, 6)) || clear) { - ui->subtitle_icon = tuple ? tuple->value->uint32 : 0; - } - if ((tuple = dict_find(iter, 7)) || clear) { - ui->image = tuple ? tuple->value->uint32 : 0; + if ((tuple = dict_find(iter, SetUi_clear))) { + simply_ui_clear(ui, tuple->value->uint32); + } + for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { + switch (tuple->key) { + case SetUi_title: + simply_ui_set_text(ui, &ui->title_text, tuple->value->cstring); + break; + case SetUi_subtitle: + simply_ui_set_text(ui, &ui->subtitle_text, tuple->value->cstring); + break; + case SetUi_body: + simply_ui_set_text(ui, &ui->body_text, tuple->value->cstring); + break; + case SetUi_icon: + ui->title_icon = tuple->value->uint32; + break; + case SetUi_subicon: + ui->title_icon = tuple->value->uint32; + break; + case SetUi_banner: + ui->image = tuple->value->uint32; + break; + case SetUi_action: + simply_ui_set_action_bar(simply->ui, tuple->value->int32); + break; + case SetUi_actionUp: + case SetUi_actionSelect: + case SetUi_actionDown: + simply_ui_set_action_bar_icon(simply->ui, tuple->key - SetUi_action, tuple->value->int32); + break; + case SetUi_style: + simply_ui_set_style(simply->ui, tuple->value->int32); + break; + case SetUi_fullscreen: + simply_ui_set_fullscreen(simply->ui, tuple->value->int32); + break; + case SetUi_scrollable: + simply_ui_set_scrollable(simply->ui, tuple->value->int32); + break; + } } simply_ui_show(simply->ui); } @@ -91,27 +127,6 @@ static void handle_vibe(DictionaryIterator *iter, Simply *simply) { } } -static void handle_set_scrollable(DictionaryIterator *iter, Simply *simply) { - Tuple *tuple; - if ((tuple = dict_find(iter, 1))) { - simply_ui_set_scrollable(simply->ui, tuple->value->int32); - } -} - -static void handle_set_style(DictionaryIterator *iter, Simply *simply) { - Tuple *tuple; - if ((tuple = dict_find(iter, 1))) { - simply_ui_set_style(simply->ui, tuple->value->int32); - } -} - -static void handle_set_fullscreen(DictionaryIterator *iter, Simply *simply) { - Tuple *tuple; - if ((tuple = dict_find(iter, 1))) { - simply_ui_set_fullscreen(simply->ui, tuple->value->int32); - } -} - static void handle_config_buttons(DictionaryIterator *iter, Simply *simply) { SimplyUi *ui = simply->ui; Tuple *tuple; @@ -246,15 +261,6 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_vibe: handle_vibe(iter, context); break; - case SimplyACmd_setScrollable: - handle_set_scrollable(iter, context); - break; - case SimplyACmd_setStyle: - handle_set_style(iter, context); - break; - case SimplyACmd_setFullscreen: - handle_set_fullscreen(iter, context); - break; case SimplyACmd_getAccelData: handle_get_accel_data(iter, context); break; From a5c1329b2033ea5d4eaa080322e8790dd8e87c60 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 May 2014 23:52:48 -0700 Subject: [PATCH 065/791] Add simply js action api --- src/js/simply-pebble.js | 120 ++++++++++++++++++++++++++++------------ src/js/simply.js | 59 ++++++++++++++++++-- 2 files changed, 139 insertions(+), 40 deletions(-) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index da8be59f..9b4b5268 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -27,6 +27,26 @@ var commands = [{ }, { name: 'banner', type: Image, + }, { + name: 'action', + type: Boolean, + }, { + name: 'actionUp', + type: Image, + }, { + name: 'actionSelect', + type: Image, + }, { + name: 'actionDown', + type: Image, + }, { + name: 'fullscreen', + type: Boolean, + }, { + name: 'style', + }, { + name: 'scrollable', + type: Boolean, }], }, { name: 'singleClick', @@ -50,21 +70,6 @@ var commands = [{ params: [{ name: 'type', }], -}, { - name: 'setScrollable', - params: [{ - name: 'scrollable', - }], -}, { - name: 'setStyle', - params: [{ - name: 'type', - }], -}, { - name: 'setFullscreen', - params: [{ - name: 'fullscreen', - }], }, { name: 'accelData', params: [{ @@ -223,6 +228,18 @@ var styleTypes = [ 'mono', ]; +var clearFlagMap = { + text: (1 << 0), + image: (1 << 1), + action: (1 << 2), +}; + +var actionBarTypeMap = { + up: 'actionUp', + select: 'actionSelect', + down: 'actionDown', +}; + var SimplyPebble = {}; SimplyPebble.init = function() { @@ -325,28 +342,38 @@ SimplyPebble.onWebViewClosed = function(e) { simply.closeSettings(e); }; -function makePacket(command, def) { +var toParam = function(param, v) { + if (param.type === String) { + v = v.toString(); + } else if (param.type === Boolean) { + v = v ? 1 : 0; + } else if (param.type === Image && typeof v !== 'number') { + v = simply.image(v); + } + return v; +}; + +var setPacket = function(packet, command, def, typeMap) { + var paramMap = command.paramMap; + for (var k in def) { + var paramName = typeMap && typeMap[k] || k; + if (!paramName) { continue; } + var param = paramMap[paramName]; + if (param) { + packet[param.id] = toParam(param, def[k]); + } + } + return packet; +}; + +var makePacket = function(command, def) { var packet = {}; packet[0] = command.id; if (def) { - var paramMap = command.paramMap; - for (var k in def) { - var param = paramMap[k]; - if (param) { - var v = def[k]; - if (param.type === String) { - v = v.toString(); - } else if (param.type === Boolean) { - v = v ? 1 : 0; - } else if (param.type === Image && typeof v !== 'number') { - v = simply.image(v); - } - packet[param.id] = v; - } - } + setPacket(packet, command, def); } return packet; -} +}; SimplyPebble.sendPacket = function(packet) { if (!simply.state.run) { @@ -370,8 +397,31 @@ SimplyPebble.buttonConfig = function(buttonConf) { }; SimplyPebble.card = function(cardDef, clear) { + if (clear === 'all') { + clear = ~0; + } else if (typeof clear === 'string') { + clear = clearFlagMap[clear]; + } else if (typeof clear === 'object') { + var flags = 0; + for (var k in clear) { + if (clear[k] === true) { + flags |= clearFlagMap[k]; + } + } + clear = flags; + } var command = commandMap.setCard; var packet = makePacket(command, cardDef); + if (clear) { + packet[command.paramMap.clear.id] = clear; + } + var actionDef = cardDef.action; + if (actionDef) { + if (typeof actionDef === 'boolean') { + actionDef = { action: actionDef }; + } + setPacket(packet, command, actionDef, actionBarTypeMap); + } SimplyPebble.sendPacket(packet); }; @@ -397,21 +447,21 @@ SimplyPebble.vibe = function(type) { }; SimplyPebble.scrollable = function(scrollable) { - var command = commandMap.setScrollable; + var command = commandMap.card; var packet = makePacket(command); packet[command.paramMap.scrollable.id] = scrollable ? 1 : 0; SimplyPebble.sendPacket(packet); }; SimplyPebble.fullscreen = function(fullscreen) { - var command = commandMap.setFullscreen; + var command = commandMap.card; var packet = makePacket(command); packet[command.paramMap.fullscreen.id] = fullscreen ? 1 : 0; SimplyPebble.sendPacket(packet); }; SimplyPebble.style = function(type) { - var command = commandMap.setStyle; + var command = commandMap.card; var packet = makePacket(command); var styleIndex = styleTypes.indexOf(type); packet[command.paramMap.type.id] = styleIndex !== -1 ? styleIndex : 1; diff --git a/src/js/simply.js b/src/js/simply.js index 721d23b9..93af2358 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -29,6 +29,26 @@ var eventTypes = [ 'menuExit', ]; +var textParams = [ + 'title', + 'subtitle', + 'body', +]; + +var imageParams = [ + 'icon', + 'subicon', + 'banner', +]; + +var actionParams = [ + 'up', + 'select', + 'back', +]; + +var cardParams = textParams.concat(imageParams).concat(actionParams); + simply.state = {}; simply.packages = {}; simply.listeners = {}; @@ -38,6 +58,7 @@ simply.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; simply.init = function() { if (!simply.inited) { simply.inited = new Date().getTime(); + ajax.onHandler = function(type, handler) { return simply.wrapHandler(handler, 2); }; @@ -310,8 +331,8 @@ var slog = function() { }; simply.fexecPackage = function(script, pkg) { - // console shim for Android - var console2 = {}; + // console shim + var console2 = simply.console2 = {}; for (var k in console) { console2[k] = console[k]; } @@ -489,7 +510,7 @@ simply.buttonAutoConfig = function() { }; simply.card = function(cardDef, clear) { - if (typeof cardDef === 'undefined') { + if (arguments.length === 0) { return simply.state.card; } else if (typeof cardDef === 'object') { if (clear) { @@ -498,7 +519,7 @@ simply.card = function(cardDef, clear) { util2.copy(cardDef, simply.state.card); } } else { - throw new Error('simply.text takes a cardDef object'); + throw new Error('simply.card takes a cardDef object'); } return simply.impl.card.apply(this, arguments); }; @@ -525,7 +546,7 @@ simply.text = simply.card; simply.setText = simply.text; var textfield = function(field, text, clear) { - if (typeof text === 'undefined') { + if (arguments.length <= 1) { return simply.state.card[field]; } if (clear) { @@ -567,6 +588,34 @@ simply.body = function(text, clear) { return textfield('body', text, clear); }; +simply.action = function(field, image, clear) { + var actionDef; + if (typeof field === 'string') { + actionDef = {}; + actionDef[field] = image; + } else { + actionDef = field; + clear = image; + image = null; + } + clear = clear === true ? 'action' : clear; + if (!simply.state.card.action) { + simply.state.card.action = {}; + } + if (arguments.length === 0) { + return simply.state.card.action; + } else if (typeof actionDef === 'object') { + if (clear === 'all' || clear === 'action') { + simply.state.card.action = actionDef; + } else { + util2.copy(actionDef, simply.state.card.action); + } + } else { + throw new Error('simply.action takes a actionDef object'); + } + return simply.impl.card({ action: actionDef }, clear); +}; + /** * Vibrates the Pebble. * There are three support vibe types: short, long, and double. From 093075cc51a9e9c1b9ed22f18b38cf50c729c7bd Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 00:02:54 -0700 Subject: [PATCH 066/791] Reduce simply ui text frame width when action bar is enabled --- src/simply_ui.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/simply_ui.c b/src/simply_ui.c index 543db84f..58e80e14 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -171,6 +171,10 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { text_frame.size.h += 1000; GPoint cursor = { margin_x, margin_y }; + if (self->is_action_bar) { + text_frame.size.w -= ACTION_BAR_WIDTH; + } + graphics_context_set_text_color(ctx, GColorBlack); bool has_title = is_string(self->title_text); @@ -225,7 +229,7 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { if (has_body) { body_rect = frame; body_rect.origin = cursor; - body_rect.size.w -= 2 * margin_x; + body_rect.size.w = text_frame.size.w; body_rect.size.h -= 2 * margin_y + cursor.y; GSize body_size = graphics_text_layout_get_content_size(self->body_text, body_font, text_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); From e26afc05fb7b83ccdab49b1c3765e38fc240e7da Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 04:00:40 -0700 Subject: [PATCH 067/791] Change simply card api to accept setting single values as well --- src/js/simply.js | 50 +++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 93af2358..47fcf6fb 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -509,19 +509,29 @@ simply.buttonAutoConfig = function() { } }; -simply.card = function(cardDef, clear) { +simply.card = function(field, value, clear) { if (arguments.length === 0) { return simply.state.card; - } else if (typeof cardDef === 'object') { - if (clear) { - simply.state.card = cardDef; - } else { - util2.copy(cardDef, simply.state.card); + } + var cardDef; + if (typeof field === 'string') { + if (arguments.length === 1) { + return simply.state.card[field]; } + cardDef = {}; + cardDef[field] = value; + } else { + cardDef = field; + clear = value; + value = null; + } + clear = clear === true ? 'all' : clear; + if (clear === 'all') { + simply.state.card = cardDef; } else { - throw new Error('simply.card takes a cardDef object'); + util2.copy(cardDef, simply.state.card); } - return simply.impl.card.apply(this, arguments); + return simply.impl.card(cardDef, clear); }; /** @@ -589,8 +599,17 @@ simply.body = function(text, clear) { }; simply.action = function(field, image, clear) { + if (!simply.state.card.action) { + simply.state.card.action = {}; + } + if (arguments.length === 0) { + return simply.state.card.action; + } var actionDef; if (typeof field === 'string') { + if (arguments.length === 1) { + return simply.state.card.action[field]; + } actionDef = {}; actionDef[field] = image; } else { @@ -599,19 +618,10 @@ simply.action = function(field, image, clear) { image = null; } clear = clear === true ? 'action' : clear; - if (!simply.state.card.action) { - simply.state.card.action = {}; - } - if (arguments.length === 0) { - return simply.state.card.action; - } else if (typeof actionDef === 'object') { - if (clear === 'all' || clear === 'action') { - simply.state.card.action = actionDef; - } else { - util2.copy(actionDef, simply.state.card.action); - } + if (clear === 'all' || clear === 'action') { + simply.state.card.action = actionDef; } else { - throw new Error('simply.action takes a actionDef object'); + util2.copy(actionDef, simply.state.card.action); } return simply.impl.card({ action: actionDef }, clear); }; From dd18da528e9b93e357238a4924036cf93e88e1de Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 04:06:07 -0700 Subject: [PATCH 068/791] Adjust simply ui body image centering with action bar --- src/simply_ui.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/simply_ui.c b/src/simply_ui.c index 58e80e14..fe1bcbf4 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -173,6 +173,7 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { if (self->is_action_bar) { text_frame.size.w -= ACTION_BAR_WIDTH; + window_frame.size.w -= ACTION_BAR_WIDTH; } graphics_context_set_text_color(ctx, GColorBlack); From ac6fbe8c65afb631acd3d5d427cc3b34979a8a2d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 04:20:16 -0700 Subject: [PATCH 069/791] Fix simply ui set fullscreen to check for the display layer --- src/simply_ui.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/simply_ui.c b/src/simply_ui.c index fe1bcbf4..bb28a9ae 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -89,6 +89,10 @@ void simply_ui_set_scrollable(SimplyUi *self, bool is_scrollable) { void simply_ui_set_fullscreen(SimplyUi *self, bool is_fullscreen) { window_set_fullscreen(self->window, is_fullscreen); + if (!self->display_layer) { + return; + } + GRect frame = layer_get_frame(window_get_root_layer(self->window)); scroll_layer_set_frame(self->scroll_layer, frame); layer_set_frame(self->display_layer, frame); From 609c1b200ad4c033ed224a9f48065b64e9c56b46 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 06:22:00 -0700 Subject: [PATCH 070/791] Split simply ui into simply ui and simply window --- src/simply_msg.c | 26 ++-- src/simply_ui.c | 285 ++++++++++++-------------------------------- src/simply_ui.h | 60 +++++----- src/simply_window.c | 195 ++++++++++++++++++++++++++++++ src/simply_window.h | 34 ++++++ 5 files changed, 349 insertions(+), 251 deletions(-) create mode 100644 src/simply_window.c create mode 100644 src/simply_window.h diff --git a/src/simply_msg.c b/src/simply_msg.c index 02c381cf..4ec55a77 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -77,39 +77,31 @@ static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { case SetUi_title: - simply_ui_set_text(ui, &ui->title_text, tuple->value->cstring); - break; case SetUi_subtitle: - simply_ui_set_text(ui, &ui->subtitle_text, tuple->value->cstring); - break; case SetUi_body: - simply_ui_set_text(ui, &ui->body_text, tuple->value->cstring); + simply_ui_set_text(ui, tuple->key - SetUi_title, tuple->value->cstring); break; case SetUi_icon: - ui->title_icon = tuple->value->uint32; - break; case SetUi_subicon: - ui->title_icon = tuple->value->uint32; - break; case SetUi_banner: - ui->image = tuple->value->uint32; + ui->ui_layer.imagefields[tuple->key - SetUi_icon] = tuple->value->uint32; break; case SetUi_action: - simply_ui_set_action_bar(simply->ui, tuple->value->int32); + simply_window_set_action_bar(&ui->window, tuple->value->int32); break; case SetUi_actionUp: case SetUi_actionSelect: case SetUi_actionDown: - simply_ui_set_action_bar_icon(simply->ui, tuple->key - SetUi_action, tuple->value->int32); + simply_window_set_action_bar_icon(&ui->window, tuple->key - SetUi_action, tuple->value->int32); break; case SetUi_style: simply_ui_set_style(simply->ui, tuple->value->int32); break; case SetUi_fullscreen: - simply_ui_set_fullscreen(simply->ui, tuple->value->int32); + simply_window_set_fullscreen(&ui->window, tuple->value->int32); break; case SetUi_scrollable: - simply_ui_set_scrollable(simply->ui, tuple->value->int32); + simply_window_set_scrollable(&ui->window, tuple->value->int32); break; } } @@ -132,7 +124,7 @@ static void handle_config_buttons(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; for (int i = 0; i < NUM_BUTTONS; ++i) { if ((tuple = dict_find(iter, i + 1))) { - simply_ui_set_button(ui, i, tuple->value->int32); + simply_window_set_button(&ui->window, i, tuple->value->int32); } } } @@ -294,8 +286,8 @@ static void sent_callback(DictionaryIterator *iter, void *context) { static void failed_callback(DictionaryIterator *iter, AppMessageResult reason, Simply *simply) { SimplyUi *ui = simply->ui; if (reason == APP_MSG_NOT_CONNECTED) { - simply_ui_set_text(ui, &ui->subtitle_text, "Disconnected"); - simply_ui_set_text(ui, &ui->body_text, "Run the Pebble Phone App"); + simply_ui_set_text(ui, UiSubtitle, "Disconnected"); + simply_ui_set_text(ui, UiBody, "Run the Pebble Phone App"); check_splash(simply); } diff --git a/src/simply_ui.c b/src/simply_ui.c index bb28a9ae..2173858b 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -36,105 +36,31 @@ static SimplyStyle STYLES[] = { }, }; -SimplyUi *s_ui = NULL; - -static void click_config_provider(void *data); - void simply_ui_clear(SimplyUi *self, uint32_t clear_mask) { if (clear_mask & (1 << 0)) { - simply_ui_set_text(self, &self->title_text, NULL); - simply_ui_set_text(self, &self->subtitle_text, NULL); - simply_ui_set_text(self, &self->body_text, NULL); + for (SimplyUiTextfield textfield = 0; textfield < NumUiTextfields; ++textfield) { + simply_ui_set_text(self, textfield, NULL); + } } if (clear_mask & (1 << 1)) { - self->title_icon = 0; - self->subtitle_icon = 0; - self->image = 0; + memset(self->ui_layer.imagefields, 0, sizeof(self->ui_layer.imagefields)); } if (clear_mask & (1 << 2)) { - simply_ui_set_action_bar(self, false); - for (ButtonId button = BUTTON_ID_UP; button <= BUTTON_ID_DOWN; ++button) { - action_bar_layer_clear_icon(self->action_bar_layer, button); - } + simply_window_set_action_bar(&self->window, false); + simply_window_action_bar_clear(&self->window); } } void simply_ui_set_style(SimplyUi *self, int style_index) { - if (self->custom_body_font) { - fonts_unload_custom_font(self->custom_body_font); - self->custom_body_font = NULL; + if (self->ui_layer.custom_body_font) { + fonts_unload_custom_font(self->ui_layer.custom_body_font); + self->ui_layer.custom_body_font = NULL; } - self->style = &STYLES[style_index]; - if (self->style->custom_body_font_id) { - self->custom_body_font = fonts_load_custom_font(self->custom_body_font); - } - layer_mark_dirty(self->display_layer); -} - -void simply_ui_set_scrollable(SimplyUi *self, bool is_scrollable) { - self->is_scrollable = is_scrollable; - scroll_layer_set_click_config_onto_window(self->scroll_layer, self->window); - - if (!is_scrollable) { - GRect bounds = layer_get_bounds(window_get_root_layer(self->window)); - layer_set_bounds(self->display_layer, bounds); - const bool animated = true; - scroll_layer_set_content_offset(self->scroll_layer, GPointZero, animated); - scroll_layer_set_content_size(self->scroll_layer, bounds.size); - } - - layer_mark_dirty(self->display_layer); -} - -void simply_ui_set_fullscreen(SimplyUi *self, bool is_fullscreen) { - window_set_fullscreen(self->window, is_fullscreen); - - if (!self->display_layer) { - return; - } - - GRect frame = layer_get_frame(window_get_root_layer(self->window)); - scroll_layer_set_frame(self->scroll_layer, frame); - layer_set_frame(self->display_layer, frame); - - if (!window_stack_contains_window(self->window)) { - return; - } - - // HACK: Refresh app chrome state - Window *window = window_create(); - window_stack_push(window, false); - window_stack_remove(window, false); - window_destroy(window); -} - -void simply_ui_set_action_bar(SimplyUi *self, bool is_action_bar) { - self->is_action_bar = is_action_bar; - action_bar_layer_remove_from_window(self->action_bar_layer); - if (is_action_bar) { - action_bar_layer_add_to_window(self->action_bar_layer, self->window); - action_bar_layer_set_click_config_provider(self->action_bar_layer, click_config_provider); - } else { - window_set_click_config_provider(self->window, click_config_provider); - } -} - -void simply_ui_set_action_bar_icon(SimplyUi *self, ButtonId button, uint32_t id) { - if (id) { - GBitmap *icon = simply_res_auto_image(self->simply->res, id, true); - action_bar_layer_set_icon(self->action_bar_layer, button, icon); - simply_ui_set_action_bar(self, true); - } else { - action_bar_layer_clear_icon(self->action_bar_layer, button); - } -} - -void simply_ui_set_button(SimplyUi *self, ButtonId button, bool enable) { - if (enable) { - self->button_mask |= 1 << button; - } else { - self->button_mask &= ~(1 << button); + self->ui_layer.style = &STYLES[style_index]; + if (self->ui_layer.style->custom_body_font_id) { + self->ui_layer.custom_body_font = fonts_load_custom_font(self->ui_layer.custom_body_font); } + layer_mark_dirty(self->ui_layer.layer); } static void set_text(char **str_field, const char *str) { @@ -148,23 +74,24 @@ static void set_text(char **str_field, const char *str) { *str_field = strdup2(str); } -void simply_ui_set_text(SimplyUi *self, char **str_field, const char *str) { +void simply_ui_set_text(SimplyUi *self, SimplyUiTextfield textfield, const char *str) { + char **str_field = &self->ui_layer.textfields[textfield]; set_text(str_field, str); - if (self->display_layer) { - layer_mark_dirty(self->display_layer); + if (self->ui_layer.layer) { + layer_mark_dirty(self->ui_layer.layer); } } -void display_layer_update_callback(Layer *layer, GContext *ctx) { - SimplyUi *self = s_ui; +void layer_update_callback(Layer *layer, GContext *ctx) { + SimplyUi *self = *(void**) layer_get_data(layer); - GRect window_frame = layer_get_frame(window_get_root_layer(self->window)); + GRect window_frame = layer_get_frame(window_get_root_layer(self->window.window)); GRect frame = layer_get_frame(layer); - const SimplyStyle *style = self->style; + const SimplyStyle *style = self->ui_layer.style; GFont title_font = fonts_get_system_font(style->title_font); GFont subtitle_font = fonts_get_system_font(style->subtitle_font); - GFont body_font = self->custom_body_font ? self->custom_body_font : fonts_get_system_font(style->body_font); + GFont body_font = self->ui_layer.custom_body_font ? self->ui_layer.custom_body_font : fonts_get_system_font(style->body_font); const int16_t margin_x = 5; const int16_t margin_y = 2; @@ -175,24 +102,31 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { text_frame.size.h += 1000; GPoint cursor = { margin_x, margin_y }; - if (self->is_action_bar) { + if (self->window.is_action_bar) { text_frame.size.w -= ACTION_BAR_WIDTH; window_frame.size.w -= ACTION_BAR_WIDTH; } graphics_context_set_text_color(ctx, GColorBlack); - bool has_title = is_string(self->title_text); - bool has_subtitle = is_string(self->subtitle_text); - bool has_body = is_string(self->body_text); + const char *title_text = self->ui_layer.textfields[UiTitle]; + const char *subtitle_text = self->ui_layer.textfields[UiSubtitle]; + const char *body_text = self->ui_layer.textfields[UiBody]; + + bool has_title = is_string(title_text); + bool has_subtitle = is_string(subtitle_text); + bool has_body = is_string(body_text); GSize title_size, subtitle_size; GPoint title_pos, subtitle_pos, image_pos = GPointZero; GRect body_rect; - GBitmap *title_icon = simply_res_get_image(self->simply->res, self->title_icon); - GBitmap *subtitle_icon = simply_res_get_image(self->simply->res, self->subtitle_icon); - GBitmap *body_image = simply_res_get_image(self->simply->res, self->image); + GBitmap *title_icon = simply_res_get_image( + self->window.simply->res, self->ui_layer.imagefields[UiTitleIcon]); + GBitmap *subtitle_icon = simply_res_get_image( + self->window.simply->res, self->ui_layer.imagefields[UiSubtitleIcon]); + GBitmap *body_image = simply_res_get_image( + self->window.simply->res, self->ui_layer.imagefields[UiBodyImage]); if (has_title) { GRect title_frame = text_frame; @@ -200,7 +134,7 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { title_frame.origin.x += title_icon->bounds.size.w; title_frame.size.w -= title_icon->bounds.size.w; } - title_size = graphics_text_layout_get_content_size(self->title_text, + title_size = graphics_text_layout_get_content_size(title_text, title_font, title_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); title_size.w = title_frame.size.w; title_pos = cursor; @@ -216,7 +150,7 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { subtitle_frame.origin.x += subtitle_icon->bounds.size.w; subtitle_frame.size.w -= subtitle_icon->bounds.size.w; } - subtitle_size = graphics_text_layout_get_content_size(self->subtitle_text, + subtitle_size = graphics_text_layout_get_content_size(subtitle_text, title_font, subtitle_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); subtitle_size.w = subtitle_frame.size.w; subtitle_pos = cursor; @@ -236,15 +170,15 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { body_rect.origin = cursor; body_rect.size.w = text_frame.size.w; body_rect.size.h -= 2 * margin_y + cursor.y; - GSize body_size = graphics_text_layout_get_content_size(self->body_text, + GSize body_size = graphics_text_layout_get_content_size(body_text, body_font, text_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); - if (self->is_scrollable) { + if (self->window.is_scrollable) { body_rect.size = body_size; int16_t new_height = cursor.y + 2 * margin_y + body_size.h; frame.size.h = window_frame.size.h > new_height ? window_frame.size.h : new_height; layer_set_frame(layer, frame); - scroll_layer_set_content_size(self->scroll_layer, frame.size); - } else if (!self->custom_body_font && body_size.h > body_rect.size.h) { + scroll_layer_set_content_size(self->window.scroll_layer, frame.size); + } else if (!self->ui_layer.custom_body_font && body_size.h > body_rect.size.h) { body_font = fonts_get_system_font(FONT_KEY_GOTHIC_18); } } @@ -260,7 +194,7 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { graphics_draw_bitmap_centered(ctx, title_icon, icon_frame); } if (has_title) { - graphics_draw_text(ctx, self->title_text, title_font, + graphics_draw_text(ctx, title_text, title_font, (GRect) { .origin = title_pos, .size = title_size }, GTextOverflowModeWordWrap, GTextAlignmentLeft, NULL); } @@ -273,7 +207,7 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { graphics_draw_bitmap_centered(ctx, subtitle_icon, subicon_frame); } if (has_subtitle) { - graphics_draw_text(ctx, self->subtitle_text, subtitle_font, + graphics_draw_text(ctx, subtitle_text, subtitle_font, (GRect) { .origin = subtitle_pos, .size = subtitle_size }, GTextOverflowModeWordWrap, GTextAlignmentLeft, NULL); } @@ -286,54 +220,26 @@ void display_layer_update_callback(Layer *layer, GContext *ctx) { graphics_draw_bitmap_centered(ctx, body_image, image_frame); } if (has_body) { - graphics_draw_text(ctx, self->body_text, body_font, body_rect, + graphics_draw_text(ctx, body_text, body_font, body_rect, GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); } } -static void single_click_handler(ClickRecognizerRef recognizer, void *context) { - SimplyUi *self = s_ui; - ButtonId button = click_recognizer_get_button_id(recognizer); - bool is_enabled = (self->button_mask & (1 << button)); - if (button == BUTTON_ID_BACK && !is_enabled) { - bool animated = true; - window_stack_pop(animated); - } - if (is_enabled) { - simply_msg_single_click(button); - } -} - -static void long_click_handler(ClickRecognizerRef recognizer, void *context) { - SimplyUi *self = s_ui; - ButtonId button = click_recognizer_get_button_id(recognizer); - bool is_enabled = (self->button_mask & (1 << button)); - if (is_enabled) { - simply_msg_long_click(button); - } -} - -static void click_config_provider(void *data) { - SimplyUi *self = data; - for (int i = 0; i < NUM_BUTTONS; ++i) { - if (!self->is_scrollable || (i != BUTTON_ID_UP && i != BUTTON_ID_DOWN)) { - window_single_click_subscribe(i, (ClickHandler) single_click_handler); - window_long_click_subscribe(i, 500, (ClickHandler) long_click_handler, NULL); - } - } -} - static void show_welcome_text(SimplyUi *self) { - if (self->title_text || self->subtitle_text || self->body_text) { + const char *title_text = self->ui_layer.textfields[UiTitle]; + const char *subtitle_text = self->ui_layer.textfields[UiSubtitle]; + const char *body_text = self->ui_layer.textfields[UiBody]; + + if (title_text || subtitle_text || body_text) { return; } - if (self->simply->menu->menu_layer) { + if (self->window.simply->menu->menu_layer) { return; } - simply_ui_set_text(self, &self->title_text, "Simply.js"); - simply_ui_set_text(self, &self->subtitle_text, "Write apps with JS!"); - simply_ui_set_text(self, &self->body_text, "Visit simplyjs.io for details."); + simply_ui_set_text(self, UiTitle, "Simply.js"); + simply_ui_set_text(self, UiSubtitle, "Write apps with JS!"); + simply_ui_set_text(self, UiBody, "Visit simplyjs.io for details."); simply_ui_show(self); } @@ -341,82 +247,57 @@ static void show_welcome_text(SimplyUi *self) { static void window_load(Window *window) { SimplyUi *self = window_get_user_data(window); + simply_window_load(&self->window); + Layer *window_layer = window_get_root_layer(window); GRect frame = layer_get_frame(window_layer); frame.origin = GPointZero; - ScrollLayer *scroll_layer = self->scroll_layer = scroll_layer_create(frame); - Layer *scroll_base_layer = scroll_layer_get_layer(scroll_layer); - layer_add_child(window_layer, scroll_base_layer); - - Layer *display_layer = self->display_layer = layer_create(frame); - layer_set_update_proc(display_layer, display_layer_update_callback); - scroll_layer_add_child(scroll_layer, display_layer); - - scroll_layer_set_context(scroll_layer, self); - scroll_layer_set_callbacks(scroll_layer, (ScrollLayerCallbacks) { - .click_config_provider = click_config_provider, - }); - scroll_layer_set_click_config_onto_window(scroll_layer, window); - - if (self->is_action_bar) { - simply_ui_set_action_bar(self, true); - } + Layer *layer = layer_create_with_data(frame, sizeof(void*)); + self->window.layer = self->ui_layer.layer = layer; + *(void**) layer_get_data(layer) = self; + layer_set_update_proc(layer, layer_update_callback); + scroll_layer_add_child(self->window.scroll_layer, layer); simply_ui_set_style(self, 1); } +static void window_disappear(Window *window) { + simply_msg_ui_exit(); +} + static void window_unload(Window *window) { SimplyUi *self = window_get_user_data(window); - layer_destroy(self->display_layer); - self->display_layer = NULL; - scroll_layer_destroy(self->scroll_layer); - self->scroll_layer = NULL; -} + layer_destroy(self->ui_layer.layer); + self->window.layer = self->ui_layer.layer = NULL; -static void window_disappear(Window *window) { - simply_msg_ui_exit(); + simply_window_unload(&self->window); } void simply_ui_show(SimplyUi *self) { - if (!self->window) { + if (!self->window.window) { return; } - if (!window_stack_contains_window(self->window)) { + if (!window_stack_contains_window(self->window.window)) { bool animated = true; - window_stack_push(self->window, animated); + window_stack_push(self->window.window, animated); } } SimplyUi *simply_ui_create(Simply *simply) { - if (s_ui) { - return s_ui; - } - SimplyUi *self = malloc(sizeof(*self)); - *self = (SimplyUi) { .simply = simply }; - s_ui = self; + *self = (SimplyUi) { .window.layer = NULL }; - for (int i = 0; i < NUM_BUTTONS; ++i) { - if (i != BUTTON_ID_BACK) { - self->button_mask |= 1 << i; - } - } + simply_window_init(&self->window, simply); - Window *window = self->window = window_create(); - window_set_user_data(window, self); - window_set_background_color(window, GColorBlack); - window_set_click_config_provider(window, click_config_provider); - window_set_window_handlers(window, (WindowHandlers) { + window_set_user_data(self->window.window, self); + window_set_window_handlers(self->window.window, (WindowHandlers) { .load = window_load, .disappear = window_disappear, .unload = window_unload, }); - ActionBarLayer *action_bar_layer = self->action_bar_layer = action_bar_layer_create(); - action_bar_layer_set_context(action_bar_layer, self); - app_timer_register(10000, (AppTimerCallback) show_welcome_text, self); return self; @@ -427,20 +308,12 @@ void simply_ui_destroy(SimplyUi *self) { return; } - action_bar_layer_destroy(self->action_bar_layer); - self->action_bar_layer = NULL; - - window_destroy(self->window); - self->window = NULL; + simply_ui_clear(self, ~0); - simply_ui_set_text(self, &self->title_text, NULL); - simply_ui_set_text(self, &self->subtitle_text, NULL); - simply_ui_set_text(self, &self->body_text, NULL); + fonts_unload_custom_font(self->ui_layer.custom_body_font); + self->ui_layer.custom_body_font = NULL; - fonts_unload_custom_font(self->custom_body_font); - self->custom_body_font = NULL; + simply_window_deinit(&self->window); free(self); - - s_ui = NULL; } diff --git a/src/simply_ui.h b/src/simply_ui.h index e102c423..150f2846 100644 --- a/src/simply_ui.h +++ b/src/simply_ui.h @@ -1,51 +1,55 @@ #pragma once +#include "simply_window.h" + #include "simplyjs.h" #include typedef struct SimplyStyle SimplyStyle; +typedef struct SimplyUiLayer SimplyUiLayer; + typedef struct SimplyUi SimplyUi; -struct SimplyUi { - Simply *simply; - Window *window; +typedef enum SimplyUiTextfield SimplyUiTextfield; + +typedef enum SimplyUiImagefield SimplyUiImagefield; + +enum SimplyUiTextfield { + UiTitle = 0, + UiSubtitle, + UiBody, + NumUiTextfields, +}; + +enum SimplyUiImagefield { + UiTitleIcon, + UiSubtitleIcon, + UiBodyImage, + NumUiImagefields, +}; + +struct SimplyUiLayer { + Layer *layer; const SimplyStyle *style; - char *title_text; - char *subtitle_text; - char *body_text; - uint32_t title_icon; - uint32_t subtitle_icon; - uint32_t image; - ScrollLayer *scroll_layer; - Layer *display_layer; - ActionBarLayer *action_bar_layer; + char *textfields[3]; + uint32_t imagefields[3]; GFont custom_body_font; - uint32_t button_mask; - bool is_scrollable; - bool is_action_bar; +}; + +struct SimplyUi { + SimplyWindow window; + SimplyUiLayer ui_layer; }; SimplyUi *simply_ui_create(Simply *simply); void simply_ui_destroy(SimplyUi *self); - void simply_ui_show(SimplyUi *self); void simply_ui_clear(SimplyUi *self, uint32_t clear_mask); void simply_ui_set_style(SimplyUi *self, int style_index); - -void simply_ui_set_text(SimplyUi *self, char **str_field, const char *str); - -void simply_ui_set_scrollable(SimplyUi *self, bool is_scrollable); - -void simply_ui_set_fullscreen(SimplyUi *self, bool is_fullscreen); - -void simply_ui_set_action_bar(SimplyUi *self, bool is_action_bar); - -void simply_ui_set_action_bar_icon(SimplyUi *self, ButtonId button, uint32_t id); - -void simply_ui_set_button(SimplyUi *self, ButtonId button, bool enable); +void simply_ui_set_text(SimplyUi *self, SimplyUiTextfield textfield, const char *str); diff --git a/src/simply_window.c b/src/simply_window.c new file mode 100644 index 00000000..04169f24 --- /dev/null +++ b/src/simply_window.c @@ -0,0 +1,195 @@ +#include "simply_window.h" + +#include "simply_msg.h" +#include "simply_res.h" +#include "simply_menu.h" + +#include "simplyjs.h" + +#include "util/graphics.h" +#include "util/string.h" + +#include + +static void click_config_provider(void *data); + +void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { + self->is_scrollable = is_scrollable; + + if (!self->scroll_layer) { + return; + } + + scroll_layer_set_click_config_onto_window(self->scroll_layer, self->window); + + if (!self->layer) { + return; + } + + if (!is_scrollable) { + GRect bounds = layer_get_bounds(window_get_root_layer(self->window)); + layer_set_bounds(self->layer, bounds); + const bool animated = true; + scroll_layer_set_content_offset(self->scroll_layer, GPointZero, animated); + scroll_layer_set_content_size(self->scroll_layer, bounds.size); + } + + layer_mark_dirty(self->layer); +} + +void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { + window_set_fullscreen(self->window, is_fullscreen); + + if (!self->layer) { + return; + } + + GRect frame = layer_get_frame(window_get_root_layer(self->window)); + scroll_layer_set_frame(self->scroll_layer, frame); + layer_set_frame(self->layer, frame); + + if (!window_stack_contains_window(self->window)) { + return; + } + + // HACK: Refresh app chrome state + Window *window = window_create(); + window_stack_push(window, false); + window_stack_remove(window, false); + window_destroy(window); +} + +void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { + self->is_action_bar = is_action_bar; + action_bar_layer_remove_from_window(self->action_bar_layer); + if (is_action_bar) { + action_bar_layer_add_to_window(self->action_bar_layer, self->window); + action_bar_layer_set_click_config_provider(self->action_bar_layer, click_config_provider); + } else { + window_set_click_config_provider(self->window, click_config_provider); + } +} + +void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint32_t id) { + if (id) { + GBitmap *icon = simply_res_auto_image(self->simply->res, id, true); + action_bar_layer_set_icon(self->action_bar_layer, button, icon); + simply_window_set_action_bar(self, true); + } else { + action_bar_layer_clear_icon(self->action_bar_layer, button); + } +} + +void simply_window_action_bar_clear(SimplyWindow *self) { + for (ButtonId button = BUTTON_ID_UP; button <= BUTTON_ID_DOWN; ++button) { + action_bar_layer_clear_icon(self->action_bar_layer, button); + } +} + +void simply_window_set_button(SimplyWindow *self, ButtonId button, bool enable) { + if (enable) { + self->button_mask |= 1 << button; + } else { + self->button_mask &= ~(1 << button); + } +} + +static void single_click_handler(ClickRecognizerRef recognizer, void *context) { + SimplyWindow *self = context; + ButtonId button = click_recognizer_get_button_id(recognizer); + bool is_enabled = (self->button_mask & (1 << button)); + if (button == BUTTON_ID_BACK && !is_enabled) { + bool animated = true; + window_stack_pop(animated); + } + if (is_enabled) { + simply_msg_single_click(button); + } +} + +static void long_click_handler(ClickRecognizerRef recognizer, void *context) { + SimplyWindow *self = context; + ButtonId button = click_recognizer_get_button_id(recognizer); + bool is_enabled = (self->button_mask & (1 << button)); + if (is_enabled) { + simply_msg_long_click(button); + } +} + +static void click_config_provider(void *context) { + SimplyWindow *self = context; + for (int i = 0; i < NUM_BUTTONS; ++i) { + if (!self->is_scrollable || (i != BUTTON_ID_UP && i != BUTTON_ID_DOWN)) { + window_single_click_subscribe(i, (ClickHandler) single_click_handler); + window_long_click_subscribe(i, 500, (ClickHandler) long_click_handler, NULL); + } + } +} + +void simply_window_load(SimplyWindow *self) { + Window *window = self->window; + + Layer *window_layer = window_get_root_layer(window); + GRect frame = layer_get_frame(window_layer); + frame.origin = GPointZero; + + ScrollLayer *scroll_layer = self->scroll_layer = scroll_layer_create(frame); + Layer *scroll_base_layer = scroll_layer_get_layer(scroll_layer); + layer_add_child(window_layer, scroll_base_layer); + + scroll_layer_set_context(scroll_layer, self); + scroll_layer_set_callbacks(scroll_layer, (ScrollLayerCallbacks) { + .click_config_provider = click_config_provider, + }); + scroll_layer_set_click_config_onto_window(scroll_layer, window); + + if (self->is_action_bar) { + simply_window_set_action_bar(self, true); + } +} + +void simply_window_unload(SimplyWindow *self) { + scroll_layer_destroy(self->scroll_layer); + self->scroll_layer = NULL; +} + +void simply_window_show(SimplyWindow *self) { + if (!self->window) { + return; + } + if (!window_stack_contains_window(self->window)) { + bool animated = true; + window_stack_push(self->window, animated); + } +} + +SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { + self->simply = simply; + + for (int i = 0; i < NUM_BUTTONS; ++i) { + if (i != BUTTON_ID_BACK) { + self->button_mask |= 1 << i; + } + } + + Window *window = self->window = window_create(); + window_set_background_color(window, GColorBlack); + window_set_click_config_provider(window, click_config_provider); + + ActionBarLayer *action_bar_layer = self->action_bar_layer = action_bar_layer_create(); + action_bar_layer_set_context(action_bar_layer, self); + + return self; +} + +void simply_window_deinit(SimplyWindow *self) { + if (!self) { + return; + } + + action_bar_layer_destroy(self->action_bar_layer); + self->action_bar_layer = NULL; + + window_destroy(self->window); + self->window = NULL; +} diff --git a/src/simply_window.h b/src/simply_window.h new file mode 100644 index 00000000..ecd95042 --- /dev/null +++ b/src/simply_window.h @@ -0,0 +1,34 @@ +#pragma once + +#include "simplyjs.h" + +#include + +typedef struct SimplyWindow SimplyWindow; + +struct SimplyWindow { + Simply *simply; + Window *window; + ScrollLayer *scroll_layer; + Layer *layer; + ActionBarLayer *action_bar_layer; + uint32_t button_mask; + bool is_scrollable; + bool is_action_bar; +}; + +SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply); +void simply_window_deinit(SimplyWindow *self); +void simply_window_show(SimplyWindow *self); + +void simply_window_load(SimplyWindow *self); +void simply_window_unload(SimplyWindow *self); + +void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable); +void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen); + +void simply_window_set_button(SimplyWindow *self, ButtonId button, bool enable); + +void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar); +void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint32_t id); +void simply_window_action_bar_clear(SimplyWindow *self); From f007145b3db31f14eacee78685d34505ee4a81fa Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 19:27:38 -0700 Subject: [PATCH 071/791] Move js files from src/js to lib --- {src/js => lib}/README.md | 0 {src/js => lib}/ajax.js | 4 ++++ {src/js => lib}/main.js | 6 +----- {src/js => lib}/simply-image.js | 9 ++++----- {src/js => lib}/simply-pebble.js | 13 +++++++------ {src/js => lib}/simply.js | 12 +++++------- {src/js => lib}/util2.js | 10 ++++++++-- {src/js => lib}/vendor/moment.min.js | 0 {src/js => lib}/vendor/png.js | 0 {src/js => lib}/vendor/zlib.js | 0 10 files changed, 29 insertions(+), 25 deletions(-) rename {src/js => lib}/README.md (100%) rename {src/js => lib}/ajax.js (98%) rename {src/js => lib}/main.js (60%) rename {src/js => lib}/simply-image.js (98%) rename {src/js => lib}/simply-pebble.js (98%) rename {src/js => lib}/simply.js (99%) rename {src/js => lib}/util2.js (93%) rename {src/js => lib}/vendor/moment.min.js (100%) rename {src/js => lib}/vendor/png.js (100%) rename {src/js => lib}/vendor/zlib.js (100%) diff --git a/src/js/README.md b/lib/README.md similarity index 100% rename from src/js/README.md rename to lib/README.md diff --git a/src/js/ajax.js b/lib/ajax.js similarity index 98% rename from src/js/ajax.js rename to lib/ajax.js index 9665e9f3..6c963f3c 100644 --- a/src/js/ajax.js +++ b/lib/ajax.js @@ -96,6 +96,10 @@ var ajax = function(opt, success, failure) { ajax.formify = formify; +if (typeof module !== 'undefined') { + module.exports = ajax; +} + return ajax; })(); diff --git a/src/js/main.js b/lib/main.js similarity index 60% rename from src/js/main.js rename to lib/main.js index e9245770..06588530 100644 --- a/src/js/main.js +++ b/lib/main.js @@ -1,9 +1,5 @@ -/* global SimplyPebble */ - -(function() { +var SimplyPebble = require('simply-pebble'); Pebble.addEventListener('ready', function(e) { SimplyPebble.init(); }); - -})(); diff --git a/src/js/simply-image.js b/lib/simply-image.js similarity index 98% rename from src/js/simply-image.js rename to lib/simply-image.js index 26be04fb..1aec77c0 100644 --- a/src/js/simply-image.js +++ b/lib/simply-image.js @@ -1,6 +1,7 @@ -/* global simply, util2, PNG */ +/* global PNG */ -var SimplyImage = (function() { +var simply = require('simply'); +var util2 = require('util2'); var SimplyImage = {}; @@ -198,6 +199,4 @@ SimplyImage.load = function(image, callback) { return image; }; -return SimplyImage; - -})(); +module.exports = SimplyImage; diff --git a/src/js/simply-pebble.js b/lib/simply-pebble.js similarity index 98% rename from src/js/simply-pebble.js rename to lib/simply-pebble.js index 9b4b5268..2d5a54d9 100644 --- a/src/js/simply-pebble.js +++ b/lib/simply-pebble.js @@ -1,8 +1,11 @@ -/* global simply, util2 */ +var simply = require('simply'); +var util2 = require('util2'); -var SimplyPebble = (function() { +var SimplyPebble = {}; -var Image = 'Image'; +if (typeof Image === 'undefined') { + window.Image = function(){}; +} var commands = [{ name: 'setCard', @@ -596,6 +599,4 @@ Pebble.addEventListener('showConfiguration', SimplyPebble.onShowConfiguration); Pebble.addEventListener('webviewclosed', SimplyPebble.onWebViewClosed); Pebble.addEventListener('appmessage', SimplyPebble.onAppMessage); -return SimplyPebble; - -})(); +module.exports = SimplyPebble; diff --git a/src/js/simply.js b/lib/simply.js similarity index 99% rename from src/js/simply.js rename to lib/simply.js index 47fcf6fb..6f94e6e0 100644 --- a/src/js/simply.js +++ b/lib/simply.js @@ -2,10 +2,10 @@ * Simply.js * @namespace simply */ -/* global util2, PNG, SimplyImage */ -var simply = (function() { -var noop = typeof util2 !== 'undefined' ? util2.noop : function() {}; +var ajax = require('ajax'); +var util2 = require('util2'); +var SimplyImage = require('simply-image'); var simply = {}; @@ -370,7 +370,7 @@ simply.loadScript = function(scriptUrl, async) { var pkg = simply.makePackage(scriptUrl); pkg.exports = {}; - var loader = noop; + var loader = util2.noop; var useScript = function(script) { loader = simply.fexecPackage(script, pkg); }; @@ -1117,9 +1117,7 @@ simply.emitMenuSelect = function(type, section, item) { simply.onMenuSelect(e); }; -return simply; - -})(); +module.exports = simply; Pebble.require = require; window.require = simply.require; diff --git a/src/js/util2.js b/lib/util2.js similarity index 93% rename from src/js/util2.js rename to lib/util2.js index 0a2a38b3..46ef6590 100644 --- a/src/js/util2.js +++ b/lib/util2.js @@ -2,7 +2,9 @@ * util2.js by Meiguro - MIT License */ -var util2 = (function(util2){ +var util2 = (function(){ + +var util2 = {}; util2.noop = function() {}; @@ -81,6 +83,10 @@ util2.format = function(text, table) { return z; }; +if (typeof module !== 'undefined') { + module.exports = util2; +} + return util2; -})(typeof util2 !== 'undefined' ? util2 : {}); +})(); diff --git a/src/js/vendor/moment.min.js b/lib/vendor/moment.min.js similarity index 100% rename from src/js/vendor/moment.min.js rename to lib/vendor/moment.min.js diff --git a/src/js/vendor/png.js b/lib/vendor/png.js similarity index 100% rename from src/js/vendor/png.js rename to lib/vendor/png.js diff --git a/src/js/vendor/zlib.js b/lib/vendor/zlib.js similarity index 100% rename from src/js/vendor/zlib.js rename to lib/vendor/zlib.js From f3fb6774446bd499bb03b70fee0a15f239f46aa7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 19:31:02 -0700 Subject: [PATCH 072/791] Move src/html files to public --- {src/html => public}/demo.js | 0 {src/html => public}/settings.html | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {src/html => public}/demo.js (100%) rename {src/html => public}/settings.html (100%) diff --git a/src/html/demo.js b/public/demo.js similarity index 100% rename from src/html/demo.js rename to public/demo.js diff --git a/src/html/settings.html b/public/settings.html similarity index 100% rename from src/html/settings.html rename to public/settings.html From e1e0331ec300315ee8dc0245467116d7a8f394d0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 19:33:44 -0700 Subject: [PATCH 073/791] Add loader for CommonJS style loading --- lib/loader.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 lib/loader.js diff --git a/lib/loader.js b/lib/loader.js new file mode 100644 index 00000000..b5484785 --- /dev/null +++ b/lib/loader.js @@ -0,0 +1,33 @@ +var __loader = (function() { + +var __loader = {}; + +__loader.packages = {}; +__loader.loaders = {}; +__loader.require = function(path) { + if (!path.match(/\.js$/)) { + path += '.js'; + } + var module = __loader.packages[path]; + if (!module) { + throw new Error("Cannot find module'" + path + "'"); + } + if (module.exports) { + return module.exports; + } + module.exports = {}; + module.loader(module, __loader.require); + module.loaded = true; + return module.exports; +}; + +__loader.define = function(path, loader) { + __loader.packages[path] = { + filename: path, + loader: loader, + }; +}; + +return __loader; + +})(); From 7503bb9b6fd70b78805b0a9f2bf24aa0f8e2896c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 19:34:10 -0700 Subject: [PATCH 074/791] Update waf javascript concatenation to use loader.js --- wscript | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/wscript b/wscript index 8c2d32e5..eddc38d4 100644 --- a/wscript +++ b/wscript @@ -1,3 +1,6 @@ +import json +import os + from waflib.Configure import conf top = '.' @@ -18,25 +21,39 @@ def build(ctx): '-Wno-missing-field-initializers'], target='pebble-app.elf') - js_target = ctx.concat_javascript(js=ctx.path.ant_glob('src/js/**/*.js')) + js_target = ctx.concat_javascript(js_path='lib') ctx.pbl_bundle(elf='pebble-app.elf', js=js_target) @conf def concat_javascript(self, *k, **kw): - js_nodes = kw['js'] + js_path = kw['js_path'] + js_nodes = self.path.ant_glob(js_path + '/**/*.js') if not js_nodes: return [] def concat_javascript_task(task): - cmd = ['cat'] - cmd.extend(['"{}"'.format(x.abspath()) for x in task.inputs]) - cmd.extend(['>', "{}".format(task.outputs[0].abspath())]) - task.exec_command(' '.join(cmd)) + LOADER_TEMPLATE = "__loader.define({relpath}, function(module, require) {{\n{body}\n}});" + + sources = [] + for node in task.inputs: + relpath = os.path.relpath(node.abspath(), js_path) + with open(node.abspath(), 'r') as f: + if relpath == 'loader.js': + sources.insert(0, f.read()) + elif relpath.startswith('vendor/'): + sources.append(f.read()) + else: + sources.append(LOADER_TEMPLATE.format(relpath=json.dumps(relpath), body=f.read())) + + sources.append('__loader.require("main");') + + with open(task.outputs[0].abspath(), 'w') as f: + f.write('\n'.join(sources)) - js_target = self.path.make_node('build/src/js/pebble-js-app.js') + js_target = self.path.make_node('src/js/pebble-js-app.js') self(rule=concat_javascript_task, source=js_nodes, From c70309c0a5c30ec157ac657c51c3c4ee45416abb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 19:34:48 -0700 Subject: [PATCH 075/791] Update simply demo --- public/demo.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/public/demo.js b/public/demo.js index 98d16a2a..22676f5a 100644 --- a/public/demo.js +++ b/public/demo.js @@ -1,23 +1,23 @@ +/* global simply */ console.log('Simply.js demo!'); +simply.text({ + title: 'Simply Demo!', + body: 'This is a demo. Press buttons or tap the watch!', +}, true); + simply.on('singleClick', function(e) { - console.log(util2.format('single clicked $button!', e)); + console.log('single clicked ' + e.button + '!'); simply.subtitle('Pressed ' + e.button + '!'); }); simply.on('longClick', function(e) { - console.log(util2.format('long clicked $button!', e)); + console.log('long clicked ' + e.button + '!'); + simply.subtitle('Long pressed ' + e.button + '!'); simply.vibe(); - simply.scrollable(e.button !== 'select'); }); simply.on('accelTap', function(e) { - console.log(util2.format('tapped accel axis $axis $direction!', e)); + console.log('accel tapped axis ' + e.axis + ' ' + e.direction + '!'); simply.subtitle('Tapped ' + (e.direction > 0 ? '+' : '-') + e.axis + '!'); }); - -simply.setText({ - title: 'Simply Demo!', - body: 'This is a demo. Press buttons or tap the watch!', -}, true); - From beb9e6853789ef3bfbb2294c15ac00786915b3f4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 19:44:50 -0700 Subject: [PATCH 076/791] Rename simply-image.js to image.js --- lib/{simply-image.js => image.js} | 81 +++++++++++++++---------------- lib/simply.js | 4 +- 2 files changed, 41 insertions(+), 44 deletions(-) rename lib/{simply-image.js => image.js} (66%) diff --git a/lib/simply-image.js b/lib/image.js similarity index 66% rename from lib/simply-image.js rename to lib/image.js index 1aec77c0..eb9d59cb 100644 --- a/lib/simply-image.js +++ b/lib/image.js @@ -1,9 +1,6 @@ /* global PNG */ -var simply = require('simply'); -var util2 = require('util2'); - -var SimplyImage = {}; +var image = {}; var getPos = function(width, x, y) { return y * width * 4 + x * 4; @@ -13,7 +10,7 @@ var getPixelGrey = function(pixels, pos) { return ((pixels[pos] + pixels[pos + 1] + pixels[pos + 2]) / 3) & 0xFF; }; -SimplyImage.greyscale = function(pixels, width, height) { +image.greyscale = function(pixels, width, height) { for (var y = 0, yy = height; y < yy; ++y) { for (var x = 0, xx = width; x < xx; ++x) { var pos = getPos(width, x, y); @@ -25,15 +22,15 @@ SimplyImage.greyscale = function(pixels, width, height) { } }; -SimplyImage.Dithers = {}; +image.dithers = {}; -SimplyImage.Dithers['floyd-steinberg'] = [ +image.dithers['floyd-steinberg'] = [ [ 1, 0, 7/16], [-1, 1, 3/16], [ 0, 1, 5/16], [ 1, 1, 1/16]]; -SimplyImage.Dithers['jarvis-judice-ninke'] = [ +image.dithers['jarvis-judice-ninke'] = [ [ 1, 0, 7/48], [ 2, 0, 5/48], [-2, 1, 3/48], @@ -47,7 +44,7 @@ SimplyImage.Dithers['jarvis-judice-ninke'] = [ [ 1, 2, 3/48], [ 2, 2, 1/48]]; -SimplyImage.Dithers.sierra = [ +image.dithers.sierra = [ [ 1, 0, 5/32], [ 2, 0, 3/32], [-2, 1, 2/32], @@ -59,11 +56,11 @@ SimplyImage.Dithers.sierra = [ [ 0, 2, 3/32], [ 1, 2, 2/32]]; -SimplyImage.Dithers['default'] = SimplyImage.Dithers.sierra; +image.dithers['default'] = image.dithers.sierra; -SimplyImage.dither = function(pixels, width, height, dithers) { - dithers = dithers || SimplyImage.Dithers['default']; - var numDithers = dithers.length; +image.dither = function(pixels, width, height, dithers) { + dithers = dithers || image.dithers['default']; + var numdithers = dithers.length; for (var y = 0, yy = height; y < yy; ++y) { for (var x = 0, xx = width; x < xx; ++x) { var pos = getPos(width, x, y); @@ -71,7 +68,7 @@ SimplyImage.dither = function(pixels, width, height, dithers) { var newColor = oldColor >= 128 ? 255 : 0; var error = oldColor - newColor; pixels[pos] = newColor; - for (var i = 0; i < numDithers; ++i) { + for (var i = 0; i < numdithers; ++i) { var dither = dithers[i]; var x2 = x + dither[0], y2 = y + dither[1]; if (x2 >= 0 && x2 < width && y < height) { @@ -85,7 +82,7 @@ SimplyImage.dither = function(pixels, width, height, dithers) { } }; -SimplyImage.resizeNearest = function(pixels, width, height, newWidth, newHeight) { +image.resizeNearest = function(pixels, width, height, newWidth, newHeight) { var newPixels = new Array(newWidth * newHeight * 4); var widthRatio = width / newWidth; var heightRatio = height / newHeight; @@ -103,7 +100,7 @@ SimplyImage.resizeNearest = function(pixels, width, height, newWidth, newHeight) return newPixels; }; -SimplyImage.resizeSample = function(pixels, width, height, newWidth, newHeight) { +image.resizeSample = function(pixels, width, height, newWidth, newHeight) { var newPixels = new Array(newWidth * newHeight * 4); var widthRatio = width / newWidth; var heightRatio = height / newHeight; @@ -123,15 +120,15 @@ SimplyImage.resizeSample = function(pixels, width, height, newWidth, newHeight) return newPixels; }; -SimplyImage.resize = function(pixels, width, height, newWidth, newHeight) { +image.resize = function(pixels, width, height, newWidth, newHeight) { if (newWidth < width || newHeight < height) { - return SimplyImage.resizeSample.apply(this, arguments); + return image.resizeSample.apply(this, arguments); } else { - return SimplyImage.resizeNearest.apply(this, arguments); + return image.resizeNearest.apply(this, arguments); } }; -SimplyImage.toGbitmap = function(pixels, width, height) { +image.toGbitmap = function(pixels, width, height) { var rowBytes = width * 4; var gpixels = []; @@ -164,39 +161,39 @@ SimplyImage.toGbitmap = function(pixels, width, height) { return gbitmap; }; -SimplyImage.load = function(image, callback) { - PNG.load(image.url, function(png) { +image.load = function(img, callback) { + PNG.load(img.url, function(png) { var pixels = png.decode(); var width = png.width; var height = png.height; - SimplyImage.greyscale(pixels, width, height); - if (image.width) { - if (!image.height) { - image.height = parseInt(height * (image.width / width)); + image.greyscale(pixels, width, height); + if (img.width) { + if (!img.height) { + img.height = parseInt(height * (img.width / width)); } - } else if (image.height) { - if (!image.width) { - image.width = parseInt(width * (image.height / height)); + } else if (img.height) { + if (!img.width) { + img.width = parseInt(width * (img.height / height)); } } else { - image.width = width; - image.height = height; + img.width = width; + img.height = height; } - if (image.width !== width || image.height !== height) { - pixels = SimplyImage.resize(pixels, width, height, image.width, image.height); - width = image.width; - height = image.height; + if (img.width !== width || img.height !== height) { + pixels = image.resize(pixels, width, height, img.width, img.height); + width = img.width; + height = img.height; } - if (image.dither) { - var dithers = SimplyImage.Dithers[image.dither]; - SimplyImage.dither(pixels, width, height, dithers); + if (img.dither) { + var dithers = image.dithers[img.dither]; + image.dither(pixels, width, height, dithers); } - image.gbitmap = SimplyImage.toGbitmap(pixels, width, height); + img.gbitmap = image.toGbitmap(pixels, width, height); if (callback) { - callback(image); + callback(img); } }); - return image; + return img; }; -module.exports = SimplyImage; +module.exports = image; diff --git a/lib/simply.js b/lib/simply.js index 6f94e6e0..db54b12a 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -5,7 +5,7 @@ var ajax = require('ajax'); var util2 = require('util2'); -var SimplyImage = require('simply-image'); +var imagelib = require('image'); var simply = {}; @@ -746,7 +746,7 @@ simply.image = function(opt, reset, callback) { dither: opt.dither, }; simply.state.image.cache[hash] = image; - SimplyImage.load(image, function() { + imagelib.load(image, function() { simply.impl.image(image.id, image.gbitmap); if (callback) { var e = { From ce40593ff2fb02451e548551891dd53427253f08 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 19:45:36 -0700 Subject: [PATCH 077/791] Remove scratchCanvas - canvas is not supported --- lib/vendor/png.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/vendor/png.js b/lib/vendor/png.js index 3cb7d72b..3d69ad69 100644 --- a/lib/vendor/png.js +++ b/lib/vendor/png.js @@ -357,8 +357,6 @@ return ret; }; - scratchCanvas = document.createElement('canvas'); - makeImage = function(imageData) { var img; scratchCtx.width = imageData.width; From 6b3f452c1505a23a929d6e8b22d509c55c0cdda2 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 20:01:51 -0700 Subject: [PATCH 078/791] Unbind simply pebble papply and protect --- lib/simply-pebble.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/simply-pebble.js b/lib/simply-pebble.js index 2d5a54d9..918803f7 100644 --- a/lib/simply-pebble.js +++ b/lib/simply-pebble.js @@ -290,7 +290,7 @@ var setHandlerPath = function(handler, path, level) { return handler; }; -SimplyPebble.papply = function(f, args, path) { +var papply = function(f, args, path) { try { return f.apply(this, args); } catch (e) { @@ -304,9 +304,9 @@ SimplyPebble.papply = function(f, args, path) { } }; -SimplyPebble.protect = function(f, path) { +var protect = function(f, path) { return function() { - return SimplyPebble.papply(f, arguments, path); + return papply(f, arguments, path); }; }; @@ -315,9 +315,9 @@ SimplyPebble.wrapHandler = function(handler, level) { setHandlerPath(handler, null, level || 1); var package = simply.packages[handler.path]; if (package) { - return SimplyPebble.protect(package.fwrap(handler), handler.path); + return protect(package.fwrap(handler), handler.path); } else { - return SimplyPebble.protect(handler, handler.path); + return protect(handler, handler.path); } }; @@ -334,7 +334,7 @@ SimplyPebble.loadPackage = function(pkg, loader) { pkg.fapply = simply.defun(pkg.execName, ['f', 'args'], 'return f.apply(this, args)'); pkg.fwrap = function(f) { return function() { return pkg.fapply(f, arguments); }; }; - return SimplyPebble.papply(loader, null, pkg.name); + return papply(loader, null, pkg.name); }; SimplyPebble.onShowConfiguration = function(e) { From 6739aa3cd5d3a47e771512a3aab447d45177643b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 19:53:08 -0700 Subject: [PATCH 079/791] Add pebble-js-app.js - prebuilt simply.js --- src/js/pebble-js-app.js | 3104 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 3104 insertions(+) create mode 100644 src/js/pebble-js-app.js diff --git a/src/js/pebble-js-app.js b/src/js/pebble-js-app.js new file mode 100644 index 00000000..f743b564 --- /dev/null +++ b/src/js/pebble-js-app.js @@ -0,0 +1,3104 @@ +var __loader = (function() { + +var __loader = {}; + +__loader.packages = {}; +__loader.loaders = {}; +__loader.require = function(path) { + if (!path.match(/\.js$/)) { + path += '.js'; + } + var module = __loader.packages[path]; + if (!module) { + throw new Error("Cannot find module'" + path + "'"); + } + if (module.exports) { + return module.exports; + } + module.exports = {}; + module.loader(module, __loader.require); + module.loaded = true; + return module.exports; +}; + +__loader.define = function(path, loader) { + __loader.packages[path] = { + filename: path, + loader: loader, + }; +}; + +return __loader; + +})(); + +__loader.define("ajax.js", function(module, require) { +/* + * ajax.js by Meiguro - MIT License + */ + +var ajax = (function(){ + +var formify = function(data) { + var params = [], i = 0; + for (var name in data) { + params[i++] = encodeURI(name) + '=' + encodeURI(data[name]); + } + return params.join('&'); +}; + +/** + * ajax options. There are various properties with url being the only required property. + * @typedef ajaxOptions + * @property {string} [method='get'] - The HTTP method to use: 'get', 'post', 'put', 'delete', 'options', + * or any other standard method supported by the running environment. + * @property {string} url - The URL to make the ajax request to. e.g. 'http://www.example.com?name=value' + * @property {string} [type='text'] - The expected response format. Specify 'json' to have ajax parse + * the response as json and pass an object as the data parameter. + * @property {object} [data] - The request body, mainly to be used in combination with 'post' or 'put'. + * e.g. { username: 'guest' } + * @property {object} headers - Custom HTTP headers. Specify additional headers. + * e.g. { 'x-extra': 'Extra Header' } + * @property {boolean} [async=true] - Whether the request will be asynchronous. + * Specify false for a blocking, synchronous request. + * @property {boolean} [cache=true] - Whether the result may be cached. + * Specify false to use the internal cache buster which appends the URL with the query parameter _ + * set to the current time in milliseconds. + */ + +/** + * ajax allows you to make various http or https requests. + * See {@link ajaxOptions} + * @global + * @param {ajaxOptions} opt - Options specifying the type of ajax request to make. + * @param {function} success - The success handler that is called when a HTTP 200 response is given. + * @param {function} failure - The failure handler when the HTTP request fails or is not 200. + */ +var ajax = function(opt, success, failure) { + if (typeof opt === 'string') { + opt = { url: opt }; + } + var method = opt.method || 'GET'; + var url = opt.url; + //console.log(method + ' ' + url); + + var onHandler = ajax.onHandler; + if (onHandler) { + if (success) { success = onHandler('success', success); } + if (failure) { failure = onHandler('failure', failure); } + } + + if (opt.cache === false) { + var appendSymbol = url.indexOf('?') === -1 ? '?' : '&'; + url += appendSymbol + '_=' + new Date().getTime(); + } + + var req = new XMLHttpRequest(); + req.open(method.toUpperCase(), url, opt.async !== false); + + var headers = opt.headers; + if (headers) { + for (var name in headers) { + req.setRequestHeader(name, headers[name]); + } + } + + var data = null; + if (opt.data) { + if (opt.type === 'json') { + req.setRequestHeader('Content-Type', 'application/json'); + data = JSON.stringify(opt.data); + } else { + data = formify(opt.data); + } + } + + req.onreadystatechange = function(e) { + if (req.readyState == 4) { + var body = req.responseText; + if (opt.type == 'json') { + body = JSON.parse(body); + } + var callback = req.status == 200 ? success : failure; + if (callback) { + callback(body, req.status, req); + } + } + }; + + req.send(data); +}; + +ajax.formify = formify; + +if (typeof module !== 'undefined') { + module.exports = ajax; +} + +return ajax; + +})(); + +}); +__loader.define("image.js", function(module, require) { +/* global PNG */ + +var image = {}; + +var getPos = function(width, x, y) { + return y * width * 4 + x * 4; +}; + +var getPixelGrey = function(pixels, pos) { + return ((pixels[pos] + pixels[pos + 1] + pixels[pos + 2]) / 3) & 0xFF; +}; + +image.greyscale = function(pixels, width, height) { + for (var y = 0, yy = height; y < yy; ++y) { + for (var x = 0, xx = width; x < xx; ++x) { + var pos = getPos(width, x, y); + var newColor = getPixelGrey(pixels, pos); + for (var i = 0; i < 3; ++i) { + pixels[pos + i] = newColor; + } + } + } +}; + +image.dithers = {}; + +image.dithers['floyd-steinberg'] = [ + [ 1, 0, 7/16], + [-1, 1, 3/16], + [ 0, 1, 5/16], + [ 1, 1, 1/16]]; + +image.dithers['jarvis-judice-ninke'] = [ + [ 1, 0, 7/48], + [ 2, 0, 5/48], + [-2, 1, 3/48], + [-1, 1, 5/48], + [ 0, 1, 7/48], + [ 1, 1, 5/48], + [ 2, 1, 3/48], + [-2, 2, 1/48], + [-1, 2, 3/48], + [ 0, 2, 5/48], + [ 1, 2, 3/48], + [ 2, 2, 1/48]]; + +image.dithers.sierra = [ + [ 1, 0, 5/32], + [ 2, 0, 3/32], + [-2, 1, 2/32], + [-1, 1, 4/32], + [ 0, 1, 5/32], + [ 1, 1, 4/32], + [ 2, 1, 2/32], + [-1, 2, 2/32], + [ 0, 2, 3/32], + [ 1, 2, 2/32]]; + +image.dithers['default'] = image.dithers.sierra; + +image.dither = function(pixels, width, height, dithers) { + dithers = dithers || image.dithers['default']; + var numdithers = dithers.length; + for (var y = 0, yy = height; y < yy; ++y) { + for (var x = 0, xx = width; x < xx; ++x) { + var pos = getPos(width, x, y); + var oldColor = pixels[pos]; + var newColor = oldColor >= 128 ? 255 : 0; + var error = oldColor - newColor; + pixels[pos] = newColor; + for (var i = 0; i < numdithers; ++i) { + var dither = dithers[i]; + var x2 = x + dither[0], y2 = y + dither[1]; + if (x2 >= 0 && x2 < width && y < height) { + pixels[getPos(width, x2, y2)] += parseInt(error * dither[2]); + } + } + for (var j = 1; j < 3; ++j) { + pixels[pos + j] = newColor; + } + } + } +}; + +image.resizeNearest = function(pixels, width, height, newWidth, newHeight) { + var newPixels = new Array(newWidth * newHeight * 4); + var widthRatio = width / newWidth; + var heightRatio = height / newHeight; + for (var y = 0, yy = newHeight; y < yy; ++y) { + for (var x = 0, xx = newWidth; x < xx; ++x) { + var x2 = parseInt(x * widthRatio); + var y2 = parseInt(y * heightRatio); + var pos2 = getPos(width, x2, y2); + var pos = getPos(newWidth, x, y); + for (var i = 0; i < 4; ++i) { + newPixels[pos + i] = pixels[pos2 + i]; + } + } + } + return newPixels; +}; + +image.resizeSample = function(pixels, width, height, newWidth, newHeight) { + var newPixels = new Array(newWidth * newHeight * 4); + var widthRatio = width / newWidth; + var heightRatio = height / newHeight; + for (var y = 0, yy = newHeight; y < yy; ++y) { + for (var x = 0, xx = newWidth; x < xx; ++x) { + var x2 = Math.min(parseInt(x * widthRatio), width - 1); + var y2 = Math.min(parseInt(y * heightRatio), height - 1); + var pos = getPos(newWidth, x, y); + for (var i = 0; i < 4; ++i) { + newPixels[pos + i] = ((pixels[getPos(width, x2 , y2 ) + i] + + pixels[getPos(width, x2+1, y2 ) + i] + + pixels[getPos(width, x2 , y2+1) + i] + + pixels[getPos(width, x2+1, y2+1) + i]) / 4) & 0xFF; + } + } + } + return newPixels; +}; + +image.resize = function(pixels, width, height, newWidth, newHeight) { + if (newWidth < width || newHeight < height) { + return image.resizeSample.apply(this, arguments); + } else { + return image.resizeNearest.apply(this, arguments); + } +}; + +image.toGbitmap = function(pixels, width, height) { + var rowBytes = width * 4; + + var gpixels = []; + var growBytes = Math.ceil(width / 32) * 4; + for (var i = 0, ii = height * growBytes; i < ii; ++i) { + gpixels[i] = 0; + } + + for (var y = 0, yy = height; y < yy; ++y) { + for (var x = 0, xx = width; x < xx; ++x) { + var grey = 0; + var pos = y * rowBytes + parseInt(x * 4); + for (var j = 0; j < 3; ++j) { + grey += pixels[pos + j]; + } + grey /= 3 * 255; + if (grey >= 0.5) { + var gbytePos = y * growBytes + parseInt(x / 8); + gpixels[gbytePos] += 1 << (x % 8); + } + } + } + + var gbitmap = { + width: width, + height: height, + pixels: gpixels, + }; + + return gbitmap; +}; + +image.load = function(img, callback) { + PNG.load(img.url, function(png) { + var pixels = png.decode(); + var width = png.width; + var height = png.height; + image.greyscale(pixels, width, height); + if (img.width) { + if (!img.height) { + img.height = parseInt(height * (img.width / width)); + } + } else if (img.height) { + if (!img.width) { + img.width = parseInt(width * (img.height / height)); + } + } else { + img.width = width; + img.height = height; + } + if (img.width !== width || img.height !== height) { + pixels = image.resize(pixels, width, height, img.width, img.height); + width = img.width; + height = img.height; + } + if (img.dither) { + var dithers = image.dithers[img.dither]; + image.dither(pixels, width, height, dithers); + } + img.gbitmap = image.toGbitmap(pixels, width, height); + if (callback) { + callback(img); + } + }); + return img; +}; + +module.exports = image; + +}); +__loader.define("main.js", function(module, require) { +var SimplyPebble = require('simply-pebble'); + +Pebble.addEventListener('ready', function(e) { + SimplyPebble.init(); +}); + +}); +__loader.define("simply-pebble.js", function(module, require) { +var simply = require('simply'); +var util2 = require('util2'); + +var SimplyPebble = {}; + +if (typeof Image === 'undefined') { + window.Image = function(){}; +} + +var commands = [{ + name: 'setCard', + params: [{ + name: 'clear', + type: Boolean, + }, { + name: 'title', + type: String, + }, { + name: 'subtitle', + type: String, + }, { + name: 'body', + type: String, + }, { + name: 'icon', + type: Image, + }, { + name: 'subicon', + type: Image, + }, { + name: 'banner', + type: Image, + }, { + name: 'action', + type: Boolean, + }, { + name: 'actionUp', + type: Image, + }, { + name: 'actionSelect', + type: Image, + }, { + name: 'actionDown', + type: Image, + }, { + name: 'fullscreen', + type: Boolean, + }, { + name: 'style', + }, { + name: 'scrollable', + type: Boolean, + }], +}, { + name: 'singleClick', + params: [{ + name: 'button', + }], +}, { + name: 'longClick', + params: [{ + name: 'button', + }], +}, { + name: 'accelTap', + params: [{ + name: 'axis', + }, { + name: 'direction', + }], +}, { + name: 'vibe', + params: [{ + name: 'type', + }], +}, { + name: 'accelData', + params: [{ + name: 'transactionId', + }, { + name: 'numSamples', + }, { + name: 'accelData', + }], +}, { + name: 'getAccelData', + params: [{ + name: 'transactionId', + }], +}, { + name: 'configAccelData', + params: [{ + name: 'rate', + }, { + name: 'samples', + }, { + name: 'subscribe', + }], +}, { + name: 'configButtons', + params: [{ + name: 'back', + }, { + name: 'up', + }, { + name: 'select', + }, { + name: 'down', + }], +}, { + name: 'cardExit', +}, { + name: 'setMenu', + params: [{ + name: 'sections', + }], +}, { + name: 'setMenuSection', + params: [{ + name: 'section', + }, { + name: 'items', + }, { + name: 'title', + type: String, + }], +}, { + name: 'getMenuSection', + params: [{ + name: 'section', + }], +}, { + name: 'setMenuItem', + params: [{ + name: 'section', + }, { + name: 'item', + }, { + name: 'title', + type: String, + }, { + name: 'subtitle', + type: String, + }, { + name: 'image', + type: Image, + }], +}, { + name: 'getMenuItem', + params: [{ + name: 'section', + }, { + name: 'item', + }], +}, { + name: 'menuSelect', + params: [{ + name: 'section', + }, { + name: 'item', + }], +}, { + name: 'menuLongSelect', + params: [{ + name: 'section', + }, { + name: 'item', + }], +}, { + name: 'menuExit', + params: [{ + name: 'section', + }, { + name: 'item', + }], +}, { + name: 'image', + params: [{ + name: 'id', + }, { + name: 'width', + }, { + name: 'height', + }, { + name: 'pixels', + }], +}]; + +var commandMap = {}; + +for (var i = 0, ii = commands.length; i < ii; ++i) { + var command = commands[i]; + commandMap[command.name] = command; + command.id = i; + + var params = command.params; + if (!params) { + continue; + } + + var paramMap = command.paramMap = {}; + for (var j = 0, jj = params.length; j < jj; ++j) { + var param = params[j]; + paramMap[param.name] = param; + param.id = j + 1; + } +} + +var buttons = [ + 'back', + 'up', + 'select', + 'down', +]; + +var accelAxes = [ + 'x', + 'y', + 'z', +]; + +var vibeTypes = [ + 'short', + 'long', + 'double', +]; + +var styleTypes = [ + 'small', + 'large', + 'mono', +]; + +var clearFlagMap = { + text: (1 << 0), + image: (1 << 1), + action: (1 << 2), +}; + +var actionBarTypeMap = { + up: 'actionUp', + select: 'actionSelect', + down: 'actionDown', +}; + +var SimplyPebble = {}; + +SimplyPebble.init = function() { + simply.impl = SimplyPebble; + simply.init(); +}; + +var getExecPackage = function(execName) { + var packages = simply.packages; + for (var path in packages) { + var package = packages[path]; + if (package && package.execName === execName) { + return path; + } + } +}; + +var getExceptionFile = function(e, level) { + var stack = e.stack.split('\n'); + for (var i = level || 0, ii = stack.length; i < ii; ++i) { + var line = stack[i]; + if (line.match(/^\$\d/)) { + var path = getExecPackage(line); + if (path) { + return path; + } + } + } + return stack[level]; +}; + +var getExceptionScope = function(e, level) { + var stack = e.stack.split('\n'); + for (var i = level || 0, ii = stack.length; i < ii; ++i) { + var line = stack[i]; + if (!line || line.match('native code')) { continue; } + return line.match(/^\$\d/) && getExecPackage(line) || line; + } + return stack[level]; +}; + +var setHandlerPath = function(handler, path, level) { + var level0 = 4; // caller -> wrap -> apply -> wrap -> set + handler.path = path || getExceptionScope(new Error(), (level || 0) + level0) || simply.basename(); + return handler; +}; + +var papply = function(f, args, path) { + try { + return f.apply(this, args); + } catch (e) { + var scope = !path && getExceptionFile(e) || getExecPackage(path) || path; + console.log(scope + ':' + e.line + ': ' + e + '\n' + e.stack); + simply.text({ + subtitle: scope, + body: e.line + ' ' + e.message, + }, true); + simply.state.run = false; + } +}; + +var protect = function(f, path) { + return function() { + return papply(f, arguments, path); + }; +}; + +SimplyPebble.wrapHandler = function(handler, level) { + if (!handler) { return; } + setHandlerPath(handler, null, level || 1); + var package = simply.packages[handler.path]; + if (package) { + return protect(package.fwrap(handler), handler.path); + } else { + return protect(handler, handler.path); + } +}; + +var toSafeName = function(name) { + name = name.replace(/[^0-9A-Za-z_$]/g, '_'); + if (name.match(/^[0-9]/)) { + name = '_' + name; + } + return name; +}; + +SimplyPebble.loadPackage = function(pkg, loader) { + pkg.execName = '$' + simply.state.numPackages++ + toSafeName(pkg.name); + pkg.fapply = simply.defun(pkg.execName, ['f', 'args'], 'return f.apply(this, args)'); + pkg.fwrap = function(f) { return function() { return pkg.fapply(f, arguments); }; }; + + return papply(loader, null, pkg.name); +}; + +SimplyPebble.onShowConfiguration = function(e) { + simply.openSettings(e); +}; + +SimplyPebble.onWebViewClosed = function(e) { + simply.closeSettings(e); +}; + +var toParam = function(param, v) { + if (param.type === String) { + v = v.toString(); + } else if (param.type === Boolean) { + v = v ? 1 : 0; + } else if (param.type === Image && typeof v !== 'number') { + v = simply.image(v); + } + return v; +}; + +var setPacket = function(packet, command, def, typeMap) { + var paramMap = command.paramMap; + for (var k in def) { + var paramName = typeMap && typeMap[k] || k; + if (!paramName) { continue; } + var param = paramMap[paramName]; + if (param) { + packet[param.id] = toParam(param, def[k]); + } + } + return packet; +}; + +var makePacket = function(command, def) { + var packet = {}; + packet[0] = command.id; + if (def) { + setPacket(packet, command, def); + } + return packet; +}; + +SimplyPebble.sendPacket = function(packet) { + if (!simply.state.run) { + return; + } + var send; + send = function() { + Pebble.sendAppMessage(packet, util2.noop, send); + }; + send(); +}; + +SimplyPebble.setMenu = function() { + SimplyPebble.sendPacket(makePacket(commandMap.setMenu)); +}; + +SimplyPebble.buttonConfig = function(buttonConf) { + var command = commandMap.configButtons; + var packet = makePacket(command, buttonConf); + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.card = function(cardDef, clear) { + if (clear === 'all') { + clear = ~0; + } else if (typeof clear === 'string') { + clear = clearFlagMap[clear]; + } else if (typeof clear === 'object') { + var flags = 0; + for (var k in clear) { + if (clear[k] === true) { + flags |= clearFlagMap[k]; + } + } + clear = flags; + } + var command = commandMap.setCard; + var packet = makePacket(command, cardDef); + if (clear) { + packet[command.paramMap.clear.id] = clear; + } + var actionDef = cardDef.action; + if (actionDef) { + if (typeof actionDef === 'boolean') { + actionDef = { action: actionDef }; + } + setPacket(packet, command, actionDef, actionBarTypeMap); + } + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.textfield = function(field, text, clear) { + var command = commandMap.setCard; + var packet = makePacket(command); + var param = command.paramMap[field]; + if (param) { + packet[param.id] = text.toString(); + } + if (clear) { + packet[command.paramMap.clear.id] = true; + } + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.vibe = function(type) { + var command = commandMap.vibe; + var packet = makePacket(command); + var vibeIndex = vibeTypes.indexOf(type); + packet[command.paramMap.type.id] = vibeIndex !== -1 ? vibeIndex : 0; + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.scrollable = function(scrollable) { + var command = commandMap.card; + var packet = makePacket(command); + packet[command.paramMap.scrollable.id] = scrollable ? 1 : 0; + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.fullscreen = function(fullscreen) { + var command = commandMap.card; + var packet = makePacket(command); + packet[command.paramMap.fullscreen.id] = fullscreen ? 1 : 0; + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.style = function(type) { + var command = commandMap.card; + var packet = makePacket(command); + var styleIndex = styleTypes.indexOf(type); + packet[command.paramMap.type.id] = styleIndex !== -1 ? styleIndex : 1; + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.accelConfig = function(configDef) { + var command = commandMap.configAccelData; + var packet = makePacket(command, configDef); + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.accelPeek = function(callback) { + simply.state.accel.listeners.push(callback); + var command = commandMap.getAccelData; + var packet = makePacket(command); + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.menu = function(menuDef) { + var command = commandMap.setMenu; + var packetDef = util2.copy(menuDef); + if (packetDef.sections instanceof Array) { + packetDef.sections = packetDef.sections.length; + } + var packet = makePacket(command, packetDef); + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.menuSection = function(sectionIndex, sectionDef) { + var command = commandMap.setMenuSection; + var packetDef = util2.copy(sectionDef); + packetDef.section = sectionIndex; + if (packetDef.items instanceof Array) { + packetDef.items = packetDef.items.length; + } + var packet = makePacket(command, packetDef); + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.menuItem = function(sectionIndex, itemIndex, itemDef) { + var command = commandMap.setMenuItem; + var packetDef = util2.copy(itemDef); + packetDef.section = sectionIndex; + packetDef.item = itemIndex; + var packet = makePacket(command, packetDef); + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.image = function(id, gbitmap) { + var command = commandMap.image; + var packetDef = util2.copy(gbitmap); + packetDef.id = id; + var packet = makePacket(command, packetDef); + SimplyPebble.sendPacket(packet); +}; + +var readInt = function(packet, width, pos, signed) { + var value = 0; + pos = pos || 0; + for (var i = 0; i < width; ++i) { + value += (packet[pos + i] & 0xFF) << (i * 8); + } + if (signed) { + var mask = 1 << (width * 8 - 1); + if (value & mask) { + value = value - (((mask - 1) << 1) + 1); + } + } + return value; +}; + +SimplyPebble.onAppMessage = function(e) { + var payload = e.payload; + var code = payload[0]; + var command = commands[code]; + + switch (command.name) { + case 'singleClick': + case 'longClick': + var button = buttons[payload[1]]; + simply.emitClick(command.name, button); + break; + case 'cardExit': + simply.emitCardExit(); + break; + case 'accelTap': + var axis = accelAxes[payload[1]]; + simply.emitAccelTap(axis, payload[2]); + break; + case 'accelData': + var transactionId = payload[1]; + var samples = payload[2]; + var data = payload[3]; + var accels = []; + for (var i = 0; i < samples; i++) { + var pos = i * 15; + var accel = { + x: readInt(data, 2, pos, true), + y: readInt(data, 2, pos + 2, true), + z: readInt(data, 2, pos + 4, true), + vibe: readInt(data, 1, pos + 6) ? true : false, + time: readInt(data, 8, pos + 7), + }; + accels[i] = accel; + } + if (typeof transactionId === 'undefined') { + simply.emitAccelData(accels); + } else { + var handlers = simply.state.accel.listeners; + simply.state.accel.listeners = []; + for (var j = 0, jj = handlers.length; j < jj; ++j) { + simply.emitAccelData(accels, handlers[j]); + } + } + break; + case 'getMenuSection': + simply.emitMenuSection(payload[1]); + break; + case 'getMenuItem': + simply.emitMenuItem(payload[1], payload[2]); + break; + case 'menuSelect': + case 'menuLongSelect': + case 'menuExit': + simply.emitMenuSelect(command.name, payload[1], payload[2]); + break; + } +}; + +Pebble.addEventListener('showConfiguration', SimplyPebble.onShowConfiguration); +Pebble.addEventListener('webviewclosed', SimplyPebble.onWebViewClosed); +Pebble.addEventListener('appmessage', SimplyPebble.onAppMessage); + +module.exports = SimplyPebble; + +}); +__loader.define("simply.js", function(module, require) { +/** + * Simply.js + * @namespace simply + */ + +var ajax = require('ajax'); +var util2 = require('util2'); +var imagelib = require('image'); + +var simply = {}; + +var buttons = [ + 'back', + 'up', + 'select', + 'down', +]; + +var eventTypes = [ + 'singleClick', + 'longClick', + 'cardExit', + 'accelTap', + 'accelData', + 'menuSection', + 'menuItem', + 'menuSelect', + 'menuLongSelect', + 'menuExit', +]; + +var textParams = [ + 'title', + 'subtitle', + 'body', +]; + +var imageParams = [ + 'icon', + 'subicon', + 'banner', +]; + +var actionParams = [ + 'up', + 'select', + 'back', +]; + +var cardParams = textParams.concat(imageParams).concat(actionParams); + +simply.state = {}; +simply.packages = {}; +simply.listeners = {}; + +simply.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; + +simply.init = function() { + if (!simply.inited) { + simply.inited = new Date().getTime(); + + ajax.onHandler = function(type, handler) { + return simply.wrapHandler(handler, 2); + }; + } + + simply.loadMainScript(); +}; + +simply.wrapHandler = function(handler) { + return simply.impl.wrapHandler.apply(this, arguments); +}; + +simply.begin = function() { +}; + +simply.end = function() { + simply.state.run = false; +}; + +simply.reset = function() { + simply.off(); + + simply.packages = {}; + + simply.state = {}; + simply.state.run = true; + simply.state.numPackages = 0; + simply.state.card = {}; + simply.state.options = {}; + + simply.state.button = { + config: {}, + configMode: 'auto', + }; + for (var i = 0, ii = buttons.length; i < ii; i++) { + var button = buttons[i]; + if (button !== 'back') { + simply.state.button.config[buttons[i]] = true; + } + } + + simply.state.image = { + cache: {}, + nextId: 1, + }; + + simply.state.webview = { + listeners: [], + }; + + simply.accelInit(); +}; + +/** + * Simply.js event handler callback. + * @callback simply.eventHandler + * @param {simply.event} event - The event object with event specific information. + */ + +var isBackEvent = function(type, subtype) { + return ((type === 'singleClick' || type === 'longClick') && subtype === 'back'); +}; + +simply.onAddHandler = function(type, subtype) { + if (isBackEvent(type, subtype)) { + simply.buttonAutoConfig(); + } else if (type === 'accelData') { + simply.accelAutoSubscribe(); + } +}; + +simply.onRemoveHandler = function(type, subtype) { + if (!type || isBackEvent(type, subtype)) { + simply.buttonAutoConfig(); + } + if (!type || type === 'accelData') { + simply.accelAutoSubscribe(); + } +}; + +simply.countHandlers = function(type, subtype) { + if (!subtype) { + subtype = 'all'; + } + var typeMap = simply.listeners; + var subtypeMap = typeMap[type]; + if (!subtypeMap) { + return 0; + } + var handlers = subtypeMap[subtype]; + return handlers ? handlers.length : 0; +}; + +var checkEventType = function(type) { + if (eventTypes.indexOf(type) === -1) { + throw new Error('Invalid event type: ' + type); + } +}; + +/** + * Subscribe to Pebble events. + * See {@link simply.event} for the possible event types to subscribe to. + * Subscribing to a Pebble event requires a handler. An event object will be passed to your handler with event information. + * Events can have a subtype which can be used to filter events before the handler is called. + * @memberOf simply + * @param {string} type - The event type. + * @param {string} [subtype] - The event subtype. + * @param {simply.eventHandler} handler - The event handler. The handler will be called with corresponding event. + * @see simply.event + */ +simply.on = function(type, subtype, handler) { + if (type) { + checkEventType(type); + } + if (!handler) { + handler = subtype; + subtype = 'all'; + } + simply.rawOn(type, subtype, handler); + simply.onAddHandler(type, subtype); +}; + +simply.rawOn = function(type, subtype, handler) { + var typeMap = simply.listeners; + var subtypeMap = (typeMap[type] || ( typeMap[type] = {} )); + (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ + id: handler, + handler: simply.wrapHandler(handler), + }); +}; + +/** + * Unsubscribe from Pebble events. + * When called without a handler, all handlers of the type and subtype are unsubscribe. + * When called with no parameters, all handlers are unsubscribed. + * @memberOf simply + * @param {string} type - The event type. + * @param {string} [subtype] - The event subtype. + * @param {function} [handler] - The event handler to unsubscribe. + * @see simply.on + */ +simply.off = function(type, subtype, handler) { + if (type) { + checkEventType(type); + } + if (!handler) { + handler = subtype; + subtype = 'all'; + } + simply.rawOff(type, subtype, handler); + simply.onRemoveHandler(type, subtype); +}; + +simply.rawOff = function(type, subtype, handler) { + if (!type) { + simply.listeners = {}; + return; + } + var typeMap = simply.listeners; + if (!handler && subtype === 'all') { + delete typeMap[type]; + return; + } + var subtypeMap = typeMap[type]; + if (!subtypeMap) { + return; + } + if (!handler) { + delete subtypeMap[subtype]; + return; + } + var handlers = subtypeMap[subtype]; + if (!handlers) { + return; + } + var index = -1; + for (var i = 0, ii = handlers.length; i < ii; ++i) { + if (handlers[i].id === handler) { + index = i; + break; + } + } + if (index === -1) { + return; + } + handlers.splice(index, 1); +}; + +simply.emitToHandlers = function(type, handlers, e) { + if (!handlers) { + return; + } + for (var i = 0, ii = handlers.length; i < ii; ++i) { + var handler = handlers[i].handler; + if (handler(e, type, i) === false) { + return true; + } + } + return false; +}; + +simply.emit = function(type, subtype, e) { + if (!simply.state.run) { + return; + } + if (!e) { + e = subtype; + subtype = null; + } + e.type = type; + if (subtype) { + e.subtype = subtype; + } + var typeMap = simply.listeners; + var subtypeMap = typeMap[type]; + if (!subtypeMap) { + return; + } + if (simply.emitToHandlers(type, subtypeMap[subtype], e) === true) { + return true; + } + if (simply.emitToHandlers(type, subtypeMap.all, e) === true) { + return true; + } + return false; +}; + +var pathToName = function(path) { + var name = path; + if (typeof name === 'string') { + name = name.replace(simply.basepath(), ''); + } + return name || simply.basename(); +}; + +simply.getPackageByPath = function(path) { + return simply.packages[pathToName(path)]; +}; + +simply.makePackage = function(path) { + var name = pathToName(path); + var saveName = 'script:' + path; + var pkg = simply.packages[name]; + + if (!pkg) { + pkg = simply.packages[name] = { + name: name, + saveName: saveName, + filename: path + }; + } + + return pkg; +}; + +simply.defun = function(fn, fargs, fbody) { + if (!fbody) { + fbody = fargs; + fargs = []; + } + return new Function('return function ' + fn + '(' + fargs.join(', ') + ') {' + fbody + '}')(); +}; + +var slog = function() { + var args = []; + for (var i = 0, ii = arguments.length; i < ii; ++i) { + args[i] = util2.toString(arguments[i]); + } + return args.join(' '); +}; + +simply.fexecPackage = function(script, pkg) { + // console shim + var console2 = simply.console2 = {}; + for (var k in console) { + console2[k] = console[k]; + } + + console2.log = function() { + var msg = pkg.name + ': ' + slog.apply(this, arguments); + var width = 45; + var prefix = (new Array(width + 1)).join('\b'); // erase Simply.js source line + var suffix = msg.length < width ? (new Array(width - msg.length + 1)).join(' ') : 0; + console.log(prefix + msg + suffix); + }; + + // loader + return function() { + if (!simply.state.run) { + return; + } + var exports = pkg.exports; + var result = simply.defun(pkg.execName, + ['module', 'require', 'console', 'Pebble', 'simply'], script) + (pkg, simply.require, console2, Pebble, simply); + + // backwards compatibility for return-style modules + if (pkg.exports === exports && result) { + pkg.exports = result; + } + + return pkg.exports; + }; +}; + +simply.loadScript = function(scriptUrl, async) { + console.log('loading: ' + scriptUrl); + + var pkg = simply.makePackage(scriptUrl); + pkg.exports = {}; + + var loader = util2.noop; + var useScript = function(script) { + loader = simply.fexecPackage(script, pkg); + }; + + ajax({ url: scriptUrl, cache: false, async: async }, function(data) { + if (data && data.length) { + localStorage.setItem(pkg.saveName, data); + useScript(data); + } + }, function(data, status) { + data = localStorage.getItem(pkg.saveName); + if (data && data.length) { + console.log(status + ': failed, loading saved script instead'); + useScript(data); + } + }); + + return simply.impl.loadPackage.call(this, pkg, loader); +}; + +simply.loadMainScriptUrl = function(scriptUrl) { + if (typeof scriptUrl === 'string' && scriptUrl.length && !scriptUrl.match(/^(\w+:)?\/\//)) { + scriptUrl = 'http://' + scriptUrl; + } + + if (scriptUrl) { + localStorage.setItem('mainJsUrl', scriptUrl); + } else { + scriptUrl = localStorage.getItem('mainJsUrl'); + } + + return scriptUrl; +}; + +simply.loadMainScript = function(scriptUrl) { + simply.reset(); + scriptUrl = simply.loadMainScriptUrl(scriptUrl); + if (!scriptUrl) { + return; + } + simply.loadOptions(); + try { + simply.loadScript(scriptUrl, false); + } catch (e) { + simply.text({ + title: 'Failed to load', + body: scriptUrl, + }, true); + return; + } + simply.begin(); +}; + +simply.basepath = function(path) { + path = path || localStorage.getItem('mainJsUrl'); + return path.replace(/[^\/]*$/, ''); +}; + +simply.basename = function(path) { + path = path || localStorage.getItem('mainJsUrl'); + return path.match(/[^\/]*$/)[0]; +}; + +/** + * Loads external dependencies, allowing you to write a multi-file project. + * Package loading loosely follows the CommonJS format. + * Exporting is possible by modifying or setting module.exports within the required file. + * The module path is also available as module.path. + * This currently only supports a relative path to another JavaScript file. + * @global + * @param {string} path - The path to the dependency. + */ +simply.require = function(path) { + if (!path.match(/\.js$/)) { + path += '.js'; + } + var package = simply.packages[path]; + if (package) { + return package.value; + } + var basepath = simply.basepath(); + return simply.loadScript(basepath + path, false); +}; + +/** + * The button configuration parameter for {@link simply.buttonConfig}. + * The button configuration allows you to enable to disable buttons without having to register or unregister handlers if that is your preferred style. + * You may also enable the back button manually as an alternative to registering a click handler with 'back' as its subtype using {@link simply.on}. + * @typedef {object} simply.buttonConf + * @property {boolean} [back] - Whether to enable the back button. Initializes as false. Simply.js can also automatically register this for you based on the amount of click handlers with subtype 'back'. + * @property {boolean} [up] - Whether to enable the up button. Initializes as true. Note that this is disabled when using {@link simply.scrollable}. + * @property {boolean} [select] - Whether to enable the select button. Initializes as true. + * @property {boolean} [down] - Whether to enable the down button. Initializes as true. Note that this is disabled when using {@link simply.scrollable}. + */ + +/** + * Changes the button configuration. + * See {@link simply.buttonConfig} + * @memberOf simply + * @param {simply.buttonConfig} buttonConf - An object defining the button configuration. + */ +simply.buttonConfig = function(buttonConf, auto) { + var buttonState = simply.state.button; + if (typeof buttonConf === 'undefined') { + var config = {}; + for (var i = 0, ii = buttons.length; i < ii; ++i) { + var name = buttons[i]; + config[name] = buttonConf.config[name]; + } + return config; + } + for (var k in buttonConf) { + if (buttons.indexOf(k) !== -1) { + if (k === 'back') { + buttonState.configMode = buttonConf.back && !auto ? 'manual' : 'auto'; + } + buttonState.config[k] = buttonConf[k]; + } + } + if (simply.impl.buttonConfig) { + return simply.impl.buttonConfig(buttonState.config); + } +}; + +simply.buttonAutoConfig = function() { + var buttonState = simply.state.button; + if (!buttonState || buttonState.configMode !== 'auto') { + return; + } + var singleBackCount = simply.countHandlers('singleClick', 'back'); + var longBackCount = simply.countHandlers('longClick', 'back'); + var useBack = singleBackCount + longBackCount > 0; + if (useBack !== buttonState.config.back) { + buttonState.config.back = useBack; + return simply.buttonConfig(buttonState.config, true); + } +}; + +simply.card = function(field, value, clear) { + if (arguments.length === 0) { + return simply.state.card; + } + var cardDef; + if (typeof field === 'string') { + if (arguments.length === 1) { + return simply.state.card[field]; + } + cardDef = {}; + cardDef[field] = value; + } else { + cardDef = field; + clear = value; + value = null; + } + clear = clear === true ? 'all' : clear; + if (clear === 'all') { + simply.state.card = cardDef; + } else { + util2.copy(cardDef, simply.state.card); + } + return simply.impl.card(cardDef, clear); +}; + +/** + * The text definition parameter for {@link simply.text}. + * @typedef {object} simply.textDef + * @property {string} [title] - A new title for the first and largest text field. + * @property {string} [subtitle] - A new subtitle for the second large text field. + * @property {string} [body] - A new body for the last text field meant to display large bodies of text. + */ + +/** + * Sets a group of text fields at once. + * For example, passing a text definition { title: 'A', subtitle: 'B', body: 'C' } + * will set the title, subtitle, and body simultaneously. Not all fields need to be specified. + * When setting a single field, consider using the specific text setters simply.title, simply.subtitle, simply.body. + * @memberOf simply + * @param {simply.textDef} textDef - An object defining new text values. + * @param {boolean} [clear] - If true, all other text fields will be cleared. + */ +simply.text = simply.card; + +simply.setText = simply.text; + +var textfield = function(field, text, clear) { + if (arguments.length <= 1) { + return simply.state.card[field]; + } + if (clear) { + simply.state.card = {}; + } + simply.state.card[field] = text; + return simply.impl.textfield(field, text, clear); +}; + +/** + * Sets the title field. The title field is the first and largest text field available. + * @memberOf simply + * @param {string} text - The desired text to display. + * @param {boolean} [clear] - If true, all other text fields will be cleared. + */ +simply.title = function(text, clear) { + return textfield('title', text, clear); +}; + +/** + * Sets the subtitle field. The subtitle field is the second large text field available. + * @memberOf simply + * @param {string} text - The desired text to display. + * @param {boolean} [clear] - If true, all other text fields will be cleared. + */ +simply.subtitle = function(text, clear) { + return textfield('subtitle', text, clear); +}; + +/** + * Sets the body field. The body field is the last text field available meant to display large bodies of text. + * This can be used to display entire text interfaces. + * You may even clear the title and subtitle fields in order to display more in the body field. + * @memberOf simply + * @param {string} text - The desired text to display. + * @param {boolean} [clear] - If true, all other text fields will be cleared. + */ +simply.body = function(text, clear) { + return textfield('body', text, clear); +}; + +simply.action = function(field, image, clear) { + if (!simply.state.card.action) { + simply.state.card.action = {}; + } + if (arguments.length === 0) { + return simply.state.card.action; + } + var actionDef; + if (typeof field === 'string') { + if (arguments.length === 1) { + return simply.state.card.action[field]; + } + actionDef = {}; + actionDef[field] = image; + } else { + actionDef = field; + clear = image; + image = null; + } + clear = clear === true ? 'action' : clear; + if (clear === 'all' || clear === 'action') { + simply.state.card.action = actionDef; + } else { + util2.copy(actionDef, simply.state.card.action); + } + return simply.impl.card({ action: actionDef }, clear); +}; + +/** + * Vibrates the Pebble. + * There are three support vibe types: short, long, and double. + * @memberOf simply + * @param {string} [type=short] - The vibe type. + */ +simply.vibe = function() { + return simply.impl.vibe.apply(this, arguments); +}; + +/** + * Enable scrolling in the Pebble UI. + * When scrolling is enabled, up and down button presses are no longer forwarded to JavaScript handlers. + * Single select, long select, and accel tap events are still available to you however. + * @memberOf simply + * @param {boolean} scrollable - Whether to enable a scrollable view. + */ + +simply.scrollable = function(scrollable) { + if (typeof scrollable === 'undefined') { + return simply.state.scrollable === true; + } + simply.state.scrollable = scrollable; + return simply.impl.scrollable.apply(this, arguments); +}; + +/** + * Enable fullscreen in the Pebble UI. + * Fullscreen removes the Pebble status bar, giving slightly more vertical display height. + * @memberOf simply + * @param {boolean} fullscreen - Whether to enable fullscreen mode. + */ + +simply.fullscreen = function(fullscreen) { + if (typeof fullscreen === 'undefined') { + return simply.state.fullscreen === true; + } + simply.state.fullscreen = fullscreen; + return simply.impl.fullscreen.apply(this, arguments); +}; + +/** + * Set the Pebble UI style. + * The available styles are 'small', 'large', and 'mono'. Small and large correspond to the system notification styles. + * Mono sets a monospace font for the body textfield, enabling more complex text UIs or ASCII art. + * @memberOf simply + * @param {string} type - The type of style to set: 'small', 'large', or 'mono'. + */ + +simply.style = function(type) { + return simply.impl.style.apply(this, arguments); +}; + +var makeImageHash = function(image) { + var url = image.url; + var hashPart = ''; + if (image.width) { + hashPart += ',width:' + image.width; + } + if (image.height) { + hashPart += ',height:' + image.height; + } + if (image.dither) { + hashPart += ',dither:' + image.dither; + } + if (hashPart) { + url += '#' + hashPart.substr(1); + } + return url; +}; + +var parseImageHash = function(hash) { + var image = {}; + hash = hash.split('#'); + image.url = hash[0]; + hash = hash[1]; + if (!hash) { return image; } + var args = hash.split(','); + for (var i = 0, ii = args.length; i < ii; ++i) { + var arg = args[i]; + if (arg.match(':')) { + arg = arg.split(':'); + var v = arg[1]; + image[arg[0]] = !isNaN(Number(v)) ? Number(v) : v; + } else { + image[arg] = true; + } + } + return image; +}; + +simply.image = function(opt, reset, callback) { + if (typeof opt === 'string') { + opt = parseImageHash(opt); + } + if (typeof reset === 'function') { + callback = reset; + reset = null; + } + var url = simply.basepath() + opt.url; + var hash = makeImageHash(opt); + var image = simply.state.image.cache[hash]; + if (image) { + if ((opt.width && image.width !== opt.width) || + (opt.height && image.height !== opt.height) || + (opt.dither && image.dither !== opt.dither)) { + reset = true; + } + if (reset !== true) { + return image.id; + } + } + image = { + id: simply.state.image.nextId++, + url: url, + width: opt.width, + height: opt.height, + dither: opt.dither, + }; + simply.state.image.cache[hash] = image; + imagelib.load(image, function() { + simply.impl.image(image.id, image.gbitmap); + if (callback) { + var e = { + type: 'image', + image: image.id, + url: image.url, + }; + callback(e); + } + }); + return image.id; +}; + +var getOptionsKey = function() { + return 'options:' + simply.basename(); +}; + +simply.saveOptions = function() { + var options = simply.state.options; + localStorage.setItem(getOptionsKey(), JSON.stringify(options)); +}; + +simply.loadOptions = function() { + simply.state.options = {}; + var options = localStorage.getItem(getOptionsKey()); + try { + simply.state.options = JSON.parse(options); + } catch (e) {} +}; + +simply.option = function(key, value) { + var options = simply.state.options; + if (arguments.length >= 2) { + if (typeof value === 'undefined') { + delete options[key]; + } else { + try { + value = JSON.stringify(value); + } catch (e) {} + options[key] = '' + value; + } + simply.saveOptions(); + } + value = options[key]; + if (!isNaN(Number(value))) { + return Number(value); + } + try { + value = JSON.parse(value); + } catch (e) {} + return value; +}; + +simply.getBaseOptions = function() { + return { + scriptUrl: localStorage.getItem('mainJsUrl'), + }; +}; + +simply.settings = function(opt, open, close) { + if (typeof opt === 'string') { + opt = { url: opt }; + } + if (typeof close === 'undefined') { + close = open; + open = util2.noop; + } + var listener = { + params: opt, + open: open, + close: close, + }; + simply.state.webview.listeners.push(listener); +}; + +simply.openSettings = function(e) { + var options; + var url; + var listener = util2.last(simply.state.webview.listeners); + if (listener && (new Date().getTime() - simply.inited) > 2000) { + url = listener.params.url; + options = simply.state.options; + e = { + originalEvent: e, + options: options, + url: listener.params.url, + }; + listener.open(e); + } else { + url = simply.settingsUrl; + options = simply.getBaseOptions(); + } + var hash = encodeURIComponent(JSON.stringify(options)); + Pebble.openURL(url + '#' + hash); +}; + +simply.closeSettings = function(e) { + var listener = util2.last(simply.state.webview.listeners); + var options = {}; + if (e.response) { + options = JSON.parse(decodeURIComponent(e.response)); + } + if (listener) { + e = { + originalEvent: e, + options: options, + url: listener.params.url, + }; + return listener.close(e); + } + if (options.scriptUrl) { + simply.loadMainScript(options.scriptUrl); + } +}; + +simply.accelInit = function() { + simply.state.accel = { + rate: 100, + samples: 25, + subscribe: false, + subscribeMode: 'auto', + listeners: [], + }; +}; + +simply.accelAutoSubscribe = function() { + var accelState = simply.state.accel; + if (!accelState || accelState.subscribeMode !== 'auto') { + return; + } + var subscribe = simply.countHandlers('accelData') > 0; + if (subscribe !== simply.state.accel.subscribe) { + return simply.accelConfig(subscribe, true); + } +}; + +/** + * The accelerometer configuration parameter for {@link simply.accelConfig}. + * The accelerometer data stream is useful for applications such as gesture recognition when accelTap is too limited. + * However, keep in mind that smaller batch sample sizes and faster rates will drastically impact the battery life of both the Pebble and phone because of the taxing use of the processors and Bluetooth modules. + * @typedef {object} simply.accelConf + * @property {number} [rate] - The rate accelerometer data points are generated in hertz. Valid values are 10, 25, 50, and 100. Initializes as 100. + * @property {number} [samples] - The number of accelerometer data points to accumulate in a batch before calling the event handler. Valid values are 1 to 25 inclusive. Initializes as 25. + * @property {boolean} [subscribe] - Whether to subscribe to accelerometer data events. {@link simply.accelPeek} cannot be used when subscribed. Simply.js will automatically (un)subscribe for you depending on the amount of accelData handlers registered. + */ + +/** + * Changes the accelerometer configuration. + * See {@link simply.accelConfig} + * @memberOf simply + * @param {simply.accelConfig} accelConf - An object defining the accelerometer configuration. + */ +simply.accelConfig = function(opt, auto) { + var accelState = simply.state.accel; + if (typeof opt === 'undefined') { + return { + rate: accelState.rate, + samples: accelState.samples, + subscribe: accelState.subscribe, + }; + } else if (typeof opt === 'boolean') { + opt = { subscribe: opt }; + } + for (var k in opt) { + if (k === 'subscribe') { + accelState.subscribeMode = opt[k] && !auto ? 'manual' : 'auto'; + } + accelState[k] = opt[k]; + } + return simply.impl.accelConfig.apply(this, arguments); +}; + +/** + * Peeks at the current accelerometer values. + * @memberOf simply + * @param {simply.eventHandler} callback - A callback function that will be provided the accel data point as an event. + */ +simply.accelPeek = function(callback) { + if (simply.state.accel.subscribe) { + throw new Error('Cannot use accelPeek when listening to accelData events'); + } + return simply.impl.accelPeek.apply(this, arguments); +}; + +var getMenuSection = function(e) { + var menu = e.menu || simply.state.menu; + if (!menu) { return; } + if (!(menu.sections instanceof Array)) { return; } + return menu.sections[e.section]; +}; + +var getMenuItem = function(e) { + var section = getMenuSection(e); + if (!section) { return; } + if (!(section.items instanceof Array)) { return; } + return section.items[e.item]; +}; + +simply.onMenuSection = function(e) { + var section = getMenuSection(e); + if (!section) { return; } + simply.menuSection(e.section, section); +}; + +simply.onMenuItem = function(e) { + var item = getMenuItem(e); + if (!item) { return; } + simply.menuItem(e.section, e.item, item); +}; + +simply.onMenuSelect = function(e) { + var menu = e.menu; + var item = getMenuItem(e); + if (!item) { return; } + switch (e.type) { + case 'menuSelect': + if (typeof item.select === 'function') { + if (item.select(e) === false) { + return false; + } + } + break; + case 'menuLongSelect': + if (typeof item.longSelect === 'function') { + if (item.longSelect(e) === false) { + return false; + } + } + break; + case 'menuExit': + if (typeof item.exit === 'function') { + if (item.exit(e) === false) { + return false; + } + } + if (typeof menu.exit === 'function') { + if (menu.exit(e) === false) { + return false; + } + } + break; + } +}; + +simply.menu = function(menuDef) { + if (!menuDef) { + return simply.state.menu; + } + simply.state.menu = menuDef; + return simply.impl.menu.apply(this, arguments); +}; + +simply.menuSection = function(sectionIndex, sectionDef) { + if (typeof sectionIndex === 'undefined') { + return getMenuSection({ section: sectionIndex }); + } + return simply.impl.menuSection.apply(this, arguments); +}; + +simply.menuItem = function(sectionIndex, itemIndex, itemDef) { + if (typeof sectionIndex === 'undefined') { + return getMenuItem({ section: sectionIndex, item: itemIndex }); + } + return simply.impl.menuItem.apply(this, arguments); +}; + +/** + * Simply.js event. See all the possible event types. Subscribe to events using {@link simply.on}. + * @typedef simply.event + * @see simply.clickEvent + * @see simply.accelTapEvent + * @see simply.accelDataEvent + */ + +/** + * Simply.js button click event. This can either be a single click or long click. + * Use the event type 'singleClick' or 'longClick' to subscribe to these events. + * @typedef simply.clickEvent + * @property {string} button - The button that was pressed: 'back', 'up', 'select', or 'down'. This is also the event subtype. + */ + +simply.emitClick = function(type, button) { + simply.emit(type, button, { + button: button, + }); +}; + +simply.emitCardExit = function() { + var cardDef = simply.state.card; + simply.emit('cardExit', util2.copy(cardDef, { + card: cardDef + })); +}; + +/** + * Simply.js accel tap event. + * Use the event type 'accelTap' to subscribe to these events. + * @typedef simply.accelTapEvent + * @property {string} axis - The axis the tap event occurred on: 'x', 'y', or 'z'. This is also the event subtype. + * @property {number} direction - The direction of the tap along the axis: 1 or -1. + */ + +simply.emitAccelTap = function(axis, direction) { + simply.emit('accelTap', axis, { + axis: axis, + direction: direction, + }); +}; + +/** + * Simply.js accel data point. + * Typical values for gravity is around -1000 on the z axis. + * @typedef simply.accelPoint + * @property {number} x - The acceleration across the x-axis. + * @property {number} y - The acceleration across the y-axis. + * @property {number} z - The acceleration across the z-axis. + * @property {boolean} vibe - Whether the watch was vibrating when measuring this point. + * @property {number} time - The amount of ticks in millisecond resolution when measuring this point. + */ + +/** + * Simply.js accel data event. + * Use the event type 'accelData' to subscribe to these events. + * @typedef simply.accelDataEvent + * @property {number} samples - The number of accelerometer samples in this event. + * @property {simply.accelPoint} accel - The first accel in the batch. This is provided for convenience. + * @property {simply.accelPoint[]} accels - The accelerometer samples in an array. + */ + +simply.emitAccelData = function(accels, callback) { + var e = { + samples: accels.length, + accel: accels[0], + accels: accels, + }; + if (callback) { + return callback(e); + } + simply.emit('accelData', e); +}; + +simply.emitMenuSection = function(section) { + var e = { + menu: simply.state.menu, + section: section + }; + if (simply.emit('menuSection', e)) { return; } + simply.onMenuSection(e); +}; + +simply.emitMenuItem = function(section, item) { + var e = { + menu: simply.state.menu, + section: section, + item: item, + }; + if (simply.emit('menuItem', e)) { return; } + simply.onMenuItem(e); +}; + +simply.emitMenuSelect = function(type, section, item) { + var e = { + menu: simply.state.menu, + section: section, + item: item, + }; + if (simply.emit(type, e)) { return; } + simply.onMenuSelect(e); +}; + +module.exports = simply; + +Pebble.require = require; +window.require = simply.require; + +}); +__loader.define("util2.js", function(module, require) { +/* + * util2.js by Meiguro - MIT License + */ + +var util2 = (function(){ + +var util2 = {}; + +util2.noop = function() {}; + +util2.copy = function(a, b) { + b = b || (a instanceof Array ? [] : {}); + for (var k in a) { b[k] = a[k]; } + return b; +}; + +util2.toInteger = function(x) { + if (!isNaN(x = parseInt(x))) { return x; } +}; + +util2.toNumber = function(x) { + if (!isNaN(x = parseFloat(x))) { return x; } +}; + +util2.toString = function(x) { + return typeof x === 'object' ? JSON.stringify.apply(this, arguments) : '' + x; +}; + +util2.toArray = function(x) { + if (x instanceof Array) { return x; } + if (x[0]) { return util2.copy(x, []); } + return [x]; +}; + +util2.trim = function(s) { + return s ? s.toString().trim() : s; +}; + +util2.last = function(a) { + return a[a.length-1]; +}; + +var chunkSize = 128; + +var randomBytes = function(chunkSize) { + var z = []; + for (var i = 0; i < chunkSize; ++i) { + z[i] = String.fromCharCode(Math.random() * 256); + } + return z.join(''); +}; + +util2.randomString = function(regex, size, acc) { + if (!size) { + return ''; + } + if (typeof regex === 'string') { + regex = new RegExp('(?!'+regex+')[\\s\\S]', 'g'); + } + acc = acc || ''; + var buf = randomBytes(chunkSize); + if (buf) { + acc += buf.replace(regex, ''); + } + if (acc.length >= size) { + return acc.substr(0, size); + } else { + return util2.randomString(regex, size, acc); + } +}; + +var varpat = new RegExp("^([\\s\\S]*?)\\$([_a-zA-Z0-9]+)", "m"); + +util2.format = function(text, table) { + var m, z = ''; + while ((m = text.match(varpat))) { + var subtext = m[0], value = table[m[2]]; + if (typeof value === 'function') { value = value(); } + z += value !== undefined ? m[1] + value.toString() : subtext; + text = text.substring(subtext.length); + } + z += text; + return z; +}; + +if (typeof module !== 'undefined') { + module.exports = util2; +} + +return util2; + +})(); + +}); +//! moment.js +//! version : 2.5.0 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +(function(a){function b(a,b){return function(c){return i(a.call(this,c),b)}}function c(a,b){return function(c){return this.lang().ordinal(a.call(this,c),b)}}function d(){}function e(a){u(a),g(this,a)}function f(a){var b=o(a),c=b.year||0,d=b.month||0,e=b.week||0,f=b.day||0,g=b.hour||0,h=b.minute||0,i=b.second||0,j=b.millisecond||0;this._milliseconds=+j+1e3*i+6e4*h+36e5*g,this._days=+f+7*e,this._months=+d+12*c,this._data={},this._bubble()}function g(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return b.hasOwnProperty("toString")&&(a.toString=b.toString),b.hasOwnProperty("valueOf")&&(a.valueOf=b.valueOf),a}function h(a){return 0>a?Math.ceil(a):Math.floor(a)}function i(a,b,c){for(var d=Math.abs(a)+"",e=a>=0;d.lengthd;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function n(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=Qb[a]||Rb[b]||b}return a}function o(a){var b,c,d={};for(c in a)a.hasOwnProperty(c)&&(b=n(c),b&&(d[b]=a[c]));return d}function p(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}cb[b]=function(e,f){var g,h,i=cb.fn._lang[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=cb().utc().set(d,a);return i.call(cb.fn._lang,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function r(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function s(a){return t(a)?366:365}function t(a){return a%4===0&&a%100!==0||a%400===0}function u(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[ib]<0||a._a[ib]>11?ib:a._a[jb]<1||a._a[jb]>r(a._a[hb],a._a[ib])?jb:a._a[kb]<0||a._a[kb]>23?kb:a._a[lb]<0||a._a[lb]>59?lb:a._a[mb]<0||a._a[mb]>59?mb:a._a[nb]<0||a._a[nb]>999?nb:-1,a._pf._overflowDayOfYear&&(hb>b||b>jb)&&(b=jb),a._pf.overflow=b)}function v(a){a._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function w(a){return null==a._isValid&&(a._isValid=!isNaN(a._d.getTime())&&a._pf.overflow<0&&!a._pf.empty&&!a._pf.invalidMonth&&!a._pf.nullInput&&!a._pf.invalidFormat&&!a._pf.userInvalidated,a._strict&&(a._isValid=a._isValid&&0===a._pf.charsLeftOver&&0===a._pf.unusedTokens.length)),a._isValid}function x(a){return a?a.toLowerCase().replace("_","-"):a}function y(a,b){return b._isUTC?cb(a).zone(b._offset||0):cb(a).local()}function z(a,b){return b.abbr=a,ob[a]||(ob[a]=new d),ob[a].set(b),ob[a]}function A(a){delete ob[a]}function B(a){var b,c,d,e,f=0,g=function(a){if(!ob[a]&&pb)try{require("./lang/"+a)}catch(b){}return ob[a]};if(!a)return cb.fn._lang;if(!k(a)){if(c=g(a))return c;a=[a]}for(;f0;){if(c=g(e.slice(0,b).join("-")))return c;if(d&&d.length>=b&&m(e,d,!0)>=b-1)break;b--}f++}return cb.fn._lang}function C(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function D(a){var b,c,d=a.match(tb);for(b=0,c=d.length;c>b;b++)d[b]=Vb[d[b]]?Vb[d[b]]:C(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function E(a,b){return a.isValid()?(b=F(b,a.lang()),Sb[b]||(Sb[b]=D(b)),Sb[b](a)):a.lang().invalidDate()}function F(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(ub.lastIndex=0;d>=0&&ub.test(a);)a=a.replace(ub,c),ub.lastIndex=0,d-=1;return a}function G(a,b){var c,d=b._strict;switch(a){case"DDDD":return Gb;case"YYYY":case"GGGG":case"gggg":return d?Hb:xb;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return d?Ib:yb;case"S":if(d)return Eb;case"SS":if(d)return Fb;case"SSS":case"DDD":return d?Gb:wb;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Ab;case"a":case"A":return B(b._l)._meridiemParse;case"X":return Db;case"Z":case"ZZ":return Bb;case"T":return Cb;case"SSSS":return zb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return d?Fb:vb;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return d?Eb:vb;default:return c=new RegExp(O(N(a.replace("\\","")),"i"))}}function H(a){a=a||"";var b=a.match(Bb)||[],c=b[b.length-1]||[],d=(c+"").match(Nb)||["-",0,0],e=+(60*d[1])+q(d[2]);return"+"===d[0]?-e:e}function I(a,b,c){var d,e=c._a;switch(a){case"M":case"MM":null!=b&&(e[ib]=q(b)-1);break;case"MMM":case"MMMM":d=B(c._l).monthsParse(b),null!=d?e[ib]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[jb]=q(b));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=q(b));break;case"YY":e[hb]=q(b)+(q(b)>68?1900:2e3);break;case"YYYY":case"YYYYY":case"YYYYYY":e[hb]=q(b);break;case"a":case"A":c._isPm=B(c._l).isPM(b);break;case"H":case"HH":case"h":case"hh":e[kb]=q(b);break;case"m":case"mm":e[lb]=q(b);break;case"s":case"ss":e[mb]=q(b);break;case"S":case"SS":case"SSS":case"SSSS":e[nb]=q(1e3*("0."+b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=H(b);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":a=a.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=b)}}function J(a){var b,c,d,e,f,g,h,i,j,k,l=[];if(!a._d){for(d=L(a),a._w&&null==a._a[jb]&&null==a._a[ib]&&(f=function(b){var c=parseInt(b,10);return b?b.length<3?c>68?1900+c:2e3+c:c:null==a._a[hb]?cb().weekYear():a._a[hb]},g=a._w,null!=g.GG||null!=g.W||null!=g.E?h=Y(f(g.GG),g.W||1,g.E,4,1):(i=B(a._l),j=null!=g.d?U(g.d,i):null!=g.e?parseInt(g.e,10)+i._week.dow:0,k=parseInt(g.w,10)||1,null!=g.d&&js(e)&&(a._pf._overflowDayOfYear=!0),c=T(e,0,a._dayOfYear),a._a[ib]=c.getUTCMonth(),a._a[jb]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=l[b]=d[b];for(;7>b;b++)a._a[b]=l[b]=null==a._a[b]?2===b?1:0:a._a[b];l[kb]+=q((a._tzm||0)/60),l[lb]+=q((a._tzm||0)%60),a._d=(a._useUTC?T:S).apply(null,l)}}function K(a){var b;a._d||(b=o(a._i),a._a=[b.year,b.month,b.day,b.hour,b.minute,b.second,b.millisecond],J(a))}function L(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function M(a){a._a=[],a._pf.empty=!0;var b,c,d,e,f,g=B(a._l),h=""+a._i,i=h.length,j=0;for(d=F(a._f,g).match(tb)||[],b=0;b0&&a._pf.unusedInput.push(f),h=h.slice(h.indexOf(c)+c.length),j+=c.length),Vb[e]?(c?a._pf.empty=!1:a._pf.unusedTokens.push(e),I(e,c,a)):a._strict&&!c&&a._pf.unusedTokens.push(e);a._pf.charsLeftOver=i-j,h.length>0&&a._pf.unusedInput.push(h),a._isPm&&a._a[kb]<12&&(a._a[kb]+=12),a._isPm===!1&&12===a._a[kb]&&(a._a[kb]=0),J(a),u(a)}function N(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function O(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function P(a){var b,c,d,e,f;if(0===a._f.length)return a._pf.invalidFormat=!0,a._d=new Date(0/0),void 0;for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function Q(a){var b,c=a._i,d=Jb.exec(c);if(d){for(a._pf.iso=!0,b=4;b>0;b--)if(d[b]){a._f=Lb[b-1]+(d[6]||" ");break}for(b=0;4>b;b++)if(Mb[b][1].exec(c)){a._f+=Mb[b][0];break}c.match(Bb)&&(a._f+="Z"),M(a)}else a._d=new Date(c)}function R(b){var c=b._i,d=qb.exec(c);c===a?b._d=new Date:d?b._d=new Date(+d[1]):"string"==typeof c?Q(b):k(c)?(b._a=c.slice(0),J(b)):l(c)?b._d=new Date(+c):"object"==typeof c?K(b):b._d=new Date(c)}function S(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function T(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function U(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function V(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function W(a,b,c){var d=gb(Math.abs(a)/1e3),e=gb(d/60),f=gb(e/60),g=gb(f/24),h=gb(g/365),i=45>d&&["s",d]||1===e&&["m"]||45>e&&["mm",e]||1===f&&["h"]||22>f&&["hh",f]||1===g&&["d"]||25>=g&&["dd",g]||45>=g&&["M"]||345>g&&["MM",gb(g/30)]||1===h&&["y"]||["yy",h];return i[2]=b,i[3]=a>0,i[4]=c,V.apply({},i)}function X(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=cb(a).add("d",f),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function Y(a,b,c,d,e){var f,g,h=new Date(i(a,6,!0)+"-01-01").getUTCDay();return c=null!=c?c:e,f=e-h+(h>d?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:s(a-1)+g}}function Z(a){var b=a._i,c=a._f;return"undefined"==typeof a._pf&&v(a),null===b?cb.invalid({nullInput:!0}):("string"==typeof b&&(a._i=b=B().preparse(b)),cb.isMoment(b)?(a=g({},b),a._d=new Date(+b._d)):c?k(c)?P(a):M(a):R(a),new e(a))}function $(a,b){cb.fn[a]=cb.fn[a+"s"]=function(a){var c=this._isUTC?"UTC":"";return null!=a?(this._d["set"+c+b](a),cb.updateOffset(this),this):this._d["get"+c+b]()}}function _(a){cb.duration.fn[a]=function(){return this._data[a]}}function ab(a,b){cb.duration.fn["as"+a]=function(){return+this/b}}function bb(a){var b=!1,c=cb;"undefined"==typeof ender&&(a?(fb.moment=function(){return!b&&console&&console.warn&&(b=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),c.apply(null,arguments)},g(fb.moment,c)):fb.moment=cb)}for(var cb,db,eb="2.5.0",fb=this,gb=Math.round,hb=0,ib=1,jb=2,kb=3,lb=4,mb=5,nb=6,ob={},pb="undefined"!=typeof module&&module.exports&&"undefined"!=typeof require,qb=/^\/?Date\((\-?\d+)/i,rb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,sb=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,tb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,ub=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,vb=/\d\d?/,wb=/\d{1,3}/,xb=/\d{1,4}/,yb=/[+\-]?\d{1,6}/,zb=/\d+/,Ab=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Bb=/Z|[\+\-]\d\d:?\d\d/gi,Cb=/T/i,Db=/[\+\-]?\d+(\.\d{1,3})?/,Eb=/\d/,Fb=/\d\d/,Gb=/\d{3}/,Hb=/\d{4}/,Ib=/[+\-]?\d{6}/,Jb=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Kb="YYYY-MM-DDTHH:mm:ssZ",Lb=["YYYY-MM-DD","GGGG-[W]WW","GGGG-[W]WW-E","YYYY-DDD"],Mb=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Nb=/([\+\-]|\d\d)/gi,Ob="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Pb={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Qb={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Rb={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Sb={},Tb="DDD w W M D d".split(" "),Ub="M D H h m s w W".split(" "),Vb={M:function(){return this.month()+1},MMM:function(a){return this.lang().monthsShort(this,a)},MMMM:function(a){return this.lang().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.lang().weekdaysMin(this,a)},ddd:function(a){return this.lang().weekdaysShort(this,a)},dddd:function(a){return this.lang().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return i(this.year()%100,2)},YYYY:function(){return i(this.year(),4)},YYYYY:function(){return i(this.year(),5)},YYYYYY:function(){var a=this.year(),b=a>=0?"+":"-";return b+i(Math.abs(a),6)},gg:function(){return i(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return i(this.weekYear(),5)},GG:function(){return i(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return i(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return q(this.milliseconds()/100)},SS:function(){return i(q(this.milliseconds()/10),2)},SSS:function(){return i(this.milliseconds(),3)},SSSS:function(){return i(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+":"+i(q(a)%60,2)},ZZ:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+i(q(a)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()},Q:function(){return this.quarter()}},Wb=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Tb.length;)db=Tb.pop(),Vb[db+"o"]=c(Vb[db],db);for(;Ub.length;)db=Ub.pop(),Vb[db+db]=b(Vb[db],2);for(Vb.DDDD=b(Vb.DDD,3),g(d.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a){var b,c,d;for(this._monthsParse||(this._monthsParse=[]),b=0;12>b;b++)if(this._monthsParse[b]||(c=cb.utc([2e3,b]),d="^"+this.months(c,"")+"|^"+this.monthsShort(c,""),this._monthsParse[b]=new RegExp(d.replace(".",""),"i")),this._monthsParse[b].test(a))return b},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=cb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b){var c=this._calendar[a];return"function"==typeof c?c.apply(b):c},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",preparse:function(a){return a},postformat:function(a){return a},week:function(a){return X(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),cb=function(b,c,d,e){return"boolean"==typeof d&&(e=d,d=a),Z({_i:b,_f:c,_l:d,_strict:e,_isUTC:!1})},cb.utc=function(b,c,d,e){var f;return"boolean"==typeof d&&(e=d,d=a),f=Z({_useUTC:!0,_isUTC:!0,_l:d,_i:b,_f:c,_strict:e}).utc()},cb.unix=function(a){return cb(1e3*a)},cb.duration=function(a,b){var c,d,e,g=a,h=null;return cb.isDuration(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=rb.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:q(h[jb])*c,h:q(h[kb])*c,m:q(h[lb])*c,s:q(h[mb])*c,ms:q(h[nb])*c}):(h=sb.exec(a))&&(c="-"===h[1]?-1:1,e=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*c},g={y:e(h[2]),M:e(h[3]),d:e(h[4]),h:e(h[5]),m:e(h[6]),s:e(h[7]),w:e(h[8])}),d=new f(g),cb.isDuration(a)&&a.hasOwnProperty("_lang")&&(d._lang=a._lang),d},cb.version=eb,cb.defaultFormat=Kb,cb.updateOffset=function(){},cb.lang=function(a,b){var c;return a?(b?z(x(a),b):null===b?(A(a),a="en"):ob[a]||B(a),c=cb.duration.fn._lang=cb.fn._lang=B(a),c._abbr):cb.fn._lang._abbr},cb.langData=function(a){return a&&a._lang&&a._lang._abbr&&(a=a._lang._abbr),B(a)},cb.isMoment=function(a){return a instanceof e},cb.isDuration=function(a){return a instanceof f},db=Wb.length-1;db>=0;--db)p(Wb[db]);for(cb.normalizeUnits=function(a){return n(a)},cb.invalid=function(a){var b=cb.utc(0/0);return null!=a?g(b._pf,a):b._pf.userInvalidated=!0,b},cb.parseZone=function(a){return cb(a).parseZone()},g(cb.fn=e.prototype,{clone:function(){return cb(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var a=cb(this).utc();return 00:!1},parsingFlags:function(){return g({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(a){var b=E(this,a||cb.defaultFormat);return this.lang().postformat(b)},add:function(a,b){var c;return c="string"==typeof a?cb.duration(+b,a):cb.duration(a,b),j(this,c,1),this},subtract:function(a,b){var c;return c="string"==typeof a?cb.duration(+b,a):cb.duration(a,b),j(this,c,-1),this},diff:function(a,b,c){var d,e,f=y(a,this),g=6e4*(this.zone()-f.zone());return b=n(b),"year"===b||"month"===b?(d=432e5*(this.daysInMonth()+f.daysInMonth()),e=12*(this.year()-f.year())+(this.month()-f.month()),e+=(this-cb(this).startOf("month")-(f-cb(f).startOf("month")))/d,e-=6e4*(this.zone()-cb(this).startOf("month").zone()-(f.zone()-cb(f).startOf("month").zone()))/d,"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:h(e)},from:function(a,b){return cb.duration(this.diff(a)).lang(this.lang()._abbr).humanize(!b)},fromNow:function(a){return this.from(cb(),a)},calendar:function(){var a=y(cb(),this).startOf("day"),b=this.diff(a,"days",!0),c=-6>b?"sameElse":-1>b?"lastWeek":0>b?"lastDay":1>b?"sameDay":2>b?"nextDay":7>b?"nextWeek":"sameElse";return this.format(this.lang().calendar(c,this))},isLeapYear:function(){return t(this.year())},isDST:function(){return this.zone()+cb(a).startOf(b)},isBefore:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)<+cb(a).startOf(b)},isSame:function(a,b){return b=b||"ms",+this.clone().startOf(b)===+y(a,this).startOf(b)},min:function(a){return a=cb.apply(null,arguments),this>a?this:a},max:function(a){return a=cb.apply(null,arguments),a>this?this:a},zone:function(a){var b=this._offset||0;return null==a?this._isUTC?b:this._d.getTimezoneOffset():("string"==typeof a&&(a=H(a)),Math.abs(a)<16&&(a=60*a),this._offset=a,this._isUTC=!0,b!==a&&j(this,cb.duration(b-a,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.zone(this._tzm):"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(a){return a=a?cb(a).zone():0,(this.zone()-a)%60===0},daysInMonth:function(){return r(this.year(),this.month())},dayOfYear:function(a){var b=gb((cb(this).startOf("day")-cb(this).startOf("year"))/864e5)+1;return null==a?b:this.add("d",a-b)},quarter:function(){return Math.ceil((this.month()+1)/3)},weekYear:function(a){var b=X(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==a?b:this.add("y",a-b)},isoWeekYear:function(a){var b=X(this,1,4).year;return null==a?b:this.add("y",a-b)},week:function(a){var b=this.lang().week(this);return null==a?b:this.add("d",7*(a-b))},isoWeek:function(a){var b=X(this,1,4).week;return null==a?b:this.add("d",7*(a-b))},weekday:function(a){var b=(this.day()+7-this.lang()._week.dow)%7;return null==a?b:this.add("d",a-b)},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},get:function(a){return a=n(a),this[a]()},set:function(a,b){return a=n(a),"function"==typeof this[a]&&this[a](b),this},lang:function(b){return b===a?this._lang:(this._lang=B(b),this)}}),db=0;db chunkSize; i = 0 <= chunkSize ? ++_i : --_i) { + data.push(this.data[this.pos++]); + } + break; + case 'tRNS': + this.transparency = {}; + switch (this.colorType) { + case 3: + this.transparency.indexed = this.read(chunkSize); + short = 255 - this.transparency.indexed.length; + if (short > 0) { + for (i = _j = 0; 0 <= short ? _j < short : _j > short; i = 0 <= short ? ++_j : --_j) { + this.transparency.indexed.push(255); + } + } + break; + case 0: + this.transparency.grayscale = this.read(chunkSize)[0]; + break; + case 2: + this.transparency.rgb = this.read(chunkSize); + } + break; + case 'tEXt': + text = this.read(chunkSize); + index = text.indexOf(0); + key = String.fromCharCode.apply(String, text.slice(0, index)); + this.text[key] = String.fromCharCode.apply(String, text.slice(index + 1)); + break; + case 'IEND': + if (frame) { + this.animation.frames.push(frame); + } + this.colors = (function() { + switch (this.colorType) { + case 0: + case 3: + case 4: + return 1; + case 2: + case 6: + return 3; + } + }).call(this); + this.hasAlphaChannel = (_ref = this.colorType) === 4 || _ref === 6; + colors = this.colors + (this.hasAlphaChannel ? 1 : 0); + this.pixelBitlength = this.bits * colors; + this.colorSpace = (function() { + switch (this.colors) { + case 1: + return 'DeviceGray'; + case 3: + return 'DeviceRGB'; + } + }).call(this); + this.imgData = new Uint8Array(this.imgData); + return; + default: + this.pos += chunkSize; + } + this.pos += 4; + if (this.pos > this.data.length) { + throw new Error("Incomplete or corrupt PNG file"); + } + } + return; + } + + PNG.prototype.read = function(bytes) { + var i, _i, _results; + _results = []; + for (i = _i = 0; 0 <= bytes ? _i < bytes : _i > bytes; i = 0 <= bytes ? ++_i : --_i) { + _results.push(this.data[this.pos++]); + } + return _results; + }; + + PNG.prototype.readUInt32 = function() { + var b1, b2, b3, b4; + b1 = this.data[this.pos++] << 24; + b2 = this.data[this.pos++] << 16; + b3 = this.data[this.pos++] << 8; + b4 = this.data[this.pos++]; + return b1 | b2 | b3 | b4; + }; + + PNG.prototype.readUInt16 = function() { + var b1, b2; + b1 = this.data[this.pos++] << 8; + b2 = this.data[this.pos++]; + return b1 | b2; + }; + + PNG.prototype.decodePixels = function(data) { + var byte, c, col, i, left, length, p, pa, paeth, pb, pc, pixelBytes, pixels, pos, row, scanlineLength, upper, upperLeft, _i, _j, _k, _l, _m; + if (data == null) { + data = this.imgData; + } + if (data.length === 0) { + return new Uint8Array(0); + } + data = new FlateStream(data); + data = data.getBytes(); + pixelBytes = this.pixelBitlength / 8; + scanlineLength = pixelBytes * this.width; + pixels = new Uint8Array(scanlineLength * this.height); + length = data.length; + row = 0; + pos = 0; + c = 0; + while (pos < length) { + switch (data[pos++]) { + case 0: + for (i = _i = 0; _i < scanlineLength; i = _i += 1) { + pixels[c++] = data[pos++]; + } + break; + case 1: + for (i = _j = 0; _j < scanlineLength; i = _j += 1) { + byte = data[pos++]; + left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; + pixels[c++] = (byte + left) % 256; + } + break; + case 2: + for (i = _k = 0; _k < scanlineLength; i = _k += 1) { + byte = data[pos++]; + col = (i - (i % pixelBytes)) / pixelBytes; + upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; + pixels[c++] = (upper + byte) % 256; + } + break; + case 3: + for (i = _l = 0; _l < scanlineLength; i = _l += 1) { + byte = data[pos++]; + col = (i - (i % pixelBytes)) / pixelBytes; + left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; + upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; + pixels[c++] = (byte + Math.floor((left + upper) / 2)) % 256; + } + break; + case 4: + for (i = _m = 0; _m < scanlineLength; i = _m += 1) { + byte = data[pos++]; + col = (i - (i % pixelBytes)) / pixelBytes; + left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; + if (row === 0) { + upper = upperLeft = 0; + } else { + upper = pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; + upperLeft = col && pixels[(row - 1) * scanlineLength + (col - 1) * pixelBytes + (i % pixelBytes)]; + } + p = left + upper - upperLeft; + pa = Math.abs(p - left); + pb = Math.abs(p - upper); + pc = Math.abs(p - upperLeft); + if (pa <= pb && pa <= pc) { + paeth = left; + } else if (pb <= pc) { + paeth = upper; + } else { + paeth = upperLeft; + } + pixels[c++] = (byte + paeth) % 256; + } + break; + default: + throw new Error("Invalid filter algorithm: " + data[pos - 1]); + } + row++; + } + return pixels; + }; + + PNG.prototype.decodePalette = function() { + var c, i, length, palette, pos, ret, transparency, _i, _ref, _ref1; + palette = this.palette; + transparency = this.transparency.indexed || []; + ret = new Uint8Array((transparency.length || 0) + palette.length); + pos = 0; + length = palette.length; + c = 0; + for (i = _i = 0, _ref = palette.length; _i < _ref; i = _i += 3) { + ret[pos++] = palette[i]; + ret[pos++] = palette[i + 1]; + ret[pos++] = palette[i + 2]; + ret[pos++] = (_ref1 = transparency[c++]) != null ? _ref1 : 255; + } + return ret; + }; + + PNG.prototype.copyToImageData = function(imageData, pixels) { + var alpha, colors, data, i, input, j, k, length, palette, v, _ref; + colors = this.colors; + palette = null; + alpha = this.hasAlphaChannel; + if (this.palette.length) { + palette = (_ref = this._decodedPalette) != null ? _ref : this._decodedPalette = this.decodePalette(); + colors = 4; + alpha = true; + } + data = imageData.data || imageData; + length = data.length; + input = palette || pixels; + i = j = 0; + if (colors === 1) { + while (i < length) { + k = palette ? pixels[i / 4] * 4 : j; + v = input[k++]; + data[i++] = v; + data[i++] = v; + data[i++] = v; + data[i++] = alpha ? input[k++] : 255; + j = k; + } + } else { + while (i < length) { + k = palette ? pixels[i / 4] * 4 : j; + data[i++] = input[k++]; + data[i++] = input[k++]; + data[i++] = input[k++]; + data[i++] = alpha ? input[k++] : 255; + j = k; + } + } + }; + + PNG.prototype.decode = function() { + var ret; + ret = new Uint8Array(this.width * this.height * 4); + this.copyToImageData(ret, this.decodePixels()); + return ret; + }; + + makeImage = function(imageData) { + var img; + scratchCtx.width = imageData.width; + scratchCtx.height = imageData.height; + scratchCtx.clearRect(0, 0, imageData.width, imageData.height); + scratchCtx.putImageData(imageData, 0, 0); + img = new Image; + img.src = scratchCanvas.toDataURL(); + return img; + }; + + PNG.prototype.decodeFrames = function(ctx) { + var frame, i, imageData, pixels, _i, _len, _ref, _results; + if (!this.animation) { + return; + } + _ref = this.animation.frames; + _results = []; + for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { + frame = _ref[i]; + imageData = ctx.createImageData(frame.width, frame.height); + pixels = this.decodePixels(new Uint8Array(frame.data)); + this.copyToImageData(imageData, pixels); + frame.imageData = imageData; + _results.push(frame.image = makeImage(imageData)); + } + return _results; + }; + + PNG.prototype.renderFrame = function(ctx, number) { + var frame, frames, prev; + frames = this.animation.frames; + frame = frames[number]; + prev = frames[number - 1]; + if (number === 0) { + ctx.clearRect(0, 0, this.width, this.height); + } + if ((prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_BACKGROUND) { + ctx.clearRect(prev.xOffset, prev.yOffset, prev.width, prev.height); + } else if ((prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_PREVIOUS) { + ctx.putImageData(prev.imageData, prev.xOffset, prev.yOffset); + } + if (frame.blendOp === APNG_BLEND_OP_SOURCE) { + ctx.clearRect(frame.xOffset, frame.yOffset, frame.width, frame.height); + } + return ctx.drawImage(frame.image, frame.xOffset, frame.yOffset); + }; + + PNG.prototype.animate = function(ctx) { + var doFrame, frameNumber, frames, numFrames, numPlays, _ref, + _this = this; + frameNumber = 0; + _ref = this.animation, numFrames = _ref.numFrames, frames = _ref.frames, numPlays = _ref.numPlays; + return (doFrame = function() { + var f, frame; + f = frameNumber++ % numFrames; + frame = frames[f]; + _this.renderFrame(ctx, f); + if (numFrames > 1 && frameNumber / numFrames < numPlays) { + return _this.animation._timeout = setTimeout(doFrame, frame.delay); + } + })(); + }; + + PNG.prototype.stopAnimation = function() { + var _ref; + return clearTimeout((_ref = this.animation) != null ? _ref._timeout : void 0); + }; + + PNG.prototype.render = function(canvas) { + var ctx, data; + if (canvas._png) { + canvas._png.stopAnimation(); + } + canvas._png = this; + canvas.width = this.width; + canvas.height = this.height; + ctx = canvas.getContext("2d"); + if (this.animation) { + this.decodeFrames(ctx); + return this.animate(ctx); + } else { + data = ctx.createImageData(this.width, this.height); + this.copyToImageData(data, this.decodePixels()); + return ctx.putImageData(data, 0, 0); + } + }; + + return PNG; + + })(); + + window.PNG = PNG; + +}).call(this); + +/* + * Extracted from pdf.js + * https://github.com/andreasgal/pdf.js + * + * Copyright (c) 2011 Mozilla Foundation + * + * Contributors: Andreas Gal + * Chris G Jones + * Shaon Barman + * Vivien Nicolas <21@vingtetun.org> + * Justin D'Arcangelo + * Yury Delendik + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +var DecodeStream = (function() { + function constructor() { + this.pos = 0; + this.bufferLength = 0; + this.eof = false; + this.buffer = null; + } + + constructor.prototype = { + ensureBuffer: function decodestream_ensureBuffer(requested) { + var buffer = this.buffer; + var current = buffer ? buffer.byteLength : 0; + if (requested < current) + return buffer; + var size = 512; + while (size < requested) + size <<= 1; + var buffer2 = new Uint8Array(size); + for (var i = 0; i < current; ++i) + buffer2[i] = buffer[i]; + return this.buffer = buffer2; + }, + getByte: function decodestream_getByte() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return null; + this.readBlock(); + } + return this.buffer[this.pos++]; + }, + getBytes: function decodestream_getBytes(length) { + var pos = this.pos; + + if (length) { + this.ensureBuffer(pos + length); + var end = pos + length; + + while (!this.eof && this.bufferLength < end) + this.readBlock(); + + var bufEnd = this.bufferLength; + if (end > bufEnd) + end = bufEnd; + } else { + while (!this.eof) + this.readBlock(); + + var end = this.bufferLength; + } + + this.pos = end; + return this.buffer.subarray(pos, end); + }, + lookChar: function decodestream_lookChar() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return null; + this.readBlock(); + } + return String.fromCharCode(this.buffer[this.pos]); + }, + getChar: function decodestream_getChar() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return null; + this.readBlock(); + } + return String.fromCharCode(this.buffer[this.pos++]); + }, + makeSubStream: function decodestream_makeSubstream(start, length, dict) { + var end = start + length; + while (this.bufferLength <= end && !this.eof) + this.readBlock(); + return new Stream(this.buffer, start, length, dict); + }, + skip: function decodestream_skip(n) { + if (!n) + n = 1; + this.pos += n; + }, + reset: function decodestream_reset() { + this.pos = 0; + } + }; + + return constructor; +})(); + +var FlateStream = (function() { + var codeLenCodeMap = new Uint32Array([ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + ]); + + var lengthDecode = new Uint32Array([ + 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, + 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, + 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, + 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102 + ]); + + var distDecode = new Uint32Array([ + 0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, + 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, + 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, + 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001 + ]); + + var fixedLitCodeTab = [new Uint32Array([ + 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, + 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, + 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, + 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, + 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, + 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, + 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, + 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, + 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, + 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, + 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, + 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, + 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, + 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, + 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, + 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, + 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, + 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, + 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, + 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, + 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, + 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, + 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, + 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, + 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, + 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, + 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, + 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, + 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, + 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, + 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, + 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, + 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, + 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, + 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, + 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, + 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, + 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, + 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, + 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, + 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, + 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, + 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, + 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, + 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, + 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, + 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, + 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, + 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, + 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, + 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, + 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, + 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, + 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, + 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, + 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, + 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, + 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, + 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, + 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, + 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, + 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, + 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, + 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff + ]), 9]; + + var fixedDistCodeTab = [new Uint32Array([ + 0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, + 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, + 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, + 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000 + ]), 5]; + + function error(e) { + throw new Error(e) + } + + function constructor(bytes) { + //var bytes = stream.getBytes(); + var bytesPos = 0; + + var cmf = bytes[bytesPos++]; + var flg = bytes[bytesPos++]; + if (cmf == -1 || flg == -1) + error('Invalid header in flate stream'); + if ((cmf & 0x0f) != 0x08) + error('Unknown compression method in flate stream'); + if ((((cmf << 8) + flg) % 31) != 0) + error('Bad FCHECK in flate stream'); + if (flg & 0x20) + error('FDICT bit set in flate stream'); + + this.bytes = bytes; + this.bytesPos = bytesPos; + + this.codeSize = 0; + this.codeBuf = 0; + + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.getBits = function(bits) { + var codeSize = this.codeSize; + var codeBuf = this.codeBuf; + var bytes = this.bytes; + var bytesPos = this.bytesPos; + + var b; + while (codeSize < bits) { + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad encoding in flate stream'); + codeBuf |= b << codeSize; + codeSize += 8; + } + b = codeBuf & ((1 << bits) - 1); + this.codeBuf = codeBuf >> bits; + this.codeSize = codeSize -= bits; + this.bytesPos = bytesPos; + return b; + }; + + constructor.prototype.getCode = function(table) { + var codes = table[0]; + var maxLen = table[1]; + var codeSize = this.codeSize; + var codeBuf = this.codeBuf; + var bytes = this.bytes; + var bytesPos = this.bytesPos; + + while (codeSize < maxLen) { + var b; + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad encoding in flate stream'); + codeBuf |= (b << codeSize); + codeSize += 8; + } + var code = codes[codeBuf & ((1 << maxLen) - 1)]; + var codeLen = code >> 16; + var codeVal = code & 0xffff; + if (codeSize == 0 || codeSize < codeLen || codeLen == 0) + error('Bad encoding in flate stream'); + this.codeBuf = (codeBuf >> codeLen); + this.codeSize = (codeSize - codeLen); + this.bytesPos = bytesPos; + return codeVal; + }; + + constructor.prototype.generateHuffmanTable = function(lengths) { + var n = lengths.length; + + // find max code length + var maxLen = 0; + for (var i = 0; i < n; ++i) { + if (lengths[i] > maxLen) + maxLen = lengths[i]; + } + + // build the table + var size = 1 << maxLen; + var codes = new Uint32Array(size); + for (var len = 1, code = 0, skip = 2; + len <= maxLen; + ++len, code <<= 1, skip <<= 1) { + for (var val = 0; val < n; ++val) { + if (lengths[val] == len) { + // bit-reverse the code + var code2 = 0; + var t = code; + for (var i = 0; i < len; ++i) { + code2 = (code2 << 1) | (t & 1); + t >>= 1; + } + + // fill the table entries + for (var i = code2; i < size; i += skip) + codes[i] = (len << 16) | val; + + ++code; + } + } + } + + return [codes, maxLen]; + }; + + constructor.prototype.readBlock = function() { + function repeat(stream, array, len, offset, what) { + var repeat = stream.getBits(len) + offset; + while (repeat-- > 0) + array[i++] = what; + } + + // read block header + var hdr = this.getBits(3); + if (hdr & 1) + this.eof = true; + hdr >>= 1; + + if (hdr == 0) { // uncompressed block + var bytes = this.bytes; + var bytesPos = this.bytesPos; + var b; + + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + var blockLen = b; + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + blockLen |= (b << 8); + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + var check = b; + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + check |= (b << 8); + if (check != (~blockLen & 0xffff)) + error('Bad uncompressed block length in flate stream'); + + this.codeBuf = 0; + this.codeSize = 0; + + var bufferLength = this.bufferLength; + var buffer = this.ensureBuffer(bufferLength + blockLen); + var end = bufferLength + blockLen; + this.bufferLength = end; + for (var n = bufferLength; n < end; ++n) { + if (typeof (b = bytes[bytesPos++]) == 'undefined') { + this.eof = true; + break; + } + buffer[n] = b; + } + this.bytesPos = bytesPos; + return; + } + + var litCodeTable; + var distCodeTable; + if (hdr == 1) { // compressed block, fixed codes + litCodeTable = fixedLitCodeTab; + distCodeTable = fixedDistCodeTab; + } else if (hdr == 2) { // compressed block, dynamic codes + var numLitCodes = this.getBits(5) + 257; + var numDistCodes = this.getBits(5) + 1; + var numCodeLenCodes = this.getBits(4) + 4; + + // build the code lengths code table + var codeLenCodeLengths = Array(codeLenCodeMap.length); + var i = 0; + while (i < numCodeLenCodes) + codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3); + var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); + + // build the literal and distance code tables + var len = 0; + var i = 0; + var codes = numLitCodes + numDistCodes; + var codeLengths = new Array(codes); + while (i < codes) { + var code = this.getCode(codeLenCodeTab); + if (code == 16) { + repeat(this, codeLengths, 2, 3, len); + } else if (code == 17) { + repeat(this, codeLengths, 3, 3, len = 0); + } else if (code == 18) { + repeat(this, codeLengths, 7, 11, len = 0); + } else { + codeLengths[i++] = len = code; + } + } + + litCodeTable = + this.generateHuffmanTable(codeLengths.slice(0, numLitCodes)); + distCodeTable = + this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes)); + } else { + error('Unknown block type in flate stream'); + } + + var buffer = this.buffer; + var limit = buffer ? buffer.length : 0; + var pos = this.bufferLength; + while (true) { + var code1 = this.getCode(litCodeTable); + if (code1 < 256) { + if (pos + 1 >= limit) { + buffer = this.ensureBuffer(pos + 1); + limit = buffer.length; + } + buffer[pos++] = code1; + continue; + } + if (code1 == 256) { + this.bufferLength = pos; + return; + } + code1 -= 257; + code1 = lengthDecode[code1]; + var code2 = code1 >> 16; + if (code2 > 0) + code2 = this.getBits(code2); + var len = (code1 & 0xffff) + code2; + code1 = this.getCode(distCodeTable); + code1 = distDecode[code1]; + code2 = code1 >> 16; + if (code2 > 0) + code2 = this.getBits(code2); + var dist = (code1 & 0xffff) + code2; + if (pos + len >= limit) { + buffer = this.ensureBuffer(pos + len); + limit = buffer.length; + } + for (var k = 0; k < len; ++k, ++pos) + buffer[pos] = buffer[pos - dist]; + } + }; + + return constructor; +})(); +__loader.require("main"); \ No newline at end of file From 6d6a5d21a7a87b02d8c4a37fba2b4e484ff7bdb3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 20:06:25 -0700 Subject: [PATCH 080/791] Update moment.js to v2.6.0 unminified --- lib/vendor/moment.js | 2489 ++++++++++++++++++++++++++++++++++++++ lib/vendor/moment.min.js | 6 - 2 files changed, 2489 insertions(+), 6 deletions(-) create mode 100644 lib/vendor/moment.js delete mode 100644 lib/vendor/moment.min.js diff --git a/lib/vendor/moment.js b/lib/vendor/moment.js new file mode 100644 index 00000000..257ee7ec --- /dev/null +++ b/lib/vendor/moment.js @@ -0,0 +1,2489 @@ +//! moment.js +//! version : 2.6.0 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com + +(function (undefined) { + + /************************************ + Constants + ************************************/ + + var moment, + VERSION = "2.6.0", + // the global-scope this is NOT the global object in Node.js + globalScope = typeof global !== 'undefined' ? global : this, + oldGlobalMoment, + round = Math.round, + i, + + YEAR = 0, + MONTH = 1, + DATE = 2, + HOUR = 3, + MINUTE = 4, + SECOND = 5, + MILLISECOND = 6, + + // internal storage for language config files + languages = {}, + + // moment internal properties + momentProperties = { + _isAMomentObject: null, + _i : null, + _f : null, + _l : null, + _strict : null, + _isUTC : null, + _offset : null, // optional. Combine with _isUTC + _pf : null, + _lang : null // optional + }, + + // check for nodeJS + hasModule = (typeof module !== 'undefined' && module.exports), + + // ASP.NET json date format regex + aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, + aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/, + + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, + + // format tokens + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, + localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, + + // parsing token regexes + parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 + parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 + parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999 + parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 + parseTokenDigits = /\d+/, // nonzero number of digits + parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. + parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z + parseTokenT = /T/i, // T (ISO separator) + parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 + parseTokenOrdinal = /\d{1,2}/, + + //strict parsing regexes + parseTokenOneDigit = /\d/, // 0 - 9 + parseTokenTwoDigits = /\d\d/, // 00 - 99 + parseTokenThreeDigits = /\d{3}/, // 000 - 999 + parseTokenFourDigits = /\d{4}/, // 0000 - 9999 + parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999 + parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf + + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + + isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', + + isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], + ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], + ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], + ['GGGG-[W]WW', /\d{4}-W\d{2}/], + ['YYYY-DDD', /\d{4}-\d{3}/] + ], + + // iso time formats and regexes + isoTimes = [ + ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], + ['HH:mm', /(T| )\d\d:\d\d/], + ['HH', /(T| )\d\d/] + ], + + // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] + parseTimezoneChunker = /([\+\-]|\d\d)/gi, + + // getter and setter names + proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), + unitMillisecondFactors = { + 'Milliseconds' : 1, + 'Seconds' : 1e3, + 'Minutes' : 6e4, + 'Hours' : 36e5, + 'Days' : 864e5, + 'Months' : 2592e6, + 'Years' : 31536e6 + }, + + unitAliases = { + ms : 'millisecond', + s : 'second', + m : 'minute', + h : 'hour', + d : 'day', + D : 'date', + w : 'week', + W : 'isoWeek', + M : 'month', + Q : 'quarter', + y : 'year', + DDD : 'dayOfYear', + e : 'weekday', + E : 'isoWeekday', + gg: 'weekYear', + GG: 'isoWeekYear' + }, + + camelFunctions = { + dayofyear : 'dayOfYear', + isoweekday : 'isoWeekday', + isoweek : 'isoWeek', + weekyear : 'weekYear', + isoweekyear : 'isoWeekYear' + }, + + // format function strings + formatFunctions = {}, + + // tokens to ordinalize and pad + ordinalizeTokens = 'DDD w W M D d'.split(' '), + paddedTokens = 'M D H h m s w W'.split(' '), + + formatTokenFunctions = { + M : function () { + return this.month() + 1; + }, + MMM : function (format) { + return this.lang().monthsShort(this, format); + }, + MMMM : function (format) { + return this.lang().months(this, format); + }, + D : function () { + return this.date(); + }, + DDD : function () { + return this.dayOfYear(); + }, + d : function () { + return this.day(); + }, + dd : function (format) { + return this.lang().weekdaysMin(this, format); + }, + ddd : function (format) { + return this.lang().weekdaysShort(this, format); + }, + dddd : function (format) { + return this.lang().weekdays(this, format); + }, + w : function () { + return this.week(); + }, + W : function () { + return this.isoWeek(); + }, + YY : function () { + return leftZeroFill(this.year() % 100, 2); + }, + YYYY : function () { + return leftZeroFill(this.year(), 4); + }, + YYYYY : function () { + return leftZeroFill(this.year(), 5); + }, + YYYYYY : function () { + var y = this.year(), sign = y >= 0 ? '+' : '-'; + return sign + leftZeroFill(Math.abs(y), 6); + }, + gg : function () { + return leftZeroFill(this.weekYear() % 100, 2); + }, + gggg : function () { + return leftZeroFill(this.weekYear(), 4); + }, + ggggg : function () { + return leftZeroFill(this.weekYear(), 5); + }, + GG : function () { + return leftZeroFill(this.isoWeekYear() % 100, 2); + }, + GGGG : function () { + return leftZeroFill(this.isoWeekYear(), 4); + }, + GGGGG : function () { + return leftZeroFill(this.isoWeekYear(), 5); + }, + e : function () { + return this.weekday(); + }, + E : function () { + return this.isoWeekday(); + }, + a : function () { + return this.lang().meridiem(this.hours(), this.minutes(), true); + }, + A : function () { + return this.lang().meridiem(this.hours(), this.minutes(), false); + }, + H : function () { + return this.hours(); + }, + h : function () { + return this.hours() % 12 || 12; + }, + m : function () { + return this.minutes(); + }, + s : function () { + return this.seconds(); + }, + S : function () { + return toInt(this.milliseconds() / 100); + }, + SS : function () { + return leftZeroFill(toInt(this.milliseconds() / 10), 2); + }, + SSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + SSSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + Z : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2); + }, + ZZ : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); + }, + z : function () { + return this.zoneAbbr(); + }, + zz : function () { + return this.zoneName(); + }, + X : function () { + return this.unix(); + }, + Q : function () { + return this.quarter(); + } + }, + + lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; + + function defaultParsingFlags() { + // We need to deep clone this object, and es5 standard is not very + // helpful. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso: false + }; + } + + function deprecate(msg, fn) { + var firstTime = true; + function printMsg() { + if (moment.suppressDeprecationWarnings === false && + typeof console !== 'undefined' && console.warn) { + console.warn("Deprecation warning: " + msg); + } + } + return extend(function () { + if (firstTime) { + printMsg(); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + + function padToken(func, count) { + return function (a) { + return leftZeroFill(func.call(this, a), count); + }; + } + function ordinalizeToken(func, period) { + return function (a) { + return this.lang().ordinal(func.call(this, a), period); + }; + } + + while (ordinalizeTokens.length) { + i = ordinalizeTokens.pop(); + formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); + } + while (paddedTokens.length) { + i = paddedTokens.pop(); + formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); + } + formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); + + + /************************************ + Constructors + ************************************/ + + function Language() { + + } + + // Moment prototype object + function Moment(config) { + checkOverflow(config); + extend(this, config); + } + + // Duration Constructor + function Duration(duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 36e5; // 1000 * 60 * 60 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; + + this._data = {}; + + this._bubble(); + } + + /************************************ + Helpers + ************************************/ + + + function extend(a, b) { + for (var i in b) { + if (b.hasOwnProperty(i)) { + a[i] = b[i]; + } + } + + if (b.hasOwnProperty("toString")) { + a.toString = b.toString; + } + + if (b.hasOwnProperty("valueOf")) { + a.valueOf = b.valueOf; + } + + return a; + } + + function cloneMoment(m) { + var result = {}, i; + for (i in m) { + if (m.hasOwnProperty(i) && momentProperties.hasOwnProperty(i)) { + result[i] = m[i]; + } + } + + return result; + } + + function absRound(number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } + } + + // left zero fill a number + // see http://jsperf.com/left-zero-filling for performance comparison + function leftZeroFill(number, targetLength, forceSign) { + var output = '' + Math.abs(number), + sign = number >= 0; + + while (output.length < targetLength) { + output = '0' + output; + } + return (sign ? (forceSign ? '+' : '') : '-') + output; + } + + // helper function for _.addTime and _.subtractTime + function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = duration._days, + months = duration._months; + updateOffset = updateOffset == null ? true : updateOffset; + + if (milliseconds) { + mom._d.setTime(+mom._d + milliseconds * isAdding); + } + if (days) { + rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding); + } + if (months) { + rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding); + } + if (updateOffset) { + moment.updateOffset(mom, days || months); + } + } + + // check if is an array + function isArray(input) { + return Object.prototype.toString.call(input) === '[object Array]'; + } + + function isDate(input) { + return Object.prototype.toString.call(input) === '[object Date]' || + input instanceof Date; + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; + } + + function normalizeUnits(units) { + if (units) { + var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); + units = unitAliases[units] || camelFunctions[lowered] || lowered; + } + return units; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (inputObject.hasOwnProperty(prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + function makeList(field) { + var count, setter; + + if (field.indexOf('week') === 0) { + count = 7; + setter = 'day'; + } + else if (field.indexOf('month') === 0) { + count = 12; + setter = 'month'; + } + else { + return; + } + + moment[field] = function (format, index) { + var i, getter, + method = moment.fn._lang[field], + results = []; + + if (typeof format === 'number') { + index = format; + format = undefined; + } + + getter = function (i) { + var m = moment().utc().set(setter, i); + return method.call(moment.fn._lang, m, format || ''); + }; + + if (index != null) { + return getter(index); + } + else { + for (i = 0; i < count; i++) { + results.push(getter(i)); + } + return results; + } + }; + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + if (coercedNumber >= 0) { + value = Math.floor(coercedNumber); + } else { + value = Math.ceil(coercedNumber); + } + } + + return value; + } + + function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); + } + + function weeksInYear(year, dow, doy) { + return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week; + } + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + function checkOverflow(m) { + var overflow; + if (m._a && m._pf.overflow === -2) { + overflow = + m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : + m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : + m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR : + m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : + m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : + m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + + m._pf.overflow = overflow; + } + } + + function isValid(m) { + if (m._isValid == null) { + m._isValid = !isNaN(m._d.getTime()) && + m._pf.overflow < 0 && + !m._pf.empty && + !m._pf.invalidMonth && + !m._pf.nullInput && + !m._pf.invalidFormat && + !m._pf.userInvalidated; + + if (m._strict) { + m._isValid = m._isValid && + m._pf.charsLeftOver === 0 && + m._pf.unusedTokens.length === 0; + } + } + return m._isValid; + } + + function normalizeLanguage(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // Return a moment from input, that is local/utc/zone equivalent to model. + function makeAs(input, model) { + return model._isUTC ? moment(input).zone(model._offset || 0) : + moment(input).local(); + } + + /************************************ + Languages + ************************************/ + + + extend(Language.prototype, { + + set : function (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (typeof prop === 'function') { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + }, + + _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), + months : function (m) { + return this._months[m.month()]; + }, + + _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), + monthsShort : function (m) { + return this._monthsShort[m.month()]; + }, + + monthsParse : function (monthName) { + var i, mom, regex; + + if (!this._monthsParse) { + this._monthsParse = []; + } + + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + if (!this._monthsParse[i]) { + mom = moment.utc([2000, i]); + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._monthsParse[i].test(monthName)) { + return i; + } + } + }, + + _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), + weekdays : function (m) { + return this._weekdays[m.day()]; + }, + + _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysShort : function (m) { + return this._weekdaysShort[m.day()]; + }, + + _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), + weekdaysMin : function (m) { + return this._weekdaysMin[m.day()]; + }, + + weekdaysParse : function (weekdayName) { + var i, mom, regex; + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + if (!this._weekdaysParse[i]) { + mom = moment([2000, 1]).day(i); + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + }, + + _longDateFormat : { + LT : "h:mm A", + L : "MM/DD/YYYY", + LL : "MMMM D YYYY", + LLL : "MMMM D YYYY LT", + LLLL : "dddd, MMMM D YYYY LT" + }, + longDateFormat : function (key) { + var output = this._longDateFormat[key]; + if (!output && this._longDateFormat[key.toUpperCase()]) { + output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + this._longDateFormat[key] = output; + } + return output; + }, + + isPM : function (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); + }, + + _meridiemParse : /[ap]\.?m?\.?/i, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + }, + + _calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + calendar : function (key, mom) { + var output = this._calendar[key]; + return typeof output === 'function' ? output.apply(mom) : output; + }, + + _relativeTime : { + future : "in %s", + past : "%s ago", + s : "a few seconds", + m : "a minute", + mm : "%d minutes", + h : "an hour", + hh : "%d hours", + d : "a day", + dd : "%d days", + M : "a month", + MM : "%d months", + y : "a year", + yy : "%d years" + }, + relativeTime : function (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (typeof output === 'function') ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + }, + pastFuture : function (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); + }, + + ordinal : function (number) { + return this._ordinal.replace("%d", number); + }, + _ordinal : "%d", + + preparse : function (string) { + return string; + }, + + postformat : function (string) { + return string; + }, + + week : function (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + }, + + _week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + }, + + _invalidDate: 'Invalid date', + invalidDate: function () { + return this._invalidDate; + } + }); + + // Loads a language definition into the `languages` cache. The function + // takes a key and optionally values. If not in the browser and no values + // are provided, it will load the language file module. As a convenience, + // this function also returns the language values. + function loadLang(key, values) { + values.abbr = key; + if (!languages[key]) { + languages[key] = new Language(); + } + languages[key].set(values); + return languages[key]; + } + + // Remove a language from the `languages` cache. Mostly useful in tests. + function unloadLang(key) { + delete languages[key]; + } + + // Determines which language definition to use and returns it. + // + // With no parameters, it will return the global language. If you + // pass in a language key, such as 'en', it will return the + // definition for 'en', so long as 'en' has already been loaded using + // moment.lang. + function getLangDefinition(key) { + var i = 0, j, lang, next, split, + get = function (k) { + if (!languages[k] && hasModule) { + try { + require('./lang/' + k); + } catch (e) { } + } + return languages[k]; + }; + + if (!key) { + return moment.fn._lang; + } + + if (!isArray(key)) { + //short-circuit everything else + lang = get(key); + if (lang) { + return lang; + } + key = [key]; + } + + //pick the language from the array + //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + while (i < key.length) { + split = normalizeLanguage(key[i]).split('-'); + j = split.length; + next = normalizeLanguage(key[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + lang = get(split.slice(0, j).join('-')); + if (lang) { + return lang; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return moment.fn._lang; + } + + /************************************ + Formatting + ************************************/ + + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ""); + } + return input.replace(/\\/g, ""); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = ""; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + + if (!m.isValid()) { + return m.lang().invalidDate(); + } + + format = expandFormat(format, m.lang()); + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + + return formatFunctions[format](m); + } + + function expandFormat(format, lang) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return lang.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + + /************************************ + Parsing + ************************************/ + + + // get the regex to find the next token + function getParseRegexForToken(token, config) { + var a, strict = config._strict; + switch (token) { + case 'Q': + return parseTokenOneDigit; + case 'DDDD': + return parseTokenThreeDigits; + case 'YYYY': + case 'GGGG': + case 'gggg': + return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; + case 'Y': + case 'G': + case 'g': + return parseTokenSignedNumber; + case 'YYYYYY': + case 'YYYYY': + case 'GGGGG': + case 'ggggg': + return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; + case 'S': + if (strict) { return parseTokenOneDigit; } + /* falls through */ + case 'SS': + if (strict) { return parseTokenTwoDigits; } + /* falls through */ + case 'SSS': + if (strict) { return parseTokenThreeDigits; } + /* falls through */ + case 'DDD': + return parseTokenOneToThreeDigits; + case 'MMM': + case 'MMMM': + case 'dd': + case 'ddd': + case 'dddd': + return parseTokenWord; + case 'a': + case 'A': + return getLangDefinition(config._l)._meridiemParse; + case 'X': + return parseTokenTimestampMs; + case 'Z': + case 'ZZ': + return parseTokenTimezone; + case 'T': + return parseTokenT; + case 'SSSS': + return parseTokenDigits; + case 'MM': + case 'DD': + case 'YY': + case 'GG': + case 'gg': + case 'HH': + case 'hh': + case 'mm': + case 'ss': + case 'ww': + case 'WW': + return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; + case 'M': + case 'D': + case 'd': + case 'H': + case 'h': + case 'm': + case 's': + case 'w': + case 'W': + case 'e': + case 'E': + return parseTokenOneOrTwoDigits; + case 'Do': + return parseTokenOrdinal; + default : + a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i")); + return a; + } + } + + function timezoneMinutesFromString(string) { + string = string || ""; + var possibleTzMatches = (string.match(parseTokenTimezone) || []), + tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], + parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], + minutes = +(parts[1] * 60) + toInt(parts[2]); + + return parts[0] === '+' ? -minutes : minutes; + } + + // function to convert string input to date + function addTimeToArrayFromToken(token, input, config) { + var a, datePartArray = config._a; + + switch (token) { + // QUARTER + case 'Q': + if (input != null) { + datePartArray[MONTH] = (toInt(input) - 1) * 3; + } + break; + // MONTH + case 'M' : // fall through to MM + case 'MM' : + if (input != null) { + datePartArray[MONTH] = toInt(input) - 1; + } + break; + case 'MMM' : // fall through to MMMM + case 'MMMM' : + a = getLangDefinition(config._l).monthsParse(input); + // if we didn't find a month name, mark the date as invalid. + if (a != null) { + datePartArray[MONTH] = a; + } else { + config._pf.invalidMonth = input; + } + break; + // DAY OF MONTH + case 'D' : // fall through to DD + case 'DD' : + if (input != null) { + datePartArray[DATE] = toInt(input); + } + break; + case 'Do' : + if (input != null) { + datePartArray[DATE] = toInt(parseInt(input, 10)); + } + break; + // DAY OF YEAR + case 'DDD' : // fall through to DDDD + case 'DDDD' : + if (input != null) { + config._dayOfYear = toInt(input); + } + + break; + // YEAR + case 'YY' : + datePartArray[YEAR] = moment.parseTwoDigitYear(input); + break; + case 'YYYY' : + case 'YYYYY' : + case 'YYYYYY' : + datePartArray[YEAR] = toInt(input); + break; + // AM / PM + case 'a' : // fall through to A + case 'A' : + config._isPm = getLangDefinition(config._l).isPM(input); + break; + // 24 HOUR + case 'H' : // fall through to hh + case 'HH' : // fall through to hh + case 'h' : // fall through to hh + case 'hh' : + datePartArray[HOUR] = toInt(input); + break; + // MINUTE + case 'm' : // fall through to mm + case 'mm' : + datePartArray[MINUTE] = toInt(input); + break; + // SECOND + case 's' : // fall through to ss + case 'ss' : + datePartArray[SECOND] = toInt(input); + break; + // MILLISECOND + case 'S' : + case 'SS' : + case 'SSS' : + case 'SSSS' : + datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); + break; + // UNIX TIMESTAMP WITH MS + case 'X': + config._d = new Date(parseFloat(input) * 1000); + break; + // TIMEZONE + case 'Z' : // fall through to ZZ + case 'ZZ' : + config._useUTC = true; + config._tzm = timezoneMinutesFromString(input); + break; + case 'w': + case 'ww': + case 'W': + case 'WW': + case 'd': + case 'dd': + case 'ddd': + case 'dddd': + case 'e': + case 'E': + token = token.substr(0, 1); + /* falls through */ + case 'gg': + case 'gggg': + case 'GG': + case 'GGGG': + case 'GGGGG': + token = token.substr(0, 2); + if (input) { + config._w = config._w || {}; + config._w[token] = input; + } + break; + } + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function dateFromConfig(config) { + var i, date, input = [], currentDate, + yearToUse, fixYear, w, temp, lang, weekday, week; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + fixYear = function (val) { + var intVal = parseInt(val, 10); + return val ? + (val.length < 3 ? (intVal > 68 ? 1900 + intVal : 2000 + intVal) : intVal) : + (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]); + }; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1); + } + else { + lang = getLangDefinition(config._l); + weekday = w.d != null ? parseWeekday(w.d, lang) : + (w.e != null ? parseInt(w.e, 10) + lang._week.dow : 0); + + week = parseInt(w.w, 10) || 1; + + //if we're parsing 'd', then the low day numbers may be next week + if (w.d != null && weekday < lang._week.dow) { + week++; + } + + temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow); + } + + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR]; + + if (config._dayOfYear > daysInYear(yearToUse)) { + config._pf._overflowDayOfYear = true; + } + + date = makeUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // add the offsets to the time to be parsed so that we can have a clean array for checking isValid + input[HOUR] += toInt((config._tzm || 0) / 60); + input[MINUTE] += toInt((config._tzm || 0) % 60); + + config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); + } + + function dateFromObject(config) { + var normalizedInput; + + if (config._d) { + return; + } + + normalizedInput = normalizeObjectUnits(config._i); + config._a = [ + normalizedInput.year, + normalizedInput.month, + normalizedInput.day, + normalizedInput.hour, + normalizedInput.minute, + normalizedInput.second, + normalizedInput.millisecond + ]; + + dateFromConfig(config); + } + + function currentDateArray(config) { + var now = new Date(); + if (config._useUTC) { + return [ + now.getUTCFullYear(), + now.getUTCMonth(), + now.getUTCDate() + ]; + } else { + return [now.getFullYear(), now.getMonth(), now.getDate()]; + } + } + + // date from string and format string + function makeDateFromStringAndFormat(config) { + + config._a = []; + config._pf.empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var lang = getLangDefinition(config._l), + string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; + + tokens = expandFormat(config._f, lang).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + config._pf.unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + config._pf.empty = false; + } + else { + config._pf.unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + config._pf.unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + config._pf.charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + config._pf.unusedInput.push(string); + } + + // handle am pm + if (config._isPm && config._a[HOUR] < 12) { + config._a[HOUR] += 12; + } + // if is 12 am, change hours to 0 + if (config._isPm === false && config._a[HOUR] === 12) { + config._a[HOUR] = 0; + } + + dateFromConfig(config); + checkOverflow(config); + } + + function unescapeFormat(s) { + return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + }); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function regexpEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + // date from string and array of format strings + function makeDateFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + config._pf.invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = extend({}, config); + tempConfig._pf = defaultParsingFlags(); + tempConfig._f = config._f[i]; + makeDateFromStringAndFormat(tempConfig); + + if (!isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += tempConfig._pf.charsLeftOver; + + //or tokens + currentScore += tempConfig._pf.unusedTokens.length * 10; + + tempConfig._pf.score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); + } + + // date from iso format + function makeDateFromString(config) { + var i, l, + string = config._i, + match = isoRegex.exec(string); + + if (match) { + config._pf.iso = true; + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(string)) { + // match[5] should be "T" or undefined + config._f = isoDates[i][0] + (match[6] || " "); + break; + } + } + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(string)) { + config._f += isoTimes[i][0]; + break; + } + } + if (string.match(parseTokenTimezone)) { + config._f += "Z"; + } + makeDateFromStringAndFormat(config); + } + else { + moment.createFromInputFallback(config); + } + } + + function makeDateFromInput(config) { + var input = config._i, + matched = aspNetJsonRegex.exec(input); + + if (input === undefined) { + config._d = new Date(); + } else if (matched) { + config._d = new Date(+matched[1]); + } else if (typeof input === 'string') { + makeDateFromString(config); + } else if (isArray(input)) { + config._a = input.slice(0); + dateFromConfig(config); + } else if (isDate(input)) { + config._d = new Date(+input); + } else if (typeof(input) === 'object') { + dateFromObject(config); + } else if (typeof(input) === 'number') { + // from milliseconds + config._d = new Date(input); + } else { + moment.createFromInputFallback(config); + } + } + + function makeDate(y, m, d, h, M, s, ms) { + //can't just apply() to create a date: + //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply + var date = new Date(y, m, d, h, M, s, ms); + + //the date constructor doesn't accept years < 1970 + if (y < 1970) { + date.setFullYear(y); + } + return date; + } + + function makeUTCDate(y) { + var date = new Date(Date.UTC.apply(null, arguments)); + if (y < 1970) { + date.setUTCFullYear(y); + } + return date; + } + + function parseWeekday(input, language) { + if (typeof input === 'string') { + if (!isNaN(input)) { + input = parseInt(input, 10); + } + else { + input = language.weekdaysParse(input); + if (typeof input !== 'number') { + return null; + } + } + } + return input; + } + + /************************************ + Relative Time + ************************************/ + + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { + return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime(milliseconds, withoutSuffix, lang) { + var seconds = round(Math.abs(milliseconds) / 1000), + minutes = round(seconds / 60), + hours = round(minutes / 60), + days = round(hours / 24), + years = round(days / 365), + args = seconds < 45 && ['s', seconds] || + minutes === 1 && ['m'] || + minutes < 45 && ['mm', minutes] || + hours === 1 && ['h'] || + hours < 22 && ['hh', hours] || + days === 1 && ['d'] || + days <= 25 && ['dd', days] || + days <= 45 && ['M'] || + days < 345 && ['MM', round(days / 30)] || + years === 1 && ['y'] || ['yy', years]; + args[2] = withoutSuffix; + args[3] = milliseconds > 0; + args[4] = lang; + return substituteTimeAgo.apply({}, args); + } + + + /************************************ + Week of Year + ************************************/ + + + // firstDayOfWeek 0 = sun, 6 = sat + // the day of the week that starts the week + // (usually sunday or monday) + // firstDayOfWeekOfYear 0 = sun, 6 = sat + // the first week is the week that contains the first + // of this day of the week + // (eg. ISO weeks use thursday (4)) + function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { + var end = firstDayOfWeekOfYear - firstDayOfWeek, + daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), + adjustedMoment; + + + if (daysToDayOfWeek > end) { + daysToDayOfWeek -= 7; + } + + if (daysToDayOfWeek < end - 7) { + daysToDayOfWeek += 7; + } + + adjustedMoment = moment(mom).add('d', daysToDayOfWeek); + return { + week: Math.ceil(adjustedMoment.dayOfYear() / 7), + year: adjustedMoment.year() + }; + } + + //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { + var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; + + weekday = weekday != null ? weekday : firstDayOfWeek; + daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); + dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; + + return { + year: dayOfYear > 0 ? year : year - 1, + dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear + }; + } + + /************************************ + Top Level Functions + ************************************/ + + function makeMoment(config) { + var input = config._i, + format = config._f; + + if (input === null || (format === undefined && input === '')) { + return moment.invalid({nullInput: true}); + } + + if (typeof input === 'string') { + config._i = input = getLangDefinition().preparse(input); + } + + if (moment.isMoment(input)) { + config = cloneMoment(input); + + config._d = new Date(+input._d); + } else if (format) { + if (isArray(format)) { + makeDateFromStringAndArray(config); + } else { + makeDateFromStringAndFormat(config); + } + } else { + makeDateFromInput(config); + } + + return new Moment(config); + } + + moment = function (input, format, lang, strict) { + var c; + + if (typeof(lang) === "boolean") { + strict = lang; + lang = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c = {}; + c._isAMomentObject = true; + c._i = input; + c._f = format; + c._l = lang; + c._strict = strict; + c._isUTC = false; + c._pf = defaultParsingFlags(); + + return makeMoment(c); + }; + + moment.suppressDeprecationWarnings = false; + + moment.createFromInputFallback = deprecate( + "moment construction falls back to js Date. This is " + + "discouraged and will be removed in upcoming major " + + "release. Please refer to " + + "https://github.com/moment/moment/issues/1407 for more info.", + function (config) { + config._d = new Date(config._i); + }); + + // creating with utc + moment.utc = function (input, format, lang, strict) { + var c; + + if (typeof(lang) === "boolean") { + strict = lang; + lang = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c = {}; + c._isAMomentObject = true; + c._useUTC = true; + c._isUTC = true; + c._l = lang; + c._i = input; + c._f = format; + c._strict = strict; + c._pf = defaultParsingFlags(); + + return makeMoment(c).utc(); + }; + + // creating with unix timestamp (in seconds) + moment.unix = function (input) { + return moment(input * 1000); + }; + + // duration + moment.duration = function (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + parseIso; + + if (moment.isDuration(input)) { + duration = { + ms: input._milliseconds, + d: input._days, + M: input._months + }; + } else if (typeof input === 'number') { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { + sign = (match[1] === "-") ? -1 : 1; + duration = { + y: 0, + d: toInt(match[DATE]) * sign, + h: toInt(match[HOUR]) * sign, + m: toInt(match[MINUTE]) * sign, + s: toInt(match[SECOND]) * sign, + ms: toInt(match[MILLISECOND]) * sign + }; + } else if (!!(match = isoDurationRegex.exec(input))) { + sign = (match[1] === "-") ? -1 : 1; + parseIso = function (inp) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + }; + duration = { + y: parseIso(match[2]), + M: parseIso(match[3]), + d: parseIso(match[4]), + h: parseIso(match[5]), + m: parseIso(match[6]), + s: parseIso(match[7]), + w: parseIso(match[8]) + }; + } + + ret = new Duration(duration); + + if (moment.isDuration(input) && input.hasOwnProperty('_lang')) { + ret._lang = input._lang; + } + + return ret; + }; + + // version number + moment.version = VERSION; + + // default format + moment.defaultFormat = isoFormat; + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + moment.momentProperties = momentProperties; + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + moment.updateOffset = function () {}; + + // This function will load languages and then set the global language. If + // no arguments are passed in, it will simply return the current global + // language key. + moment.lang = function (key, values) { + var r; + if (!key) { + return moment.fn._lang._abbr; + } + if (values) { + loadLang(normalizeLanguage(key), values); + } else if (values === null) { + unloadLang(key); + key = 'en'; + } else if (!languages[key]) { + getLangDefinition(key); + } + r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); + return r._abbr; + }; + + // returns language data + moment.langData = function (key) { + if (key && key._lang && key._lang._abbr) { + key = key._lang._abbr; + } + return getLangDefinition(key); + }; + + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment || + (obj != null && obj.hasOwnProperty('_isAMomentObject')); + }; + + // for typechecking Duration objects + moment.isDuration = function (obj) { + return obj instanceof Duration; + }; + + for (i = lists.length - 1; i >= 0; --i) { + makeList(lists[i]); + } + + moment.normalizeUnits = function (units) { + return normalizeUnits(units); + }; + + moment.invalid = function (flags) { + var m = moment.utc(NaN); + if (flags != null) { + extend(m._pf, flags); + } + else { + m._pf.userInvalidated = true; + } + + return m; + }; + + moment.parseZone = function () { + return moment.apply(null, arguments).parseZone(); + }; + + moment.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; + + /************************************ + Moment Prototype + ************************************/ + + + extend(moment.fn = Moment.prototype, { + + clone : function () { + return moment(this); + }, + + valueOf : function () { + return +this._d + ((this._offset || 0) * 60000); + }, + + unix : function () { + return Math.floor(+this / 1000); + }, + + toString : function () { + return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); + }, + + toDate : function () { + return this._offset ? new Date(+this) : this._d; + }, + + toISOString : function () { + var m = moment(this).utc(); + if (0 < m.year() && m.year() <= 9999) { + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } else { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + }, + + toArray : function () { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hours(), + m.minutes(), + m.seconds(), + m.milliseconds() + ]; + }, + + isValid : function () { + return isValid(this); + }, + + isDSTShifted : function () { + + if (this._a) { + return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; + } + + return false; + }, + + parsingFlags : function () { + return extend({}, this._pf); + }, + + invalidAt: function () { + return this._pf.overflow; + }, + + utc : function () { + return this.zone(0); + }, + + local : function () { + this.zone(0); + this._isUTC = false; + return this; + }, + + format : function (inputString) { + var output = formatMoment(this, inputString || moment.defaultFormat); + return this.lang().postformat(output); + }, + + add : function (input, val) { + var dur; + // switch args to support add('s', 1) and add(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, 1); + return this; + }, + + subtract : function (input, val) { + var dur; + // switch args to support subtract('s', 1) and subtract(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, -1); + return this; + }, + + diff : function (input, units, asFloat) { + var that = makeAs(input, this), + zoneDiff = (this.zone() - that.zone()) * 6e4, + diff, output; + + units = normalizeUnits(units); + + if (units === 'year' || units === 'month') { + // average number of days in the months in the given dates + diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 + // difference in months + output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); + // adjust by taking difference in days, average number of days + // and dst in the given months. + output += ((this - moment(this).startOf('month')) - + (that - moment(that).startOf('month'))) / diff; + // same as above but with zones, to negate all dst + output -= ((this.zone() - moment(this).startOf('month').zone()) - + (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff; + if (units === 'year') { + output = output / 12; + } + } else { + diff = (this - that); + output = units === 'second' ? diff / 1e3 : // 1000 + units === 'minute' ? diff / 6e4 : // 1000 * 60 + units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + diff; + } + return asFloat ? output : absRound(output); + }, + + from : function (time, withoutSuffix) { + return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); + }, + + fromNow : function (withoutSuffix) { + return this.from(moment(), withoutSuffix); + }, + + calendar : function () { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're zone'd or not. + var sod = makeAs(moment(), this).startOf('day'), + diff = this.diff(sod, 'days', true), + format = diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + return this.format(this.lang().calendar(format, this)); + }, + + isLeapYear : function () { + return isLeapYear(this.year()); + }, + + isDST : function () { + return (this.zone() < this.clone().month(0).zone() || + this.zone() < this.clone().month(5).zone()); + }, + + day : function (input) { + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.lang()); + return this.add({ d : input - day }); + } else { + return day; + } + }, + + month : makeAccessor('Month', true), + + startOf: function (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'quarter': + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + /* falls through */ + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } else if (units === 'isoWeek') { + this.isoWeekday(1); + } + + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } + + return this; + }, + + endOf: function (units) { + units = normalizeUnits(units); + return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1); + }, + + isAfter: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) > +moment(input).startOf(units); + }, + + isBefore: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) < +moment(input).startOf(units); + }, + + isSame: function (input, units) { + units = units || 'ms'; + return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); + }, + + min: function (other) { + other = moment.apply(null, arguments); + return other < this ? this : other; + }, + + max: function (other) { + other = moment.apply(null, arguments); + return other > this ? this : other; + }, + + // keepTime = true means only change the timezone, without affecting + // the local hour. So 5:31:26 +0300 --[zone(2, true)]--> 5:31:26 +0200 + // It is possible that 5:31:26 doesn't exist int zone +0200, so we + // adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + zone : function (input, keepTime) { + var offset = this._offset || 0; + if (input != null) { + if (typeof input === "string") { + input = timezoneMinutesFromString(input); + } + if (Math.abs(input) < 16) { + input = input * 60; + } + this._offset = input; + this._isUTC = true; + if (offset !== input) { + if (!keepTime || this._changeInProgress) { + addOrSubtractDurationFromMoment(this, + moment.duration(offset - input, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + moment.updateOffset(this, true); + this._changeInProgress = null; + } + } + } else { + return this._isUTC ? offset : this._d.getTimezoneOffset(); + } + return this; + }, + + zoneAbbr : function () { + return this._isUTC ? "UTC" : ""; + }, + + zoneName : function () { + return this._isUTC ? "Coordinated Universal Time" : ""; + }, + + parseZone : function () { + if (this._tzm) { + this.zone(this._tzm); + } else if (typeof this._i === 'string') { + this.zone(this._i); + } + return this; + }, + + hasAlignedHourOffset : function (input) { + if (!input) { + input = 0; + } + else { + input = moment(input).zone(); + } + + return (this.zone() - input) % 60 === 0; + }, + + daysInMonth : function () { + return daysInMonth(this.year(), this.month()); + }, + + dayOfYear : function (input) { + var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); + }, + + quarter : function (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + }, + + weekYear : function (input) { + var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year; + return input == null ? year : this.add("y", (input - year)); + }, + + isoWeekYear : function (input) { + var year = weekOfYear(this, 1, 4).year; + return input == null ? year : this.add("y", (input - year)); + }, + + week : function (input) { + var week = this.lang().week(this); + return input == null ? week : this.add("d", (input - week) * 7); + }, + + isoWeek : function (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add("d", (input - week) * 7); + }, + + weekday : function (input) { + var weekday = (this.day() + 7 - this.lang()._week.dow) % 7; + return input == null ? weekday : this.add("d", input - weekday); + }, + + isoWeekday : function (input) { + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); + }, + + isoWeeksInYear : function () { + return weeksInYear(this.year(), 1, 4); + }, + + weeksInYear : function () { + var weekInfo = this._lang._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units](); + }, + + set : function (units, value) { + units = normalizeUnits(units); + if (typeof this[units] === 'function') { + this[units](value); + } + return this; + }, + + // If passed a language key, it will set the language for this + // instance. Otherwise, it will return the language configuration + // variables for this instance. + lang : function (key) { + if (key === undefined) { + return this._lang; + } else { + this._lang = getLangDefinition(key); + return this; + } + } + }); + + function rawMonthSetter(mom, value) { + var dayOfMonth; + + // TODO: Move this out of here! + if (typeof value === 'string') { + value = mom.lang().monthsParse(value); + // TODO: Another silent failure? + if (typeof value !== 'number') { + return mom; + } + } + + dayOfMonth = Math.min(mom.date(), + daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function rawGetter(mom, unit) { + return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); + } + + function rawSetter(mom, unit, value) { + if (unit === 'Month') { + return rawMonthSetter(mom, value); + } else { + return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + + function makeAccessor(unit, keepTime) { + return function (value) { + if (value != null) { + rawSetter(this, unit, value); + moment.updateOffset(this, keepTime); + return this; + } else { + return rawGetter(this, unit); + } + }; + } + + moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false); + moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false); + moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false); + // Setting the hour should keep the time, because the user explicitly + // specified which hour he wants. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true); + // moment.fn.month is defined separately + moment.fn.date = makeAccessor('Date', true); + moment.fn.dates = deprecate("dates accessor is deprecated. Use date instead.", makeAccessor('Date', true)); + moment.fn.year = makeAccessor('FullYear', true); + moment.fn.years = deprecate("years accessor is deprecated. Use year instead.", makeAccessor('FullYear', true)); + + // add plural methods + moment.fn.days = moment.fn.day; + moment.fn.months = moment.fn.month; + moment.fn.weeks = moment.fn.week; + moment.fn.isoWeeks = moment.fn.isoWeek; + moment.fn.quarters = moment.fn.quarter; + + // add aliased format methods + moment.fn.toJSON = moment.fn.toISOString; + + /************************************ + Duration Prototype + ************************************/ + + + extend(moment.duration.fn = Duration.prototype, { + + _bubble : function () { + var milliseconds = this._milliseconds, + days = this._days, + months = this._months, + data = this._data, + seconds, minutes, hours, years; + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absRound(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absRound(seconds / 60); + data.minutes = minutes % 60; + + hours = absRound(minutes / 60); + data.hours = hours % 24; + + days += absRound(hours / 24); + data.days = days % 30; + + months += absRound(days / 30); + data.months = months % 12; + + years = absRound(months / 12); + data.years = years; + }, + + weeks : function () { + return absRound(this.days() / 7); + }, + + valueOf : function () { + return this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6; + }, + + humanize : function (withSuffix) { + var difference = +this, + output = relativeTime(difference, !withSuffix, this.lang()); + + if (withSuffix) { + output = this.lang().pastFuture(difference, output); + } + + return this.lang().postformat(output); + }, + + add : function (input, val) { + // supports only 2.0-style add(1, 's') or add(moment) + var dur = moment.duration(input, val); + + this._milliseconds += dur._milliseconds; + this._days += dur._days; + this._months += dur._months; + + this._bubble(); + + return this; + }, + + subtract : function (input, val) { + var dur = moment.duration(input, val); + + this._milliseconds -= dur._milliseconds; + this._days -= dur._days; + this._months -= dur._months; + + this._bubble(); + + return this; + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units.toLowerCase() + 's'](); + }, + + as : function (units) { + units = normalizeUnits(units); + return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's'](); + }, + + lang : moment.fn.lang, + + toIsoString : function () { + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var years = Math.abs(this.years()), + months = Math.abs(this.months()), + days = Math.abs(this.days()), + hours = Math.abs(this.hours()), + minutes = Math.abs(this.minutes()), + seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); + + if (!this.asSeconds()) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + return (this.asSeconds() < 0 ? '-' : '') + + 'P' + + (years ? years + 'Y' : '') + + (months ? months + 'M' : '') + + (days ? days + 'D' : '') + + ((hours || minutes || seconds) ? 'T' : '') + + (hours ? hours + 'H' : '') + + (minutes ? minutes + 'M' : '') + + (seconds ? seconds + 'S' : ''); + } + }); + + function makeDurationGetter(name) { + moment.duration.fn[name] = function () { + return this._data[name]; + }; + } + + function makeDurationAsGetter(name, factor) { + moment.duration.fn['as' + name] = function () { + return +this / factor; + }; + } + + for (i in unitMillisecondFactors) { + if (unitMillisecondFactors.hasOwnProperty(i)) { + makeDurationAsGetter(i, unitMillisecondFactors[i]); + makeDurationGetter(i.toLowerCase()); + } + } + + makeDurationAsGetter('Weeks', 6048e5); + moment.duration.fn.asMonths = function () { + return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12; + }; + + + /************************************ + Default Lang + ************************************/ + + + // Set default language, other languages will inherit from English. + moment.lang('en', { + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + /* EMBED_LANGUAGES */ + + /************************************ + Exposing Moment + ************************************/ + + function makeGlobal(shouldDeprecate) { + /*global ender:false */ + if (typeof ender !== 'undefined') { + return; + } + oldGlobalMoment = globalScope.moment; + if (shouldDeprecate) { + globalScope.moment = deprecate( + "Accessing Moment through the global scope is " + + "deprecated, and will be removed in an upcoming " + + "release.", + moment); + } else { + globalScope.moment = moment; + } + } + + // CommonJS module is defined + if (hasModule) { + module.exports = moment; + } else if (typeof define === "function" && define.amd) { + define("moment", function (require, exports, module) { + if (module.config && module.config() && module.config().noGlobal === true) { + // release the global variable + globalScope.moment = oldGlobalMoment; + } + + return moment; + }); + makeGlobal(true); + } else { + makeGlobal(); + } +}).call(this); diff --git a/lib/vendor/moment.min.js b/lib/vendor/moment.min.js deleted file mode 100644 index f2534886..00000000 --- a/lib/vendor/moment.min.js +++ /dev/null @@ -1,6 +0,0 @@ -//! moment.js -//! version : 2.5.0 -//! authors : Tim Wood, Iskren Chernev, Moment.js contributors -//! license : MIT -//! momentjs.com -(function(a){function b(a,b){return function(c){return i(a.call(this,c),b)}}function c(a,b){return function(c){return this.lang().ordinal(a.call(this,c),b)}}function d(){}function e(a){u(a),g(this,a)}function f(a){var b=o(a),c=b.year||0,d=b.month||0,e=b.week||0,f=b.day||0,g=b.hour||0,h=b.minute||0,i=b.second||0,j=b.millisecond||0;this._milliseconds=+j+1e3*i+6e4*h+36e5*g,this._days=+f+7*e,this._months=+d+12*c,this._data={},this._bubble()}function g(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return b.hasOwnProperty("toString")&&(a.toString=b.toString),b.hasOwnProperty("valueOf")&&(a.valueOf=b.valueOf),a}function h(a){return 0>a?Math.ceil(a):Math.floor(a)}function i(a,b,c){for(var d=Math.abs(a)+"",e=a>=0;d.lengthd;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function n(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=Qb[a]||Rb[b]||b}return a}function o(a){var b,c,d={};for(c in a)a.hasOwnProperty(c)&&(b=n(c),b&&(d[b]=a[c]));return d}function p(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}cb[b]=function(e,f){var g,h,i=cb.fn._lang[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=cb().utc().set(d,a);return i.call(cb.fn._lang,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function r(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function s(a){return t(a)?366:365}function t(a){return a%4===0&&a%100!==0||a%400===0}function u(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[ib]<0||a._a[ib]>11?ib:a._a[jb]<1||a._a[jb]>r(a._a[hb],a._a[ib])?jb:a._a[kb]<0||a._a[kb]>23?kb:a._a[lb]<0||a._a[lb]>59?lb:a._a[mb]<0||a._a[mb]>59?mb:a._a[nb]<0||a._a[nb]>999?nb:-1,a._pf._overflowDayOfYear&&(hb>b||b>jb)&&(b=jb),a._pf.overflow=b)}function v(a){a._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function w(a){return null==a._isValid&&(a._isValid=!isNaN(a._d.getTime())&&a._pf.overflow<0&&!a._pf.empty&&!a._pf.invalidMonth&&!a._pf.nullInput&&!a._pf.invalidFormat&&!a._pf.userInvalidated,a._strict&&(a._isValid=a._isValid&&0===a._pf.charsLeftOver&&0===a._pf.unusedTokens.length)),a._isValid}function x(a){return a?a.toLowerCase().replace("_","-"):a}function y(a,b){return b._isUTC?cb(a).zone(b._offset||0):cb(a).local()}function z(a,b){return b.abbr=a,ob[a]||(ob[a]=new d),ob[a].set(b),ob[a]}function A(a){delete ob[a]}function B(a){var b,c,d,e,f=0,g=function(a){if(!ob[a]&&pb)try{require("./lang/"+a)}catch(b){}return ob[a]};if(!a)return cb.fn._lang;if(!k(a)){if(c=g(a))return c;a=[a]}for(;f0;){if(c=g(e.slice(0,b).join("-")))return c;if(d&&d.length>=b&&m(e,d,!0)>=b-1)break;b--}f++}return cb.fn._lang}function C(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function D(a){var b,c,d=a.match(tb);for(b=0,c=d.length;c>b;b++)d[b]=Vb[d[b]]?Vb[d[b]]:C(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function E(a,b){return a.isValid()?(b=F(b,a.lang()),Sb[b]||(Sb[b]=D(b)),Sb[b](a)):a.lang().invalidDate()}function F(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(ub.lastIndex=0;d>=0&&ub.test(a);)a=a.replace(ub,c),ub.lastIndex=0,d-=1;return a}function G(a,b){var c,d=b._strict;switch(a){case"DDDD":return Gb;case"YYYY":case"GGGG":case"gggg":return d?Hb:xb;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return d?Ib:yb;case"S":if(d)return Eb;case"SS":if(d)return Fb;case"SSS":case"DDD":return d?Gb:wb;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Ab;case"a":case"A":return B(b._l)._meridiemParse;case"X":return Db;case"Z":case"ZZ":return Bb;case"T":return Cb;case"SSSS":return zb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return d?Fb:vb;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return d?Eb:vb;default:return c=new RegExp(O(N(a.replace("\\","")),"i"))}}function H(a){a=a||"";var b=a.match(Bb)||[],c=b[b.length-1]||[],d=(c+"").match(Nb)||["-",0,0],e=+(60*d[1])+q(d[2]);return"+"===d[0]?-e:e}function I(a,b,c){var d,e=c._a;switch(a){case"M":case"MM":null!=b&&(e[ib]=q(b)-1);break;case"MMM":case"MMMM":d=B(c._l).monthsParse(b),null!=d?e[ib]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[jb]=q(b));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=q(b));break;case"YY":e[hb]=q(b)+(q(b)>68?1900:2e3);break;case"YYYY":case"YYYYY":case"YYYYYY":e[hb]=q(b);break;case"a":case"A":c._isPm=B(c._l).isPM(b);break;case"H":case"HH":case"h":case"hh":e[kb]=q(b);break;case"m":case"mm":e[lb]=q(b);break;case"s":case"ss":e[mb]=q(b);break;case"S":case"SS":case"SSS":case"SSSS":e[nb]=q(1e3*("0."+b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=H(b);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":a=a.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=b)}}function J(a){var b,c,d,e,f,g,h,i,j,k,l=[];if(!a._d){for(d=L(a),a._w&&null==a._a[jb]&&null==a._a[ib]&&(f=function(b){var c=parseInt(b,10);return b?b.length<3?c>68?1900+c:2e3+c:c:null==a._a[hb]?cb().weekYear():a._a[hb]},g=a._w,null!=g.GG||null!=g.W||null!=g.E?h=Y(f(g.GG),g.W||1,g.E,4,1):(i=B(a._l),j=null!=g.d?U(g.d,i):null!=g.e?parseInt(g.e,10)+i._week.dow:0,k=parseInt(g.w,10)||1,null!=g.d&&js(e)&&(a._pf._overflowDayOfYear=!0),c=T(e,0,a._dayOfYear),a._a[ib]=c.getUTCMonth(),a._a[jb]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=l[b]=d[b];for(;7>b;b++)a._a[b]=l[b]=null==a._a[b]?2===b?1:0:a._a[b];l[kb]+=q((a._tzm||0)/60),l[lb]+=q((a._tzm||0)%60),a._d=(a._useUTC?T:S).apply(null,l)}}function K(a){var b;a._d||(b=o(a._i),a._a=[b.year,b.month,b.day,b.hour,b.minute,b.second,b.millisecond],J(a))}function L(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function M(a){a._a=[],a._pf.empty=!0;var b,c,d,e,f,g=B(a._l),h=""+a._i,i=h.length,j=0;for(d=F(a._f,g).match(tb)||[],b=0;b0&&a._pf.unusedInput.push(f),h=h.slice(h.indexOf(c)+c.length),j+=c.length),Vb[e]?(c?a._pf.empty=!1:a._pf.unusedTokens.push(e),I(e,c,a)):a._strict&&!c&&a._pf.unusedTokens.push(e);a._pf.charsLeftOver=i-j,h.length>0&&a._pf.unusedInput.push(h),a._isPm&&a._a[kb]<12&&(a._a[kb]+=12),a._isPm===!1&&12===a._a[kb]&&(a._a[kb]=0),J(a),u(a)}function N(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function O(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function P(a){var b,c,d,e,f;if(0===a._f.length)return a._pf.invalidFormat=!0,a._d=new Date(0/0),void 0;for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function Q(a){var b,c=a._i,d=Jb.exec(c);if(d){for(a._pf.iso=!0,b=4;b>0;b--)if(d[b]){a._f=Lb[b-1]+(d[6]||" ");break}for(b=0;4>b;b++)if(Mb[b][1].exec(c)){a._f+=Mb[b][0];break}c.match(Bb)&&(a._f+="Z"),M(a)}else a._d=new Date(c)}function R(b){var c=b._i,d=qb.exec(c);c===a?b._d=new Date:d?b._d=new Date(+d[1]):"string"==typeof c?Q(b):k(c)?(b._a=c.slice(0),J(b)):l(c)?b._d=new Date(+c):"object"==typeof c?K(b):b._d=new Date(c)}function S(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function T(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function U(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function V(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function W(a,b,c){var d=gb(Math.abs(a)/1e3),e=gb(d/60),f=gb(e/60),g=gb(f/24),h=gb(g/365),i=45>d&&["s",d]||1===e&&["m"]||45>e&&["mm",e]||1===f&&["h"]||22>f&&["hh",f]||1===g&&["d"]||25>=g&&["dd",g]||45>=g&&["M"]||345>g&&["MM",gb(g/30)]||1===h&&["y"]||["yy",h];return i[2]=b,i[3]=a>0,i[4]=c,V.apply({},i)}function X(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=cb(a).add("d",f),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function Y(a,b,c,d,e){var f,g,h=new Date(i(a,6,!0)+"-01-01").getUTCDay();return c=null!=c?c:e,f=e-h+(h>d?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:s(a-1)+g}}function Z(a){var b=a._i,c=a._f;return"undefined"==typeof a._pf&&v(a),null===b?cb.invalid({nullInput:!0}):("string"==typeof b&&(a._i=b=B().preparse(b)),cb.isMoment(b)?(a=g({},b),a._d=new Date(+b._d)):c?k(c)?P(a):M(a):R(a),new e(a))}function $(a,b){cb.fn[a]=cb.fn[a+"s"]=function(a){var c=this._isUTC?"UTC":"";return null!=a?(this._d["set"+c+b](a),cb.updateOffset(this),this):this._d["get"+c+b]()}}function _(a){cb.duration.fn[a]=function(){return this._data[a]}}function ab(a,b){cb.duration.fn["as"+a]=function(){return+this/b}}function bb(a){var b=!1,c=cb;"undefined"==typeof ender&&(a?(fb.moment=function(){return!b&&console&&console.warn&&(b=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),c.apply(null,arguments)},g(fb.moment,c)):fb.moment=cb)}for(var cb,db,eb="2.5.0",fb=this,gb=Math.round,hb=0,ib=1,jb=2,kb=3,lb=4,mb=5,nb=6,ob={},pb="undefined"!=typeof module&&module.exports&&"undefined"!=typeof require,qb=/^\/?Date\((\-?\d+)/i,rb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,sb=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,tb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,ub=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,vb=/\d\d?/,wb=/\d{1,3}/,xb=/\d{1,4}/,yb=/[+\-]?\d{1,6}/,zb=/\d+/,Ab=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Bb=/Z|[\+\-]\d\d:?\d\d/gi,Cb=/T/i,Db=/[\+\-]?\d+(\.\d{1,3})?/,Eb=/\d/,Fb=/\d\d/,Gb=/\d{3}/,Hb=/\d{4}/,Ib=/[+\-]?\d{6}/,Jb=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Kb="YYYY-MM-DDTHH:mm:ssZ",Lb=["YYYY-MM-DD","GGGG-[W]WW","GGGG-[W]WW-E","YYYY-DDD"],Mb=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Nb=/([\+\-]|\d\d)/gi,Ob="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Pb={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Qb={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Rb={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Sb={},Tb="DDD w W M D d".split(" "),Ub="M D H h m s w W".split(" "),Vb={M:function(){return this.month()+1},MMM:function(a){return this.lang().monthsShort(this,a)},MMMM:function(a){return this.lang().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.lang().weekdaysMin(this,a)},ddd:function(a){return this.lang().weekdaysShort(this,a)},dddd:function(a){return this.lang().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return i(this.year()%100,2)},YYYY:function(){return i(this.year(),4)},YYYYY:function(){return i(this.year(),5)},YYYYYY:function(){var a=this.year(),b=a>=0?"+":"-";return b+i(Math.abs(a),6)},gg:function(){return i(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return i(this.weekYear(),5)},GG:function(){return i(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return i(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return q(this.milliseconds()/100)},SS:function(){return i(q(this.milliseconds()/10),2)},SSS:function(){return i(this.milliseconds(),3)},SSSS:function(){return i(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+":"+i(q(a)%60,2)},ZZ:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+i(q(a)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()},Q:function(){return this.quarter()}},Wb=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Tb.length;)db=Tb.pop(),Vb[db+"o"]=c(Vb[db],db);for(;Ub.length;)db=Ub.pop(),Vb[db+db]=b(Vb[db],2);for(Vb.DDDD=b(Vb.DDD,3),g(d.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a){var b,c,d;for(this._monthsParse||(this._monthsParse=[]),b=0;12>b;b++)if(this._monthsParse[b]||(c=cb.utc([2e3,b]),d="^"+this.months(c,"")+"|^"+this.monthsShort(c,""),this._monthsParse[b]=new RegExp(d.replace(".",""),"i")),this._monthsParse[b].test(a))return b},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=cb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b){var c=this._calendar[a];return"function"==typeof c?c.apply(b):c},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",preparse:function(a){return a},postformat:function(a){return a},week:function(a){return X(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),cb=function(b,c,d,e){return"boolean"==typeof d&&(e=d,d=a),Z({_i:b,_f:c,_l:d,_strict:e,_isUTC:!1})},cb.utc=function(b,c,d,e){var f;return"boolean"==typeof d&&(e=d,d=a),f=Z({_useUTC:!0,_isUTC:!0,_l:d,_i:b,_f:c,_strict:e}).utc()},cb.unix=function(a){return cb(1e3*a)},cb.duration=function(a,b){var c,d,e,g=a,h=null;return cb.isDuration(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=rb.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:q(h[jb])*c,h:q(h[kb])*c,m:q(h[lb])*c,s:q(h[mb])*c,ms:q(h[nb])*c}):(h=sb.exec(a))&&(c="-"===h[1]?-1:1,e=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*c},g={y:e(h[2]),M:e(h[3]),d:e(h[4]),h:e(h[5]),m:e(h[6]),s:e(h[7]),w:e(h[8])}),d=new f(g),cb.isDuration(a)&&a.hasOwnProperty("_lang")&&(d._lang=a._lang),d},cb.version=eb,cb.defaultFormat=Kb,cb.updateOffset=function(){},cb.lang=function(a,b){var c;return a?(b?z(x(a),b):null===b?(A(a),a="en"):ob[a]||B(a),c=cb.duration.fn._lang=cb.fn._lang=B(a),c._abbr):cb.fn._lang._abbr},cb.langData=function(a){return a&&a._lang&&a._lang._abbr&&(a=a._lang._abbr),B(a)},cb.isMoment=function(a){return a instanceof e},cb.isDuration=function(a){return a instanceof f},db=Wb.length-1;db>=0;--db)p(Wb[db]);for(cb.normalizeUnits=function(a){return n(a)},cb.invalid=function(a){var b=cb.utc(0/0);return null!=a?g(b._pf,a):b._pf.userInvalidated=!0,b},cb.parseZone=function(a){return cb(a).parseZone()},g(cb.fn=e.prototype,{clone:function(){return cb(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var a=cb(this).utc();return 00:!1},parsingFlags:function(){return g({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(a){var b=E(this,a||cb.defaultFormat);return this.lang().postformat(b)},add:function(a,b){var c;return c="string"==typeof a?cb.duration(+b,a):cb.duration(a,b),j(this,c,1),this},subtract:function(a,b){var c;return c="string"==typeof a?cb.duration(+b,a):cb.duration(a,b),j(this,c,-1),this},diff:function(a,b,c){var d,e,f=y(a,this),g=6e4*(this.zone()-f.zone());return b=n(b),"year"===b||"month"===b?(d=432e5*(this.daysInMonth()+f.daysInMonth()),e=12*(this.year()-f.year())+(this.month()-f.month()),e+=(this-cb(this).startOf("month")-(f-cb(f).startOf("month")))/d,e-=6e4*(this.zone()-cb(this).startOf("month").zone()-(f.zone()-cb(f).startOf("month").zone()))/d,"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:h(e)},from:function(a,b){return cb.duration(this.diff(a)).lang(this.lang()._abbr).humanize(!b)},fromNow:function(a){return this.from(cb(),a)},calendar:function(){var a=y(cb(),this).startOf("day"),b=this.diff(a,"days",!0),c=-6>b?"sameElse":-1>b?"lastWeek":0>b?"lastDay":1>b?"sameDay":2>b?"nextDay":7>b?"nextWeek":"sameElse";return this.format(this.lang().calendar(c,this))},isLeapYear:function(){return t(this.year())},isDST:function(){return this.zone()+cb(a).startOf(b)},isBefore:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)<+cb(a).startOf(b)},isSame:function(a,b){return b=b||"ms",+this.clone().startOf(b)===+y(a,this).startOf(b)},min:function(a){return a=cb.apply(null,arguments),this>a?this:a},max:function(a){return a=cb.apply(null,arguments),a>this?this:a},zone:function(a){var b=this._offset||0;return null==a?this._isUTC?b:this._d.getTimezoneOffset():("string"==typeof a&&(a=H(a)),Math.abs(a)<16&&(a=60*a),this._offset=a,this._isUTC=!0,b!==a&&j(this,cb.duration(b-a,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.zone(this._tzm):"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(a){return a=a?cb(a).zone():0,(this.zone()-a)%60===0},daysInMonth:function(){return r(this.year(),this.month())},dayOfYear:function(a){var b=gb((cb(this).startOf("day")-cb(this).startOf("year"))/864e5)+1;return null==a?b:this.add("d",a-b)},quarter:function(){return Math.ceil((this.month()+1)/3)},weekYear:function(a){var b=X(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==a?b:this.add("y",a-b)},isoWeekYear:function(a){var b=X(this,1,4).year;return null==a?b:this.add("y",a-b)},week:function(a){var b=this.lang().week(this);return null==a?b:this.add("d",7*(a-b))},isoWeek:function(a){var b=X(this,1,4).week;return null==a?b:this.add("d",7*(a-b))},weekday:function(a){var b=(this.day()+7-this.lang()._week.dow)%7;return null==a?b:this.add("d",a-b)},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},get:function(a){return a=n(a),this[a]()},set:function(a,b){return a=n(a),"function"==typeof this[a]&&this[a](b),this},lang:function(b){return b===a?this._lang:(this._lang=B(b),this)}}),db=0;db Date: Thu, 22 May 2014 20:20:43 -0700 Subject: [PATCH 081/791] Use state from local context --- lib/simply.js | 112 +++++++++++++++++++++++++------------------------- 1 file changed, 57 insertions(+), 55 deletions(-) diff --git a/lib/simply.js b/lib/simply.js index db54b12a..f11d15db 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -49,7 +49,9 @@ var actionParams = [ var cardParams = textParams.concat(imageParams).concat(actionParams); -simply.state = {}; +var state = {}; + +simply.state = state; simply.packages = {}; simply.listeners = {}; @@ -75,37 +77,37 @@ simply.begin = function() { }; simply.end = function() { - simply.state.run = false; + state.run = false; }; simply.reset = function() { simply.off(); + simply.state = state = {}; simply.packages = {}; - simply.state = {}; - simply.state.run = true; - simply.state.numPackages = 0; - simply.state.card = {}; - simply.state.options = {}; + state.run = true; + state.numPackages = 0; + state.card = {}; + state.options = {}; - simply.state.button = { + state.button = { config: {}, configMode: 'auto', }; for (var i = 0, ii = buttons.length; i < ii; i++) { var button = buttons[i]; if (button !== 'back') { - simply.state.button.config[buttons[i]] = true; + state.button.config[buttons[i]] = true; } } - simply.state.image = { + state.image = { cache: {}, nextId: 1, }; - simply.state.webview = { + state.webview = { listeners: [], }; @@ -261,7 +263,7 @@ simply.emitToHandlers = function(type, handlers, e) { }; simply.emit = function(type, subtype, e) { - if (!simply.state.run) { + if (!state.run) { return; } if (!e) { @@ -347,7 +349,7 @@ simply.fexecPackage = function(script, pkg) { // loader return function() { - if (!simply.state.run) { + if (!state.run) { return; } var exports = pkg.exports; @@ -473,7 +475,7 @@ simply.require = function(path) { * @param {simply.buttonConfig} buttonConf - An object defining the button configuration. */ simply.buttonConfig = function(buttonConf, auto) { - var buttonState = simply.state.button; + var buttonState = state.button; if (typeof buttonConf === 'undefined') { var config = {}; for (var i = 0, ii = buttons.length; i < ii; ++i) { @@ -496,7 +498,7 @@ simply.buttonConfig = function(buttonConf, auto) { }; simply.buttonAutoConfig = function() { - var buttonState = simply.state.button; + var buttonState = state.button; if (!buttonState || buttonState.configMode !== 'auto') { return; } @@ -511,12 +513,12 @@ simply.buttonAutoConfig = function() { simply.card = function(field, value, clear) { if (arguments.length === 0) { - return simply.state.card; + return state.card; } var cardDef; if (typeof field === 'string') { if (arguments.length === 1) { - return simply.state.card[field]; + return state.card[field]; } cardDef = {}; cardDef[field] = value; @@ -527,9 +529,9 @@ simply.card = function(field, value, clear) { } clear = clear === true ? 'all' : clear; if (clear === 'all') { - simply.state.card = cardDef; + state.card = cardDef; } else { - util2.copy(cardDef, simply.state.card); + util2.copy(cardDef, state.card); } return simply.impl.card(cardDef, clear); }; @@ -557,12 +559,12 @@ simply.setText = simply.text; var textfield = function(field, text, clear) { if (arguments.length <= 1) { - return simply.state.card[field]; + return state.card[field]; } if (clear) { - simply.state.card = {}; + state.card = {}; } - simply.state.card[field] = text; + state.card[field] = text; return simply.impl.textfield(field, text, clear); }; @@ -599,16 +601,16 @@ simply.body = function(text, clear) { }; simply.action = function(field, image, clear) { - if (!simply.state.card.action) { - simply.state.card.action = {}; + if (!state.card.action) { + state.card.action = {}; } if (arguments.length === 0) { - return simply.state.card.action; + return state.card.action; } var actionDef; if (typeof field === 'string') { if (arguments.length === 1) { - return simply.state.card.action[field]; + return state.card.action[field]; } actionDef = {}; actionDef[field] = image; @@ -619,9 +621,9 @@ simply.action = function(field, image, clear) { } clear = clear === true ? 'action' : clear; if (clear === 'all' || clear === 'action') { - simply.state.card.action = actionDef; + state.card.action = actionDef; } else { - util2.copy(actionDef, simply.state.card.action); + util2.copy(actionDef, state.card.action); } return simply.impl.card({ action: actionDef }, clear); }; @@ -646,9 +648,9 @@ simply.vibe = function() { simply.scrollable = function(scrollable) { if (typeof scrollable === 'undefined') { - return simply.state.scrollable === true; + return state.scrollable === true; } - simply.state.scrollable = scrollable; + state.scrollable = scrollable; return simply.impl.scrollable.apply(this, arguments); }; @@ -661,9 +663,9 @@ simply.scrollable = function(scrollable) { simply.fullscreen = function(fullscreen) { if (typeof fullscreen === 'undefined') { - return simply.state.fullscreen === true; + return state.fullscreen === true; } - simply.state.fullscreen = fullscreen; + state.fullscreen = fullscreen; return simply.impl.fullscreen.apply(this, arguments); }; @@ -727,7 +729,7 @@ simply.image = function(opt, reset, callback) { } var url = simply.basepath() + opt.url; var hash = makeImageHash(opt); - var image = simply.state.image.cache[hash]; + var image = state.image.cache[hash]; if (image) { if ((opt.width && image.width !== opt.width) || (opt.height && image.height !== opt.height) || @@ -739,13 +741,13 @@ simply.image = function(opt, reset, callback) { } } image = { - id: simply.state.image.nextId++, + id: state.image.nextId++, url: url, width: opt.width, height: opt.height, dither: opt.dither, }; - simply.state.image.cache[hash] = image; + state.image.cache[hash] = image; imagelib.load(image, function() { simply.impl.image(image.id, image.gbitmap); if (callback) { @@ -765,20 +767,20 @@ var getOptionsKey = function() { }; simply.saveOptions = function() { - var options = simply.state.options; + var options = state.options; localStorage.setItem(getOptionsKey(), JSON.stringify(options)); }; simply.loadOptions = function() { - simply.state.options = {}; + state.options = {}; var options = localStorage.getItem(getOptionsKey()); try { - simply.state.options = JSON.parse(options); + state.options = JSON.parse(options); } catch (e) {} }; simply.option = function(key, value) { - var options = simply.state.options; + var options = state.options; if (arguments.length >= 2) { if (typeof value === 'undefined') { delete options[key]; @@ -819,16 +821,16 @@ simply.settings = function(opt, open, close) { open: open, close: close, }; - simply.state.webview.listeners.push(listener); + state.webview.listeners.push(listener); }; simply.openSettings = function(e) { var options; var url; - var listener = util2.last(simply.state.webview.listeners); + var listener = util2.last(state.webview.listeners); if (listener && (new Date().getTime() - simply.inited) > 2000) { url = listener.params.url; - options = simply.state.options; + options = state.options; e = { originalEvent: e, options: options, @@ -844,7 +846,7 @@ simply.openSettings = function(e) { }; simply.closeSettings = function(e) { - var listener = util2.last(simply.state.webview.listeners); + var listener = util2.last(state.webview.listeners); var options = {}; if (e.response) { options = JSON.parse(decodeURIComponent(e.response)); @@ -863,7 +865,7 @@ simply.closeSettings = function(e) { }; simply.accelInit = function() { - simply.state.accel = { + state.accel = { rate: 100, samples: 25, subscribe: false, @@ -873,12 +875,12 @@ simply.accelInit = function() { }; simply.accelAutoSubscribe = function() { - var accelState = simply.state.accel; + var accelState = state.accel; if (!accelState || accelState.subscribeMode !== 'auto') { return; } var subscribe = simply.countHandlers('accelData') > 0; - if (subscribe !== simply.state.accel.subscribe) { + if (subscribe !== state.accel.subscribe) { return simply.accelConfig(subscribe, true); } }; @@ -900,7 +902,7 @@ simply.accelAutoSubscribe = function() { * @param {simply.accelConfig} accelConf - An object defining the accelerometer configuration. */ simply.accelConfig = function(opt, auto) { - var accelState = simply.state.accel; + var accelState = state.accel; if (typeof opt === 'undefined') { return { rate: accelState.rate, @@ -925,14 +927,14 @@ simply.accelConfig = function(opt, auto) { * @param {simply.eventHandler} callback - A callback function that will be provided the accel data point as an event. */ simply.accelPeek = function(callback) { - if (simply.state.accel.subscribe) { + if (state.accel.subscribe) { throw new Error('Cannot use accelPeek when listening to accelData events'); } return simply.impl.accelPeek.apply(this, arguments); }; var getMenuSection = function(e) { - var menu = e.menu || simply.state.menu; + var menu = e.menu || state.menu; if (!menu) { return; } if (!(menu.sections instanceof Array)) { return; } return menu.sections[e.section]; @@ -993,9 +995,9 @@ simply.onMenuSelect = function(e) { simply.menu = function(menuDef) { if (!menuDef) { - return simply.state.menu; + return state.menu; } - simply.state.menu = menuDef; + state.menu = menuDef; return simply.impl.menu.apply(this, arguments); }; @@ -1035,7 +1037,7 @@ simply.emitClick = function(type, button) { }; simply.emitCardExit = function() { - var cardDef = simply.state.card; + var cardDef = state.card; simply.emit('cardExit', util2.copy(cardDef, { card: cardDef })); @@ -1090,7 +1092,7 @@ simply.emitAccelData = function(accels, callback) { simply.emitMenuSection = function(section) { var e = { - menu: simply.state.menu, + menu: state.menu, section: section }; if (simply.emit('menuSection', e)) { return; } @@ -1099,7 +1101,7 @@ simply.emitMenuSection = function(section) { simply.emitMenuItem = function(section, item) { var e = { - menu: simply.state.menu, + menu: state.menu, section: section, item: item, }; @@ -1109,7 +1111,7 @@ simply.emitMenuItem = function(section, item) { simply.emitMenuSelect = function(type, section, item) { var e = { - menu: simply.state.menu, + menu: state.menu, section: section, item: item, }; From 27ef43fe2aafa5602ddafcb87567497ef3052653 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 20:37:51 -0700 Subject: [PATCH 082/791] Add emitter.js --- lib/emitter.js | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 lib/emitter.js diff --git a/lib/emitter.js b/lib/emitter.js new file mode 100644 index 00000000..e2fab607 --- /dev/null +++ b/lib/emitter.js @@ -0,0 +1,90 @@ + +function Emitter() { + this.listeners = {}; +} + +Emitter.prototype.wrapHandler = function(handler) { + return handler; +}; + +Emitter.prototype.on = function(type, subtype, handler) { + var typeMap = this.listeners; + var subtypeMap = (typeMap[type] || ( typeMap[type] = {} )); + (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ + id: handler, + handler: this.wrapHandler(handler), + }); +}; + +Emitter.prototype.off = function(type, subtype, handler) { + if (!type) { + this.listeners = {}; + return; + } + var typeMap = this.listeners; + if (!handler && subtype === 'all') { + delete typeMap[type]; + return; + } + var subtypeMap = typeMap[type]; + if (!subtypeMap) { + return; + } + if (!handler) { + delete subtypeMap[subtype]; + return; + } + var handlers = subtypeMap[subtype]; + if (!handlers) { + return; + } + var index = -1; + for (var i = 0, ii = handlers.length; i < ii; ++i) { + if (handlers[i].id === handler) { + index = i; + break; + } + } + if (index === -1) { + return; + } + handlers.splice(index, 1); +}; + +var emitToHandlers = function(type, handlers, e) { + if (!handlers) { + return; + } + for (var i = 0, ii = handlers.length; i < ii; ++i) { + var handler = handlers[i].handler; + if (handler(e, type, i) === false) { + return true; + } + } + return false; +}; + +Emitter.prototype.emit = function(type, subtype, e) { + if (!e) { + e = subtype; + subtype = null; + } + e.type = type; + if (subtype) { + e.subtype = subtype; + } + var typeMap = this.listeners; + var subtypeMap = typeMap[type]; + if (!subtypeMap) { + return; + } + if (emitToHandlers(type, subtypeMap[subtype], e) === true) { + return true; + } + if (emitToHandlers(type, subtypeMap.all, e) === true) { + return true; + } + return false; +}; + +module.exports = Emitter; From 4cb4a9f49b6d68cb2d0213a631d8ca1c38fd1f10 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 20:38:13 -0700 Subject: [PATCH 083/791] Change simply.js to use emitter.js --- lib/simply.js | 97 +++++++-------------------------------------------- 1 file changed, 13 insertions(+), 84 deletions(-) diff --git a/lib/simply.js b/lib/simply.js index f11d15db..e4232ef9 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -7,6 +7,8 @@ var ajax = require('ajax'); var util2 = require('util2'); var imagelib = require('image'); +var Emitter = require('emitter'); + var simply = {}; var buttons = [ @@ -81,11 +83,17 @@ simply.end = function() { }; simply.reset = function() { - simply.off(); + if (state.emitter) { + simply.off(); + } simply.state = state = {}; simply.packages = {}; + var emitter = new Emitter(); + emitter.wrapHandler = simply.wrapHandler; + + state.emitter = emitter; state.run = true; state.numPackages = 0; state.card = {}; @@ -179,19 +187,10 @@ simply.on = function(type, subtype, handler) { handler = subtype; subtype = 'all'; } - simply.rawOn(type, subtype, handler); + state.emitter.on(type, subtype, handler); simply.onAddHandler(type, subtype); }; -simply.rawOn = function(type, subtype, handler) { - var typeMap = simply.listeners; - var subtypeMap = (typeMap[type] || ( typeMap[type] = {} )); - (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ - id: handler, - handler: simply.wrapHandler(handler), - }); -}; - /** * Unsubscribe from Pebble events. * When called without a handler, all handlers of the type and subtype are unsubscribe. @@ -210,82 +209,12 @@ simply.off = function(type, subtype, handler) { handler = subtype; subtype = 'all'; } - simply.rawOff(type, subtype, handler); + state.emitter.off(type, subtype, handler); simply.onRemoveHandler(type, subtype); }; -simply.rawOff = function(type, subtype, handler) { - if (!type) { - simply.listeners = {}; - return; - } - var typeMap = simply.listeners; - if (!handler && subtype === 'all') { - delete typeMap[type]; - return; - } - var subtypeMap = typeMap[type]; - if (!subtypeMap) { - return; - } - if (!handler) { - delete subtypeMap[subtype]; - return; - } - var handlers = subtypeMap[subtype]; - if (!handlers) { - return; - } - var index = -1; - for (var i = 0, ii = handlers.length; i < ii; ++i) { - if (handlers[i].id === handler) { - index = i; - break; - } - } - if (index === -1) { - return; - } - handlers.splice(index, 1); -}; - -simply.emitToHandlers = function(type, handlers, e) { - if (!handlers) { - return; - } - for (var i = 0, ii = handlers.length; i < ii; ++i) { - var handler = handlers[i].handler; - if (handler(e, type, i) === false) { - return true; - } - } - return false; -}; - -simply.emit = function(type, subtype, e) { - if (!state.run) { - return; - } - if (!e) { - e = subtype; - subtype = null; - } - e.type = type; - if (subtype) { - e.subtype = subtype; - } - var typeMap = simply.listeners; - var subtypeMap = typeMap[type]; - if (!subtypeMap) { - return; - } - if (simply.emitToHandlers(type, subtypeMap[subtype], e) === true) { - return true; - } - if (simply.emitToHandlers(type, subtypeMap.all, e) === true) { - return true; - } - return false; +simply.emit = function(type, subtype, handler) { + state.emitter.emit(type, subtype, handler); }; var pathToName = function(path) { From f13e59898c9e62eef10e083aad9f25fe38b9de4e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 22 May 2014 20:38:54 -0700 Subject: [PATCH 084/791] Remove deprecated simply begin and end functions --- lib/simply.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/simply.js b/lib/simply.js index e4232ef9..a8cbba05 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -75,13 +75,6 @@ simply.wrapHandler = function(handler) { return simply.impl.wrapHandler.apply(this, arguments); }; -simply.begin = function() { -}; - -simply.end = function() { - state.run = false; -}; - simply.reset = function() { if (state.emitter) { simply.off(); @@ -352,7 +345,6 @@ simply.loadMainScript = function(scriptUrl) { }, true); return; } - simply.begin(); }; simply.basepath = function(path) { From 8cf9255352a2c1730abf101c7d04367b8196530f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 03:48:41 -0700 Subject: [PATCH 085/791] Change emitter to create listeners if missing --- lib/emitter.js | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/emitter.js b/lib/emitter.js index e2fab607..4d976875 100644 --- a/lib/emitter.js +++ b/lib/emitter.js @@ -1,14 +1,14 @@ -function Emitter() { +var Emitter = function() { this.listeners = {}; -} +}; Emitter.prototype.wrapHandler = function(handler) { return handler; }; Emitter.prototype.on = function(type, subtype, handler) { - var typeMap = this.listeners; + var typeMap = this.listeners || ( this.listeners = {} ); var subtypeMap = (typeMap[type] || ( typeMap[type] = {} )); (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ id: handler, @@ -27,17 +27,13 @@ Emitter.prototype.off = function(type, subtype, handler) { return; } var subtypeMap = typeMap[type]; - if (!subtypeMap) { - return; - } + if (!subtypeMap) { return; } if (!handler) { delete subtypeMap[subtype]; return; } var handlers = subtypeMap[subtype]; - if (!handlers) { - return; - } + if (!handlers) { return; } var index = -1; for (var i = 0, ii = handlers.length; i < ii; ++i) { if (handlers[i].id === handler) { @@ -45,9 +41,7 @@ Emitter.prototype.off = function(type, subtype, handler) { break; } } - if (index === -1) { - return; - } + if (index === -1) { return; } handlers.splice(index, 1); }; @@ -74,10 +68,9 @@ Emitter.prototype.emit = function(type, subtype, e) { e.subtype = subtype; } var typeMap = this.listeners; + if (!typeMap) { return; } var subtypeMap = typeMap[type]; - if (!subtypeMap) { - return; - } + if (!subtypeMap) { return; } if (emitToHandlers(type, subtypeMap[subtype], e) === true) { return true; } From be26fb0c35439e91e76f4424a7e99e0b03aa8ee3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 03:49:02 -0700 Subject: [PATCH 086/791] Add simply ui card class --- lib/simply/card.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 lib/simply/card.js diff --git a/lib/simply/card.js b/lib/simply/card.js new file mode 100644 index 00000000..4b0be1a2 --- /dev/null +++ b/lib/simply/card.js @@ -0,0 +1,52 @@ +var util2 = require('util2'); + +var simply = require('simply'); + +var Emitter = require('emitter'); + +var textParams = [ + 'title', + 'subtitle', + 'body', +]; + +var imageParams = [ + 'icon', + 'subicon', + 'banner', +]; + +var actionParams = [ + 'up', + 'select', + 'back', +]; + +var Card = function(cardDef) { + this.state = cardDef || {}; +}; + +util2.copy(Emitter.prototype, Card.prototype); + +var makeAccessor = function(k) { + return function(value) { + if (arguments.length === 0) { + return this.state[k]; + } else { + this.state[k] = value; + simply.card.call(this, k, value); + return this; + } + }; +}; + +textParams.concat(imageParams).forEach(function(k) { + Card.prototype[k] = makeAccessor(k); +}); + +Card.prototype.show = function() { + simply.card.call(this, {}); + return this; +}; + +module.exports = Card; From 4a5179f7b382c30298fa7bc4d2a9c5eea442e5c5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 03:50:09 -0700 Subject: [PATCH 087/791] Change simply card to use the card class --- lib/simply.js | 85 +++++++++++++++++++++++---------------------------- 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/lib/simply.js b/lib/simply.js index a8cbba05..f46d1d7b 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -9,7 +9,11 @@ var imagelib = require('image'); var Emitter = require('emitter'); -var simply = {}; +var simply = module.exports; + +simply.ui = {}; + +var Card = simply.ui.Card = require('simply/card'); var buttons = [ 'back', @@ -31,25 +35,6 @@ var eventTypes = [ 'menuExit', ]; -var textParams = [ - 'title', - 'subtitle', - 'body', -]; - -var imageParams = [ - 'icon', - 'subicon', - 'banner', -]; - -var actionParams = [ - 'up', - 'select', - 'back', -]; - -var cardParams = textParams.concat(imageParams).concat(actionParams); var state = {}; @@ -83,15 +68,16 @@ simply.reset = function() { simply.state = state = {}; simply.packages = {}; - var emitter = new Emitter(); - emitter.wrapHandler = simply.wrapHandler; - - state.emitter = emitter; state.run = true; state.numPackages = 0; - state.card = {}; state.options = {}; + var emitter = new Emitter(); + emitter.wrapHandler = simply.wrapHandler; + state.emitter = emitter; + + state.card = new Card(); + state.button = { config: {}, configMode: 'auto', @@ -271,9 +257,6 @@ simply.fexecPackage = function(script, pkg) { // loader return function() { - if (!state.run) { - return; - } var exports = pkg.exports; var result = simply.defun(pkg.execName, ['module', 'require', 'console', 'Pebble', 'simply'], script) @@ -432,29 +415,41 @@ simply.buttonAutoConfig = function() { } }; -simply.card = function(field, value, clear) { +simply.card = function(field, value) { + if (this instanceof Card && !this.state) { + return Card.call(this); + } + var card = state.card; if (arguments.length === 0) { - return state.card; + return card; + } + if (arguments.length === 1 && typeof field !== 'object') { + return card.state[field]; } var cardDef; - if (typeof field === 'string') { - if (arguments.length === 1) { - return state.card[field]; - } - cardDef = {}; - cardDef[field] = value; - } else { + var clear; + if (typeof field === 'object') { cardDef = field; - clear = value; - value = null; + } else { + cardDef = {}; + if (field) { + cardDef[field] = value; + } } - clear = clear === true ? 'all' : clear; - if (clear === 'all') { - state.card = cardDef; + if (this instanceof Card) { + if (this !== card) { + util2.copy(this.state, cardDef); + card = this; + } } else { - util2.copy(cardDef, state.card); + util2.copy(cardDef, card.state); } - return simply.impl.card(cardDef, clear); + if (state.card !== card) { + state.card = card; + clear = 'all'; + } + simply.impl.card(cardDef, clear); + return card; }; /** @@ -1040,7 +1035,5 @@ simply.emitMenuSelect = function(type, section, item) { simply.onMenuSelect(e); }; -module.exports = simply; - Pebble.require = require; window.require = simply.require; From 1b3d4a9858dea66475999affc9022e1f27f99e2e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 03:51:07 -0700 Subject: [PATCH 088/791] Don't show the card when setting values --- lib/simply/card.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/simply/card.js b/lib/simply/card.js index 4b0be1a2..5d7adfb6 100644 --- a/lib/simply/card.js +++ b/lib/simply/card.js @@ -34,7 +34,9 @@ var makeAccessor = function(k) { return this.state[k]; } else { this.state[k] = value; - simply.card.call(this, k, value); + if (simply.card() === this) { + simply.card.call(this, k, value); + } return this; } }; From 9d0f98c4e00624816f6fe566b1940d6eb547816d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 04:21:36 -0700 Subject: [PATCH 089/791] Add card action method --- lib/simply.js | 43 +- lib/simply/card.js | 30 + src/js/pebble-js-app.js | 2952 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 2799 insertions(+), 226 deletions(-) diff --git a/lib/simply.js b/lib/simply.js index f46d1d7b..9a864a36 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -415,7 +415,7 @@ simply.buttonAutoConfig = function() { } }; -simply.card = function(field, value) { +simply.card = function(field, value, clear) { if (this instanceof Card && !this.state) { return Card.call(this); } @@ -427,14 +427,12 @@ simply.card = function(field, value) { return card.state[field]; } var cardDef; - var clear; if (typeof field === 'object') { cardDef = field; + value = null; } else { cardDef = {}; - if (field) { - cardDef[field] = value; - } + cardDef[field] = value; } if (this instanceof Card) { if (this !== card) { @@ -447,6 +445,11 @@ simply.card = function(field, value) { if (state.card !== card) { state.card = card; clear = 'all'; + } else { + clear = clear === true ? 'all' : clear; + if (clear === 'all') { + card = state.card = new Card(cardDef); + } } simply.impl.card(cardDef, clear); return card; @@ -517,31 +520,13 @@ simply.body = function(text, clear) { }; simply.action = function(field, image, clear) { - if (!state.card.action) { - state.card.action = {}; - } - if (arguments.length === 0) { - return state.card.action; - } - var actionDef; - if (typeof field === 'string') { - if (arguments.length === 1) { - return state.card.action[field]; - } - actionDef = {}; - actionDef[field] = image; - } else { - actionDef = field; - clear = image; - image = null; - } - clear = clear === true ? 'action' : clear; - if (clear === 'all' || clear === 'action') { - state.card.action = actionDef; - } else { - util2.copy(actionDef, state.card.action); + var card = state.card; + var result = this instanceof Card ? this : card.action(field, image, clear); + if (card === result) { + clear = clear === true ? 'action' : clear; + simply.impl.card({ action: card.state.action }, clear); } - return simply.impl.card({ action: actionDef }, clear); + return state.card.action; }; /** diff --git a/lib/simply/card.js b/lib/simply/card.js index 5d7adfb6..5b8adc49 100644 --- a/lib/simply/card.js +++ b/lib/simply/card.js @@ -51,4 +51,34 @@ Card.prototype.show = function() { return this; }; +Card.prototype.action = function(field, value, clear) { + var action = this.state.action; + if (!action) { + action = this.state.action = {}; + } + if (arguments.length === 0) { + return action; + } + if (arguments.length === 1 && typeof field !== 'object') { + return action[field]; + } + var actionDef; + if (typeof field === 'object') { + actionDef = field; + clear = value; + value = null; + } else { + actionDef = {}; + actionDef[field] = value; + } + clear = clear === true ? 'action' : clear; + if (clear === 'all' || clear === 'action') { + this.state.action = actionDef; + } else { + util2.copy(actionDef, this.state.action); + } + simply.action.call(this); + return this; +}; + module.exports = Card; diff --git a/src/js/pebble-js-app.js b/src/js/pebble-js-app.js index f743b564..70e4ec5b 100644 --- a/src/js/pebble-js-app.js +++ b/src/js/pebble-js-app.js @@ -139,6 +139,92 @@ return ajax; })(); +}); +__loader.define("emitter.js", function(module, require) { + +var Emitter = function() { + this.listeners = {}; +}; + +Emitter.prototype.wrapHandler = function(handler) { + return handler; +}; + +Emitter.prototype.on = function(type, subtype, handler) { + var typeMap = this.listeners || ( this.listeners = {} ); + var subtypeMap = (typeMap[type] || ( typeMap[type] = {} )); + (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ + id: handler, + handler: this.wrapHandler(handler), + }); +}; + +Emitter.prototype.off = function(type, subtype, handler) { + if (!type) { + this.listeners = {}; + return; + } + var typeMap = this.listeners; + if (!handler && subtype === 'all') { + delete typeMap[type]; + return; + } + var subtypeMap = typeMap[type]; + if (!subtypeMap) { return; } + if (!handler) { + delete subtypeMap[subtype]; + return; + } + var handlers = subtypeMap[subtype]; + if (!handlers) { return; } + var index = -1; + for (var i = 0, ii = handlers.length; i < ii; ++i) { + if (handlers[i].id === handler) { + index = i; + break; + } + } + if (index === -1) { return; } + handlers.splice(index, 1); +}; + +var emitToHandlers = function(type, handlers, e) { + if (!handlers) { + return; + } + for (var i = 0, ii = handlers.length; i < ii; ++i) { + var handler = handlers[i].handler; + if (handler(e, type, i) === false) { + return true; + } + } + return false; +}; + +Emitter.prototype.emit = function(type, subtype, e) { + if (!e) { + e = subtype; + subtype = null; + } + e.type = type; + if (subtype) { + e.subtype = subtype; + } + var typeMap = this.listeners; + if (!typeMap) { return; } + var subtypeMap = typeMap[type]; + if (!subtypeMap) { return; } + if (emitToHandlers(type, subtypeMap[subtype], e) === true) { + return true; + } + if (emitToHandlers(type, subtypeMap.all, e) === true) { + return true; + } + return false; +}; + +module.exports = Emitter; + }); __loader.define("image.js", function(module, require) { /* global PNG */ @@ -349,6 +435,93 @@ Pebble.addEventListener('ready', function(e) { SimplyPebble.init(); }); +}); +__loader.define("simply/card.js", function(module, require) { +var util2 = require('util2'); + +var simply = require('simply'); + +var Emitter = require('emitter'); + +var textParams = [ + 'title', + 'subtitle', + 'body', +]; + +var valueParams = [ + 'icon', + 'subicon', + 'banner', +]; + +var actionParams = [ + 'up', + 'select', + 'back', +]; + +var Card = function(cardDef) { + this.state = cardDef || {}; +}; + +util2.copy(Emitter.prototype, Card.prototype); + +var makeAccessor = function(k) { + return function(value) { + if (arguments.length === 0) { + return this.state[k]; + } else { + this.state[k] = value; + if (simply.card() === this) { + simply.card.call(this, k, value); + } + return this; + } + }; +}; + +textParams.concat(valueParams).forEach(function(k) { + Card.prototype[k] = makeAccessor(k); +}); + +Card.prototype.show = function() { + simply.card.call(this, {}); + return this; +}; + +Card.prototype.action = function(field, value, clear) { + var action = this.state.action; + if (!action) { + action = this.state.action = {}; + } + if (arguments.length === 0) { + return action; + } + if (arguments.length === 1 && typeof field !== 'object') { + return action[field]; + } + var actionDef; + if (typeof field === 'object') { + actionDef = field; + clear = value; + value = null; + } else { + actionDef = {}; + actionDef[field] = value; + } + clear = clear === true ? 'action' : clear; + if (clear === 'all' || clear === 'action') { + this.state.action = actionDef; + } else { + util2.copy(actionDef, this.state.action); + } + simply.action.call(this); + return this; +}; + +module.exports = Card; + }); __loader.define("simply-pebble.js", function(module, require) { var simply = require('simply'); @@ -965,7 +1138,13 @@ var ajax = require('ajax'); var util2 = require('util2'); var imagelib = require('image'); -var simply = {}; +var Emitter = require('emitter'); + +var simply = module.exports; + +simply.ui = {}; + +var Card = simply.ui.Card = require('simply/card'); var buttons = [ 'back', @@ -987,27 +1166,10 @@ var eventTypes = [ 'menuExit', ]; -var textParams = [ - 'title', - 'subtitle', - 'body', -]; - -var imageParams = [ - 'icon', - 'subicon', - 'banner', -]; - -var actionParams = [ - 'up', - 'select', - 'back', -]; -var cardParams = textParams.concat(imageParams).concat(actionParams); +var state = {}; -simply.state = {}; +simply.state = state; simply.packages = {}; simply.listeners = {}; @@ -1029,41 +1191,41 @@ simply.wrapHandler = function(handler) { return simply.impl.wrapHandler.apply(this, arguments); }; -simply.begin = function() { -}; - -simply.end = function() { - simply.state.run = false; -}; - simply.reset = function() { - simply.off(); + if (state.emitter) { + simply.off(); + } + simply.state = state = {}; simply.packages = {}; - simply.state = {}; - simply.state.run = true; - simply.state.numPackages = 0; - simply.state.card = {}; - simply.state.options = {}; + state.run = true; + state.numPackages = 0; + state.options = {}; + + var emitter = new Emitter(); + emitter.wrapHandler = simply.wrapHandler; + state.emitter = emitter; - simply.state.button = { + state.card = new Card(); + + state.button = { config: {}, configMode: 'auto', }; for (var i = 0, ii = buttons.length; i < ii; i++) { var button = buttons[i]; if (button !== 'back') { - simply.state.button.config[buttons[i]] = true; + state.button.config[buttons[i]] = true; } } - simply.state.image = { + state.image = { cache: {}, nextId: 1, }; - simply.state.webview = { + state.webview = { listeners: [], }; @@ -1135,19 +1297,10 @@ simply.on = function(type, subtype, handler) { handler = subtype; subtype = 'all'; } - simply.rawOn(type, subtype, handler); + state.emitter.on(type, subtype, handler); simply.onAddHandler(type, subtype); }; -simply.rawOn = function(type, subtype, handler) { - var typeMap = simply.listeners; - var subtypeMap = (typeMap[type] || ( typeMap[type] = {} )); - (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ - id: handler, - handler: simply.wrapHandler(handler), - }); -}; - /** * Unsubscribe from Pebble events. * When called without a handler, all handlers of the type and subtype are unsubscribe. @@ -1166,82 +1319,12 @@ simply.off = function(type, subtype, handler) { handler = subtype; subtype = 'all'; } - simply.rawOff(type, subtype, handler); + state.emitter.off(type, subtype, handler); simply.onRemoveHandler(type, subtype); }; -simply.rawOff = function(type, subtype, handler) { - if (!type) { - simply.listeners = {}; - return; - } - var typeMap = simply.listeners; - if (!handler && subtype === 'all') { - delete typeMap[type]; - return; - } - var subtypeMap = typeMap[type]; - if (!subtypeMap) { - return; - } - if (!handler) { - delete subtypeMap[subtype]; - return; - } - var handlers = subtypeMap[subtype]; - if (!handlers) { - return; - } - var index = -1; - for (var i = 0, ii = handlers.length; i < ii; ++i) { - if (handlers[i].id === handler) { - index = i; - break; - } - } - if (index === -1) { - return; - } - handlers.splice(index, 1); -}; - -simply.emitToHandlers = function(type, handlers, e) { - if (!handlers) { - return; - } - for (var i = 0, ii = handlers.length; i < ii; ++i) { - var handler = handlers[i].handler; - if (handler(e, type, i) === false) { - return true; - } - } - return false; -}; - -simply.emit = function(type, subtype, e) { - if (!simply.state.run) { - return; - } - if (!e) { - e = subtype; - subtype = null; - } - e.type = type; - if (subtype) { - e.subtype = subtype; - } - var typeMap = simply.listeners; - var subtypeMap = typeMap[type]; - if (!subtypeMap) { - return; - } - if (simply.emitToHandlers(type, subtypeMap[subtype], e) === true) { - return true; - } - if (simply.emitToHandlers(type, subtypeMap.all, e) === true) { - return true; - } - return false; +simply.emit = function(type, subtype, handler) { + state.emitter.emit(type, subtype, handler); }; var pathToName = function(path) { @@ -1305,9 +1388,6 @@ simply.fexecPackage = function(script, pkg) { // loader return function() { - if (!simply.state.run) { - return; - } var exports = pkg.exports; var result = simply.defun(pkg.execName, ['module', 'require', 'console', 'Pebble', 'simply'], script) @@ -1379,7 +1459,6 @@ simply.loadMainScript = function(scriptUrl) { }, true); return; } - simply.begin(); }; simply.basepath = function(path) { @@ -1431,7 +1510,7 @@ simply.require = function(path) { * @param {simply.buttonConfig} buttonConf - An object defining the button configuration. */ simply.buttonConfig = function(buttonConf, auto) { - var buttonState = simply.state.button; + var buttonState = state.button; if (typeof buttonConf === 'undefined') { var config = {}; for (var i = 0, ii = buttons.length; i < ii; ++i) { @@ -1454,7 +1533,7 @@ simply.buttonConfig = function(buttonConf, auto) { }; simply.buttonAutoConfig = function() { - var buttonState = simply.state.button; + var buttonState = state.button; if (!buttonState || buttonState.configMode !== 'auto') { return; } @@ -1468,28 +1547,43 @@ simply.buttonAutoConfig = function() { }; simply.card = function(field, value, clear) { + if (this instanceof Card && !this.state) { + return Card.call(this); + } + var card = state.card; if (arguments.length === 0) { - return simply.state.card; + return card; + } + if (arguments.length === 1 && typeof field !== 'object') { + return card.state[field]; } var cardDef; - if (typeof field === 'string') { - if (arguments.length === 1) { - return simply.state.card[field]; - } + if (typeof field === 'object') { + cardDef = field; + value = null; + } else { cardDef = {}; cardDef[field] = value; + } + if (this instanceof Card) { + if (this !== card) { + util2.copy(this.state, cardDef); + card = this; + } } else { - cardDef = field; - clear = value; - value = null; + util2.copy(cardDef, card.state); } - clear = clear === true ? 'all' : clear; - if (clear === 'all') { - simply.state.card = cardDef; + if (state.card !== card) { + state.card = card; + clear = 'all'; } else { - util2.copy(cardDef, simply.state.card); + clear = clear === true ? 'all' : clear; + if (clear === 'all') { + card = state.card = new Card(cardDef); + } } - return simply.impl.card(cardDef, clear); + simply.impl.card(cardDef, clear); + return card; }; /** @@ -1515,12 +1609,12 @@ simply.setText = simply.text; var textfield = function(field, text, clear) { if (arguments.length <= 1) { - return simply.state.card[field]; + return state.card[field]; } if (clear) { - simply.state.card = {}; + state.card = {}; } - simply.state.card[field] = text; + state.card[field] = text; return simply.impl.textfield(field, text, clear); }; @@ -1557,31 +1651,13 @@ simply.body = function(text, clear) { }; simply.action = function(field, image, clear) { - if (!simply.state.card.action) { - simply.state.card.action = {}; - } - if (arguments.length === 0) { - return simply.state.card.action; - } - var actionDef; - if (typeof field === 'string') { - if (arguments.length === 1) { - return simply.state.card.action[field]; - } - actionDef = {}; - actionDef[field] = image; - } else { - actionDef = field; - clear = image; - image = null; + var card = state.card; + var result = this instanceof Card ? this : card.action(field, image, clear); + if (card === result) { + clear = clear === true ? 'action' : clear; + simply.impl.card({ action: card.state.action }, clear); } - clear = clear === true ? 'action' : clear; - if (clear === 'all' || clear === 'action') { - simply.state.card.action = actionDef; - } else { - util2.copy(actionDef, simply.state.card.action); - } - return simply.impl.card({ action: actionDef }, clear); + return state.card.action; }; /** @@ -1604,9 +1680,9 @@ simply.vibe = function() { simply.scrollable = function(scrollable) { if (typeof scrollable === 'undefined') { - return simply.state.scrollable === true; + return state.scrollable === true; } - simply.state.scrollable = scrollable; + state.scrollable = scrollable; return simply.impl.scrollable.apply(this, arguments); }; @@ -1619,9 +1695,9 @@ simply.scrollable = function(scrollable) { simply.fullscreen = function(fullscreen) { if (typeof fullscreen === 'undefined') { - return simply.state.fullscreen === true; + return state.fullscreen === true; } - simply.state.fullscreen = fullscreen; + state.fullscreen = fullscreen; return simply.impl.fullscreen.apply(this, arguments); }; @@ -1685,7 +1761,7 @@ simply.image = function(opt, reset, callback) { } var url = simply.basepath() + opt.url; var hash = makeImageHash(opt); - var image = simply.state.image.cache[hash]; + var image = state.image.cache[hash]; if (image) { if ((opt.width && image.width !== opt.width) || (opt.height && image.height !== opt.height) || @@ -1697,13 +1773,13 @@ simply.image = function(opt, reset, callback) { } } image = { - id: simply.state.image.nextId++, + id: state.image.nextId++, url: url, width: opt.width, height: opt.height, dither: opt.dither, }; - simply.state.image.cache[hash] = image; + state.image.cache[hash] = image; imagelib.load(image, function() { simply.impl.image(image.id, image.gbitmap); if (callback) { @@ -1723,20 +1799,20 @@ var getOptionsKey = function() { }; simply.saveOptions = function() { - var options = simply.state.options; + var options = state.options; localStorage.setItem(getOptionsKey(), JSON.stringify(options)); }; simply.loadOptions = function() { - simply.state.options = {}; + state.options = {}; var options = localStorage.getItem(getOptionsKey()); try { - simply.state.options = JSON.parse(options); + state.options = JSON.parse(options); } catch (e) {} }; simply.option = function(key, value) { - var options = simply.state.options; + var options = state.options; if (arguments.length >= 2) { if (typeof value === 'undefined') { delete options[key]; @@ -1777,16 +1853,16 @@ simply.settings = function(opt, open, close) { open: open, close: close, }; - simply.state.webview.listeners.push(listener); + state.webview.listeners.push(listener); }; simply.openSettings = function(e) { var options; var url; - var listener = util2.last(simply.state.webview.listeners); + var listener = util2.last(state.webview.listeners); if (listener && (new Date().getTime() - simply.inited) > 2000) { url = listener.params.url; - options = simply.state.options; + options = state.options; e = { originalEvent: e, options: options, @@ -1802,7 +1878,7 @@ simply.openSettings = function(e) { }; simply.closeSettings = function(e) { - var listener = util2.last(simply.state.webview.listeners); + var listener = util2.last(state.webview.listeners); var options = {}; if (e.response) { options = JSON.parse(decodeURIComponent(e.response)); @@ -1821,7 +1897,7 @@ simply.closeSettings = function(e) { }; simply.accelInit = function() { - simply.state.accel = { + state.accel = { rate: 100, samples: 25, subscribe: false, @@ -1831,12 +1907,12 @@ simply.accelInit = function() { }; simply.accelAutoSubscribe = function() { - var accelState = simply.state.accel; + var accelState = state.accel; if (!accelState || accelState.subscribeMode !== 'auto') { return; } var subscribe = simply.countHandlers('accelData') > 0; - if (subscribe !== simply.state.accel.subscribe) { + if (subscribe !== state.accel.subscribe) { return simply.accelConfig(subscribe, true); } }; @@ -1858,7 +1934,7 @@ simply.accelAutoSubscribe = function() { * @param {simply.accelConfig} accelConf - An object defining the accelerometer configuration. */ simply.accelConfig = function(opt, auto) { - var accelState = simply.state.accel; + var accelState = state.accel; if (typeof opt === 'undefined') { return { rate: accelState.rate, @@ -1883,14 +1959,14 @@ simply.accelConfig = function(opt, auto) { * @param {simply.eventHandler} callback - A callback function that will be provided the accel data point as an event. */ simply.accelPeek = function(callback) { - if (simply.state.accel.subscribe) { + if (state.accel.subscribe) { throw new Error('Cannot use accelPeek when listening to accelData events'); } return simply.impl.accelPeek.apply(this, arguments); }; var getMenuSection = function(e) { - var menu = e.menu || simply.state.menu; + var menu = e.menu || state.menu; if (!menu) { return; } if (!(menu.sections instanceof Array)) { return; } return menu.sections[e.section]; @@ -1951,9 +2027,9 @@ simply.onMenuSelect = function(e) { simply.menu = function(menuDef) { if (!menuDef) { - return simply.state.menu; + return state.menu; } - simply.state.menu = menuDef; + state.menu = menuDef; return simply.impl.menu.apply(this, arguments); }; @@ -1993,7 +2069,7 @@ simply.emitClick = function(type, button) { }; simply.emitCardExit = function() { - var cardDef = simply.state.card; + var cardDef = state.card; simply.emit('cardExit', util2.copy(cardDef, { card: cardDef })); @@ -2048,7 +2124,7 @@ simply.emitAccelData = function(accels, callback) { simply.emitMenuSection = function(section) { var e = { - menu: simply.state.menu, + menu: state.menu, section: section }; if (simply.emit('menuSection', e)) { return; } @@ -2057,7 +2133,7 @@ simply.emitMenuSection = function(section) { simply.emitMenuItem = function(section, item) { var e = { - menu: simply.state.menu, + menu: state.menu, section: section, item: item, }; @@ -2067,7 +2143,7 @@ simply.emitMenuItem = function(section, item) { simply.emitMenuSelect = function(type, section, item) { var e = { - menu: simply.state.menu, + menu: state.menu, section: section, item: item, }; @@ -2075,8 +2151,6 @@ simply.emitMenuSelect = function(type, section, item) { simply.onMenuSelect(e); }; -module.exports = simply; - Pebble.require = require; window.require = simply.require; @@ -2177,11 +2251,2495 @@ return util2; }); //! moment.js -//! version : 2.5.0 +//! version : 2.6.0 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com -(function(a){function b(a,b){return function(c){return i(a.call(this,c),b)}}function c(a,b){return function(c){return this.lang().ordinal(a.call(this,c),b)}}function d(){}function e(a){u(a),g(this,a)}function f(a){var b=o(a),c=b.year||0,d=b.month||0,e=b.week||0,f=b.day||0,g=b.hour||0,h=b.minute||0,i=b.second||0,j=b.millisecond||0;this._milliseconds=+j+1e3*i+6e4*h+36e5*g,this._days=+f+7*e,this._months=+d+12*c,this._data={},this._bubble()}function g(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return b.hasOwnProperty("toString")&&(a.toString=b.toString),b.hasOwnProperty("valueOf")&&(a.valueOf=b.valueOf),a}function h(a){return 0>a?Math.ceil(a):Math.floor(a)}function i(a,b,c){for(var d=Math.abs(a)+"",e=a>=0;d.lengthd;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function n(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=Qb[a]||Rb[b]||b}return a}function o(a){var b,c,d={};for(c in a)a.hasOwnProperty(c)&&(b=n(c),b&&(d[b]=a[c]));return d}function p(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}cb[b]=function(e,f){var g,h,i=cb.fn._lang[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=cb().utc().set(d,a);return i.call(cb.fn._lang,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function r(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function s(a){return t(a)?366:365}function t(a){return a%4===0&&a%100!==0||a%400===0}function u(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[ib]<0||a._a[ib]>11?ib:a._a[jb]<1||a._a[jb]>r(a._a[hb],a._a[ib])?jb:a._a[kb]<0||a._a[kb]>23?kb:a._a[lb]<0||a._a[lb]>59?lb:a._a[mb]<0||a._a[mb]>59?mb:a._a[nb]<0||a._a[nb]>999?nb:-1,a._pf._overflowDayOfYear&&(hb>b||b>jb)&&(b=jb),a._pf.overflow=b)}function v(a){a._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function w(a){return null==a._isValid&&(a._isValid=!isNaN(a._d.getTime())&&a._pf.overflow<0&&!a._pf.empty&&!a._pf.invalidMonth&&!a._pf.nullInput&&!a._pf.invalidFormat&&!a._pf.userInvalidated,a._strict&&(a._isValid=a._isValid&&0===a._pf.charsLeftOver&&0===a._pf.unusedTokens.length)),a._isValid}function x(a){return a?a.toLowerCase().replace("_","-"):a}function y(a,b){return b._isUTC?cb(a).zone(b._offset||0):cb(a).local()}function z(a,b){return b.abbr=a,ob[a]||(ob[a]=new d),ob[a].set(b),ob[a]}function A(a){delete ob[a]}function B(a){var b,c,d,e,f=0,g=function(a){if(!ob[a]&&pb)try{require("./lang/"+a)}catch(b){}return ob[a]};if(!a)return cb.fn._lang;if(!k(a)){if(c=g(a))return c;a=[a]}for(;f0;){if(c=g(e.slice(0,b).join("-")))return c;if(d&&d.length>=b&&m(e,d,!0)>=b-1)break;b--}f++}return cb.fn._lang}function C(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function D(a){var b,c,d=a.match(tb);for(b=0,c=d.length;c>b;b++)d[b]=Vb[d[b]]?Vb[d[b]]:C(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function E(a,b){return a.isValid()?(b=F(b,a.lang()),Sb[b]||(Sb[b]=D(b)),Sb[b](a)):a.lang().invalidDate()}function F(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(ub.lastIndex=0;d>=0&&ub.test(a);)a=a.replace(ub,c),ub.lastIndex=0,d-=1;return a}function G(a,b){var c,d=b._strict;switch(a){case"DDDD":return Gb;case"YYYY":case"GGGG":case"gggg":return d?Hb:xb;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return d?Ib:yb;case"S":if(d)return Eb;case"SS":if(d)return Fb;case"SSS":case"DDD":return d?Gb:wb;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Ab;case"a":case"A":return B(b._l)._meridiemParse;case"X":return Db;case"Z":case"ZZ":return Bb;case"T":return Cb;case"SSSS":return zb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return d?Fb:vb;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return d?Eb:vb;default:return c=new RegExp(O(N(a.replace("\\","")),"i"))}}function H(a){a=a||"";var b=a.match(Bb)||[],c=b[b.length-1]||[],d=(c+"").match(Nb)||["-",0,0],e=+(60*d[1])+q(d[2]);return"+"===d[0]?-e:e}function I(a,b,c){var d,e=c._a;switch(a){case"M":case"MM":null!=b&&(e[ib]=q(b)-1);break;case"MMM":case"MMMM":d=B(c._l).monthsParse(b),null!=d?e[ib]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[jb]=q(b));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=q(b));break;case"YY":e[hb]=q(b)+(q(b)>68?1900:2e3);break;case"YYYY":case"YYYYY":case"YYYYYY":e[hb]=q(b);break;case"a":case"A":c._isPm=B(c._l).isPM(b);break;case"H":case"HH":case"h":case"hh":e[kb]=q(b);break;case"m":case"mm":e[lb]=q(b);break;case"s":case"ss":e[mb]=q(b);break;case"S":case"SS":case"SSS":case"SSSS":e[nb]=q(1e3*("0."+b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=H(b);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":a=a.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=b)}}function J(a){var b,c,d,e,f,g,h,i,j,k,l=[];if(!a._d){for(d=L(a),a._w&&null==a._a[jb]&&null==a._a[ib]&&(f=function(b){var c=parseInt(b,10);return b?b.length<3?c>68?1900+c:2e3+c:c:null==a._a[hb]?cb().weekYear():a._a[hb]},g=a._w,null!=g.GG||null!=g.W||null!=g.E?h=Y(f(g.GG),g.W||1,g.E,4,1):(i=B(a._l),j=null!=g.d?U(g.d,i):null!=g.e?parseInt(g.e,10)+i._week.dow:0,k=parseInt(g.w,10)||1,null!=g.d&&js(e)&&(a._pf._overflowDayOfYear=!0),c=T(e,0,a._dayOfYear),a._a[ib]=c.getUTCMonth(),a._a[jb]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=l[b]=d[b];for(;7>b;b++)a._a[b]=l[b]=null==a._a[b]?2===b?1:0:a._a[b];l[kb]+=q((a._tzm||0)/60),l[lb]+=q((a._tzm||0)%60),a._d=(a._useUTC?T:S).apply(null,l)}}function K(a){var b;a._d||(b=o(a._i),a._a=[b.year,b.month,b.day,b.hour,b.minute,b.second,b.millisecond],J(a))}function L(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function M(a){a._a=[],a._pf.empty=!0;var b,c,d,e,f,g=B(a._l),h=""+a._i,i=h.length,j=0;for(d=F(a._f,g).match(tb)||[],b=0;b0&&a._pf.unusedInput.push(f),h=h.slice(h.indexOf(c)+c.length),j+=c.length),Vb[e]?(c?a._pf.empty=!1:a._pf.unusedTokens.push(e),I(e,c,a)):a._strict&&!c&&a._pf.unusedTokens.push(e);a._pf.charsLeftOver=i-j,h.length>0&&a._pf.unusedInput.push(h),a._isPm&&a._a[kb]<12&&(a._a[kb]+=12),a._isPm===!1&&12===a._a[kb]&&(a._a[kb]=0),J(a),u(a)}function N(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function O(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function P(a){var b,c,d,e,f;if(0===a._f.length)return a._pf.invalidFormat=!0,a._d=new Date(0/0),void 0;for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function Q(a){var b,c=a._i,d=Jb.exec(c);if(d){for(a._pf.iso=!0,b=4;b>0;b--)if(d[b]){a._f=Lb[b-1]+(d[6]||" ");break}for(b=0;4>b;b++)if(Mb[b][1].exec(c)){a._f+=Mb[b][0];break}c.match(Bb)&&(a._f+="Z"),M(a)}else a._d=new Date(c)}function R(b){var c=b._i,d=qb.exec(c);c===a?b._d=new Date:d?b._d=new Date(+d[1]):"string"==typeof c?Q(b):k(c)?(b._a=c.slice(0),J(b)):l(c)?b._d=new Date(+c):"object"==typeof c?K(b):b._d=new Date(c)}function S(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function T(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function U(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function V(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function W(a,b,c){var d=gb(Math.abs(a)/1e3),e=gb(d/60),f=gb(e/60),g=gb(f/24),h=gb(g/365),i=45>d&&["s",d]||1===e&&["m"]||45>e&&["mm",e]||1===f&&["h"]||22>f&&["hh",f]||1===g&&["d"]||25>=g&&["dd",g]||45>=g&&["M"]||345>g&&["MM",gb(g/30)]||1===h&&["y"]||["yy",h];return i[2]=b,i[3]=a>0,i[4]=c,V.apply({},i)}function X(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=cb(a).add("d",f),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function Y(a,b,c,d,e){var f,g,h=new Date(i(a,6,!0)+"-01-01").getUTCDay();return c=null!=c?c:e,f=e-h+(h>d?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:s(a-1)+g}}function Z(a){var b=a._i,c=a._f;return"undefined"==typeof a._pf&&v(a),null===b?cb.invalid({nullInput:!0}):("string"==typeof b&&(a._i=b=B().preparse(b)),cb.isMoment(b)?(a=g({},b),a._d=new Date(+b._d)):c?k(c)?P(a):M(a):R(a),new e(a))}function $(a,b){cb.fn[a]=cb.fn[a+"s"]=function(a){var c=this._isUTC?"UTC":"";return null!=a?(this._d["set"+c+b](a),cb.updateOffset(this),this):this._d["get"+c+b]()}}function _(a){cb.duration.fn[a]=function(){return this._data[a]}}function ab(a,b){cb.duration.fn["as"+a]=function(){return+this/b}}function bb(a){var b=!1,c=cb;"undefined"==typeof ender&&(a?(fb.moment=function(){return!b&&console&&console.warn&&(b=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),c.apply(null,arguments)},g(fb.moment,c)):fb.moment=cb)}for(var cb,db,eb="2.5.0",fb=this,gb=Math.round,hb=0,ib=1,jb=2,kb=3,lb=4,mb=5,nb=6,ob={},pb="undefined"!=typeof module&&module.exports&&"undefined"!=typeof require,qb=/^\/?Date\((\-?\d+)/i,rb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,sb=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,tb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,ub=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,vb=/\d\d?/,wb=/\d{1,3}/,xb=/\d{1,4}/,yb=/[+\-]?\d{1,6}/,zb=/\d+/,Ab=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Bb=/Z|[\+\-]\d\d:?\d\d/gi,Cb=/T/i,Db=/[\+\-]?\d+(\.\d{1,3})?/,Eb=/\d/,Fb=/\d\d/,Gb=/\d{3}/,Hb=/\d{4}/,Ib=/[+\-]?\d{6}/,Jb=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Kb="YYYY-MM-DDTHH:mm:ssZ",Lb=["YYYY-MM-DD","GGGG-[W]WW","GGGG-[W]WW-E","YYYY-DDD"],Mb=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Nb=/([\+\-]|\d\d)/gi,Ob="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Pb={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Qb={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Rb={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Sb={},Tb="DDD w W M D d".split(" "),Ub="M D H h m s w W".split(" "),Vb={M:function(){return this.month()+1},MMM:function(a){return this.lang().monthsShort(this,a)},MMMM:function(a){return this.lang().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.lang().weekdaysMin(this,a)},ddd:function(a){return this.lang().weekdaysShort(this,a)},dddd:function(a){return this.lang().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return i(this.year()%100,2)},YYYY:function(){return i(this.year(),4)},YYYYY:function(){return i(this.year(),5)},YYYYYY:function(){var a=this.year(),b=a>=0?"+":"-";return b+i(Math.abs(a),6)},gg:function(){return i(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return i(this.weekYear(),5)},GG:function(){return i(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return i(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return q(this.milliseconds()/100)},SS:function(){return i(q(this.milliseconds()/10),2)},SSS:function(){return i(this.milliseconds(),3)},SSSS:function(){return i(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+":"+i(q(a)%60,2)},ZZ:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+i(q(a)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()},Q:function(){return this.quarter()}},Wb=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Tb.length;)db=Tb.pop(),Vb[db+"o"]=c(Vb[db],db);for(;Ub.length;)db=Ub.pop(),Vb[db+db]=b(Vb[db],2);for(Vb.DDDD=b(Vb.DDD,3),g(d.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a){var b,c,d;for(this._monthsParse||(this._monthsParse=[]),b=0;12>b;b++)if(this._monthsParse[b]||(c=cb.utc([2e3,b]),d="^"+this.months(c,"")+"|^"+this.monthsShort(c,""),this._monthsParse[b]=new RegExp(d.replace(".",""),"i")),this._monthsParse[b].test(a))return b},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=cb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b){var c=this._calendar[a];return"function"==typeof c?c.apply(b):c},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",preparse:function(a){return a},postformat:function(a){return a},week:function(a){return X(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),cb=function(b,c,d,e){return"boolean"==typeof d&&(e=d,d=a),Z({_i:b,_f:c,_l:d,_strict:e,_isUTC:!1})},cb.utc=function(b,c,d,e){var f;return"boolean"==typeof d&&(e=d,d=a),f=Z({_useUTC:!0,_isUTC:!0,_l:d,_i:b,_f:c,_strict:e}).utc()},cb.unix=function(a){return cb(1e3*a)},cb.duration=function(a,b){var c,d,e,g=a,h=null;return cb.isDuration(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=rb.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:q(h[jb])*c,h:q(h[kb])*c,m:q(h[lb])*c,s:q(h[mb])*c,ms:q(h[nb])*c}):(h=sb.exec(a))&&(c="-"===h[1]?-1:1,e=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*c},g={y:e(h[2]),M:e(h[3]),d:e(h[4]),h:e(h[5]),m:e(h[6]),s:e(h[7]),w:e(h[8])}),d=new f(g),cb.isDuration(a)&&a.hasOwnProperty("_lang")&&(d._lang=a._lang),d},cb.version=eb,cb.defaultFormat=Kb,cb.updateOffset=function(){},cb.lang=function(a,b){var c;return a?(b?z(x(a),b):null===b?(A(a),a="en"):ob[a]||B(a),c=cb.duration.fn._lang=cb.fn._lang=B(a),c._abbr):cb.fn._lang._abbr},cb.langData=function(a){return a&&a._lang&&a._lang._abbr&&(a=a._lang._abbr),B(a)},cb.isMoment=function(a){return a instanceof e},cb.isDuration=function(a){return a instanceof f},db=Wb.length-1;db>=0;--db)p(Wb[db]);for(cb.normalizeUnits=function(a){return n(a)},cb.invalid=function(a){var b=cb.utc(0/0);return null!=a?g(b._pf,a):b._pf.userInvalidated=!0,b},cb.parseZone=function(a){return cb(a).parseZone()},g(cb.fn=e.prototype,{clone:function(){return cb(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var a=cb(this).utc();return 00:!1},parsingFlags:function(){return g({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(a){var b=E(this,a||cb.defaultFormat);return this.lang().postformat(b)},add:function(a,b){var c;return c="string"==typeof a?cb.duration(+b,a):cb.duration(a,b),j(this,c,1),this},subtract:function(a,b){var c;return c="string"==typeof a?cb.duration(+b,a):cb.duration(a,b),j(this,c,-1),this},diff:function(a,b,c){var d,e,f=y(a,this),g=6e4*(this.zone()-f.zone());return b=n(b),"year"===b||"month"===b?(d=432e5*(this.daysInMonth()+f.daysInMonth()),e=12*(this.year()-f.year())+(this.month()-f.month()),e+=(this-cb(this).startOf("month")-(f-cb(f).startOf("month")))/d,e-=6e4*(this.zone()-cb(this).startOf("month").zone()-(f.zone()-cb(f).startOf("month").zone()))/d,"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:h(e)},from:function(a,b){return cb.duration(this.diff(a)).lang(this.lang()._abbr).humanize(!b)},fromNow:function(a){return this.from(cb(),a)},calendar:function(){var a=y(cb(),this).startOf("day"),b=this.diff(a,"days",!0),c=-6>b?"sameElse":-1>b?"lastWeek":0>b?"lastDay":1>b?"sameDay":2>b?"nextDay":7>b?"nextWeek":"sameElse";return this.format(this.lang().calendar(c,this))},isLeapYear:function(){return t(this.year())},isDST:function(){return this.zone()+cb(a).startOf(b)},isBefore:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)<+cb(a).startOf(b)},isSame:function(a,b){return b=b||"ms",+this.clone().startOf(b)===+y(a,this).startOf(b)},min:function(a){return a=cb.apply(null,arguments),this>a?this:a},max:function(a){return a=cb.apply(null,arguments),a>this?this:a},zone:function(a){var b=this._offset||0;return null==a?this._isUTC?b:this._d.getTimezoneOffset():("string"==typeof a&&(a=H(a)),Math.abs(a)<16&&(a=60*a),this._offset=a,this._isUTC=!0,b!==a&&j(this,cb.duration(b-a,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.zone(this._tzm):"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(a){return a=a?cb(a).zone():0,(this.zone()-a)%60===0},daysInMonth:function(){return r(this.year(),this.month())},dayOfYear:function(a){var b=gb((cb(this).startOf("day")-cb(this).startOf("year"))/864e5)+1;return null==a?b:this.add("d",a-b)},quarter:function(){return Math.ceil((this.month()+1)/3)},weekYear:function(a){var b=X(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==a?b:this.add("y",a-b)},isoWeekYear:function(a){var b=X(this,1,4).year;return null==a?b:this.add("y",a-b)},week:function(a){var b=this.lang().week(this);return null==a?b:this.add("d",7*(a-b))},isoWeek:function(a){var b=X(this,1,4).week;return null==a?b:this.add("d",7*(a-b))},weekday:function(a){var b=(this.day()+7-this.lang()._week.dow)%7;return null==a?b:this.add("d",a-b)},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},get:function(a){return a=n(a),this[a]()},set:function(a,b){return a=n(a),"function"==typeof this[a]&&this[a](b),this},lang:function(b){return b===a?this._lang:(this._lang=B(b),this)}}),db=0;db ["10", "00"] or "-1530" > ["-15", "30"] + parseTimezoneChunker = /([\+\-]|\d\d)/gi, + + // getter and setter names + proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), + unitMillisecondFactors = { + 'Milliseconds' : 1, + 'Seconds' : 1e3, + 'Minutes' : 6e4, + 'Hours' : 36e5, + 'Days' : 864e5, + 'Months' : 2592e6, + 'Years' : 31536e6 + }, + + unitAliases = { + ms : 'millisecond', + s : 'second', + m : 'minute', + h : 'hour', + d : 'day', + D : 'date', + w : 'week', + W : 'isoWeek', + M : 'month', + Q : 'quarter', + y : 'year', + DDD : 'dayOfYear', + e : 'weekday', + E : 'isoWeekday', + gg: 'weekYear', + GG: 'isoWeekYear' + }, + + camelFunctions = { + dayofyear : 'dayOfYear', + isoweekday : 'isoWeekday', + isoweek : 'isoWeek', + weekyear : 'weekYear', + isoweekyear : 'isoWeekYear' + }, + + // format function strings + formatFunctions = {}, + + // tokens to ordinalize and pad + ordinalizeTokens = 'DDD w W M D d'.split(' '), + paddedTokens = 'M D H h m s w W'.split(' '), + + formatTokenFunctions = { + M : function () { + return this.month() + 1; + }, + MMM : function (format) { + return this.lang().monthsShort(this, format); + }, + MMMM : function (format) { + return this.lang().months(this, format); + }, + D : function () { + return this.date(); + }, + DDD : function () { + return this.dayOfYear(); + }, + d : function () { + return this.day(); + }, + dd : function (format) { + return this.lang().weekdaysMin(this, format); + }, + ddd : function (format) { + return this.lang().weekdaysShort(this, format); + }, + dddd : function (format) { + return this.lang().weekdays(this, format); + }, + w : function () { + return this.week(); + }, + W : function () { + return this.isoWeek(); + }, + YY : function () { + return leftZeroFill(this.year() % 100, 2); + }, + YYYY : function () { + return leftZeroFill(this.year(), 4); + }, + YYYYY : function () { + return leftZeroFill(this.year(), 5); + }, + YYYYYY : function () { + var y = this.year(), sign = y >= 0 ? '+' : '-'; + return sign + leftZeroFill(Math.abs(y), 6); + }, + gg : function () { + return leftZeroFill(this.weekYear() % 100, 2); + }, + gggg : function () { + return leftZeroFill(this.weekYear(), 4); + }, + ggggg : function () { + return leftZeroFill(this.weekYear(), 5); + }, + GG : function () { + return leftZeroFill(this.isoWeekYear() % 100, 2); + }, + GGGG : function () { + return leftZeroFill(this.isoWeekYear(), 4); + }, + GGGGG : function () { + return leftZeroFill(this.isoWeekYear(), 5); + }, + e : function () { + return this.weekday(); + }, + E : function () { + return this.isoWeekday(); + }, + a : function () { + return this.lang().meridiem(this.hours(), this.minutes(), true); + }, + A : function () { + return this.lang().meridiem(this.hours(), this.minutes(), false); + }, + H : function () { + return this.hours(); + }, + h : function () { + return this.hours() % 12 || 12; + }, + m : function () { + return this.minutes(); + }, + s : function () { + return this.seconds(); + }, + S : function () { + return toInt(this.milliseconds() / 100); + }, + SS : function () { + return leftZeroFill(toInt(this.milliseconds() / 10), 2); + }, + SSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + SSSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + Z : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2); + }, + ZZ : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); + }, + z : function () { + return this.zoneAbbr(); + }, + zz : function () { + return this.zoneName(); + }, + X : function () { + return this.unix(); + }, + Q : function () { + return this.quarter(); + } + }, + + lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; + + function defaultParsingFlags() { + // We need to deep clone this object, and es5 standard is not very + // helpful. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso: false + }; + } + + function deprecate(msg, fn) { + var firstTime = true; + function printMsg() { + if (moment.suppressDeprecationWarnings === false && + typeof console !== 'undefined' && console.warn) { + console.warn("Deprecation warning: " + msg); + } + } + return extend(function () { + if (firstTime) { + printMsg(); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + + function padToken(func, count) { + return function (a) { + return leftZeroFill(func.call(this, a), count); + }; + } + function ordinalizeToken(func, period) { + return function (a) { + return this.lang().ordinal(func.call(this, a), period); + }; + } + + while (ordinalizeTokens.length) { + i = ordinalizeTokens.pop(); + formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); + } + while (paddedTokens.length) { + i = paddedTokens.pop(); + formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); + } + formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); + + + /************************************ + Constructors + ************************************/ + + function Language() { + + } + + // Moment prototype object + function Moment(config) { + checkOverflow(config); + extend(this, config); + } + + // Duration Constructor + function Duration(duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 36e5; // 1000 * 60 * 60 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; + + this._data = {}; + + this._bubble(); + } + + /************************************ + Helpers + ************************************/ + + + function extend(a, b) { + for (var i in b) { + if (b.hasOwnProperty(i)) { + a[i] = b[i]; + } + } + + if (b.hasOwnProperty("toString")) { + a.toString = b.toString; + } + + if (b.hasOwnProperty("valueOf")) { + a.valueOf = b.valueOf; + } + + return a; + } + + function cloneMoment(m) { + var result = {}, i; + for (i in m) { + if (m.hasOwnProperty(i) && momentProperties.hasOwnProperty(i)) { + result[i] = m[i]; + } + } + + return result; + } + + function absRound(number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } + } + + // left zero fill a number + // see http://jsperf.com/left-zero-filling for performance comparison + function leftZeroFill(number, targetLength, forceSign) { + var output = '' + Math.abs(number), + sign = number >= 0; + + while (output.length < targetLength) { + output = '0' + output; + } + return (sign ? (forceSign ? '+' : '') : '-') + output; + } + + // helper function for _.addTime and _.subtractTime + function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = duration._days, + months = duration._months; + updateOffset = updateOffset == null ? true : updateOffset; + + if (milliseconds) { + mom._d.setTime(+mom._d + milliseconds * isAdding); + } + if (days) { + rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding); + } + if (months) { + rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding); + } + if (updateOffset) { + moment.updateOffset(mom, days || months); + } + } + + // check if is an array + function isArray(input) { + return Object.prototype.toString.call(input) === '[object Array]'; + } + + function isDate(input) { + return Object.prototype.toString.call(input) === '[object Date]' || + input instanceof Date; + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; + } + + function normalizeUnits(units) { + if (units) { + var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); + units = unitAliases[units] || camelFunctions[lowered] || lowered; + } + return units; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (inputObject.hasOwnProperty(prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + function makeList(field) { + var count, setter; + + if (field.indexOf('week') === 0) { + count = 7; + setter = 'day'; + } + else if (field.indexOf('month') === 0) { + count = 12; + setter = 'month'; + } + else { + return; + } + + moment[field] = function (format, index) { + var i, getter, + method = moment.fn._lang[field], + results = []; + + if (typeof format === 'number') { + index = format; + format = undefined; + } + + getter = function (i) { + var m = moment().utc().set(setter, i); + return method.call(moment.fn._lang, m, format || ''); + }; + + if (index != null) { + return getter(index); + } + else { + for (i = 0; i < count; i++) { + results.push(getter(i)); + } + return results; + } + }; + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + if (coercedNumber >= 0) { + value = Math.floor(coercedNumber); + } else { + value = Math.ceil(coercedNumber); + } + } + + return value; + } + + function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); + } + + function weeksInYear(year, dow, doy) { + return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week; + } + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + function checkOverflow(m) { + var overflow; + if (m._a && m._pf.overflow === -2) { + overflow = + m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : + m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : + m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR : + m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : + m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : + m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + + m._pf.overflow = overflow; + } + } + + function isValid(m) { + if (m._isValid == null) { + m._isValid = !isNaN(m._d.getTime()) && + m._pf.overflow < 0 && + !m._pf.empty && + !m._pf.invalidMonth && + !m._pf.nullInput && + !m._pf.invalidFormat && + !m._pf.userInvalidated; + + if (m._strict) { + m._isValid = m._isValid && + m._pf.charsLeftOver === 0 && + m._pf.unusedTokens.length === 0; + } + } + return m._isValid; + } + + function normalizeLanguage(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // Return a moment from input, that is local/utc/zone equivalent to model. + function makeAs(input, model) { + return model._isUTC ? moment(input).zone(model._offset || 0) : + moment(input).local(); + } + + /************************************ + Languages + ************************************/ + + + extend(Language.prototype, { + + set : function (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (typeof prop === 'function') { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + }, + + _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), + months : function (m) { + return this._months[m.month()]; + }, + + _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), + monthsShort : function (m) { + return this._monthsShort[m.month()]; + }, + + monthsParse : function (monthName) { + var i, mom, regex; + + if (!this._monthsParse) { + this._monthsParse = []; + } + + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + if (!this._monthsParse[i]) { + mom = moment.utc([2000, i]); + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._monthsParse[i].test(monthName)) { + return i; + } + } + }, + + _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), + weekdays : function (m) { + return this._weekdays[m.day()]; + }, + + _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysShort : function (m) { + return this._weekdaysShort[m.day()]; + }, + + _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), + weekdaysMin : function (m) { + return this._weekdaysMin[m.day()]; + }, + + weekdaysParse : function (weekdayName) { + var i, mom, regex; + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + if (!this._weekdaysParse[i]) { + mom = moment([2000, 1]).day(i); + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + }, + + _longDateFormat : { + LT : "h:mm A", + L : "MM/DD/YYYY", + LL : "MMMM D YYYY", + LLL : "MMMM D YYYY LT", + LLLL : "dddd, MMMM D YYYY LT" + }, + longDateFormat : function (key) { + var output = this._longDateFormat[key]; + if (!output && this._longDateFormat[key.toUpperCase()]) { + output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + this._longDateFormat[key] = output; + } + return output; + }, + + isPM : function (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); + }, + + _meridiemParse : /[ap]\.?m?\.?/i, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + }, + + _calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + calendar : function (key, mom) { + var output = this._calendar[key]; + return typeof output === 'function' ? output.apply(mom) : output; + }, + + _relativeTime : { + future : "in %s", + past : "%s ago", + s : "a few seconds", + m : "a minute", + mm : "%d minutes", + h : "an hour", + hh : "%d hours", + d : "a day", + dd : "%d days", + M : "a month", + MM : "%d months", + y : "a year", + yy : "%d years" + }, + relativeTime : function (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (typeof output === 'function') ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + }, + pastFuture : function (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); + }, + + ordinal : function (number) { + return this._ordinal.replace("%d", number); + }, + _ordinal : "%d", + + preparse : function (string) { + return string; + }, + + postformat : function (string) { + return string; + }, + + week : function (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + }, + + _week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + }, + + _invalidDate: 'Invalid date', + invalidDate: function () { + return this._invalidDate; + } + }); + + // Loads a language definition into the `languages` cache. The function + // takes a key and optionally values. If not in the browser and no values + // are provided, it will load the language file module. As a convenience, + // this function also returns the language values. + function loadLang(key, values) { + values.abbr = key; + if (!languages[key]) { + languages[key] = new Language(); + } + languages[key].set(values); + return languages[key]; + } + + // Remove a language from the `languages` cache. Mostly useful in tests. + function unloadLang(key) { + delete languages[key]; + } + + // Determines which language definition to use and returns it. + // + // With no parameters, it will return the global language. If you + // pass in a language key, such as 'en', it will return the + // definition for 'en', so long as 'en' has already been loaded using + // moment.lang. + function getLangDefinition(key) { + var i = 0, j, lang, next, split, + get = function (k) { + if (!languages[k] && hasModule) { + try { + require('./lang/' + k); + } catch (e) { } + } + return languages[k]; + }; + + if (!key) { + return moment.fn._lang; + } + + if (!isArray(key)) { + //short-circuit everything else + lang = get(key); + if (lang) { + return lang; + } + key = [key]; + } + + //pick the language from the array + //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + while (i < key.length) { + split = normalizeLanguage(key[i]).split('-'); + j = split.length; + next = normalizeLanguage(key[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + lang = get(split.slice(0, j).join('-')); + if (lang) { + return lang; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return moment.fn._lang; + } + + /************************************ + Formatting + ************************************/ + + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ""); + } + return input.replace(/\\/g, ""); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = ""; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + + if (!m.isValid()) { + return m.lang().invalidDate(); + } + + format = expandFormat(format, m.lang()); + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + + return formatFunctions[format](m); + } + + function expandFormat(format, lang) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return lang.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + + /************************************ + Parsing + ************************************/ + + + // get the regex to find the next token + function getParseRegexForToken(token, config) { + var a, strict = config._strict; + switch (token) { + case 'Q': + return parseTokenOneDigit; + case 'DDDD': + return parseTokenThreeDigits; + case 'YYYY': + case 'GGGG': + case 'gggg': + return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; + case 'Y': + case 'G': + case 'g': + return parseTokenSignedNumber; + case 'YYYYYY': + case 'YYYYY': + case 'GGGGG': + case 'ggggg': + return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; + case 'S': + if (strict) { return parseTokenOneDigit; } + /* falls through */ + case 'SS': + if (strict) { return parseTokenTwoDigits; } + /* falls through */ + case 'SSS': + if (strict) { return parseTokenThreeDigits; } + /* falls through */ + case 'DDD': + return parseTokenOneToThreeDigits; + case 'MMM': + case 'MMMM': + case 'dd': + case 'ddd': + case 'dddd': + return parseTokenWord; + case 'a': + case 'A': + return getLangDefinition(config._l)._meridiemParse; + case 'X': + return parseTokenTimestampMs; + case 'Z': + case 'ZZ': + return parseTokenTimezone; + case 'T': + return parseTokenT; + case 'SSSS': + return parseTokenDigits; + case 'MM': + case 'DD': + case 'YY': + case 'GG': + case 'gg': + case 'HH': + case 'hh': + case 'mm': + case 'ss': + case 'ww': + case 'WW': + return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; + case 'M': + case 'D': + case 'd': + case 'H': + case 'h': + case 'm': + case 's': + case 'w': + case 'W': + case 'e': + case 'E': + return parseTokenOneOrTwoDigits; + case 'Do': + return parseTokenOrdinal; + default : + a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i")); + return a; + } + } + + function timezoneMinutesFromString(string) { + string = string || ""; + var possibleTzMatches = (string.match(parseTokenTimezone) || []), + tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], + parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], + minutes = +(parts[1] * 60) + toInt(parts[2]); + + return parts[0] === '+' ? -minutes : minutes; + } + + // function to convert string input to date + function addTimeToArrayFromToken(token, input, config) { + var a, datePartArray = config._a; + + switch (token) { + // QUARTER + case 'Q': + if (input != null) { + datePartArray[MONTH] = (toInt(input) - 1) * 3; + } + break; + // MONTH + case 'M' : // fall through to MM + case 'MM' : + if (input != null) { + datePartArray[MONTH] = toInt(input) - 1; + } + break; + case 'MMM' : // fall through to MMMM + case 'MMMM' : + a = getLangDefinition(config._l).monthsParse(input); + // if we didn't find a month name, mark the date as invalid. + if (a != null) { + datePartArray[MONTH] = a; + } else { + config._pf.invalidMonth = input; + } + break; + // DAY OF MONTH + case 'D' : // fall through to DD + case 'DD' : + if (input != null) { + datePartArray[DATE] = toInt(input); + } + break; + case 'Do' : + if (input != null) { + datePartArray[DATE] = toInt(parseInt(input, 10)); + } + break; + // DAY OF YEAR + case 'DDD' : // fall through to DDDD + case 'DDDD' : + if (input != null) { + config._dayOfYear = toInt(input); + } + + break; + // YEAR + case 'YY' : + datePartArray[YEAR] = moment.parseTwoDigitYear(input); + break; + case 'YYYY' : + case 'YYYYY' : + case 'YYYYYY' : + datePartArray[YEAR] = toInt(input); + break; + // AM / PM + case 'a' : // fall through to A + case 'A' : + config._isPm = getLangDefinition(config._l).isPM(input); + break; + // 24 HOUR + case 'H' : // fall through to hh + case 'HH' : // fall through to hh + case 'h' : // fall through to hh + case 'hh' : + datePartArray[HOUR] = toInt(input); + break; + // MINUTE + case 'm' : // fall through to mm + case 'mm' : + datePartArray[MINUTE] = toInt(input); + break; + // SECOND + case 's' : // fall through to ss + case 'ss' : + datePartArray[SECOND] = toInt(input); + break; + // MILLISECOND + case 'S' : + case 'SS' : + case 'SSS' : + case 'SSSS' : + datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); + break; + // UNIX TIMESTAMP WITH MS + case 'X': + config._d = new Date(parseFloat(input) * 1000); + break; + // TIMEZONE + case 'Z' : // fall through to ZZ + case 'ZZ' : + config._useUTC = true; + config._tzm = timezoneMinutesFromString(input); + break; + case 'w': + case 'ww': + case 'W': + case 'WW': + case 'd': + case 'dd': + case 'ddd': + case 'dddd': + case 'e': + case 'E': + token = token.substr(0, 1); + /* falls through */ + case 'gg': + case 'gggg': + case 'GG': + case 'GGGG': + case 'GGGGG': + token = token.substr(0, 2); + if (input) { + config._w = config._w || {}; + config._w[token] = input; + } + break; + } + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function dateFromConfig(config) { + var i, date, input = [], currentDate, + yearToUse, fixYear, w, temp, lang, weekday, week; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + fixYear = function (val) { + var intVal = parseInt(val, 10); + return val ? + (val.length < 3 ? (intVal > 68 ? 1900 + intVal : 2000 + intVal) : intVal) : + (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]); + }; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1); + } + else { + lang = getLangDefinition(config._l); + weekday = w.d != null ? parseWeekday(w.d, lang) : + (w.e != null ? parseInt(w.e, 10) + lang._week.dow : 0); + + week = parseInt(w.w, 10) || 1; + + //if we're parsing 'd', then the low day numbers may be next week + if (w.d != null && weekday < lang._week.dow) { + week++; + } + + temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow); + } + + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR]; + + if (config._dayOfYear > daysInYear(yearToUse)) { + config._pf._overflowDayOfYear = true; + } + + date = makeUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // add the offsets to the time to be parsed so that we can have a clean array for checking isValid + input[HOUR] += toInt((config._tzm || 0) / 60); + input[MINUTE] += toInt((config._tzm || 0) % 60); + + config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); + } + + function dateFromObject(config) { + var normalizedInput; + + if (config._d) { + return; + } + + normalizedInput = normalizeObjectUnits(config._i); + config._a = [ + normalizedInput.year, + normalizedInput.month, + normalizedInput.day, + normalizedInput.hour, + normalizedInput.minute, + normalizedInput.second, + normalizedInput.millisecond + ]; + + dateFromConfig(config); + } + + function currentDateArray(config) { + var now = new Date(); + if (config._useUTC) { + return [ + now.getUTCFullYear(), + now.getUTCMonth(), + now.getUTCDate() + ]; + } else { + return [now.getFullYear(), now.getMonth(), now.getDate()]; + } + } + + // date from string and format string + function makeDateFromStringAndFormat(config) { + + config._a = []; + config._pf.empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var lang = getLangDefinition(config._l), + string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; + + tokens = expandFormat(config._f, lang).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + config._pf.unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + config._pf.empty = false; + } + else { + config._pf.unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + config._pf.unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + config._pf.charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + config._pf.unusedInput.push(string); + } + + // handle am pm + if (config._isPm && config._a[HOUR] < 12) { + config._a[HOUR] += 12; + } + // if is 12 am, change hours to 0 + if (config._isPm === false && config._a[HOUR] === 12) { + config._a[HOUR] = 0; + } + + dateFromConfig(config); + checkOverflow(config); + } + + function unescapeFormat(s) { + return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + }); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function regexpEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + // date from string and array of format strings + function makeDateFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + config._pf.invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = extend({}, config); + tempConfig._pf = defaultParsingFlags(); + tempConfig._f = config._f[i]; + makeDateFromStringAndFormat(tempConfig); + + if (!isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += tempConfig._pf.charsLeftOver; + + //or tokens + currentScore += tempConfig._pf.unusedTokens.length * 10; + + tempConfig._pf.score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); + } + + // date from iso format + function makeDateFromString(config) { + var i, l, + string = config._i, + match = isoRegex.exec(string); + + if (match) { + config._pf.iso = true; + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(string)) { + // match[5] should be "T" or undefined + config._f = isoDates[i][0] + (match[6] || " "); + break; + } + } + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(string)) { + config._f += isoTimes[i][0]; + break; + } + } + if (string.match(parseTokenTimezone)) { + config._f += "Z"; + } + makeDateFromStringAndFormat(config); + } + else { + moment.createFromInputFallback(config); + } + } + + function makeDateFromInput(config) { + var input = config._i, + matched = aspNetJsonRegex.exec(input); + + if (input === undefined) { + config._d = new Date(); + } else if (matched) { + config._d = new Date(+matched[1]); + } else if (typeof input === 'string') { + makeDateFromString(config); + } else if (isArray(input)) { + config._a = input.slice(0); + dateFromConfig(config); + } else if (isDate(input)) { + config._d = new Date(+input); + } else if (typeof(input) === 'object') { + dateFromObject(config); + } else if (typeof(input) === 'number') { + // from milliseconds + config._d = new Date(input); + } else { + moment.createFromInputFallback(config); + } + } + + function makeDate(y, m, d, h, M, s, ms) { + //can't just apply() to create a date: + //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply + var date = new Date(y, m, d, h, M, s, ms); + + //the date constructor doesn't accept years < 1970 + if (y < 1970) { + date.setFullYear(y); + } + return date; + } + + function makeUTCDate(y) { + var date = new Date(Date.UTC.apply(null, arguments)); + if (y < 1970) { + date.setUTCFullYear(y); + } + return date; + } + + function parseWeekday(input, language) { + if (typeof input === 'string') { + if (!isNaN(input)) { + input = parseInt(input, 10); + } + else { + input = language.weekdaysParse(input); + if (typeof input !== 'number') { + return null; + } + } + } + return input; + } + + /************************************ + Relative Time + ************************************/ + + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { + return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime(milliseconds, withoutSuffix, lang) { + var seconds = round(Math.abs(milliseconds) / 1000), + minutes = round(seconds / 60), + hours = round(minutes / 60), + days = round(hours / 24), + years = round(days / 365), + args = seconds < 45 && ['s', seconds] || + minutes === 1 && ['m'] || + minutes < 45 && ['mm', minutes] || + hours === 1 && ['h'] || + hours < 22 && ['hh', hours] || + days === 1 && ['d'] || + days <= 25 && ['dd', days] || + days <= 45 && ['M'] || + days < 345 && ['MM', round(days / 30)] || + years === 1 && ['y'] || ['yy', years]; + args[2] = withoutSuffix; + args[3] = milliseconds > 0; + args[4] = lang; + return substituteTimeAgo.apply({}, args); + } + + + /************************************ + Week of Year + ************************************/ + + + // firstDayOfWeek 0 = sun, 6 = sat + // the day of the week that starts the week + // (usually sunday or monday) + // firstDayOfWeekOfYear 0 = sun, 6 = sat + // the first week is the week that contains the first + // of this day of the week + // (eg. ISO weeks use thursday (4)) + function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { + var end = firstDayOfWeekOfYear - firstDayOfWeek, + daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), + adjustedMoment; + + + if (daysToDayOfWeek > end) { + daysToDayOfWeek -= 7; + } + + if (daysToDayOfWeek < end - 7) { + daysToDayOfWeek += 7; + } + + adjustedMoment = moment(mom).add('d', daysToDayOfWeek); + return { + week: Math.ceil(adjustedMoment.dayOfYear() / 7), + year: adjustedMoment.year() + }; + } + + //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { + var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; + + weekday = weekday != null ? weekday : firstDayOfWeek; + daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); + dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; + + return { + year: dayOfYear > 0 ? year : year - 1, + dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear + }; + } + + /************************************ + Top Level Functions + ************************************/ + + function makeMoment(config) { + var input = config._i, + format = config._f; + + if (input === null || (format === undefined && input === '')) { + return moment.invalid({nullInput: true}); + } + + if (typeof input === 'string') { + config._i = input = getLangDefinition().preparse(input); + } + + if (moment.isMoment(input)) { + config = cloneMoment(input); + + config._d = new Date(+input._d); + } else if (format) { + if (isArray(format)) { + makeDateFromStringAndArray(config); + } else { + makeDateFromStringAndFormat(config); + } + } else { + makeDateFromInput(config); + } + + return new Moment(config); + } + + moment = function (input, format, lang, strict) { + var c; + + if (typeof(lang) === "boolean") { + strict = lang; + lang = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c = {}; + c._isAMomentObject = true; + c._i = input; + c._f = format; + c._l = lang; + c._strict = strict; + c._isUTC = false; + c._pf = defaultParsingFlags(); + + return makeMoment(c); + }; + + moment.suppressDeprecationWarnings = false; + + moment.createFromInputFallback = deprecate( + "moment construction falls back to js Date. This is " + + "discouraged and will be removed in upcoming major " + + "release. Please refer to " + + "https://github.com/moment/moment/issues/1407 for more info.", + function (config) { + config._d = new Date(config._i); + }); + + // creating with utc + moment.utc = function (input, format, lang, strict) { + var c; + + if (typeof(lang) === "boolean") { + strict = lang; + lang = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c = {}; + c._isAMomentObject = true; + c._useUTC = true; + c._isUTC = true; + c._l = lang; + c._i = input; + c._f = format; + c._strict = strict; + c._pf = defaultParsingFlags(); + + return makeMoment(c).utc(); + }; + + // creating with unix timestamp (in seconds) + moment.unix = function (input) { + return moment(input * 1000); + }; + + // duration + moment.duration = function (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + parseIso; + + if (moment.isDuration(input)) { + duration = { + ms: input._milliseconds, + d: input._days, + M: input._months + }; + } else if (typeof input === 'number') { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { + sign = (match[1] === "-") ? -1 : 1; + duration = { + y: 0, + d: toInt(match[DATE]) * sign, + h: toInt(match[HOUR]) * sign, + m: toInt(match[MINUTE]) * sign, + s: toInt(match[SECOND]) * sign, + ms: toInt(match[MILLISECOND]) * sign + }; + } else if (!!(match = isoDurationRegex.exec(input))) { + sign = (match[1] === "-") ? -1 : 1; + parseIso = function (inp) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + }; + duration = { + y: parseIso(match[2]), + M: parseIso(match[3]), + d: parseIso(match[4]), + h: parseIso(match[5]), + m: parseIso(match[6]), + s: parseIso(match[7]), + w: parseIso(match[8]) + }; + } + + ret = new Duration(duration); + + if (moment.isDuration(input) && input.hasOwnProperty('_lang')) { + ret._lang = input._lang; + } + + return ret; + }; + + // version number + moment.version = VERSION; + + // default format + moment.defaultFormat = isoFormat; + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + moment.momentProperties = momentProperties; + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + moment.updateOffset = function () {}; + + // This function will load languages and then set the global language. If + // no arguments are passed in, it will simply return the current global + // language key. + moment.lang = function (key, values) { + var r; + if (!key) { + return moment.fn._lang._abbr; + } + if (values) { + loadLang(normalizeLanguage(key), values); + } else if (values === null) { + unloadLang(key); + key = 'en'; + } else if (!languages[key]) { + getLangDefinition(key); + } + r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); + return r._abbr; + }; + + // returns language data + moment.langData = function (key) { + if (key && key._lang && key._lang._abbr) { + key = key._lang._abbr; + } + return getLangDefinition(key); + }; + + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment || + (obj != null && obj.hasOwnProperty('_isAMomentObject')); + }; + + // for typechecking Duration objects + moment.isDuration = function (obj) { + return obj instanceof Duration; + }; + + for (i = lists.length - 1; i >= 0; --i) { + makeList(lists[i]); + } + + moment.normalizeUnits = function (units) { + return normalizeUnits(units); + }; + + moment.invalid = function (flags) { + var m = moment.utc(NaN); + if (flags != null) { + extend(m._pf, flags); + } + else { + m._pf.userInvalidated = true; + } + + return m; + }; + + moment.parseZone = function () { + return moment.apply(null, arguments).parseZone(); + }; + + moment.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; + + /************************************ + Moment Prototype + ************************************/ + + + extend(moment.fn = Moment.prototype, { + + clone : function () { + return moment(this); + }, + + valueOf : function () { + return +this._d + ((this._offset || 0) * 60000); + }, + + unix : function () { + return Math.floor(+this / 1000); + }, + + toString : function () { + return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); + }, + + toDate : function () { + return this._offset ? new Date(+this) : this._d; + }, + + toISOString : function () { + var m = moment(this).utc(); + if (0 < m.year() && m.year() <= 9999) { + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } else { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + }, + + toArray : function () { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hours(), + m.minutes(), + m.seconds(), + m.milliseconds() + ]; + }, + + isValid : function () { + return isValid(this); + }, + + isDSTShifted : function () { + + if (this._a) { + return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; + } + + return false; + }, + + parsingFlags : function () { + return extend({}, this._pf); + }, + + invalidAt: function () { + return this._pf.overflow; + }, + + utc : function () { + return this.zone(0); + }, + + local : function () { + this.zone(0); + this._isUTC = false; + return this; + }, + + format : function (inputString) { + var output = formatMoment(this, inputString || moment.defaultFormat); + return this.lang().postformat(output); + }, + + add : function (input, val) { + var dur; + // switch args to support add('s', 1) and add(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, 1); + return this; + }, + + subtract : function (input, val) { + var dur; + // switch args to support subtract('s', 1) and subtract(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, -1); + return this; + }, + + diff : function (input, units, asFloat) { + var that = makeAs(input, this), + zoneDiff = (this.zone() - that.zone()) * 6e4, + diff, output; + + units = normalizeUnits(units); + + if (units === 'year' || units === 'month') { + // average number of days in the months in the given dates + diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 + // difference in months + output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); + // adjust by taking difference in days, average number of days + // and dst in the given months. + output += ((this - moment(this).startOf('month')) - + (that - moment(that).startOf('month'))) / diff; + // same as above but with zones, to negate all dst + output -= ((this.zone() - moment(this).startOf('month').zone()) - + (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff; + if (units === 'year') { + output = output / 12; + } + } else { + diff = (this - that); + output = units === 'second' ? diff / 1e3 : // 1000 + units === 'minute' ? diff / 6e4 : // 1000 * 60 + units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + diff; + } + return asFloat ? output : absRound(output); + }, + + from : function (time, withoutSuffix) { + return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); + }, + + fromNow : function (withoutSuffix) { + return this.from(moment(), withoutSuffix); + }, + + calendar : function () { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're zone'd or not. + var sod = makeAs(moment(), this).startOf('day'), + diff = this.diff(sod, 'days', true), + format = diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + return this.format(this.lang().calendar(format, this)); + }, + + isLeapYear : function () { + return isLeapYear(this.year()); + }, + + isDST : function () { + return (this.zone() < this.clone().month(0).zone() || + this.zone() < this.clone().month(5).zone()); + }, + + day : function (input) { + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.lang()); + return this.add({ d : input - day }); + } else { + return day; + } + }, + + month : makeAccessor('Month', true), + + startOf: function (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'quarter': + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + /* falls through */ + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } else if (units === 'isoWeek') { + this.isoWeekday(1); + } + + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } + + return this; + }, + + endOf: function (units) { + units = normalizeUnits(units); + return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1); + }, + + isAfter: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) > +moment(input).startOf(units); + }, + + isBefore: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) < +moment(input).startOf(units); + }, + + isSame: function (input, units) { + units = units || 'ms'; + return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); + }, + + min: function (other) { + other = moment.apply(null, arguments); + return other < this ? this : other; + }, + + max: function (other) { + other = moment.apply(null, arguments); + return other > this ? this : other; + }, + + // keepTime = true means only change the timezone, without affecting + // the local hour. So 5:31:26 +0300 --[zone(2, true)]--> 5:31:26 +0200 + // It is possible that 5:31:26 doesn't exist int zone +0200, so we + // adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + zone : function (input, keepTime) { + var offset = this._offset || 0; + if (input != null) { + if (typeof input === "string") { + input = timezoneMinutesFromString(input); + } + if (Math.abs(input) < 16) { + input = input * 60; + } + this._offset = input; + this._isUTC = true; + if (offset !== input) { + if (!keepTime || this._changeInProgress) { + addOrSubtractDurationFromMoment(this, + moment.duration(offset - input, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + moment.updateOffset(this, true); + this._changeInProgress = null; + } + } + } else { + return this._isUTC ? offset : this._d.getTimezoneOffset(); + } + return this; + }, + + zoneAbbr : function () { + return this._isUTC ? "UTC" : ""; + }, + + zoneName : function () { + return this._isUTC ? "Coordinated Universal Time" : ""; + }, + + parseZone : function () { + if (this._tzm) { + this.zone(this._tzm); + } else if (typeof this._i === 'string') { + this.zone(this._i); + } + return this; + }, + + hasAlignedHourOffset : function (input) { + if (!input) { + input = 0; + } + else { + input = moment(input).zone(); + } + + return (this.zone() - input) % 60 === 0; + }, + + daysInMonth : function () { + return daysInMonth(this.year(), this.month()); + }, + + dayOfYear : function (input) { + var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); + }, + + quarter : function (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + }, + + weekYear : function (input) { + var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year; + return input == null ? year : this.add("y", (input - year)); + }, + + isoWeekYear : function (input) { + var year = weekOfYear(this, 1, 4).year; + return input == null ? year : this.add("y", (input - year)); + }, + + week : function (input) { + var week = this.lang().week(this); + return input == null ? week : this.add("d", (input - week) * 7); + }, + + isoWeek : function (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add("d", (input - week) * 7); + }, + + weekday : function (input) { + var weekday = (this.day() + 7 - this.lang()._week.dow) % 7; + return input == null ? weekday : this.add("d", input - weekday); + }, + + isoWeekday : function (input) { + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); + }, + + isoWeeksInYear : function () { + return weeksInYear(this.year(), 1, 4); + }, + + weeksInYear : function () { + var weekInfo = this._lang._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units](); + }, + + set : function (units, value) { + units = normalizeUnits(units); + if (typeof this[units] === 'function') { + this[units](value); + } + return this; + }, + + // If passed a language key, it will set the language for this + // instance. Otherwise, it will return the language configuration + // variables for this instance. + lang : function (key) { + if (key === undefined) { + return this._lang; + } else { + this._lang = getLangDefinition(key); + return this; + } + } + }); + + function rawMonthSetter(mom, value) { + var dayOfMonth; + + // TODO: Move this out of here! + if (typeof value === 'string') { + value = mom.lang().monthsParse(value); + // TODO: Another silent failure? + if (typeof value !== 'number') { + return mom; + } + } + + dayOfMonth = Math.min(mom.date(), + daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function rawGetter(mom, unit) { + return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); + } + + function rawSetter(mom, unit, value) { + if (unit === 'Month') { + return rawMonthSetter(mom, value); + } else { + return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + + function makeAccessor(unit, keepTime) { + return function (value) { + if (value != null) { + rawSetter(this, unit, value); + moment.updateOffset(this, keepTime); + return this; + } else { + return rawGetter(this, unit); + } + }; + } + + moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false); + moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false); + moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false); + // Setting the hour should keep the time, because the user explicitly + // specified which hour he wants. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true); + // moment.fn.month is defined separately + moment.fn.date = makeAccessor('Date', true); + moment.fn.dates = deprecate("dates accessor is deprecated. Use date instead.", makeAccessor('Date', true)); + moment.fn.year = makeAccessor('FullYear', true); + moment.fn.years = deprecate("years accessor is deprecated. Use year instead.", makeAccessor('FullYear', true)); + + // add plural methods + moment.fn.days = moment.fn.day; + moment.fn.months = moment.fn.month; + moment.fn.weeks = moment.fn.week; + moment.fn.isoWeeks = moment.fn.isoWeek; + moment.fn.quarters = moment.fn.quarter; + + // add aliased format methods + moment.fn.toJSON = moment.fn.toISOString; + + /************************************ + Duration Prototype + ************************************/ + + + extend(moment.duration.fn = Duration.prototype, { + + _bubble : function () { + var milliseconds = this._milliseconds, + days = this._days, + months = this._months, + data = this._data, + seconds, minutes, hours, years; + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absRound(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absRound(seconds / 60); + data.minutes = minutes % 60; + + hours = absRound(minutes / 60); + data.hours = hours % 24; + + days += absRound(hours / 24); + data.days = days % 30; + + months += absRound(days / 30); + data.months = months % 12; + + years = absRound(months / 12); + data.years = years; + }, + + weeks : function () { + return absRound(this.days() / 7); + }, + + valueOf : function () { + return this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6; + }, + + humanize : function (withSuffix) { + var difference = +this, + output = relativeTime(difference, !withSuffix, this.lang()); + + if (withSuffix) { + output = this.lang().pastFuture(difference, output); + } + + return this.lang().postformat(output); + }, + + add : function (input, val) { + // supports only 2.0-style add(1, 's') or add(moment) + var dur = moment.duration(input, val); + + this._milliseconds += dur._milliseconds; + this._days += dur._days; + this._months += dur._months; + + this._bubble(); + + return this; + }, + + subtract : function (input, val) { + var dur = moment.duration(input, val); + + this._milliseconds -= dur._milliseconds; + this._days -= dur._days; + this._months -= dur._months; + + this._bubble(); + + return this; + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units.toLowerCase() + 's'](); + }, + + as : function (units) { + units = normalizeUnits(units); + return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's'](); + }, + + lang : moment.fn.lang, + + toIsoString : function () { + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var years = Math.abs(this.years()), + months = Math.abs(this.months()), + days = Math.abs(this.days()), + hours = Math.abs(this.hours()), + minutes = Math.abs(this.minutes()), + seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); + + if (!this.asSeconds()) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + return (this.asSeconds() < 0 ? '-' : '') + + 'P' + + (years ? years + 'Y' : '') + + (months ? months + 'M' : '') + + (days ? days + 'D' : '') + + ((hours || minutes || seconds) ? 'T' : '') + + (hours ? hours + 'H' : '') + + (minutes ? minutes + 'M' : '') + + (seconds ? seconds + 'S' : ''); + } + }); + + function makeDurationGetter(name) { + moment.duration.fn[name] = function () { + return this._data[name]; + }; + } + + function makeDurationAsGetter(name, factor) { + moment.duration.fn['as' + name] = function () { + return +this / factor; + }; + } + + for (i in unitMillisecondFactors) { + if (unitMillisecondFactors.hasOwnProperty(i)) { + makeDurationAsGetter(i, unitMillisecondFactors[i]); + makeDurationGetter(i.toLowerCase()); + } + } + + makeDurationAsGetter('Weeks', 6048e5); + moment.duration.fn.asMonths = function () { + return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12; + }; + + + /************************************ + Default Lang + ************************************/ + + + // Set default language, other languages will inherit from English. + moment.lang('en', { + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + /* EMBED_LANGUAGES */ + + /************************************ + Exposing Moment + ************************************/ + + function makeGlobal(shouldDeprecate) { + /*global ender:false */ + if (typeof ender !== 'undefined') { + return; + } + oldGlobalMoment = globalScope.moment; + if (shouldDeprecate) { + globalScope.moment = deprecate( + "Accessing Moment through the global scope is " + + "deprecated, and will be removed in an upcoming " + + "release.", + moment); + } else { + globalScope.moment = moment; + } + } + + // CommonJS module is defined + if (hasModule) { + module.exports = moment; + } else if (typeof define === "function" && define.amd) { + define("moment", function (require, exports, module) { + if (module.config && module.config() && module.config().noGlobal === true) { + // release the global variable + globalScope.moment = oldGlobalMoment; + } + + return moment; + }); + makeGlobal(true); + } else { + makeGlobal(); + } +}).call(this); + // Generated by CoffeeScript 1.4.0 /* From 7b6a10b4e03fd8c62c2fee31917408299b3d5f2b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 05:02:04 -0700 Subject: [PATCH 090/791] Change emitter to return false on break, true on emitted, else undefined --- lib/emitter.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/emitter.js b/lib/emitter.js index 4d976875..12be7ecb 100644 --- a/lib/emitter.js +++ b/lib/emitter.js @@ -9,7 +9,7 @@ Emitter.prototype.wrapHandler = function(handler) { Emitter.prototype.on = function(type, subtype, handler) { var typeMap = this.listeners || ( this.listeners = {} ); - var subtypeMap = (typeMap[type] || ( typeMap[type] = {} )); + var subtypeMap = typeMap[type] || ( typeMap[type] = {} ); (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ id: handler, handler: this.wrapHandler(handler), @@ -46,16 +46,14 @@ Emitter.prototype.off = function(type, subtype, handler) { }; var emitToHandlers = function(type, handlers, e) { - if (!handlers) { - return; - } + if (!handlers) { return; } for (var i = 0, ii = handlers.length; i < ii; ++i) { var handler = handlers[i].handler; if (handler(e, type, i) === false) { - return true; + return false; } } - return false; + return true; }; Emitter.prototype.emit = function(type, subtype, e) { @@ -71,13 +69,17 @@ Emitter.prototype.emit = function(type, subtype, e) { if (!typeMap) { return; } var subtypeMap = typeMap[type]; if (!subtypeMap) { return; } - if (emitToHandlers(type, subtypeMap[subtype], e) === true) { - return true; + var hadSubtype = emitToHandlers(type, subtypeMap[subtype], e); + if (hadSubtype === false) { + return false; + } + var hadAll = emitToHandlers(type, subtypeMap.all, e); + if (hadAll === false) { + return false; } - if (emitToHandlers(type, subtypeMap.all, e) === true) { + if (hadSubtype || hadAll) { return true; } - return false; }; module.exports = Emitter; From 5838f849316d393298264152afa0049a346353d8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 05:03:00 -0700 Subject: [PATCH 091/791] Change emitt on, off subtype to be optional --- lib/emitter.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/emitter.js b/lib/emitter.js index 12be7ecb..b049d6e4 100644 --- a/lib/emitter.js +++ b/lib/emitter.js @@ -8,6 +8,10 @@ Emitter.prototype.wrapHandler = function(handler) { }; Emitter.prototype.on = function(type, subtype, handler) { + if (!handler) { + handler = subtype; + subtype = 'all'; + } var typeMap = this.listeners || ( this.listeners = {} ); var subtypeMap = typeMap[type] || ( typeMap[type] = {} ); (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ @@ -17,6 +21,10 @@ Emitter.prototype.on = function(type, subtype, handler) { }; Emitter.prototype.off = function(type, subtype, handler) { + if (!handler) { + handler = subtype; + subtype = 'all'; + } if (!type) { this.listeners = {}; return; From 6b7463af625131da2e86b9b497383f6512bb4f43 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 05:04:10 -0700 Subject: [PATCH 092/791] Add emitter listeners which returns all listeners --- lib/emitter.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/emitter.js b/lib/emitter.js index b049d6e4..5d0ee4dd 100644 --- a/lib/emitter.js +++ b/lib/emitter.js @@ -1,6 +1,6 @@ var Emitter = function() { - this.listeners = {}; + this._events = {}; }; Emitter.prototype.wrapHandler = function(handler) { @@ -12,7 +12,7 @@ Emitter.prototype.on = function(type, subtype, handler) { handler = subtype; subtype = 'all'; } - var typeMap = this.listeners || ( this.listeners = {} ); + var typeMap = this._events || ( this._events = {} ); var subtypeMap = typeMap[type] || ( typeMap[type] = {} ); (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ id: handler, @@ -26,10 +26,10 @@ Emitter.prototype.off = function(type, subtype, handler) { subtype = 'all'; } if (!type) { - this.listeners = {}; + this._events = {}; return; } - var typeMap = this.listeners; + var typeMap = this._events; if (!handler && subtype === 'all') { delete typeMap[type]; return; @@ -53,6 +53,17 @@ Emitter.prototype.off = function(type, subtype, handler) { handlers.splice(index, 1); }; +Emitter.prototype.listeners = function(type, subtype) { + if (!subtype) { + subtype = 'all'; + } + var typeMap = this._events; + if (!typeMap) { return; } + var subtypeMap = typeMap[type]; + if (!subtypeMap) { return; } + return subtypeMap[subtype]; +}; + var emitToHandlers = function(type, handlers, e) { if (!handlers) { return; } for (var i = 0, ii = handlers.length; i < ii; ++i) { @@ -73,7 +84,7 @@ Emitter.prototype.emit = function(type, subtype, e) { if (subtype) { e.subtype = subtype; } - var typeMap = this.listeners; + var typeMap = this._events; if (!typeMap) { return; } var subtypeMap = typeMap[type]; if (!subtypeMap) { return; } From 177fad7421fd1b8d7243a108ea531a1d6495a9ff Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 05:21:50 -0700 Subject: [PATCH 093/791] Add emitter forEachListener method --- lib/emitter.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/emitter.js b/lib/emitter.js index 5d0ee4dd..7f1922b9 100644 --- a/lib/emitter.js +++ b/lib/emitter.js @@ -64,6 +64,31 @@ Emitter.prototype.listeners = function(type, subtype) { return subtypeMap[subtype]; }; +Emitter.prototype.forEachListener = function(type, subtype, f) { + var typeMap = this._events; + if (!typeMap) { return; } + var subtypeMap; + if (typeof f === 'function') { + var handlers = this.listeners(type, subtype); + if (!handlers) { return; } + for (var i = 0, ii = handlers.length; i < ii; ++i) { + f(type, subtype, handlers[i]); + } + } else if (typeof subtype === 'function') { + f = subtype; + subtypeMap = typeMap[type]; + if (!subtypeMap) { return; } + for (subtype in subtypeMap) { + this.forEachListener(type, subtype, f); + } + } else if (typeof type === 'function') { + f = type; + for (type in typeMap) { + this.forEachListener(type, f); + } + } +}; + var emitToHandlers = function(type, handlers, e) { if (!handlers) { return; } for (var i = 0, ii = handlers.length; i < ii; ++i) { From f2e99f1d3f2f8696959abb6e6e4e866d4a2ffd53 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 05:26:25 -0700 Subject: [PATCH 094/791] Add emitter optional global add/remove hooks --- lib/emitter.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/emitter.js b/lib/emitter.js index 7f1922b9..c04be9f3 100644 --- a/lib/emitter.js +++ b/lib/emitter.js @@ -12,6 +12,9 @@ Emitter.prototype.on = function(type, subtype, handler) { handler = subtype; subtype = 'all'; } + if (Emitter.onAddHandler) { + Emitter.onAddHandler(type, subtype, handler); + } var typeMap = this._events || ( this._events = {} ); var subtypeMap = typeMap[type] || ( typeMap[type] = {} ); (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ @@ -25,6 +28,9 @@ Emitter.prototype.off = function(type, subtype, handler) { handler = subtype; subtype = 'all'; } + if (Emitter.onRemoveHandler) { + Emitter.onRemoveHandler(type, subtype, handler); + } if (!type) { this._events = {}; return; From 32bac89ff23c91a9f31849f1109825cbe66ad031 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 05:27:16 -0700 Subject: [PATCH 095/791] Update simply.js auto handling to consider card listeners --- lib/simply.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/simply.js b/lib/simply.js index 9a864a36..00ccc499 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -51,6 +51,9 @@ simply.init = function() { ajax.onHandler = function(type, handler) { return simply.wrapHandler(handler, 2); }; + + Emitter.onAddHandler = simply.onAddHandler; + Emitter.onRemoveHandler = simply.onRemoveHandler; } simply.loadMainScript(); @@ -129,16 +132,23 @@ simply.onRemoveHandler = function(type, subtype) { }; simply.countHandlers = function(type, subtype) { - if (!subtype) { - subtype = 'all'; + var count = 0; + var listeners = state.emitter.listeners(type, subtype); + count += (listeners ? listeners.length : 0); + listeners = state.card && state.card.emitter.listeners(type, subtype); + count += (listeners ? listeners.length : 0); + return count; +}; + +var setCard = function(card) { + var other = state.card; + if (other) { + other.forEachListener(simply.onRemoveHandler); } - var typeMap = simply.listeners; - var subtypeMap = typeMap[type]; - if (!subtypeMap) { - return 0; + if (card) { + card.forEachListener(simply.onAddHandler); } - var handlers = subtypeMap[subtype]; - return handlers ? handlers.length : 0; + state.card = card; }; var checkEventType = function(type) { @@ -167,7 +177,6 @@ simply.on = function(type, subtype, handler) { subtype = 'all'; } state.emitter.on(type, subtype, handler); - simply.onAddHandler(type, subtype); }; /** @@ -189,7 +198,6 @@ simply.off = function(type, subtype, handler) { subtype = 'all'; } state.emitter.off(type, subtype, handler); - simply.onRemoveHandler(type, subtype); }; simply.emit = function(type, subtype, handler) { @@ -443,7 +451,7 @@ simply.card = function(field, value, clear) { util2.copy(cardDef, card.state); } if (state.card !== card) { - state.card = card; + setCard(card); clear = 'all'; } else { clear = clear === true ? 'all' : clear; From ac4dc196c8c663df4eca2354c3ddc780d54c2a27 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 05:27:58 -0700 Subject: [PATCH 096/791] Add card event emitting --- lib/simply.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/simply.js b/lib/simply.js index 00ccc499..9162125f 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -939,17 +939,23 @@ simply.menuItem = function(sectionIndex, itemIndex, itemDef) { * @property {string} button - The button that was pressed: 'back', 'up', 'select', or 'down'. This is also the event subtype. */ +simply.emitCard = function(type, subtype, e, globalType) { + var card = e.card = state.card; + if (card && card.emit(type, subtype, e) === false) { + return false; + } + return simply.emit(globalType || type, subtype, e); +}; + simply.emitClick = function(type, button) { - simply.emit(type, button, { + var e = { button: button, - }); + }; + return simply.emitCard(type, button, e); }; simply.emitCardExit = function() { - var cardDef = state.card; - simply.emit('cardExit', util2.copy(cardDef, { - card: cardDef - })); + return simply.emitCard('exit', null, {}, 'cardExit'); }; /** @@ -961,10 +967,11 @@ simply.emitCardExit = function() { */ simply.emitAccelTap = function(axis, direction) { - simply.emit('accelTap', axis, { + var e = { axis: axis, direction: direction, - }); + }; + return simply.emitCard('accelTap', axis, e); }; /** @@ -996,7 +1003,7 @@ simply.emitAccelData = function(accels, callback) { if (callback) { return callback(e); } - simply.emit('accelData', e); + return simply.emitCard('accelData', null, e); }; simply.emitMenuSection = function(section) { From 191433300966afe5a654bd0c421f04d8eb7ceaf5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 05:28:39 -0700 Subject: [PATCH 097/791] Add card style, fullscreen, scrollable accessors --- lib/simply/card.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/simply/card.js b/lib/simply/card.js index 5b8adc49..bf86089e 100644 --- a/lib/simply/card.js +++ b/lib/simply/card.js @@ -16,6 +16,12 @@ var imageParams = [ 'banner', ]; +var configParams = [ + 'fullscreen', + 'style', + 'scrollable', +]; + var actionParams = [ 'up', 'select', @@ -42,7 +48,7 @@ var makeAccessor = function(k) { }; }; -textParams.concat(imageParams).forEach(function(k) { +textParams.concat(imageParams).concat(configParams).forEach(function(k) { Card.prototype[k] = makeAccessor(k); }); @@ -63,7 +69,7 @@ Card.prototype.action = function(field, value, clear) { return action[field]; } var actionDef; - if (typeof field === 'object') { + if (typeof field !== 'string') { actionDef = field; clear = value; value = null; @@ -74,8 +80,10 @@ Card.prototype.action = function(field, value, clear) { clear = clear === true ? 'action' : clear; if (clear === 'all' || clear === 'action') { this.state.action = actionDef; - } else { + } else if (typeof actionDef === 'object') { util2.copy(actionDef, this.state.action); + } else { + this.state.action = actionDef; } simply.action.call(this); return this; From 3f80f4ea0a87fb361559833bc44c91f6a130fa45 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 07:48:54 -0700 Subject: [PATCH 098/791] Add myutil for simply.js specific utility functions --- lib/myutil.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 lib/myutil.js diff --git a/lib/myutil.js b/lib/myutil.js new file mode 100644 index 00000000..b87e0ec5 --- /dev/null +++ b/lib/myutil.js @@ -0,0 +1,42 @@ + +var myutil = (function(){ + +var myutil = {}; + +myutil.toObject = function(key, value) { + if (typeof key === 'object') { + return key; + } + var obj = {}; + obj[key] = value; + return obj; +}; + +myutil.flag = function(flags) { + if (typeof flags === 'boolean') { + return flags; + } + for (var i = 1, ii = arguments.length; i < ii; ++i) { + if (flags[arguments[i]]) { + return true; + } + } + return false; +}; + +myutil.toFlags = function(flags) { + if (typeof flags === 'string') { + flags = myutil.toObject(flags, true); + } else { + flags = !!flags; + } + return flags; +}; + +if (typeof module !== 'undefined') { + module.exports = myutil; +} + +return myutil; + +})(); From a5842df888f1374060a70167ef7474295260c122 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 07:49:11 -0700 Subject: [PATCH 099/791] Add card prop method --- lib/simply.js | 51 ++++++++++++------------------- lib/simply/card.js | 75 +++++++++++++++++++++++++++++++++------------- 2 files changed, 74 insertions(+), 52 deletions(-) diff --git a/lib/simply.js b/lib/simply.js index 9162125f..587717b0 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -5,6 +5,7 @@ var ajax = require('ajax'); var util2 = require('util2'); +var myutil = require('myutil'); var imagelib = require('image'); var Emitter = require('emitter'); @@ -148,7 +149,7 @@ var setCard = function(card) { if (card) { card.forEachListener(simply.onAddHandler); } - state.card = card; + return (state.card = card); }; var checkEventType = function(type) { @@ -424,43 +425,30 @@ simply.buttonAutoConfig = function() { }; simply.card = function(field, value, clear) { - if (this instanceof Card && !this.state) { - return Card.call(this); - } var card = state.card; - if (arguments.length === 0) { - return card; - } - if (arguments.length === 1 && typeof field !== 'object') { - return card.state[field]; - } - var cardDef; - if (typeof field === 'object') { - cardDef = field; - value = null; - } else { - cardDef = {}; - cardDef[field] = value; + var result; + var cardDef = myutil.toObject(field, value); + if (typeof clear === 'undefined') { + clear = value; } + clear = clear ? 'all' : clear; if (this instanceof Card) { + result = this; if (this !== card) { - util2.copy(this.state, cardDef); - card = this; + card = setCard(this); + util2.copy(card.state, cardDef); + clear = 'all'; } } else { - util2.copy(cardDef, card.state); - } - if (state.card !== card) { - setCard(card); - clear = 'all'; - } else { - clear = clear === true ? 'all' : clear; if (clear === 'all') { - card = state.card = new Card(cardDef); + card = state.card = new Card(); } + result = card.prop(cardDef, clear); + } + if (card === result) { + simply.impl.card(cardDef, clear); } - simply.impl.card(cardDef, clear); - return card; + return result; }; /** @@ -531,10 +519,9 @@ simply.action = function(field, image, clear) { var card = state.card; var result = this instanceof Card ? this : card.action(field, image, clear); if (card === result) { - clear = clear === true ? 'action' : clear; - simply.impl.card({ action: card.state.action }, clear); + simply.impl.card({ action: typeof field === 'boolean' ? field : card.state.action }, 'action'); } - return state.card.action; + return result; }; /** diff --git a/lib/simply/card.js b/lib/simply/card.js index bf86089e..8c0526ad 100644 --- a/lib/simply/card.js +++ b/lib/simply/card.js @@ -1,33 +1,36 @@ var util2 = require('util2'); - +var myutil = require('myutil'); var simply = require('simply'); var Emitter = require('emitter'); -var textParams = [ +var textProps = [ 'title', 'subtitle', 'body', ]; -var imageParams = [ +var imageProps = [ 'icon', 'subicon', 'banner', ]; -var configParams = [ +var configProps = [ 'fullscreen', 'style', 'scrollable', ]; -var actionParams = [ +var actionProps = [ 'up', 'select', 'back', ]; +var accessorProps = textProps.concat(imageProps).concat(configProps); +var clearableProps = textProps.concat(imageProps); + var Card = function(cardDef) { this.state = cardDef || {}; }; @@ -48,7 +51,7 @@ var makeAccessor = function(k) { }; }; -textParams.concat(imageParams).concat(configParams).forEach(function(k) { +accessorProps.forEach(function(k) { Card.prototype[k] = makeAccessor(k); }); @@ -57,6 +60,46 @@ Card.prototype.show = function() { return this; }; +var unset = function(obj, k) { + if (typeof k === 'undefined') { + k = obj; + obj = this.state; + } + if (typeof obj === 'object') { + delete obj[k]; + } +}; + +var clearProps = function(flags) { + flags = myutil.toFlags(flags); + if (myutil.flag(flags, 'all')) { + clearableProps.forEach(unset.bind(this)); + } else if (myutil.flag(flags, 'action')) { + actionProps.forEach(unset.bind(this, this.state.action)); + } +}; + +Card.prototype.prop = function(field, value, clear) { + if (arguments.length === 0) { + return util2.copy(this.state); + } + if (arguments.length === 1 && typeof field !== 'object') { + return this.state[field]; + } + if (typeof clear === 'undefined') { + clear = value; + } + if (clear) { + clearProps.call(this, 'all'); + } + var cardDef = myutil.toObject(field, value); + util2.copy(cardDef, this.state); + if (simply.card() === this) { + simply.card.call(this, cardDef); + } + return this; +}; + Card.prototype.action = function(field, value, clear) { var action = this.state.action; if (!action) { @@ -68,22 +111,14 @@ Card.prototype.action = function(field, value, clear) { if (arguments.length === 1 && typeof field !== 'object') { return action[field]; } - var actionDef; - if (typeof field !== 'string') { - actionDef = field; + if (typeof clear === 'undefined') { clear = value; - value = null; - } else { - actionDef = {}; - actionDef[field] = value; } - clear = clear === true ? 'action' : clear; - if (clear === 'all' || clear === 'action') { - this.state.action = actionDef; - } else if (typeof actionDef === 'object') { - util2.copy(actionDef, this.state.action); - } else { - this.state.action = actionDef; + if (clear) { + clearProps.call(this, 'action'); + } + if (typeof field !== 'boolean') { + util2.copy(myutil.toObject(field, value), this.state.action); } simply.action.call(this); return this; From 0af774c4bb820465c2ef87ace822616e5ac2b862 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 11:24:46 -0700 Subject: [PATCH 100/791] Rename card, menu exit to hide --- lib/simply-pebble.js | 10 +++++----- lib/simply.js | 30 +++++++++++++++++------------- src/simply_menu.c | 2 +- src/simply_msg.c | 12 ++++++------ src/simply_msg.h | 4 ++-- src/simply_ui.c | 2 +- 6 files changed, 32 insertions(+), 28 deletions(-) diff --git a/lib/simply-pebble.js b/lib/simply-pebble.js index 918803f7..3c1043db 100644 --- a/lib/simply-pebble.js +++ b/lib/simply-pebble.js @@ -108,7 +108,7 @@ var commands = [{ name: 'down', }], }, { - name: 'cardExit', + name: 'cardHide', }, { name: 'setMenu', params: [{ @@ -167,7 +167,7 @@ var commands = [{ name: 'item', }], }, { - name: 'menuExit', + name: 'menuHide', params: [{ name: 'section', }, { @@ -548,8 +548,8 @@ SimplyPebble.onAppMessage = function(e) { var button = buttons[payload[1]]; simply.emitClick(command.name, button); break; - case 'cardExit': - simply.emitCardExit(); + case 'cardHide': + simply.emitCardHide(); break; case 'accelTap': var axis = accelAxes[payload[1]]; @@ -589,7 +589,7 @@ SimplyPebble.onAppMessage = function(e) { break; case 'menuSelect': case 'menuLongSelect': - case 'menuExit': + case 'menuHide': simply.emitMenuSelect(command.name, payload[1], payload[2]); break; } diff --git a/lib/simply.js b/lib/simply.js index 587717b0..9e7e8f3c 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -26,14 +26,14 @@ var buttons = [ var eventTypes = [ 'singleClick', 'longClick', - 'cardExit', + 'cardHide', 'accelTap', 'accelData', 'menuSection', 'menuItem', 'menuSelect', 'menuLongSelect', - 'menuExit', + 'menuHide', ]; @@ -145,11 +145,14 @@ var setCard = function(card) { var other = state.card; if (other) { other.forEachListener(simply.onRemoveHandler); + simply.emitCardHide(); } + state.card = card; if (card) { + simply.emitCardShow(); card.forEachListener(simply.onAddHandler); } - return (state.card = card); + return card; }; var checkEventType = function(type) { @@ -440,9 +443,6 @@ simply.card = function(field, value, clear) { clear = 'all'; } } else { - if (clear === 'all') { - card = state.card = new Card(); - } result = card.prop(cardDef, clear); } if (card === result) { @@ -874,14 +874,14 @@ simply.onMenuSelect = function(e) { } } break; - case 'menuExit': - if (typeof item.exit === 'function') { - if (item.exit(e) === false) { + case 'menuHide': + if (typeof item.hide === 'function') { + if (item.hide(e) === false) { return false; } } - if (typeof menu.exit === 'function') { - if (menu.exit(e) === false) { + if (typeof menu.hide === 'function') { + if (menu.hide(e) === false) { return false; } } @@ -941,8 +941,12 @@ simply.emitClick = function(type, button) { return simply.emitCard(type, button, e); }; -simply.emitCardExit = function() { - return simply.emitCard('exit', null, {}, 'cardExit'); +simply.emitCardShow = function() { + return simply.emitCard('show', null, {}, 'cardShow'); +}; + +simply.emitCardHide = function() { + return simply.emitCard('hide', null, {}, 'cardHide'); }; /** diff --git a/src/simply_menu.c b/src/simply_menu.c index e334d26b..9a594998 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -245,7 +245,7 @@ static void window_disappear(Window *window) { SimplyMenu *self = window_get_user_data(window); MenuIndex cell_index = menu_layer_get_selected_index(self->menu_layer); - simply_msg_menu_exit(cell_index.section, cell_index.row); + simply_msg_menu_hide(cell_index.section, cell_index.row); while (self->sections) { destroy_section(self, (SimplyMenuSection*) self->sections); diff --git a/src/simply_msg.c b/src/simply_msg.c index 4ec55a77..d7b858e0 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -23,7 +23,7 @@ enum SimplyACmd { SimplyACmd_getAccelData, SimplyACmd_configAccelData, SimplyACmd_configButtons, - SimplyACmd_uiExit, + SimplyACmd_uiHide, SimplyACmd_setMenu, SimplyACmd_setMenuSection, SimplyACmd_getMenuSection, @@ -31,7 +31,7 @@ enum SimplyACmd { SimplyACmd_getMenuItem, SimplyACmd_menuSelect, SimplyACmd_menuLongSelect, - SimplyACmd_menuExit, + SimplyACmd_menuHide, SimplyACmd_image, }; @@ -328,12 +328,12 @@ bool simply_msg_long_click(ButtonId button) { return send_click(SimplyACmd_longClick, button); } -bool simply_msg_ui_exit() { +bool simply_msg_ui_hide() { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { return false; } - dict_write_uint8(iter, 0, SimplyACmd_uiExit); + dict_write_uint8(iter, 0, SimplyACmd_uiHide); return (app_message_outbox_send() == APP_MSG_OK); } @@ -395,7 +395,7 @@ bool simply_msg_menu_select_long_click(uint16_t section, uint16_t index) { return send_menu_item(SimplyACmd_menuLongSelect, section, index); } -bool simply_msg_menu_exit(uint16_t section, uint16_t index) { - return send_menu_item(SimplyACmd_menuExit, section, index); +bool simply_msg_menu_hide(uint16_t section, uint16_t index) { + return send_menu_item(SimplyACmd_menuHide, section, index); } diff --git a/src/simply_msg.h b/src/simply_msg.h index 368a3576..6e0ce086 100644 --- a/src/simply_msg.h +++ b/src/simply_msg.h @@ -14,7 +14,7 @@ bool simply_msg_single_click(ButtonId button); bool simply_msg_long_click(ButtonId button); -bool simply_msg_ui_exit(); +bool simply_msg_ui_hide(); bool simply_msg_accel_tap(AccelAxisType axis, int32_t direction); @@ -28,5 +28,5 @@ bool simply_msg_menu_select_click(uint16_t section, uint16_t index); bool simply_msg_menu_select_long_click(uint16_t section, uint16_t index); -bool simply_msg_menu_exit(uint16_t section, uint16_t index); +bool simply_msg_menu_hide(uint16_t section, uint16_t index); diff --git a/src/simply_ui.c b/src/simply_ui.c index 2173858b..845f85b1 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -263,7 +263,7 @@ static void window_load(Window *window) { } static void window_disappear(Window *window) { - simply_msg_ui_exit(); + simply_msg_ui_hide(); } static void window_unload(Window *window) { From 39e93676e14c737e98f2aa2abc40a1ce4f9c1688 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 11:25:25 -0700 Subject: [PATCH 101/791] Add menu class --- lib/simply.js | 28 +++--- lib/simply/menu.js | 206 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+), 10 deletions(-) create mode 100644 lib/simply/menu.js diff --git a/lib/simply.js b/lib/simply.js index 9e7e8f3c..2f98678f 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -15,6 +15,7 @@ var simply = module.exports; simply.ui = {}; var Card = simply.ui.Card = require('simply/card'); +var Menu = simply.ui.Menu = require('simply/menu'); var buttons = [ 'back', @@ -832,15 +833,13 @@ simply.accelPeek = function(callback) { var getMenuSection = function(e) { var menu = e.menu || state.menu; if (!menu) { return; } - if (!(menu.sections instanceof Array)) { return; } - return menu.sections[e.section]; + return menu._section(e); }; var getMenuItem = function(e) { - var section = getMenuSection(e); - if (!section) { return; } - if (!(section.items instanceof Array)) { return; } - return section.items[e.item]; + var menu = e.menu || state.menu; + if (!menu) { return; } + return menu._item(e); }; simply.onMenuSection = function(e) { @@ -890,11 +889,20 @@ simply.onMenuSelect = function(e) { }; simply.menu = function(menuDef) { - if (!menuDef) { - return state.menu; + var menu = state.menu; + if (arguments.length === 0) { + return menu; + } + if (this instanceof Menu) { + menu = this; + } else if (menuDef instanceof Menu) { + menu = menuDef; + } else { + menu = new Menu(menuDef); } - state.menu = menuDef; - return simply.impl.menu.apply(this, arguments); + menu._menu(); + state.menu = menu; + return simply.impl.menu(menu.state); }; simply.menuSection = function(sectionIndex, sectionDef) { diff --git a/lib/simply/menu.js b/lib/simply/menu.js new file mode 100644 index 00000000..2d887453 --- /dev/null +++ b/lib/simply/menu.js @@ -0,0 +1,206 @@ +var util2 = require('util2'); +var myutil = require('myutil'); +var simply = require('simply'); + +var Emitter = require('emitter'); + +var Menu = function(menuDef) { + this._sections = {}; + this.state = menuDef || {}; +}; + +util2.copy(Emitter.prototype, Menu.prototype); + +Menu.prototype.show = function() { + simply.menu.call(this); + return this; +}; + +var getMetaSection = function(sectionIndex) { + return (this._sections[sectionIndex] || ( this._sections[sectionIndex] = {} )); +}; + +var targetType = { + menu: 1, + section: 2, + item: 3, +}; + +var isEnumerable = function(x, isArray) { + if (isArray) { + return (x instanceof Array); + } else { + return (typeof x === 'number' || x instanceof Array); + } +}; + +var getSections = function(target) { + var sections = this.state.sections; + if (isEnumerable(sections, target > targetType.menu)) { + return sections; + } + if (typeof sections === 'function') { + this.sectionsProvider = this.state.sections; + delete this.state.sections; + } + if (this.sectionsProvider) { + sections = this.sectionsProvider.call(this); + if (sections) { + return (this.state.sections = sections); + } + } +}; + +var getSection = function(e) { + var sections = getSections.call(this, targetType.section); + var section; + if (sections) { + section = sections[e.section]; + if (section) { + return section; + } + } + if (this.sectionProvider) { + section = this.sectionProvider.call(this, e); + if (section) { + if (!(sections instanceof Array)) { + sections = this.state.sections = []; + } + return (sections[e.section] = section); + } + } +}; + +var getItems = function(e, target) { + var section = getSection.call(this, e); + if (!section) { return; } + if (isEnumerable(section.items, target > targetType.section)) { + return section.items; + } + if (typeof section.items === 'function') { + this._sections[e.section] = section.items; + delete section.items; + } + var itemsProvider = getMetaSection.call(this, e.section).items || this.itemsProvider; + if (itemsProvider) { + var items = itemsProvider.call(this, e); + if (items) { + return (section.items = items); + } + } +}; + +var getItem = function(e) { + var items = getItems.call(this, e, targetType.item); + var item; + if (items) { + item = items[e.item]; + if (item) { + return item; + } + } + var section = getSection.call(this, e); + var itemProvider = getMetaSection.call(this, e.section).item || this.itemProvider; + if (itemProvider) { + item = itemProvider.call(this, e); + if (item) { + if (!(section.items instanceof Array)) { + section.items = []; + } + return (section.items[e.item] = item); + } + } +}; + +Menu.prototype._menu = function() { + this.state.sections = getSections.call(this, targetType.menu); + if (isEnumerable(this.state.sections)) { + return simply.menu.call(this); + } +}; + +Menu.prototype._section = function(e) { + var section = getSection.call(this, e); + if (section) { + if (!(section.items instanceof Array)) { + section.items = getItems.call(this, e, targetType.section); + } + if (isEnumerable(section.items)) { + return simply.menuSection.call(this, e.section, section); + } + } +}; + +Menu.prototype._item = function(e) { + var item = getItem.call(this, e); + if (item) { + return simply.menuItem.call(this, e.section, e.item, item); + } +}; + +Menu.prototype.sections = function(sections) { + if (typeof sections === 'function') { + this.sectionsProvider = sections; + return this; + } + this.state.sections = sections; + this._menu(); + return this; +}; + +Menu.prototype.section = function(sectionIndex, section) { + if (typeof sectionIndex === 'function') { + this.sectionProvider = sectionIndex; + return this; + } + var sections = this.state.sections; + if (sections instanceof Array) { + sections[sectionIndex] = section; + } + this._section({ section: sectionIndex }); + return this; +}; + +Menu.prototype.items = function(sectionIndex, items) { + if (typeof sectionIndex === 'function') { + this.itemsProvider = sectionIndex; + return this; + } + if (typeof items === 'function') { + getMetaSection.call(this, sectionIndex).items = items; + return this; + } + var section = getSection.call(this, { section: sectionIndex }); + if (section) { + if (items) { + section.items = items; + } else { + return section.items; + } + } + this._section({ section: sectionIndex }); + return this; +}; + +Menu.prototype.item = function(sectionIndex, itemIndex, item) { + if (typeof sectionIndex === 'function') { + this.itemProvider = sectionIndex; + return this; + } + if (typeof itemIndex === 'function') { + getMetaSection.call(this, sectionIndex).item = itemIndex; + return this; + } + if (item) { + var section = getSection.call(this, { section: sectionIndex }); + if (section.items instanceof Array) { + section.items[itemIndex] = item; + } + } else { + return getItem.call(this, { section: sectionIndex, item: itemIndex }); + } + this._item({ section: sectionIndex, item: itemIndex }); + return this; +}; + +module.exports = Menu; From b4309bc7faa56935d6a360405c54d9b70d822e2f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 12:32:02 -0700 Subject: [PATCH 102/791] Change menu methods to accept menu index objects --- lib/simply/menu.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/simply/menu.js b/lib/simply/menu.js index 2d887453..4781bc0a 100644 --- a/lib/simply/menu.js +++ b/lib/simply/menu.js @@ -113,8 +113,12 @@ var getItem = function(e) { }; Menu.prototype._menu = function() { - this.state.sections = getSections.call(this, targetType.menu); - if (isEnumerable(this.state.sections)) { + var sections = getSections.call(this, targetType.menu); + this.state.sections = sections; + if (isEnumerable(sections)) { + if (typeof sections === 'number') { + this.state.sections = new Array(sections); + } return simply.menu.call(this); } }; @@ -126,6 +130,9 @@ Menu.prototype._section = function(e) { section.items = getItems.call(this, e, targetType.section); } if (isEnumerable(section.items)) { + if (typeof section.items === 'number') { + section.items = new Array(section.items); + } return simply.menuSection.call(this, e.section, section); } } @@ -149,7 +156,9 @@ Menu.prototype.sections = function(sections) { }; Menu.prototype.section = function(sectionIndex, section) { - if (typeof sectionIndex === 'function') { + if (typeof sectionIndex === 'object') { + sectionIndex = sectionIndex.section; + } else if (typeof sectionIndex === 'function') { this.sectionProvider = sectionIndex; return this; } @@ -162,7 +171,9 @@ Menu.prototype.section = function(sectionIndex, section) { }; Menu.prototype.items = function(sectionIndex, items) { - if (typeof sectionIndex === 'function') { + if (typeof sectionIndex === 'object') { + sectionIndex = sectionIndex.section; + } else if (typeof sectionIndex === 'function') { this.itemsProvider = sectionIndex; return this; } @@ -183,12 +194,20 @@ Menu.prototype.items = function(sectionIndex, items) { }; Menu.prototype.item = function(sectionIndex, itemIndex, item) { - if (typeof sectionIndex === 'function') { + if (typeof sectionIndex === 'object') { + item = itemIndex || item; + itemIndex = sectionIndex.item; + sectionIndex = sectionIndex.section; + } else if (typeof sectionIndex === 'function') { this.itemProvider = sectionIndex; return this; } if (typeof itemIndex === 'function') { - getMetaSection.call(this, sectionIndex).item = itemIndex; + item = itemIndex; + itemIndex = null; + } + if (typeof item === 'function') { + getMetaSection.call(this, sectionIndex).item = item; return this; } if (item) { From 14a49906adf1579ac7df1b10e92dfdf0f43a0fd2 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 12:32:20 -0700 Subject: [PATCH 103/791] Add menu class menu event emitting --- lib/simply.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/simply.js b/lib/simply.js index 2f98678f..f63fe0f0 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -879,11 +879,6 @@ simply.onMenuSelect = function(e) { return false; } } - if (typeof menu.hide === 'function') { - if (menu.hide(e) === false) { - return false; - } - } break; } }; @@ -1005,12 +1000,22 @@ simply.emitAccelData = function(accels, callback) { return simply.emitCard('accelData', null, e); }; +simply.emitMenu = function(type, subtype, e, globalType) { + var menu = e.menu = state.menu; + if (menu && menu.emit(type, subtype, e) === false) { + return false; + } + return simply.emit(globalType || type, subtype, e); +}; + simply.emitMenuSection = function(section) { var e = { menu: state.menu, section: section }; - if (simply.emit('menuSection', e)) { return; } + if (simply.emitMenu('section', null, e, 'menuSection') === false) { + return false; + } simply.onMenuSection(e); }; @@ -1020,7 +1025,9 @@ simply.emitMenuItem = function(section, item) { section: section, item: item, }; - if (simply.emit('menuItem', e)) { return; } + if (simply.emitMenu('item', null, e, 'menuItem') === false) { + return false; + } simply.onMenuItem(e); }; @@ -1030,7 +1037,9 @@ simply.emitMenuSelect = function(type, section, item) { section: section, item: item, }; - if (simply.emit(type, e)) { return; } + if (simply.emitMenu(type, null, e) === false) { + return false; + } simply.onMenuSelect(e); }; From 8928747fbb57b6e82c32508511da7230ffdddf32 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 14:28:57 -0700 Subject: [PATCH 104/791] Change simply menu to use simply window --- src/simply_menu.c | 110 +++++++++++++++++++++----------------------- src/simply_menu.h | 13 ++++-- src/simply_ui.c | 3 +- src/simply_window.c | 1 - 4 files changed, 65 insertions(+), 62 deletions(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index 9a594998..fb0f046e 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -14,8 +14,6 @@ #define REQUEST_DELAY_MS 10 -SimplyMenu *s_menu = NULL; - static bool section_filter(List1Node *node, void *data) { SimplyMenuSection *section = (SimplyMenuSection*) node; uint16_t section_index = (uint16_t)(uintptr_t) data; @@ -36,12 +34,12 @@ static bool empty_filter(List1Node *node, void *data) { } static SimplyMenuSection *get_menu_section(SimplyMenu *self, int index) { - return (SimplyMenuSection*) list1_find(self->sections, section_filter, (void*)(uintptr_t) index); + return (SimplyMenuSection*) list1_find(self->menu_layer.sections, section_filter, (void*)(uintptr_t) index); } static void destroy_section(SimplyMenu *self, SimplyMenuSection *section) { if (!section) { return; } - list1_remove(&self->sections, §ion->node); + list1_remove(&self->menu_layer.sections, §ion->node); if (section->title) { free(section->title); section->title = NULL; @@ -51,17 +49,17 @@ static void destroy_section(SimplyMenu *self, SimplyMenuSection *section) { static void destroy_section_by_index(SimplyMenu *self, int section) { destroy_section(self, (SimplyMenuSection*) list1_find( - self->sections, section_filter, (void*)(uintptr_t) section)); + self->menu_layer.sections, section_filter, (void*)(uintptr_t) section)); } static SimplyMenuItem *get_menu_item(SimplyMenu *self, int section, int index) { uint32_t cell_index = section | (index << 16); - return (SimplyMenuItem*) list1_find(self->items, item_filter, (void*)(uintptr_t) cell_index); + return (SimplyMenuItem*) list1_find(self->menu_layer.items, item_filter, (void*)(uintptr_t) cell_index); } static void destroy_item(SimplyMenu *self, SimplyMenuItem *item) { if (!item) { return; } - list1_remove(&self->items, &item->node); + list1_remove(&self->menu_layer.items, &item->node); if (item->title) { free(item->title); item->title = NULL; @@ -76,21 +74,21 @@ static void destroy_item(SimplyMenu *self, SimplyMenuItem *item) { static void destroy_item_by_index(SimplyMenu *self, int section, int index) { uint32_t cell_index = section | (index << 16); destroy_item(self, (SimplyMenuItem*) list1_find( - self->items, item_filter, (void*)(uintptr_t) cell_index)); + self->menu_layer.items, item_filter, (void*)(uintptr_t) cell_index)); } static void schedule_get_timer(SimplyMenu *self); static void request_menu_node(void *data) { SimplyMenu *self = data; - self->get_timer = NULL; - SimplyMenuSection *section = (SimplyMenuSection*) list1_find(self->sections, empty_filter, NULL); + self->menu_layer.get_timer = NULL; + SimplyMenuSection *section = (SimplyMenuSection*) list1_find(self->menu_layer.sections, empty_filter, NULL); bool found = false; if (section) { simply_msg_menu_get_section(section->index); found = true; } - SimplyMenuItem *item = (SimplyMenuItem*) list1_find(self->items, empty_filter, NULL); + SimplyMenuItem *item = (SimplyMenuItem*) list1_find(self->menu_layer.items, empty_filter, NULL); if (item) { simply_msg_menu_get_item(item->section, item->index); found = true; @@ -101,25 +99,25 @@ static void request_menu_node(void *data) { } static void schedule_get_timer(SimplyMenu *self) { - if (self->get_timer) { return; } - self->get_timer = app_timer_register(self->request_delay_ms, request_menu_node, self); - self->request_delay_ms *= 2; + if (self->menu_layer.get_timer) { return; } + self->menu_layer.get_timer = app_timer_register(self->menu_layer.request_delay_ms, request_menu_node, self); + self->menu_layer.request_delay_ms *= 2; } static void add_section(SimplyMenu *self, SimplyMenuSection *section) { - if (list1_size(self->sections) >= MAX_CACHED_SECTIONS) { - destroy_section(self, (SimplyMenuSection*) list1_last(self->sections)); + if (list1_size(self->menu_layer.sections) >= MAX_CACHED_SECTIONS) { + destroy_section(self, (SimplyMenuSection*) list1_last(self->menu_layer.sections)); } destroy_section_by_index(self, section->index); - list1_prepend(&self->sections, §ion->node); + list1_prepend(&self->menu_layer.sections, §ion->node); } static void add_item(SimplyMenu *self, SimplyMenuItem *item) { - if (list1_size(self->items) >= MAX_CACHED_ITEMS) { - destroy_item(self, (SimplyMenuItem*) list1_last(self->items)); + if (list1_size(self->menu_layer.items) >= MAX_CACHED_ITEMS) { + destroy_item(self, (SimplyMenuItem*) list1_last(self->menu_layer.items)); } destroy_item_by_index(self, item->section, item->index); - list1_prepend(&self->items, &item->node); + list1_prepend(&self->menu_layer.items, &item->node); } static void request_menu_section(SimplyMenu *self, uint16_t section_index) { @@ -142,17 +140,17 @@ static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t } static void mark_dirty(SimplyMenu *self) { - if (!self->menu_layer) { return; } - menu_layer_reload_data(self->menu_layer); + if (!self->menu_layer.menu_layer) { return; } + menu_layer_reload_data(self->menu_layer.menu_layer); request_menu_node(self); - self->request_delay_ms = REQUEST_DELAY_MS; + self->menu_layer.request_delay_ms = REQUEST_DELAY_MS; } void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections) { if (num_sections == 0) { num_sections = 1; } - self->num_sections = num_sections; + self->menu_layer.num_sections = num_sections; } void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section) { @@ -167,7 +165,7 @@ void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item) { static uint16_t menu_get_num_sections_callback(MenuLayer *menu_layer, void *data) { SimplyMenu *self = data; - return self->num_sections; + return self->menu_layer.num_sections; } static uint16_t menu_get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *data) { @@ -188,8 +186,8 @@ static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer, ui return; } - list1_remove(&self->sections, §ion->node); - list1_prepend(&self->sections, §ion->node); + list1_remove(&self->menu_layer.sections, §ion->node); + list1_prepend(&self->menu_layer.sections, §ion->node); menu_cell_basic_header_draw(ctx, cell_layer, section->title); } @@ -202,10 +200,10 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI return; } - list1_remove(&self->items, &item->node); - list1_prepend(&self->items, &item->node); + list1_remove(&self->menu_layer.items, &item->node); + list1_prepend(&self->menu_layer.items, &item->node); - GBitmap *bitmap = simply_res_get_image(self->simply->res, item->icon); + GBitmap *bitmap = simply_res_get_image(self->window.simply->res, item->icon); menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, bitmap); } @@ -220,12 +218,15 @@ static void menu_select_long_click_callback(MenuLayer *menu_layer, MenuIndex *ce static void window_load(Window *window) { SimplyMenu *self = window_get_user_data(window); + simply_window_load(&self->window); + Layer *window_layer = window_get_root_layer(window); GRect frame = layer_get_frame(window_layer); frame.origin = GPointZero; - MenuLayer *menu_layer = self->menu_layer = menu_layer_create(frame); + MenuLayer *menu_layer = self->menu_layer.menu_layer = menu_layer_create(frame); Layer *menu_base_layer = menu_layer_get_layer(menu_layer); + self->window.layer = menu_base_layer; layer_add_child(window_layer, menu_base_layer); menu_layer_set_callbacks(menu_layer, self, (MenuLayerCallbacks){ @@ -244,52 +245,50 @@ static void window_load(Window *window) { static void window_disappear(Window *window) { SimplyMenu *self = window_get_user_data(window); - MenuIndex cell_index = menu_layer_get_selected_index(self->menu_layer); + MenuIndex cell_index = menu_layer_get_selected_index(self->menu_layer.menu_layer); simply_msg_menu_hide(cell_index.section, cell_index.row); - while (self->sections) { - destroy_section(self, (SimplyMenuSection*) self->sections); + while (self->menu_layer.sections) { + destroy_section(self, (SimplyMenuSection*) self->menu_layer.sections); } - while (self->items) { - destroy_item(self, (SimplyMenuItem*) self->items); + while (self->menu_layer.items) { + destroy_item(self, (SimplyMenuItem*) self->menu_layer.items); } } static void window_unload(Window *window) { SimplyMenu *self = window_get_user_data(window); - menu_layer_destroy(self->menu_layer); - self->menu_layer = NULL; + menu_layer_destroy(self->menu_layer.menu_layer); + self->menu_layer.menu_layer = NULL; + + simply_window_unload(&self->window); } void simply_menu_show(SimplyMenu *self) { - if (!self->window) { + if (!self->window.window) { return; } - if (!window_stack_contains_window(self->window)) { + if (!window_stack_contains_window(self->window.window)) { bool animated = true; - window_stack_push(self->window, animated); + window_stack_push(self->window.window, animated); } } SimplyMenu *simply_menu_create(Simply *simply) { - if (s_menu) { - return s_menu; - } - SimplyMenu *self = malloc(sizeof(*self)); *self = (SimplyMenu) { - .simply = simply, - .num_sections = 1, - .request_delay_ms = REQUEST_DELAY_MS, + .window.simply = simply, + .menu_layer.num_sections = 1, + .menu_layer.request_delay_ms = REQUEST_DELAY_MS, }; - s_menu = self; - Window *window = self->window = window_create(); - window_set_user_data(window, self); - window_set_background_color(window, GColorWhite); - window_set_window_handlers(window, (WindowHandlers) { + simply_window_init(&self->window, simply); + + window_set_user_data(self->window.window, self); + window_set_background_color(self->window.window, GColorWhite); + window_set_window_handlers(self->window.window, (WindowHandlers) { .load = window_load, .disappear = window_disappear, .unload = window_unload, @@ -303,10 +302,7 @@ void simply_menu_destroy(SimplyMenu *self) { return; } - window_destroy(self->window); - self->window = NULL; + simply_window_deinit(&self->window); free(self); - - s_menu = NULL; } diff --git a/src/simply_menu.h b/src/simply_menu.h index 5c9f9c5d..d59c66db 100644 --- a/src/simply_menu.h +++ b/src/simply_menu.h @@ -1,11 +1,15 @@ #pragma once +#include "simply_window.h" + #include "simplyjs.h" #include "util/list1.h" #include +typedef struct SimplyMenuLayer SimplyMenuLayer; + typedef struct SimplyMenu SimplyMenu; typedef struct SimplyMenuSection SimplyMenuSection; @@ -20,9 +24,7 @@ enum SimplyMenuType { SimplyMenuTypeItem, }; -struct SimplyMenu { - Simply *simply; - Window *window; +struct SimplyMenuLayer { MenuLayer *menu_layer; List1Node *sections; List1Node *items; @@ -31,6 +33,11 @@ struct SimplyMenu { uint16_t num_sections; }; +struct SimplyMenu { + SimplyWindow window; + SimplyMenuLayer menu_layer; +}; + typedef struct SimplyMenuCommon SimplyMenuCommon; #define SimplyMenuCommonDef { \ diff --git a/src/simply_ui.c b/src/simply_ui.c index 845f85b1..4917d55d 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -233,7 +233,7 @@ static void show_welcome_text(SimplyUi *self) { if (title_text || subtitle_text || body_text) { return; } - if (self->window.simply->menu->menu_layer) { + if (self->window.simply->menu->menu_layer.menu_layer) { return; } @@ -258,6 +258,7 @@ static void window_load(Window *window) { *(void**) layer_get_data(layer) = self; layer_set_update_proc(layer, layer_update_callback); scroll_layer_add_child(self->window.scroll_layer, layer); + scroll_layer_set_click_config_onto_window(self->window.scroll_layer, window); simply_ui_set_style(self, 1); } diff --git a/src/simply_window.c b/src/simply_window.c index 04169f24..ee2de81c 100644 --- a/src/simply_window.c +++ b/src/simply_window.c @@ -141,7 +141,6 @@ void simply_window_load(SimplyWindow *self) { scroll_layer_set_callbacks(scroll_layer, (ScrollLayerCallbacks) { .click_config_provider = click_config_provider, }); - scroll_layer_set_click_config_onto_window(scroll_layer, window); if (self->is_action_bar) { simply_window_set_action_bar(self, true); From 6a2d06592992cea832ca0b87b3bdd778bb8b2609 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 16:55:47 -0700 Subject: [PATCH 105/791] Add myutil unset and makeAccessor --- lib/myutil.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/myutil.js b/lib/myutil.js index b87e0ec5..17b168ca 100644 --- a/lib/myutil.js +++ b/lib/myutil.js @@ -33,6 +33,30 @@ myutil.toFlags = function(flags) { return flags; }; +myutil.unset = function(obj, k) { + if (typeof k === 'undefined') { + k = obj; + obj = this.state; + } + if (typeof obj === 'object') { + delete obj[k]; + } +}; + +myutil.makeAccessor = function(k) { + return function(value) { + if (arguments.length === 0) { + return this.state[k]; + } else { + this.state[k] = value; + if (this._prop() === this) { + this._prop(k, value); + } + return this; + } + }; +}; + if (typeof module !== 'undefined') { module.exports = myutil; } From 602747e0d9b2d57700e33a7d982b0a208d9f6d36 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 16:56:09 -0700 Subject: [PATCH 106/791] Add simply window class --- lib/simply/window.js | 99 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 lib/simply/window.js diff --git a/lib/simply/window.js b/lib/simply/window.js new file mode 100644 index 00000000..020f1672 --- /dev/null +++ b/lib/simply/window.js @@ -0,0 +1,99 @@ +var util2 = require('util2'); +var myutil = require('myutil'); +var simply = require('simply'); + +var Emitter = require('emitter'); + +var configProps = [ + 'fullscreen', + 'style', + 'scrollable', +]; + +var actionProps = [ + 'up', + 'select', + 'back', +]; + +var accessorProps = configProps; + +var nextId = 1; + +var Window = function(windowDef) { + this.state = windowDef || {}; + this._id = nextId++; +}; + +util2.copy(Emitter.prototype, Window.prototype); + +accessorProps.forEach(function(k) { + Window.prototype[k] = myutil.makeAccessor(k); +}); + +Window.prototype._prop = function() { + return simply.window.apply(this, arguments); +}; + +Window.prototype.show = function() { + this._prop({}); + return this; +}; + +Window.prototype._clearAction = function() { + actionProps.forEach(myutil.unset.bind(this, this.state.action)); +}; + +Window.prototype._clear = function(flags) { + flags = myutil.toFlags(flags); + if (myutil.flag(flags, 'action')) { + this._clearAction(); + } +}; + +Window.prototype.prop = function(field, value, clear) { + if (arguments.length === 0) { + return util2.copy(this.state); + } + if (arguments.length === 1 && typeof field !== 'object') { + return this.state[field]; + } + if (typeof clear === 'undefined') { + clear = value; + } + if (clear) { + this._clear('all'); + } + var windowDef = myutil.toObject(field, value); + util2.copy(windowDef, this.state); + if (this._prop() === this) { + this._prop(windowDef); + } + return this; +}; + +Window.prototype.action = function(field, value, clear) { + var action = this.state.action; + if (!action) { + action = this.state.action = {}; + } + if (arguments.length === 0) { + return action; + } + if (arguments.length === 1 && typeof field !== 'object') { + return action[field]; + } + if (typeof clear === 'undefined') { + clear = value; + } + if (clear) { + this._clear('action'); + } + if (typeof field !== 'boolean') { + util2.copy(myutil.toObject(field, value), this.state.action); + } + simply.action.call(this); + return this; +}; + +module.exports = Window; From 23de12078e3552070f45597a5d79efe019c15c11 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 16:56:37 -0700 Subject: [PATCH 107/791] Add util2 inherit --- lib/util2.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/util2.js b/lib/util2.js index 46ef6590..fdc82f89 100644 --- a/lib/util2.js +++ b/lib/util2.js @@ -40,6 +40,15 @@ util2.last = function(a) { return a[a.length-1]; }; +util2.inherit = function(child, parent, proto) { + child.prototype = Object.create(parent.prototype); + child.prototype.constructor = child; + if (proto) { + util2.copy(proto, child.prototype); + } + return child.prototype; +}; + var chunkSize = 128; var randomBytes = function(chunkSize) { From 6a710630830f0267adf0e8782420ac2b5d137751 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 16:56:51 -0700 Subject: [PATCH 108/791] Change simply card to extend window --- lib/simply/card.js | 93 +++++----------------------------------------- 1 file changed, 10 insertions(+), 83 deletions(-) diff --git a/lib/simply/card.js b/lib/simply/card.js index 8c0526ad..829f1284 100644 --- a/lib/simply/card.js +++ b/lib/simply/card.js @@ -3,6 +3,7 @@ var myutil = require('myutil'); var simply = require('simply'); var Emitter = require('emitter'); +var Window = require('simply/window'); var textProps = [ 'title', @@ -16,112 +17,38 @@ var imageProps = [ 'banner', ]; -var configProps = [ - 'fullscreen', - 'style', - 'scrollable', -]; - var actionProps = [ 'up', 'select', 'back', ]; -var accessorProps = textProps.concat(imageProps).concat(configProps); +var accessorProps = textProps.concat(imageProps); var clearableProps = textProps.concat(imageProps); var Card = function(cardDef) { this.state = cardDef || {}; }; -util2.copy(Emitter.prototype, Card.prototype); +util2.inherit(Card, Window); -var makeAccessor = function(k) { - return function(value) { - if (arguments.length === 0) { - return this.state[k]; - } else { - this.state[k] = value; - if (simply.card() === this) { - simply.card.call(this, k, value); - } - return this; - } - }; -}; +util2.copy(Emitter.prototype, Card.prototype); accessorProps.forEach(function(k) { - Card.prototype[k] = makeAccessor(k); + Card.prototype[k] = myutil.makeAccessor(k); }); -Card.prototype.show = function() { - simply.card.call(this, {}); - return this; -}; - -var unset = function(obj, k) { - if (typeof k === 'undefined') { - k = obj; - obj = this.state; - } - if (typeof obj === 'object') { - delete obj[k]; - } +Card.prototype._prop = function() { + return simply.card.apply(this, arguments); }; -var clearProps = function(flags) { +Card.prototype._clear = function(flags) { flags = myutil.toFlags(flags); if (myutil.flag(flags, 'all')) { - clearableProps.forEach(unset.bind(this)); + clearableProps.forEach(myutil.unset.bind(this)); } else if (myutil.flag(flags, 'action')) { - actionProps.forEach(unset.bind(this, this.state.action)); - } -}; - -Card.prototype.prop = function(field, value, clear) { - if (arguments.length === 0) { - return util2.copy(this.state); - } - if (arguments.length === 1 && typeof field !== 'object') { - return this.state[field]; - } - if (typeof clear === 'undefined') { - clear = value; - } - if (clear) { - clearProps.call(this, 'all'); - } - var cardDef = myutil.toObject(field, value); - util2.copy(cardDef, this.state); - if (simply.card() === this) { - simply.card.call(this, cardDef); - } - return this; -}; - -Card.prototype.action = function(field, value, clear) { - var action = this.state.action; - if (!action) { - action = this.state.action = {}; - } - if (arguments.length === 0) { - return action; - } - if (arguments.length === 1 && typeof field !== 'object') { - return action[field]; - } - if (typeof clear === 'undefined') { - clear = value; - } - if (clear) { - clearProps.call(this, 'action'); - } - if (typeof field !== 'boolean') { - util2.copy(myutil.toObject(field, value), this.state.action); + this._clearAction(); } - simply.action.call(this); - return this; }; module.exports = Card; From 8a4ed0390e06c5d93b087366083abba9c93d7d4b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 16:57:08 -0700 Subject: [PATCH 109/791] Change simply menu to extend window --- lib/simply/menu.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/simply/menu.js b/lib/simply/menu.js index 4781bc0a..b2ca1dd4 100644 --- a/lib/simply/menu.js +++ b/lib/simply/menu.js @@ -3,12 +3,15 @@ var myutil = require('myutil'); var simply = require('simply'); var Emitter = require('emitter'); +var Window = require('simply/window'); var Menu = function(menuDef) { this._sections = {}; this.state = menuDef || {}; }; +util2.inherit(Menu, Window); + util2.copy(Emitter.prototype, Menu.prototype); Menu.prototype.show = function() { From 17d5d71d15e2edf9df65fabe5b6cb922fd38acd7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 16:57:37 -0700 Subject: [PATCH 110/791] Change card and menu exit to window show and hide --- src/simply_menu.c | 10 ++-- src/simply_msg.c | 111 ++++++++++++++++++++++++++++++-------------- src/simply_msg.h | 4 +- src/simply_ui.c | 23 ++++++--- src/simply_window.h | 1 + 5 files changed, 104 insertions(+), 45 deletions(-) diff --git a/src/simply_menu.c b/src/simply_menu.c index fb0f046e..074b5686 100644 --- a/src/simply_menu.c +++ b/src/simply_menu.c @@ -242,11 +242,14 @@ static void window_load(Window *window) { menu_layer_set_click_config_onto_window(menu_layer, window); } -static void window_disappear(Window *window) { +static void window_appear(Window *window) { SimplyMenu *self = window_get_user_data(window); + simply_msg_window_show(self->window.id); +} - MenuIndex cell_index = menu_layer_get_selected_index(self->menu_layer.menu_layer); - simply_msg_menu_hide(cell_index.section, cell_index.row); +static void window_disappear(Window *window) { + SimplyMenu *self = window_get_user_data(window); + simply_msg_window_hide(self->window.id); while (self->menu_layer.sections) { destroy_section(self, (SimplyMenuSection*) self->menu_layer.sections); @@ -290,6 +293,7 @@ SimplyMenu *simply_menu_create(Simply *simply) { window_set_background_color(self->window.window, GColorWhite); window_set_window_handlers(self->window.window, (WindowHandlers) { .load = window_load, + .appear = window_appear, .disappear = window_disappear, .unload = window_unload, }); diff --git a/src/simply_msg.c b/src/simply_msg.c index d7b858e0..833f0297 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -14,7 +14,10 @@ typedef enum SimplyACmd SimplyACmd; enum SimplyACmd { - SimplyACmd_setUi = 0, + SimplyACmd_setWindow = 0, + SimplyACmd_windowShow, + SimplyACmd_windowHide, + SimplyACmd_setUi, SimplyACmd_singleClick, SimplyACmd_longClick, SimplyACmd_accelTap, @@ -23,7 +26,6 @@ enum SimplyACmd { SimplyACmd_getAccelData, SimplyACmd_configAccelData, SimplyACmd_configButtons, - SimplyACmd_uiHide, SimplyACmd_setMenu, SimplyACmd_setMenuSection, SimplyACmd_getMenuSection, @@ -31,27 +33,33 @@ enum SimplyACmd { SimplyACmd_getMenuItem, SimplyACmd_menuSelect, SimplyACmd_menuLongSelect, - SimplyACmd_menuHide, SimplyACmd_image, }; typedef enum SimplySetUiParam SimplySetUiParam; +enum SimplySetWindowParam { + SetWindow_clear = 1, + SetWindow_id, + SetWindow_action, + SetWindow_actionUp, + SetWindow_actionSelect, + SetWindow_actionDown, + SetWindow_fullscreen, + SetWindow_scrollable, + SetWindowLast, +}; + enum SimplySetUiParam { - SetUi_clear = 1, - SetUi_title, + SetUi_clear = SetWindow_clear, + SetUi_id, + SetUi_title = SetWindowLast, SetUi_subtitle, SetUi_body, SetUi_icon, SetUi_subicon, SetUi_banner, - SetUi_action, - SetUi_actionUp, - SetUi_actionSelect, - SetUi_actionDown, - SetUi_fullscreen, SetUi_style, - SetUi_scrollable, }; typedef enum VibeType VibeType; @@ -68,6 +76,39 @@ static void check_splash(Simply *simply) { } } +static void handle_set_window(DictionaryIterator *iter, Simply *simply) { + Window *base_window = window_stack_get_top_window(); + SimplyWindow *window = window_get_user_data(base_window); + if (!window || (void*) window == simply->splash) { + return; + } + Tuple *tuple; + if ((tuple = dict_find(iter, SetWindow_clear))) { + simply_window_set_action_bar(window, false); + simply_window_action_bar_clear(window); + } + for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { + switch (tuple->key) { + case SetWindow_id: + window->id = tuple->value->uint32; + case SetWindow_action: + simply_window_set_action_bar(window, tuple->value->int32); + break; + case SetWindow_actionUp: + case SetWindow_actionSelect: + case SetWindow_actionDown: + simply_window_set_action_bar_icon(window, tuple->key - SetWindow_action, tuple->value->int32); + break; + case SetWindow_fullscreen: + simply_window_set_fullscreen(window, tuple->value->int32); + break; + case SetWindow_scrollable: + simply_window_set_scrollable(window, tuple->value->int32); + break; + } + } +} + static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { SimplyUi *ui = simply->ui; Tuple *tuple; @@ -76,6 +117,8 @@ static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { } for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { + case SetUi_id: + ui->window.id = tuple->value->uint32; case SetUi_title: case SetUi_subtitle: case SetUi_body: @@ -86,26 +129,13 @@ static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { case SetUi_banner: ui->ui_layer.imagefields[tuple->key - SetUi_icon] = tuple->value->uint32; break; - case SetUi_action: - simply_window_set_action_bar(&ui->window, tuple->value->int32); - break; - case SetUi_actionUp: - case SetUi_actionSelect: - case SetUi_actionDown: - simply_window_set_action_bar_icon(&ui->window, tuple->key - SetUi_action, tuple->value->int32); - break; case SetUi_style: simply_ui_set_style(simply->ui, tuple->value->int32); break; - case SetUi_fullscreen: - simply_window_set_fullscreen(&ui->window, tuple->value->int32); - break; - case SetUi_scrollable: - simply_window_set_scrollable(&ui->window, tuple->value->int32); - break; } } simply_ui_show(simply->ui); + handle_set_window(iter, simply); } static void handle_vibe(DictionaryIterator *iter, Simply *simply) { @@ -156,11 +186,15 @@ static void handle_set_accel_config(DictionaryIterator *iter, Simply *simply) { } static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { + SimplyMenu *menu = simply->menu; Tuple *tuple; if ((tuple = dict_find(iter, 1))) { - simply_menu_set_num_sections(simply->menu, tuple->value->int32); + menu->window.id = tuple->value->uint32; + } + if ((tuple = dict_find(iter, 2))) { + simply_menu_set_num_sections(menu, tuple->value->int32); } - simply_menu_show(simply->menu); + simply_menu_show(menu); } static void handle_set_menu_section(DictionaryIterator *iter, Simply *simply) { @@ -247,6 +281,9 @@ static void received_callback(DictionaryIterator *iter, void *context) { } switch (tuple->value->uint8) { + case SimplyACmd_setWindow: + handle_set_window(iter, context); + break; case SimplyACmd_setUi: handle_set_ui(iter, context); break; @@ -328,12 +365,23 @@ bool simply_msg_long_click(ButtonId button) { return send_click(SimplyACmd_longClick, button); } -bool simply_msg_ui_hide() { +bool simply_msg_window_show(uint32_t id) { + DictionaryIterator *iter = NULL; + if (app_message_outbox_begin(&iter) != APP_MSG_OK) { + return false; + } + dict_write_uint8(iter, 0, SimplyACmd_windowShow); + dict_write_uint32(iter, 1, id); + return (app_message_outbox_send() == APP_MSG_OK); +} + +bool simply_msg_window_hide(uint32_t id) { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { return false; } - dict_write_uint8(iter, 0, SimplyACmd_uiHide); + dict_write_uint8(iter, 0, SimplyACmd_windowHide); + dict_write_uint32(iter, 1, id); return (app_message_outbox_send() == APP_MSG_OK); } @@ -394,8 +442,3 @@ bool simply_msg_menu_select_click(uint16_t section, uint16_t index) { bool simply_msg_menu_select_long_click(uint16_t section, uint16_t index) { return send_menu_item(SimplyACmd_menuLongSelect, section, index); } - -bool simply_msg_menu_hide(uint16_t section, uint16_t index) { - return send_menu_item(SimplyACmd_menuHide, section, index); -} - diff --git a/src/simply_msg.h b/src/simply_msg.h index 6e0ce086..179a4651 100644 --- a/src/simply_msg.h +++ b/src/simply_msg.h @@ -14,7 +14,9 @@ bool simply_msg_single_click(ButtonId button); bool simply_msg_long_click(ButtonId button); -bool simply_msg_ui_hide(); +bool simply_msg_window_show(uint32_t id); + +bool simply_msg_window_hide(uint32_t id); bool simply_msg_accel_tap(AccelAxisType axis, int32_t direction); diff --git a/src/simply_ui.c b/src/simply_ui.c index 4917d55d..23f3df51 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -18,6 +18,12 @@ struct SimplyStyle { int custom_body_font_id; }; +enum ClearIndex { + ClearAction = 0, + ClearText, + ClearImage, +}; + static SimplyStyle STYLES[] = { { .title_font = FONT_KEY_GOTHIC_24_BOLD, @@ -37,18 +43,14 @@ static SimplyStyle STYLES[] = { }; void simply_ui_clear(SimplyUi *self, uint32_t clear_mask) { - if (clear_mask & (1 << 0)) { + if (clear_mask & (1 << ClearText)) { for (SimplyUiTextfield textfield = 0; textfield < NumUiTextfields; ++textfield) { simply_ui_set_text(self, textfield, NULL); } } - if (clear_mask & (1 << 1)) { + if (clear_mask & (1 << ClearImage)) { memset(self->ui_layer.imagefields, 0, sizeof(self->ui_layer.imagefields)); } - if (clear_mask & (1 << 2)) { - simply_window_set_action_bar(&self->window, false); - simply_window_action_bar_clear(&self->window); - } } void simply_ui_set_style(SimplyUi *self, int style_index) { @@ -263,8 +265,14 @@ static void window_load(Window *window) { simply_ui_set_style(self, 1); } +static void window_appear(Window *window) { + SimplyUi *self = window_get_user_data(window); + simply_msg_window_show(self->window.id); +} + static void window_disappear(Window *window) { - simply_msg_ui_hide(); + SimplyUi *self = window_get_user_data(window); + simply_msg_window_hide(self->window.id); } static void window_unload(Window *window) { @@ -295,6 +303,7 @@ SimplyUi *simply_ui_create(Simply *simply) { window_set_user_data(self->window.window, self); window_set_window_handlers(self->window.window, (WindowHandlers) { .load = window_load, + .appear = window_appear, .disappear = window_disappear, .unload = window_unload, }); diff --git a/src/simply_window.h b/src/simply_window.h index ecd95042..490dc687 100644 --- a/src/simply_window.h +++ b/src/simply_window.h @@ -12,6 +12,7 @@ struct SimplyWindow { ScrollLayer *scroll_layer; Layer *layer; ActionBarLayer *action_bar_layer; + uint32_t id; uint32_t button_mask; bool is_scrollable; bool is_action_bar; From 8113322c128c43a321159af8c8f0c4a9a76c3615 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 17:08:07 -0700 Subject: [PATCH 111/791] Update simply.js to use window extended card --- lib/simply-pebble.js | 153 ++++++++++++++++++++++++++----------------- lib/simply.js | 60 +++++++++++++---- 2 files changed, 140 insertions(+), 73 deletions(-) diff --git a/lib/simply-pebble.js b/lib/simply-pebble.js index 3c1043db..584674ad 100644 --- a/lib/simply-pebble.js +++ b/lib/simply-pebble.js @@ -7,50 +7,70 @@ if (typeof Image === 'undefined') { window.Image = function(){}; } +var setWindowParams = [{ + name: 'clear', + type: Boolean, +}, { + name: 'id', +}, { + name: 'action', + type: Boolean, +}, { + name: 'actionUp', + type: Image, +}, { + name: 'actionSelect', + type: Image, +}, { + name: 'actionDown', + type: Image, +}, { + name: 'fullscreen', + type: Boolean, +}, { + name: 'scrollable', + type: Boolean, +}]; + +var setCardParams = setWindowParams.concat([{ + name: 'title', + type: String, +}, { + name: 'subtitle', + type: String, +}, { + name: 'body', + type: String, +}, { + name: 'icon', + type: Image, +}, { + name: 'subicon', + type: Image, +}, { + name: 'banner', + type: Image, +}, { + name: 'style', + type: Boolean, +}]); + var commands = [{ - name: 'setCard', + name: 'setWindow', + params: setWindowParams, +}, { + name: 'windowShow', params: [{ - name: 'clear', - type: Boolean, - }, { - name: 'title', - type: String, - }, { - name: 'subtitle', - type: String, - }, { - name: 'body', - type: String, - }, { - name: 'icon', - type: Image, - }, { - name: 'subicon', - type: Image, - }, { - name: 'banner', - type: Image, - }, { - name: 'action', - type: Boolean, - }, { - name: 'actionUp', - type: Image, - }, { - name: 'actionSelect', - type: Image, - }, { - name: 'actionDown', - type: Image, - }, { - name: 'fullscreen', - type: Boolean, - }, { - name: 'style', - }, { - name: 'scrollable', - type: Boolean, - }], + name: 'id' + }] +}, { + name: 'windowHide', + params: [{ + name: 'id' + }] +}, { + name: 'setCard', + params: setCardParams, }, { name: 'singleClick', params: [{ @@ -107,11 +127,11 @@ var commands = [{ }, { name: 'down', }], -}, { - name: 'cardHide', }, { name: 'setMenu', params: [{ + name: 'id', + }, { name: 'sections', }], }, { @@ -166,13 +186,6 @@ var commands = [{ }, { name: 'item', }], -}, { - name: 'menuHide', - params: [{ - name: 'section', - }, { - name: 'item', - }], }, { name: 'image', params: [{ @@ -232,9 +245,9 @@ var styleTypes = [ ]; var clearFlagMap = { - text: (1 << 0), - image: (1 << 1), - action: (1 << 2), + action: (1 << 0), + text: (1 << 1), + image: (1 << 2), }; var actionBarTypeMap = { @@ -399,7 +412,7 @@ SimplyPebble.buttonConfig = function(buttonConf) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.card = function(cardDef, clear) { +var toClearFlags = function(clear) { if (clear === 'all') { clear = ~0; } else if (typeof clear === 'string') { @@ -413,6 +426,28 @@ SimplyPebble.card = function(cardDef, clear) { } clear = flags; } + return clear; +}; + +SimplyPebble.window = function(windowDef, clear) { + clear = toClearFlags(clear); + var command = commandMap.setWindow; + var packet = makePacket(command, windowDef); + if (clear) { + packet[command.paramMap.clear.id] = clear; + } + var actionDef = windowDef.action; + if (actionDef) { + if (typeof actionDef === 'boolean') { + actionDef = { action: actionDef }; + } + setPacket(packet, command, actionDef, actionBarTypeMap); + } + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.card = function(cardDef, clear) { + clear = toClearFlags(clear); var command = commandMap.setCard; var packet = makePacket(command, cardDef); if (clear) { @@ -450,14 +485,14 @@ SimplyPebble.vibe = function(type) { }; SimplyPebble.scrollable = function(scrollable) { - var command = commandMap.card; + var command = commandMap.window; var packet = makePacket(command); packet[command.paramMap.scrollable.id] = scrollable ? 1 : 0; SimplyPebble.sendPacket(packet); }; SimplyPebble.fullscreen = function(fullscreen) { - var command = commandMap.card; + var command = commandMap.window; var packet = makePacket(command); packet[command.paramMap.fullscreen.id] = fullscreen ? 1 : 0; SimplyPebble.sendPacket(packet); @@ -548,9 +583,6 @@ SimplyPebble.onAppMessage = function(e) { var button = buttons[payload[1]]; simply.emitClick(command.name, button); break; - case 'cardHide': - simply.emitCardHide(); - break; case 'accelTap': var axis = accelAxes[payload[1]]; simply.emitAccelTap(axis, payload[2]); @@ -589,7 +621,6 @@ SimplyPebble.onAppMessage = function(e) { break; case 'menuSelect': case 'menuLongSelect': - case 'menuHide': simply.emitMenuSelect(command.name, payload[1], payload[2]); break; } diff --git a/lib/simply.js b/lib/simply.js index f63fe0f0..1c352157 100644 --- a/lib/simply.js +++ b/lib/simply.js @@ -14,6 +14,7 @@ var simply = module.exports; simply.ui = {}; +var Window = require('simply/window'); var Card = simply.ui.Card = require('simply/card'); var Menu = simply.ui.Menu = require('simply/menu'); @@ -27,6 +28,7 @@ var buttons = [ var eventTypes = [ 'singleClick', 'longClick', + 'cardShow', 'cardHide', 'accelTap', 'accelData', @@ -34,6 +36,7 @@ var eventTypes = [ 'menuItem', 'menuSelect', 'menuLongSelect', + 'menuShow', 'menuHide', ]; @@ -142,18 +145,17 @@ simply.countHandlers = function(type, subtype) { return count; }; -var setCard = function(card) { - var other = state.card; +var setWindow = function(wind, field) { + field = field || 'window'; + var other = state[field]; if (other) { other.forEachListener(simply.onRemoveHandler); - simply.emitCardHide(); } - state.card = card; - if (card) { - simply.emitCardShow(); - card.forEachListener(simply.onAddHandler); + state[field] = wind; + if (wind) { + wind.forEachListener(simply.onAddHandler); } - return card; + return wind; }; var checkEventType = function(type) { @@ -428,6 +430,33 @@ simply.buttonAutoConfig = function() { } }; +simply.window = function(field, value, clear) { + var wind = state.window; + var result; + var windowDef = myutil.toObject(field, value); + if (typeof clear === 'undefined') { + clear = value; + } + clear = clear ? 'all' : clear; + if (this instanceof Window) { + result = this; + if (wind !== this) { + wind = this; + } + } else { + result = wind.prop(windowDef, clear); + } + if (wind !== state.window) { + wind = setWindow(wind); + util2.copy(wind.state, windowDef); + clear = 'all'; + } + if (wind === result) { + simply.impl.window(windowDef, clear); + } + return result; +}; + simply.card = function(field, value, clear) { var card = state.card; var result; @@ -438,14 +467,17 @@ simply.card = function(field, value, clear) { clear = clear ? 'all' : clear; if (this instanceof Card) { result = this; - if (this !== card) { - card = setCard(this); - util2.copy(card.state, cardDef); - clear = 'all'; + if (card !== this) { + card = this; } } else { result = card.prop(cardDef, clear); } + if (card !== state.card) { + card = setWindow(card, 'card'); + util2.copy(card.state, cardDef); + clear = 'all'; + } if (card === result) { simply.impl.card(cardDef, clear); } @@ -895,6 +927,10 @@ simply.menu = function(menuDef) { } else { menu = new Menu(menuDef); } + if (menu !== state.menu) { + menu = setWindow(menu, 'menu'); + util2.copy(menu.state, menuDef); + } menu._menu(); state.menu = menu; return simply.impl.menu(menu.state); From b1e1d2858943668f7c61f38412b72b7f8f37cc6b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 17:22:35 -0700 Subject: [PATCH 112/791] Stub out unsupported window settings for menus --- lib/simply/menu.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/simply/menu.js b/lib/simply/menu.js index b2ca1dd4..196b106f 100644 --- a/lib/simply/menu.js +++ b/lib/simply/menu.js @@ -14,9 +14,12 @@ util2.inherit(Menu, Window); util2.copy(Emitter.prototype, Menu.prototype); -Menu.prototype.show = function() { - simply.menu.call(this); - return this; +Menu.prototype_prop = function() { + return simply.menu.apply(this, arguments); +}; + +Menu.prototype.action = function() { + throw new Error("Menus don't support action bars."); }; var getMetaSection = function(sectionIndex) { From 5c2563d6199947ef2bc7f2aea2d482fb1f29c6b8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 22:54:44 -0700 Subject: [PATCH 113/791] Move js files from lib to src/js --- {lib => src/js}/README.md | 0 {lib => src/js}/ajax.js | 0 {lib => src/js}/emitter.js | 0 {lib => src/js}/image.js | 0 {lib => src/js}/loader.js | 0 {lib => src/js}/main.js | 0 {lib => src/js}/myutil.js | 0 {lib => src/js}/simply-pebble.js | 0 {lib => src/js}/simply.js | 0 {lib => src/js}/simply/card.js | 0 {lib => src/js}/simply/menu.js | 0 {lib => src/js}/simply/window.js | 0 {lib => src/js}/util2.js | 0 {lib => src/js}/vendor/moment.js | 0 {lib => src/js}/vendor/png.js | 0 {lib => src/js}/vendor/zlib.js | 0 wscript | 4 ++-- 17 files changed, 2 insertions(+), 2 deletions(-) rename {lib => src/js}/README.md (100%) rename {lib => src/js}/ajax.js (100%) rename {lib => src/js}/emitter.js (100%) rename {lib => src/js}/image.js (100%) rename {lib => src/js}/loader.js (100%) rename {lib => src/js}/main.js (100%) rename {lib => src/js}/myutil.js (100%) rename {lib => src/js}/simply-pebble.js (100%) rename {lib => src/js}/simply.js (100%) rename {lib => src/js}/simply/card.js (100%) rename {lib => src/js}/simply/menu.js (100%) rename {lib => src/js}/simply/window.js (100%) rename {lib => src/js}/util2.js (100%) rename {lib => src/js}/vendor/moment.js (100%) rename {lib => src/js}/vendor/png.js (100%) rename {lib => src/js}/vendor/zlib.js (100%) diff --git a/lib/README.md b/src/js/README.md similarity index 100% rename from lib/README.md rename to src/js/README.md diff --git a/lib/ajax.js b/src/js/ajax.js similarity index 100% rename from lib/ajax.js rename to src/js/ajax.js diff --git a/lib/emitter.js b/src/js/emitter.js similarity index 100% rename from lib/emitter.js rename to src/js/emitter.js diff --git a/lib/image.js b/src/js/image.js similarity index 100% rename from lib/image.js rename to src/js/image.js diff --git a/lib/loader.js b/src/js/loader.js similarity index 100% rename from lib/loader.js rename to src/js/loader.js diff --git a/lib/main.js b/src/js/main.js similarity index 100% rename from lib/main.js rename to src/js/main.js diff --git a/lib/myutil.js b/src/js/myutil.js similarity index 100% rename from lib/myutil.js rename to src/js/myutil.js diff --git a/lib/simply-pebble.js b/src/js/simply-pebble.js similarity index 100% rename from lib/simply-pebble.js rename to src/js/simply-pebble.js diff --git a/lib/simply.js b/src/js/simply.js similarity index 100% rename from lib/simply.js rename to src/js/simply.js diff --git a/lib/simply/card.js b/src/js/simply/card.js similarity index 100% rename from lib/simply/card.js rename to src/js/simply/card.js diff --git a/lib/simply/menu.js b/src/js/simply/menu.js similarity index 100% rename from lib/simply/menu.js rename to src/js/simply/menu.js diff --git a/lib/simply/window.js b/src/js/simply/window.js similarity index 100% rename from lib/simply/window.js rename to src/js/simply/window.js diff --git a/lib/util2.js b/src/js/util2.js similarity index 100% rename from lib/util2.js rename to src/js/util2.js diff --git a/lib/vendor/moment.js b/src/js/vendor/moment.js similarity index 100% rename from lib/vendor/moment.js rename to src/js/vendor/moment.js diff --git a/lib/vendor/png.js b/src/js/vendor/png.js similarity index 100% rename from lib/vendor/png.js rename to src/js/vendor/png.js diff --git a/lib/vendor/zlib.js b/src/js/vendor/zlib.js similarity index 100% rename from lib/vendor/zlib.js rename to src/js/vendor/zlib.js diff --git a/wscript b/wscript index eddc38d4..5972df10 100644 --- a/wscript +++ b/wscript @@ -21,7 +21,7 @@ def build(ctx): '-Wno-missing-field-initializers'], target='pebble-app.elf') - js_target = ctx.concat_javascript(js_path='lib') + js_target = ctx.concat_javascript(js_path='src/js') ctx.pbl_bundle(elf='pebble-app.elf', js=js_target) @@ -53,7 +53,7 @@ def concat_javascript(self, *k, **kw): with open(task.outputs[0].abspath(), 'w') as f: f.write('\n'.join(sources)) - js_target = self.path.make_node('src/js/pebble-js-app.js') + js_target = self.path.make_node('build/src/js/pebble-js-app.js') self(rule=concat_javascript_task, source=js_nodes, From e92bf0335a3b5c5c7bc56f184be13b525392169d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 May 2014 22:56:23 -0700 Subject: [PATCH 114/791] Change custom settings to always show regardless of open order --- src/js/simply.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/simply.js b/src/js/simply.js index 1c352157..47e0bbdd 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -757,7 +757,7 @@ simply.openSettings = function(e) { var options; var url; var listener = util2.last(state.webview.listeners); - if (listener && (new Date().getTime() - simply.inited) > 2000) { + if (listener) { url = listener.params.url; options = state.options; e = { From 25cbcd98bd7c160719250d1b0f80da49074d3463 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 24 May 2014 16:22:53 -0700 Subject: [PATCH 115/791] Add absolute path require support --- src/js/simply.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/js/simply.js b/src/js/simply.js index 47e0bbdd..cf418a7b 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -373,7 +373,14 @@ simply.require = function(path) { return package.value; } var basepath = simply.basepath(); - return simply.loadScript(basepath + path, false); + if (path.match(/^\/\//)) { + var m = basepath.match(/^(\w+:)\/\//); + path = (m ? m[1] : 'http:') + path; + } + if (!path.match(/^\w+:\/\//)) { + path = basepath + path; + } + return simply.loadScript(path, false); }; /** From 69c00267bddd35811ef145cae802d321dfa5a79a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 24 May 2014 16:29:13 -0700 Subject: [PATCH 116/791] Add absolute path image support --- src/js/simply.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index cf418a7b..09941570 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -372,14 +372,7 @@ simply.require = function(path) { if (package) { return package.value; } - var basepath = simply.basepath(); - if (path.match(/^\/\//)) { - var m = basepath.match(/^(\w+:)\/\//); - path = (m ? m[1] : 'http:') + path; - } - if (!path.match(/^\w+:\/\//)) { - path = basepath + path; - } + path = baseTransformPath(path); return simply.loadScript(path, false); }; @@ -655,6 +648,18 @@ var parseImageHash = function(hash) { return image; }; +var baseTransformPath = function(path) { + var basepath = simply.basepath(); + if (path.match(/^\/\//)) { + var m = basepath.match(/^(\w+:)\/\//); + path = (m ? m[1] : 'http:') + path; + } + if (!path.match(/^\w+:\/\//)) { + path = basepath + path; + } + return path; +}; + simply.image = function(opt, reset, callback) { if (typeof opt === 'string') { opt = parseImageHash(opt); @@ -663,7 +668,7 @@ simply.image = function(opt, reset, callback) { callback = reset; reset = null; } - var url = simply.basepath() + opt.url; + var url = baseTransformPath(opt.url); var hash = makeImageHash(opt); var image = state.image.cache[hash]; if (image) { From 8b688442749eddb0105aec6e397a691b988525cb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 24 May 2014 16:57:55 -0700 Subject: [PATCH 117/791] Change emitter to call handlers with this --- src/js/emitter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/emitter.js b/src/js/emitter.js index c04be9f3..69b4c082 100644 --- a/src/js/emitter.js +++ b/src/js/emitter.js @@ -99,7 +99,7 @@ var emitToHandlers = function(type, handlers, e) { if (!handlers) { return; } for (var i = 0, ii = handlers.length; i < ii; ++i) { var handler = handlers[i].handler; - if (handler(e, type, i) === false) { + if (handler.call(this, e, type, i) === false) { return false; } } @@ -119,11 +119,11 @@ Emitter.prototype.emit = function(type, subtype, e) { if (!typeMap) { return; } var subtypeMap = typeMap[type]; if (!subtypeMap) { return; } - var hadSubtype = emitToHandlers(type, subtypeMap[subtype], e); + var hadSubtype = emitToHandlers.call(this, type, subtypeMap[subtype], e); if (hadSubtype === false) { return false; } - var hadAll = emitToHandlers(type, subtypeMap.all, e); + var hadAll = emitToHandlers.call(this, type, subtypeMap.all, e); if (hadAll === false) { return false; } From 8cb6bfd73040d1afd74ae37d4d762bd3d1796a60 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 24 May 2014 17:35:00 -0700 Subject: [PATCH 118/791] Remove card and menu event types from being globally accessible --- src/js/simply.js | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 09941570..1348279c 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -26,21 +26,10 @@ var buttons = [ ]; var eventTypes = [ - 'singleClick', - 'longClick', - 'cardShow', - 'cardHide', 'accelTap', 'accelData', - 'menuSection', - 'menuItem', - 'menuSelect', - 'menuLongSelect', - 'menuShow', - 'menuHide', ]; - var state = {}; simply.state = state; @@ -982,7 +971,9 @@ simply.emitCard = function(type, subtype, e, globalType) { if (card && card.emit(type, subtype, e) === false) { return false; } - return simply.emit(globalType || type, subtype, e); + if (globalType) { + return simply.emit(globalType, subtype, e); + } }; simply.emitClick = function(type, button) { @@ -993,11 +984,11 @@ simply.emitClick = function(type, button) { }; simply.emitCardShow = function() { - return simply.emitCard('show', null, {}, 'cardShow'); + return simply.emitCard('show', null, {}); }; simply.emitCardHide = function() { - return simply.emitCard('hide', null, {}, 'cardHide'); + return simply.emitCard('hide', null, {}); }; /** @@ -1013,7 +1004,7 @@ simply.emitAccelTap = function(axis, direction) { axis: axis, direction: direction, }; - return simply.emitCard('accelTap', axis, e); + return simply.emitCard('accelTap', axis, e, 'accelTap'); }; /** @@ -1045,7 +1036,7 @@ simply.emitAccelData = function(accels, callback) { if (callback) { return callback(e); } - return simply.emitCard('accelData', null, e); + return simply.emitCard('accelData', null, e, 'accelData'); }; simply.emitMenu = function(type, subtype, e, globalType) { @@ -1053,7 +1044,9 @@ simply.emitMenu = function(type, subtype, e, globalType) { if (menu && menu.emit(type, subtype, e) === false) { return false; } - return simply.emit(globalType || type, subtype, e); + if (globalType) { + return simply.emit(globalType, subtype, e); + } }; simply.emitMenuSection = function(section) { @@ -1085,6 +1078,10 @@ simply.emitMenuSelect = function(type, section, item) { section: section, item: item, }; + switch (type) { + case 'menuSelect': type = 'select'; break; + case 'menuLongSelect': type = 'longSelect'; break; + } if (simply.emitMenu(type, null, e) === false) { return false; } From 3e44aeb18645558e421b9c43379731ae561e6ba6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 24 May 2014 17:56:24 -0700 Subject: [PATCH 119/791] Rename src/js/simply to src/js/ui --- src/js/pebble-js-app.js | 5662 ------------------------------- src/js/simply.js | 6 +- src/js/{simply => ui}/card.js | 2 +- src/js/{simply => ui}/menu.js | 2 +- src/js/{simply => ui}/window.js | 0 5 files changed, 5 insertions(+), 5667 deletions(-) delete mode 100644 src/js/pebble-js-app.js rename src/js/{simply => ui}/card.js (96%) rename src/js/{simply => ui}/menu.js (99%) rename src/js/{simply => ui}/window.js (100%) diff --git a/src/js/pebble-js-app.js b/src/js/pebble-js-app.js deleted file mode 100644 index 70e4ec5b..00000000 --- a/src/js/pebble-js-app.js +++ /dev/null @@ -1,5662 +0,0 @@ -var __loader = (function() { - -var __loader = {}; - -__loader.packages = {}; -__loader.loaders = {}; -__loader.require = function(path) { - if (!path.match(/\.js$/)) { - path += '.js'; - } - var module = __loader.packages[path]; - if (!module) { - throw new Error("Cannot find module'" + path + "'"); - } - if (module.exports) { - return module.exports; - } - module.exports = {}; - module.loader(module, __loader.require); - module.loaded = true; - return module.exports; -}; - -__loader.define = function(path, loader) { - __loader.packages[path] = { - filename: path, - loader: loader, - }; -}; - -return __loader; - -})(); - -__loader.define("ajax.js", function(module, require) { -/* - * ajax.js by Meiguro - MIT License - */ - -var ajax = (function(){ - -var formify = function(data) { - var params = [], i = 0; - for (var name in data) { - params[i++] = encodeURI(name) + '=' + encodeURI(data[name]); - } - return params.join('&'); -}; - -/** - * ajax options. There are various properties with url being the only required property. - * @typedef ajaxOptions - * @property {string} [method='get'] - The HTTP method to use: 'get', 'post', 'put', 'delete', 'options', - * or any other standard method supported by the running environment. - * @property {string} url - The URL to make the ajax request to. e.g. 'http://www.example.com?name=value' - * @property {string} [type='text'] - The expected response format. Specify 'json' to have ajax parse - * the response as json and pass an object as the data parameter. - * @property {object} [data] - The request body, mainly to be used in combination with 'post' or 'put'. - * e.g. { username: 'guest' } - * @property {object} headers - Custom HTTP headers. Specify additional headers. - * e.g. { 'x-extra': 'Extra Header' } - * @property {boolean} [async=true] - Whether the request will be asynchronous. - * Specify false for a blocking, synchronous request. - * @property {boolean} [cache=true] - Whether the result may be cached. - * Specify false to use the internal cache buster which appends the URL with the query parameter _ - * set to the current time in milliseconds. - */ - -/** - * ajax allows you to make various http or https requests. - * See {@link ajaxOptions} - * @global - * @param {ajaxOptions} opt - Options specifying the type of ajax request to make. - * @param {function} success - The success handler that is called when a HTTP 200 response is given. - * @param {function} failure - The failure handler when the HTTP request fails or is not 200. - */ -var ajax = function(opt, success, failure) { - if (typeof opt === 'string') { - opt = { url: opt }; - } - var method = opt.method || 'GET'; - var url = opt.url; - //console.log(method + ' ' + url); - - var onHandler = ajax.onHandler; - if (onHandler) { - if (success) { success = onHandler('success', success); } - if (failure) { failure = onHandler('failure', failure); } - } - - if (opt.cache === false) { - var appendSymbol = url.indexOf('?') === -1 ? '?' : '&'; - url += appendSymbol + '_=' + new Date().getTime(); - } - - var req = new XMLHttpRequest(); - req.open(method.toUpperCase(), url, opt.async !== false); - - var headers = opt.headers; - if (headers) { - for (var name in headers) { - req.setRequestHeader(name, headers[name]); - } - } - - var data = null; - if (opt.data) { - if (opt.type === 'json') { - req.setRequestHeader('Content-Type', 'application/json'); - data = JSON.stringify(opt.data); - } else { - data = formify(opt.data); - } - } - - req.onreadystatechange = function(e) { - if (req.readyState == 4) { - var body = req.responseText; - if (opt.type == 'json') { - body = JSON.parse(body); - } - var callback = req.status == 200 ? success : failure; - if (callback) { - callback(body, req.status, req); - } - } - }; - - req.send(data); -}; - -ajax.formify = formify; - -if (typeof module !== 'undefined') { - module.exports = ajax; -} - -return ajax; - -})(); - -}); -__loader.define("emitter.js", function(module, require) { - -var Emitter = function() { - this.listeners = {}; -}; - -Emitter.prototype.wrapHandler = function(handler) { - return handler; -}; - -Emitter.prototype.on = function(type, subtype, handler) { - var typeMap = this.listeners || ( this.listeners = {} ); - var subtypeMap = (typeMap[type] || ( typeMap[type] = {} )); - (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ - id: handler, - handler: this.wrapHandler(handler), - }); -}; - -Emitter.prototype.off = function(type, subtype, handler) { - if (!type) { - this.listeners = {}; - return; - } - var typeMap = this.listeners; - if (!handler && subtype === 'all') { - delete typeMap[type]; - return; - } - var subtypeMap = typeMap[type]; - if (!subtypeMap) { return; } - if (!handler) { - delete subtypeMap[subtype]; - return; - } - var handlers = subtypeMap[subtype]; - if (!handlers) { return; } - var index = -1; - for (var i = 0, ii = handlers.length; i < ii; ++i) { - if (handlers[i].id === handler) { - index = i; - break; - } - } - if (index === -1) { return; } - handlers.splice(index, 1); -}; - -var emitToHandlers = function(type, handlers, e) { - if (!handlers) { - return; - } - for (var i = 0, ii = handlers.length; i < ii; ++i) { - var handler = handlers[i].handler; - if (handler(e, type, i) === false) { - return true; - } - } - return false; -}; - -Emitter.prototype.emit = function(type, subtype, e) { - if (!e) { - e = subtype; - subtype = null; - } - e.type = type; - if (subtype) { - e.subtype = subtype; - } - var typeMap = this.listeners; - if (!typeMap) { return; } - var subtypeMap = typeMap[type]; - if (!subtypeMap) { return; } - if (emitToHandlers(type, subtypeMap[subtype], e) === true) { - return true; - } - if (emitToHandlers(type, subtypeMap.all, e) === true) { - return true; - } - return false; -}; - -module.exports = Emitter; - -}); -__loader.define("image.js", function(module, require) { -/* global PNG */ - -var image = {}; - -var getPos = function(width, x, y) { - return y * width * 4 + x * 4; -}; - -var getPixelGrey = function(pixels, pos) { - return ((pixels[pos] + pixels[pos + 1] + pixels[pos + 2]) / 3) & 0xFF; -}; - -image.greyscale = function(pixels, width, height) { - for (var y = 0, yy = height; y < yy; ++y) { - for (var x = 0, xx = width; x < xx; ++x) { - var pos = getPos(width, x, y); - var newColor = getPixelGrey(pixels, pos); - for (var i = 0; i < 3; ++i) { - pixels[pos + i] = newColor; - } - } - } -}; - -image.dithers = {}; - -image.dithers['floyd-steinberg'] = [ - [ 1, 0, 7/16], - [-1, 1, 3/16], - [ 0, 1, 5/16], - [ 1, 1, 1/16]]; - -image.dithers['jarvis-judice-ninke'] = [ - [ 1, 0, 7/48], - [ 2, 0, 5/48], - [-2, 1, 3/48], - [-1, 1, 5/48], - [ 0, 1, 7/48], - [ 1, 1, 5/48], - [ 2, 1, 3/48], - [-2, 2, 1/48], - [-1, 2, 3/48], - [ 0, 2, 5/48], - [ 1, 2, 3/48], - [ 2, 2, 1/48]]; - -image.dithers.sierra = [ - [ 1, 0, 5/32], - [ 2, 0, 3/32], - [-2, 1, 2/32], - [-1, 1, 4/32], - [ 0, 1, 5/32], - [ 1, 1, 4/32], - [ 2, 1, 2/32], - [-1, 2, 2/32], - [ 0, 2, 3/32], - [ 1, 2, 2/32]]; - -image.dithers['default'] = image.dithers.sierra; - -image.dither = function(pixels, width, height, dithers) { - dithers = dithers || image.dithers['default']; - var numdithers = dithers.length; - for (var y = 0, yy = height; y < yy; ++y) { - for (var x = 0, xx = width; x < xx; ++x) { - var pos = getPos(width, x, y); - var oldColor = pixels[pos]; - var newColor = oldColor >= 128 ? 255 : 0; - var error = oldColor - newColor; - pixels[pos] = newColor; - for (var i = 0; i < numdithers; ++i) { - var dither = dithers[i]; - var x2 = x + dither[0], y2 = y + dither[1]; - if (x2 >= 0 && x2 < width && y < height) { - pixels[getPos(width, x2, y2)] += parseInt(error * dither[2]); - } - } - for (var j = 1; j < 3; ++j) { - pixels[pos + j] = newColor; - } - } - } -}; - -image.resizeNearest = function(pixels, width, height, newWidth, newHeight) { - var newPixels = new Array(newWidth * newHeight * 4); - var widthRatio = width / newWidth; - var heightRatio = height / newHeight; - for (var y = 0, yy = newHeight; y < yy; ++y) { - for (var x = 0, xx = newWidth; x < xx; ++x) { - var x2 = parseInt(x * widthRatio); - var y2 = parseInt(y * heightRatio); - var pos2 = getPos(width, x2, y2); - var pos = getPos(newWidth, x, y); - for (var i = 0; i < 4; ++i) { - newPixels[pos + i] = pixels[pos2 + i]; - } - } - } - return newPixels; -}; - -image.resizeSample = function(pixels, width, height, newWidth, newHeight) { - var newPixels = new Array(newWidth * newHeight * 4); - var widthRatio = width / newWidth; - var heightRatio = height / newHeight; - for (var y = 0, yy = newHeight; y < yy; ++y) { - for (var x = 0, xx = newWidth; x < xx; ++x) { - var x2 = Math.min(parseInt(x * widthRatio), width - 1); - var y2 = Math.min(parseInt(y * heightRatio), height - 1); - var pos = getPos(newWidth, x, y); - for (var i = 0; i < 4; ++i) { - newPixels[pos + i] = ((pixels[getPos(width, x2 , y2 ) + i] + - pixels[getPos(width, x2+1, y2 ) + i] + - pixels[getPos(width, x2 , y2+1) + i] + - pixels[getPos(width, x2+1, y2+1) + i]) / 4) & 0xFF; - } - } - } - return newPixels; -}; - -image.resize = function(pixels, width, height, newWidth, newHeight) { - if (newWidth < width || newHeight < height) { - return image.resizeSample.apply(this, arguments); - } else { - return image.resizeNearest.apply(this, arguments); - } -}; - -image.toGbitmap = function(pixels, width, height) { - var rowBytes = width * 4; - - var gpixels = []; - var growBytes = Math.ceil(width / 32) * 4; - for (var i = 0, ii = height * growBytes; i < ii; ++i) { - gpixels[i] = 0; - } - - for (var y = 0, yy = height; y < yy; ++y) { - for (var x = 0, xx = width; x < xx; ++x) { - var grey = 0; - var pos = y * rowBytes + parseInt(x * 4); - for (var j = 0; j < 3; ++j) { - grey += pixels[pos + j]; - } - grey /= 3 * 255; - if (grey >= 0.5) { - var gbytePos = y * growBytes + parseInt(x / 8); - gpixels[gbytePos] += 1 << (x % 8); - } - } - } - - var gbitmap = { - width: width, - height: height, - pixels: gpixels, - }; - - return gbitmap; -}; - -image.load = function(img, callback) { - PNG.load(img.url, function(png) { - var pixels = png.decode(); - var width = png.width; - var height = png.height; - image.greyscale(pixels, width, height); - if (img.width) { - if (!img.height) { - img.height = parseInt(height * (img.width / width)); - } - } else if (img.height) { - if (!img.width) { - img.width = parseInt(width * (img.height / height)); - } - } else { - img.width = width; - img.height = height; - } - if (img.width !== width || img.height !== height) { - pixels = image.resize(pixels, width, height, img.width, img.height); - width = img.width; - height = img.height; - } - if (img.dither) { - var dithers = image.dithers[img.dither]; - image.dither(pixels, width, height, dithers); - } - img.gbitmap = image.toGbitmap(pixels, width, height); - if (callback) { - callback(img); - } - }); - return img; -}; - -module.exports = image; - -}); -__loader.define("main.js", function(module, require) { -var SimplyPebble = require('simply-pebble'); - -Pebble.addEventListener('ready', function(e) { - SimplyPebble.init(); -}); - -}); -__loader.define("simply/card.js", function(module, require) { -var util2 = require('util2'); - -var simply = require('simply'); - -var Emitter = require('emitter'); - -var textParams = [ - 'title', - 'subtitle', - 'body', -]; - -var valueParams = [ - 'icon', - 'subicon', - 'banner', -]; - -var actionParams = [ - 'up', - 'select', - 'back', -]; - -var Card = function(cardDef) { - this.state = cardDef || {}; -}; - -util2.copy(Emitter.prototype, Card.prototype); - -var makeAccessor = function(k) { - return function(value) { - if (arguments.length === 0) { - return this.state[k]; - } else { - this.state[k] = value; - if (simply.card() === this) { - simply.card.call(this, k, value); - } - return this; - } - }; -}; - -textParams.concat(valueParams).forEach(function(k) { - Card.prototype[k] = makeAccessor(k); -}); - -Card.prototype.show = function() { - simply.card.call(this, {}); - return this; -}; - -Card.prototype.action = function(field, value, clear) { - var action = this.state.action; - if (!action) { - action = this.state.action = {}; - } - if (arguments.length === 0) { - return action; - } - if (arguments.length === 1 && typeof field !== 'object') { - return action[field]; - } - var actionDef; - if (typeof field === 'object') { - actionDef = field; - clear = value; - value = null; - } else { - actionDef = {}; - actionDef[field] = value; - } - clear = clear === true ? 'action' : clear; - if (clear === 'all' || clear === 'action') { - this.state.action = actionDef; - } else { - util2.copy(actionDef, this.state.action); - } - simply.action.call(this); - return this; -}; - -module.exports = Card; - -}); -__loader.define("simply-pebble.js", function(module, require) { -var simply = require('simply'); -var util2 = require('util2'); - -var SimplyPebble = {}; - -if (typeof Image === 'undefined') { - window.Image = function(){}; -} - -var commands = [{ - name: 'setCard', - params: [{ - name: 'clear', - type: Boolean, - }, { - name: 'title', - type: String, - }, { - name: 'subtitle', - type: String, - }, { - name: 'body', - type: String, - }, { - name: 'icon', - type: Image, - }, { - name: 'subicon', - type: Image, - }, { - name: 'banner', - type: Image, - }, { - name: 'action', - type: Boolean, - }, { - name: 'actionUp', - type: Image, - }, { - name: 'actionSelect', - type: Image, - }, { - name: 'actionDown', - type: Image, - }, { - name: 'fullscreen', - type: Boolean, - }, { - name: 'style', - }, { - name: 'scrollable', - type: Boolean, - }], -}, { - name: 'singleClick', - params: [{ - name: 'button', - }], -}, { - name: 'longClick', - params: [{ - name: 'button', - }], -}, { - name: 'accelTap', - params: [{ - name: 'axis', - }, { - name: 'direction', - }], -}, { - name: 'vibe', - params: [{ - name: 'type', - }], -}, { - name: 'accelData', - params: [{ - name: 'transactionId', - }, { - name: 'numSamples', - }, { - name: 'accelData', - }], -}, { - name: 'getAccelData', - params: [{ - name: 'transactionId', - }], -}, { - name: 'configAccelData', - params: [{ - name: 'rate', - }, { - name: 'samples', - }, { - name: 'subscribe', - }], -}, { - name: 'configButtons', - params: [{ - name: 'back', - }, { - name: 'up', - }, { - name: 'select', - }, { - name: 'down', - }], -}, { - name: 'cardExit', -}, { - name: 'setMenu', - params: [{ - name: 'sections', - }], -}, { - name: 'setMenuSection', - params: [{ - name: 'section', - }, { - name: 'items', - }, { - name: 'title', - type: String, - }], -}, { - name: 'getMenuSection', - params: [{ - name: 'section', - }], -}, { - name: 'setMenuItem', - params: [{ - name: 'section', - }, { - name: 'item', - }, { - name: 'title', - type: String, - }, { - name: 'subtitle', - type: String, - }, { - name: 'image', - type: Image, - }], -}, { - name: 'getMenuItem', - params: [{ - name: 'section', - }, { - name: 'item', - }], -}, { - name: 'menuSelect', - params: [{ - name: 'section', - }, { - name: 'item', - }], -}, { - name: 'menuLongSelect', - params: [{ - name: 'section', - }, { - name: 'item', - }], -}, { - name: 'menuExit', - params: [{ - name: 'section', - }, { - name: 'item', - }], -}, { - name: 'image', - params: [{ - name: 'id', - }, { - name: 'width', - }, { - name: 'height', - }, { - name: 'pixels', - }], -}]; - -var commandMap = {}; - -for (var i = 0, ii = commands.length; i < ii; ++i) { - var command = commands[i]; - commandMap[command.name] = command; - command.id = i; - - var params = command.params; - if (!params) { - continue; - } - - var paramMap = command.paramMap = {}; - for (var j = 0, jj = params.length; j < jj; ++j) { - var param = params[j]; - paramMap[param.name] = param; - param.id = j + 1; - } -} - -var buttons = [ - 'back', - 'up', - 'select', - 'down', -]; - -var accelAxes = [ - 'x', - 'y', - 'z', -]; - -var vibeTypes = [ - 'short', - 'long', - 'double', -]; - -var styleTypes = [ - 'small', - 'large', - 'mono', -]; - -var clearFlagMap = { - text: (1 << 0), - image: (1 << 1), - action: (1 << 2), -}; - -var actionBarTypeMap = { - up: 'actionUp', - select: 'actionSelect', - down: 'actionDown', -}; - -var SimplyPebble = {}; - -SimplyPebble.init = function() { - simply.impl = SimplyPebble; - simply.init(); -}; - -var getExecPackage = function(execName) { - var packages = simply.packages; - for (var path in packages) { - var package = packages[path]; - if (package && package.execName === execName) { - return path; - } - } -}; - -var getExceptionFile = function(e, level) { - var stack = e.stack.split('\n'); - for (var i = level || 0, ii = stack.length; i < ii; ++i) { - var line = stack[i]; - if (line.match(/^\$\d/)) { - var path = getExecPackage(line); - if (path) { - return path; - } - } - } - return stack[level]; -}; - -var getExceptionScope = function(e, level) { - var stack = e.stack.split('\n'); - for (var i = level || 0, ii = stack.length; i < ii; ++i) { - var line = stack[i]; - if (!line || line.match('native code')) { continue; } - return line.match(/^\$\d/) && getExecPackage(line) || line; - } - return stack[level]; -}; - -var setHandlerPath = function(handler, path, level) { - var level0 = 4; // caller -> wrap -> apply -> wrap -> set - handler.path = path || getExceptionScope(new Error(), (level || 0) + level0) || simply.basename(); - return handler; -}; - -var papply = function(f, args, path) { - try { - return f.apply(this, args); - } catch (e) { - var scope = !path && getExceptionFile(e) || getExecPackage(path) || path; - console.log(scope + ':' + e.line + ': ' + e + '\n' + e.stack); - simply.text({ - subtitle: scope, - body: e.line + ' ' + e.message, - }, true); - simply.state.run = false; - } -}; - -var protect = function(f, path) { - return function() { - return papply(f, arguments, path); - }; -}; - -SimplyPebble.wrapHandler = function(handler, level) { - if (!handler) { return; } - setHandlerPath(handler, null, level || 1); - var package = simply.packages[handler.path]; - if (package) { - return protect(package.fwrap(handler), handler.path); - } else { - return protect(handler, handler.path); - } -}; - -var toSafeName = function(name) { - name = name.replace(/[^0-9A-Za-z_$]/g, '_'); - if (name.match(/^[0-9]/)) { - name = '_' + name; - } - return name; -}; - -SimplyPebble.loadPackage = function(pkg, loader) { - pkg.execName = '$' + simply.state.numPackages++ + toSafeName(pkg.name); - pkg.fapply = simply.defun(pkg.execName, ['f', 'args'], 'return f.apply(this, args)'); - pkg.fwrap = function(f) { return function() { return pkg.fapply(f, arguments); }; }; - - return papply(loader, null, pkg.name); -}; - -SimplyPebble.onShowConfiguration = function(e) { - simply.openSettings(e); -}; - -SimplyPebble.onWebViewClosed = function(e) { - simply.closeSettings(e); -}; - -var toParam = function(param, v) { - if (param.type === String) { - v = v.toString(); - } else if (param.type === Boolean) { - v = v ? 1 : 0; - } else if (param.type === Image && typeof v !== 'number') { - v = simply.image(v); - } - return v; -}; - -var setPacket = function(packet, command, def, typeMap) { - var paramMap = command.paramMap; - for (var k in def) { - var paramName = typeMap && typeMap[k] || k; - if (!paramName) { continue; } - var param = paramMap[paramName]; - if (param) { - packet[param.id] = toParam(param, def[k]); - } - } - return packet; -}; - -var makePacket = function(command, def) { - var packet = {}; - packet[0] = command.id; - if (def) { - setPacket(packet, command, def); - } - return packet; -}; - -SimplyPebble.sendPacket = function(packet) { - if (!simply.state.run) { - return; - } - var send; - send = function() { - Pebble.sendAppMessage(packet, util2.noop, send); - }; - send(); -}; - -SimplyPebble.setMenu = function() { - SimplyPebble.sendPacket(makePacket(commandMap.setMenu)); -}; - -SimplyPebble.buttonConfig = function(buttonConf) { - var command = commandMap.configButtons; - var packet = makePacket(command, buttonConf); - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.card = function(cardDef, clear) { - if (clear === 'all') { - clear = ~0; - } else if (typeof clear === 'string') { - clear = clearFlagMap[clear]; - } else if (typeof clear === 'object') { - var flags = 0; - for (var k in clear) { - if (clear[k] === true) { - flags |= clearFlagMap[k]; - } - } - clear = flags; - } - var command = commandMap.setCard; - var packet = makePacket(command, cardDef); - if (clear) { - packet[command.paramMap.clear.id] = clear; - } - var actionDef = cardDef.action; - if (actionDef) { - if (typeof actionDef === 'boolean') { - actionDef = { action: actionDef }; - } - setPacket(packet, command, actionDef, actionBarTypeMap); - } - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.textfield = function(field, text, clear) { - var command = commandMap.setCard; - var packet = makePacket(command); - var param = command.paramMap[field]; - if (param) { - packet[param.id] = text.toString(); - } - if (clear) { - packet[command.paramMap.clear.id] = true; - } - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.vibe = function(type) { - var command = commandMap.vibe; - var packet = makePacket(command); - var vibeIndex = vibeTypes.indexOf(type); - packet[command.paramMap.type.id] = vibeIndex !== -1 ? vibeIndex : 0; - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.scrollable = function(scrollable) { - var command = commandMap.card; - var packet = makePacket(command); - packet[command.paramMap.scrollable.id] = scrollable ? 1 : 0; - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.fullscreen = function(fullscreen) { - var command = commandMap.card; - var packet = makePacket(command); - packet[command.paramMap.fullscreen.id] = fullscreen ? 1 : 0; - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.style = function(type) { - var command = commandMap.card; - var packet = makePacket(command); - var styleIndex = styleTypes.indexOf(type); - packet[command.paramMap.type.id] = styleIndex !== -1 ? styleIndex : 1; - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.accelConfig = function(configDef) { - var command = commandMap.configAccelData; - var packet = makePacket(command, configDef); - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.accelPeek = function(callback) { - simply.state.accel.listeners.push(callback); - var command = commandMap.getAccelData; - var packet = makePacket(command); - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.menu = function(menuDef) { - var command = commandMap.setMenu; - var packetDef = util2.copy(menuDef); - if (packetDef.sections instanceof Array) { - packetDef.sections = packetDef.sections.length; - } - var packet = makePacket(command, packetDef); - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.menuSection = function(sectionIndex, sectionDef) { - var command = commandMap.setMenuSection; - var packetDef = util2.copy(sectionDef); - packetDef.section = sectionIndex; - if (packetDef.items instanceof Array) { - packetDef.items = packetDef.items.length; - } - var packet = makePacket(command, packetDef); - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.menuItem = function(sectionIndex, itemIndex, itemDef) { - var command = commandMap.setMenuItem; - var packetDef = util2.copy(itemDef); - packetDef.section = sectionIndex; - packetDef.item = itemIndex; - var packet = makePacket(command, packetDef); - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.image = function(id, gbitmap) { - var command = commandMap.image; - var packetDef = util2.copy(gbitmap); - packetDef.id = id; - var packet = makePacket(command, packetDef); - SimplyPebble.sendPacket(packet); -}; - -var readInt = function(packet, width, pos, signed) { - var value = 0; - pos = pos || 0; - for (var i = 0; i < width; ++i) { - value += (packet[pos + i] & 0xFF) << (i * 8); - } - if (signed) { - var mask = 1 << (width * 8 - 1); - if (value & mask) { - value = value - (((mask - 1) << 1) + 1); - } - } - return value; -}; - -SimplyPebble.onAppMessage = function(e) { - var payload = e.payload; - var code = payload[0]; - var command = commands[code]; - - switch (command.name) { - case 'singleClick': - case 'longClick': - var button = buttons[payload[1]]; - simply.emitClick(command.name, button); - break; - case 'cardExit': - simply.emitCardExit(); - break; - case 'accelTap': - var axis = accelAxes[payload[1]]; - simply.emitAccelTap(axis, payload[2]); - break; - case 'accelData': - var transactionId = payload[1]; - var samples = payload[2]; - var data = payload[3]; - var accels = []; - for (var i = 0; i < samples; i++) { - var pos = i * 15; - var accel = { - x: readInt(data, 2, pos, true), - y: readInt(data, 2, pos + 2, true), - z: readInt(data, 2, pos + 4, true), - vibe: readInt(data, 1, pos + 6) ? true : false, - time: readInt(data, 8, pos + 7), - }; - accels[i] = accel; - } - if (typeof transactionId === 'undefined') { - simply.emitAccelData(accels); - } else { - var handlers = simply.state.accel.listeners; - simply.state.accel.listeners = []; - for (var j = 0, jj = handlers.length; j < jj; ++j) { - simply.emitAccelData(accels, handlers[j]); - } - } - break; - case 'getMenuSection': - simply.emitMenuSection(payload[1]); - break; - case 'getMenuItem': - simply.emitMenuItem(payload[1], payload[2]); - break; - case 'menuSelect': - case 'menuLongSelect': - case 'menuExit': - simply.emitMenuSelect(command.name, payload[1], payload[2]); - break; - } -}; - -Pebble.addEventListener('showConfiguration', SimplyPebble.onShowConfiguration); -Pebble.addEventListener('webviewclosed', SimplyPebble.onWebViewClosed); -Pebble.addEventListener('appmessage', SimplyPebble.onAppMessage); - -module.exports = SimplyPebble; - -}); -__loader.define("simply.js", function(module, require) { -/** - * Simply.js - * @namespace simply - */ - -var ajax = require('ajax'); -var util2 = require('util2'); -var imagelib = require('image'); - -var Emitter = require('emitter'); - -var simply = module.exports; - -simply.ui = {}; - -var Card = simply.ui.Card = require('simply/card'); - -var buttons = [ - 'back', - 'up', - 'select', - 'down', -]; - -var eventTypes = [ - 'singleClick', - 'longClick', - 'cardExit', - 'accelTap', - 'accelData', - 'menuSection', - 'menuItem', - 'menuSelect', - 'menuLongSelect', - 'menuExit', -]; - - -var state = {}; - -simply.state = state; -simply.packages = {}; -simply.listeners = {}; - -simply.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; - -simply.init = function() { - if (!simply.inited) { - simply.inited = new Date().getTime(); - - ajax.onHandler = function(type, handler) { - return simply.wrapHandler(handler, 2); - }; - } - - simply.loadMainScript(); -}; - -simply.wrapHandler = function(handler) { - return simply.impl.wrapHandler.apply(this, arguments); -}; - -simply.reset = function() { - if (state.emitter) { - simply.off(); - } - - simply.state = state = {}; - simply.packages = {}; - - state.run = true; - state.numPackages = 0; - state.options = {}; - - var emitter = new Emitter(); - emitter.wrapHandler = simply.wrapHandler; - state.emitter = emitter; - - state.card = new Card(); - - state.button = { - config: {}, - configMode: 'auto', - }; - for (var i = 0, ii = buttons.length; i < ii; i++) { - var button = buttons[i]; - if (button !== 'back') { - state.button.config[buttons[i]] = true; - } - } - - state.image = { - cache: {}, - nextId: 1, - }; - - state.webview = { - listeners: [], - }; - - simply.accelInit(); -}; - -/** - * Simply.js event handler callback. - * @callback simply.eventHandler - * @param {simply.event} event - The event object with event specific information. - */ - -var isBackEvent = function(type, subtype) { - return ((type === 'singleClick' || type === 'longClick') && subtype === 'back'); -}; - -simply.onAddHandler = function(type, subtype) { - if (isBackEvent(type, subtype)) { - simply.buttonAutoConfig(); - } else if (type === 'accelData') { - simply.accelAutoSubscribe(); - } -}; - -simply.onRemoveHandler = function(type, subtype) { - if (!type || isBackEvent(type, subtype)) { - simply.buttonAutoConfig(); - } - if (!type || type === 'accelData') { - simply.accelAutoSubscribe(); - } -}; - -simply.countHandlers = function(type, subtype) { - if (!subtype) { - subtype = 'all'; - } - var typeMap = simply.listeners; - var subtypeMap = typeMap[type]; - if (!subtypeMap) { - return 0; - } - var handlers = subtypeMap[subtype]; - return handlers ? handlers.length : 0; -}; - -var checkEventType = function(type) { - if (eventTypes.indexOf(type) === -1) { - throw new Error('Invalid event type: ' + type); - } -}; - -/** - * Subscribe to Pebble events. - * See {@link simply.event} for the possible event types to subscribe to. - * Subscribing to a Pebble event requires a handler. An event object will be passed to your handler with event information. - * Events can have a subtype which can be used to filter events before the handler is called. - * @memberOf simply - * @param {string} type - The event type. - * @param {string} [subtype] - The event subtype. - * @param {simply.eventHandler} handler - The event handler. The handler will be called with corresponding event. - * @see simply.event - */ -simply.on = function(type, subtype, handler) { - if (type) { - checkEventType(type); - } - if (!handler) { - handler = subtype; - subtype = 'all'; - } - state.emitter.on(type, subtype, handler); - simply.onAddHandler(type, subtype); -}; - -/** - * Unsubscribe from Pebble events. - * When called without a handler, all handlers of the type and subtype are unsubscribe. - * When called with no parameters, all handlers are unsubscribed. - * @memberOf simply - * @param {string} type - The event type. - * @param {string} [subtype] - The event subtype. - * @param {function} [handler] - The event handler to unsubscribe. - * @see simply.on - */ -simply.off = function(type, subtype, handler) { - if (type) { - checkEventType(type); - } - if (!handler) { - handler = subtype; - subtype = 'all'; - } - state.emitter.off(type, subtype, handler); - simply.onRemoveHandler(type, subtype); -}; - -simply.emit = function(type, subtype, handler) { - state.emitter.emit(type, subtype, handler); -}; - -var pathToName = function(path) { - var name = path; - if (typeof name === 'string') { - name = name.replace(simply.basepath(), ''); - } - return name || simply.basename(); -}; - -simply.getPackageByPath = function(path) { - return simply.packages[pathToName(path)]; -}; - -simply.makePackage = function(path) { - var name = pathToName(path); - var saveName = 'script:' + path; - var pkg = simply.packages[name]; - - if (!pkg) { - pkg = simply.packages[name] = { - name: name, - saveName: saveName, - filename: path - }; - } - - return pkg; -}; - -simply.defun = function(fn, fargs, fbody) { - if (!fbody) { - fbody = fargs; - fargs = []; - } - return new Function('return function ' + fn + '(' + fargs.join(', ') + ') {' + fbody + '}')(); -}; - -var slog = function() { - var args = []; - for (var i = 0, ii = arguments.length; i < ii; ++i) { - args[i] = util2.toString(arguments[i]); - } - return args.join(' '); -}; - -simply.fexecPackage = function(script, pkg) { - // console shim - var console2 = simply.console2 = {}; - for (var k in console) { - console2[k] = console[k]; - } - - console2.log = function() { - var msg = pkg.name + ': ' + slog.apply(this, arguments); - var width = 45; - var prefix = (new Array(width + 1)).join('\b'); // erase Simply.js source line - var suffix = msg.length < width ? (new Array(width - msg.length + 1)).join(' ') : 0; - console.log(prefix + msg + suffix); - }; - - // loader - return function() { - var exports = pkg.exports; - var result = simply.defun(pkg.execName, - ['module', 'require', 'console', 'Pebble', 'simply'], script) - (pkg, simply.require, console2, Pebble, simply); - - // backwards compatibility for return-style modules - if (pkg.exports === exports && result) { - pkg.exports = result; - } - - return pkg.exports; - }; -}; - -simply.loadScript = function(scriptUrl, async) { - console.log('loading: ' + scriptUrl); - - var pkg = simply.makePackage(scriptUrl); - pkg.exports = {}; - - var loader = util2.noop; - var useScript = function(script) { - loader = simply.fexecPackage(script, pkg); - }; - - ajax({ url: scriptUrl, cache: false, async: async }, function(data) { - if (data && data.length) { - localStorage.setItem(pkg.saveName, data); - useScript(data); - } - }, function(data, status) { - data = localStorage.getItem(pkg.saveName); - if (data && data.length) { - console.log(status + ': failed, loading saved script instead'); - useScript(data); - } - }); - - return simply.impl.loadPackage.call(this, pkg, loader); -}; - -simply.loadMainScriptUrl = function(scriptUrl) { - if (typeof scriptUrl === 'string' && scriptUrl.length && !scriptUrl.match(/^(\w+:)?\/\//)) { - scriptUrl = 'http://' + scriptUrl; - } - - if (scriptUrl) { - localStorage.setItem('mainJsUrl', scriptUrl); - } else { - scriptUrl = localStorage.getItem('mainJsUrl'); - } - - return scriptUrl; -}; - -simply.loadMainScript = function(scriptUrl) { - simply.reset(); - scriptUrl = simply.loadMainScriptUrl(scriptUrl); - if (!scriptUrl) { - return; - } - simply.loadOptions(); - try { - simply.loadScript(scriptUrl, false); - } catch (e) { - simply.text({ - title: 'Failed to load', - body: scriptUrl, - }, true); - return; - } -}; - -simply.basepath = function(path) { - path = path || localStorage.getItem('mainJsUrl'); - return path.replace(/[^\/]*$/, ''); -}; - -simply.basename = function(path) { - path = path || localStorage.getItem('mainJsUrl'); - return path.match(/[^\/]*$/)[0]; -}; - -/** - * Loads external dependencies, allowing you to write a multi-file project. - * Package loading loosely follows the CommonJS format. - * Exporting is possible by modifying or setting module.exports within the required file. - * The module path is also available as module.path. - * This currently only supports a relative path to another JavaScript file. - * @global - * @param {string} path - The path to the dependency. - */ -simply.require = function(path) { - if (!path.match(/\.js$/)) { - path += '.js'; - } - var package = simply.packages[path]; - if (package) { - return package.value; - } - var basepath = simply.basepath(); - return simply.loadScript(basepath + path, false); -}; - -/** - * The button configuration parameter for {@link simply.buttonConfig}. - * The button configuration allows you to enable to disable buttons without having to register or unregister handlers if that is your preferred style. - * You may also enable the back button manually as an alternative to registering a click handler with 'back' as its subtype using {@link simply.on}. - * @typedef {object} simply.buttonConf - * @property {boolean} [back] - Whether to enable the back button. Initializes as false. Simply.js can also automatically register this for you based on the amount of click handlers with subtype 'back'. - * @property {boolean} [up] - Whether to enable the up button. Initializes as true. Note that this is disabled when using {@link simply.scrollable}. - * @property {boolean} [select] - Whether to enable the select button. Initializes as true. - * @property {boolean} [down] - Whether to enable the down button. Initializes as true. Note that this is disabled when using {@link simply.scrollable}. - */ - -/** - * Changes the button configuration. - * See {@link simply.buttonConfig} - * @memberOf simply - * @param {simply.buttonConfig} buttonConf - An object defining the button configuration. - */ -simply.buttonConfig = function(buttonConf, auto) { - var buttonState = state.button; - if (typeof buttonConf === 'undefined') { - var config = {}; - for (var i = 0, ii = buttons.length; i < ii; ++i) { - var name = buttons[i]; - config[name] = buttonConf.config[name]; - } - return config; - } - for (var k in buttonConf) { - if (buttons.indexOf(k) !== -1) { - if (k === 'back') { - buttonState.configMode = buttonConf.back && !auto ? 'manual' : 'auto'; - } - buttonState.config[k] = buttonConf[k]; - } - } - if (simply.impl.buttonConfig) { - return simply.impl.buttonConfig(buttonState.config); - } -}; - -simply.buttonAutoConfig = function() { - var buttonState = state.button; - if (!buttonState || buttonState.configMode !== 'auto') { - return; - } - var singleBackCount = simply.countHandlers('singleClick', 'back'); - var longBackCount = simply.countHandlers('longClick', 'back'); - var useBack = singleBackCount + longBackCount > 0; - if (useBack !== buttonState.config.back) { - buttonState.config.back = useBack; - return simply.buttonConfig(buttonState.config, true); - } -}; - -simply.card = function(field, value, clear) { - if (this instanceof Card && !this.state) { - return Card.call(this); - } - var card = state.card; - if (arguments.length === 0) { - return card; - } - if (arguments.length === 1 && typeof field !== 'object') { - return card.state[field]; - } - var cardDef; - if (typeof field === 'object') { - cardDef = field; - value = null; - } else { - cardDef = {}; - cardDef[field] = value; - } - if (this instanceof Card) { - if (this !== card) { - util2.copy(this.state, cardDef); - card = this; - } - } else { - util2.copy(cardDef, card.state); - } - if (state.card !== card) { - state.card = card; - clear = 'all'; - } else { - clear = clear === true ? 'all' : clear; - if (clear === 'all') { - card = state.card = new Card(cardDef); - } - } - simply.impl.card(cardDef, clear); - return card; -}; - -/** - * The text definition parameter for {@link simply.text}. - * @typedef {object} simply.textDef - * @property {string} [title] - A new title for the first and largest text field. - * @property {string} [subtitle] - A new subtitle for the second large text field. - * @property {string} [body] - A new body for the last text field meant to display large bodies of text. - */ - -/** - * Sets a group of text fields at once. - * For example, passing a text definition { title: 'A', subtitle: 'B', body: 'C' } - * will set the title, subtitle, and body simultaneously. Not all fields need to be specified. - * When setting a single field, consider using the specific text setters simply.title, simply.subtitle, simply.body. - * @memberOf simply - * @param {simply.textDef} textDef - An object defining new text values. - * @param {boolean} [clear] - If true, all other text fields will be cleared. - */ -simply.text = simply.card; - -simply.setText = simply.text; - -var textfield = function(field, text, clear) { - if (arguments.length <= 1) { - return state.card[field]; - } - if (clear) { - state.card = {}; - } - state.card[field] = text; - return simply.impl.textfield(field, text, clear); -}; - -/** - * Sets the title field. The title field is the first and largest text field available. - * @memberOf simply - * @param {string} text - The desired text to display. - * @param {boolean} [clear] - If true, all other text fields will be cleared. - */ -simply.title = function(text, clear) { - return textfield('title', text, clear); -}; - -/** - * Sets the subtitle field. The subtitle field is the second large text field available. - * @memberOf simply - * @param {string} text - The desired text to display. - * @param {boolean} [clear] - If true, all other text fields will be cleared. - */ -simply.subtitle = function(text, clear) { - return textfield('subtitle', text, clear); -}; - -/** - * Sets the body field. The body field is the last text field available meant to display large bodies of text. - * This can be used to display entire text interfaces. - * You may even clear the title and subtitle fields in order to display more in the body field. - * @memberOf simply - * @param {string} text - The desired text to display. - * @param {boolean} [clear] - If true, all other text fields will be cleared. - */ -simply.body = function(text, clear) { - return textfield('body', text, clear); -}; - -simply.action = function(field, image, clear) { - var card = state.card; - var result = this instanceof Card ? this : card.action(field, image, clear); - if (card === result) { - clear = clear === true ? 'action' : clear; - simply.impl.card({ action: card.state.action }, clear); - } - return state.card.action; -}; - -/** - * Vibrates the Pebble. - * There are three support vibe types: short, long, and double. - * @memberOf simply - * @param {string} [type=short] - The vibe type. - */ -simply.vibe = function() { - return simply.impl.vibe.apply(this, arguments); -}; - -/** - * Enable scrolling in the Pebble UI. - * When scrolling is enabled, up and down button presses are no longer forwarded to JavaScript handlers. - * Single select, long select, and accel tap events are still available to you however. - * @memberOf simply - * @param {boolean} scrollable - Whether to enable a scrollable view. - */ - -simply.scrollable = function(scrollable) { - if (typeof scrollable === 'undefined') { - return state.scrollable === true; - } - state.scrollable = scrollable; - return simply.impl.scrollable.apply(this, arguments); -}; - -/** - * Enable fullscreen in the Pebble UI. - * Fullscreen removes the Pebble status bar, giving slightly more vertical display height. - * @memberOf simply - * @param {boolean} fullscreen - Whether to enable fullscreen mode. - */ - -simply.fullscreen = function(fullscreen) { - if (typeof fullscreen === 'undefined') { - return state.fullscreen === true; - } - state.fullscreen = fullscreen; - return simply.impl.fullscreen.apply(this, arguments); -}; - -/** - * Set the Pebble UI style. - * The available styles are 'small', 'large', and 'mono'. Small and large correspond to the system notification styles. - * Mono sets a monospace font for the body textfield, enabling more complex text UIs or ASCII art. - * @memberOf simply - * @param {string} type - The type of style to set: 'small', 'large', or 'mono'. - */ - -simply.style = function(type) { - return simply.impl.style.apply(this, arguments); -}; - -var makeImageHash = function(image) { - var url = image.url; - var hashPart = ''; - if (image.width) { - hashPart += ',width:' + image.width; - } - if (image.height) { - hashPart += ',height:' + image.height; - } - if (image.dither) { - hashPart += ',dither:' + image.dither; - } - if (hashPart) { - url += '#' + hashPart.substr(1); - } - return url; -}; - -var parseImageHash = function(hash) { - var image = {}; - hash = hash.split('#'); - image.url = hash[0]; - hash = hash[1]; - if (!hash) { return image; } - var args = hash.split(','); - for (var i = 0, ii = args.length; i < ii; ++i) { - var arg = args[i]; - if (arg.match(':')) { - arg = arg.split(':'); - var v = arg[1]; - image[arg[0]] = !isNaN(Number(v)) ? Number(v) : v; - } else { - image[arg] = true; - } - } - return image; -}; - -simply.image = function(opt, reset, callback) { - if (typeof opt === 'string') { - opt = parseImageHash(opt); - } - if (typeof reset === 'function') { - callback = reset; - reset = null; - } - var url = simply.basepath() + opt.url; - var hash = makeImageHash(opt); - var image = state.image.cache[hash]; - if (image) { - if ((opt.width && image.width !== opt.width) || - (opt.height && image.height !== opt.height) || - (opt.dither && image.dither !== opt.dither)) { - reset = true; - } - if (reset !== true) { - return image.id; - } - } - image = { - id: state.image.nextId++, - url: url, - width: opt.width, - height: opt.height, - dither: opt.dither, - }; - state.image.cache[hash] = image; - imagelib.load(image, function() { - simply.impl.image(image.id, image.gbitmap); - if (callback) { - var e = { - type: 'image', - image: image.id, - url: image.url, - }; - callback(e); - } - }); - return image.id; -}; - -var getOptionsKey = function() { - return 'options:' + simply.basename(); -}; - -simply.saveOptions = function() { - var options = state.options; - localStorage.setItem(getOptionsKey(), JSON.stringify(options)); -}; - -simply.loadOptions = function() { - state.options = {}; - var options = localStorage.getItem(getOptionsKey()); - try { - state.options = JSON.parse(options); - } catch (e) {} -}; - -simply.option = function(key, value) { - var options = state.options; - if (arguments.length >= 2) { - if (typeof value === 'undefined') { - delete options[key]; - } else { - try { - value = JSON.stringify(value); - } catch (e) {} - options[key] = '' + value; - } - simply.saveOptions(); - } - value = options[key]; - if (!isNaN(Number(value))) { - return Number(value); - } - try { - value = JSON.parse(value); - } catch (e) {} - return value; -}; - -simply.getBaseOptions = function() { - return { - scriptUrl: localStorage.getItem('mainJsUrl'), - }; -}; - -simply.settings = function(opt, open, close) { - if (typeof opt === 'string') { - opt = { url: opt }; - } - if (typeof close === 'undefined') { - close = open; - open = util2.noop; - } - var listener = { - params: opt, - open: open, - close: close, - }; - state.webview.listeners.push(listener); -}; - -simply.openSettings = function(e) { - var options; - var url; - var listener = util2.last(state.webview.listeners); - if (listener && (new Date().getTime() - simply.inited) > 2000) { - url = listener.params.url; - options = state.options; - e = { - originalEvent: e, - options: options, - url: listener.params.url, - }; - listener.open(e); - } else { - url = simply.settingsUrl; - options = simply.getBaseOptions(); - } - var hash = encodeURIComponent(JSON.stringify(options)); - Pebble.openURL(url + '#' + hash); -}; - -simply.closeSettings = function(e) { - var listener = util2.last(state.webview.listeners); - var options = {}; - if (e.response) { - options = JSON.parse(decodeURIComponent(e.response)); - } - if (listener) { - e = { - originalEvent: e, - options: options, - url: listener.params.url, - }; - return listener.close(e); - } - if (options.scriptUrl) { - simply.loadMainScript(options.scriptUrl); - } -}; - -simply.accelInit = function() { - state.accel = { - rate: 100, - samples: 25, - subscribe: false, - subscribeMode: 'auto', - listeners: [], - }; -}; - -simply.accelAutoSubscribe = function() { - var accelState = state.accel; - if (!accelState || accelState.subscribeMode !== 'auto') { - return; - } - var subscribe = simply.countHandlers('accelData') > 0; - if (subscribe !== state.accel.subscribe) { - return simply.accelConfig(subscribe, true); - } -}; - -/** - * The accelerometer configuration parameter for {@link simply.accelConfig}. - * The accelerometer data stream is useful for applications such as gesture recognition when accelTap is too limited. - * However, keep in mind that smaller batch sample sizes and faster rates will drastically impact the battery life of both the Pebble and phone because of the taxing use of the processors and Bluetooth modules. - * @typedef {object} simply.accelConf - * @property {number} [rate] - The rate accelerometer data points are generated in hertz. Valid values are 10, 25, 50, and 100. Initializes as 100. - * @property {number} [samples] - The number of accelerometer data points to accumulate in a batch before calling the event handler. Valid values are 1 to 25 inclusive. Initializes as 25. - * @property {boolean} [subscribe] - Whether to subscribe to accelerometer data events. {@link simply.accelPeek} cannot be used when subscribed. Simply.js will automatically (un)subscribe for you depending on the amount of accelData handlers registered. - */ - -/** - * Changes the accelerometer configuration. - * See {@link simply.accelConfig} - * @memberOf simply - * @param {simply.accelConfig} accelConf - An object defining the accelerometer configuration. - */ -simply.accelConfig = function(opt, auto) { - var accelState = state.accel; - if (typeof opt === 'undefined') { - return { - rate: accelState.rate, - samples: accelState.samples, - subscribe: accelState.subscribe, - }; - } else if (typeof opt === 'boolean') { - opt = { subscribe: opt }; - } - for (var k in opt) { - if (k === 'subscribe') { - accelState.subscribeMode = opt[k] && !auto ? 'manual' : 'auto'; - } - accelState[k] = opt[k]; - } - return simply.impl.accelConfig.apply(this, arguments); -}; - -/** - * Peeks at the current accelerometer values. - * @memberOf simply - * @param {simply.eventHandler} callback - A callback function that will be provided the accel data point as an event. - */ -simply.accelPeek = function(callback) { - if (state.accel.subscribe) { - throw new Error('Cannot use accelPeek when listening to accelData events'); - } - return simply.impl.accelPeek.apply(this, arguments); -}; - -var getMenuSection = function(e) { - var menu = e.menu || state.menu; - if (!menu) { return; } - if (!(menu.sections instanceof Array)) { return; } - return menu.sections[e.section]; -}; - -var getMenuItem = function(e) { - var section = getMenuSection(e); - if (!section) { return; } - if (!(section.items instanceof Array)) { return; } - return section.items[e.item]; -}; - -simply.onMenuSection = function(e) { - var section = getMenuSection(e); - if (!section) { return; } - simply.menuSection(e.section, section); -}; - -simply.onMenuItem = function(e) { - var item = getMenuItem(e); - if (!item) { return; } - simply.menuItem(e.section, e.item, item); -}; - -simply.onMenuSelect = function(e) { - var menu = e.menu; - var item = getMenuItem(e); - if (!item) { return; } - switch (e.type) { - case 'menuSelect': - if (typeof item.select === 'function') { - if (item.select(e) === false) { - return false; - } - } - break; - case 'menuLongSelect': - if (typeof item.longSelect === 'function') { - if (item.longSelect(e) === false) { - return false; - } - } - break; - case 'menuExit': - if (typeof item.exit === 'function') { - if (item.exit(e) === false) { - return false; - } - } - if (typeof menu.exit === 'function') { - if (menu.exit(e) === false) { - return false; - } - } - break; - } -}; - -simply.menu = function(menuDef) { - if (!menuDef) { - return state.menu; - } - state.menu = menuDef; - return simply.impl.menu.apply(this, arguments); -}; - -simply.menuSection = function(sectionIndex, sectionDef) { - if (typeof sectionIndex === 'undefined') { - return getMenuSection({ section: sectionIndex }); - } - return simply.impl.menuSection.apply(this, arguments); -}; - -simply.menuItem = function(sectionIndex, itemIndex, itemDef) { - if (typeof sectionIndex === 'undefined') { - return getMenuItem({ section: sectionIndex, item: itemIndex }); - } - return simply.impl.menuItem.apply(this, arguments); -}; - -/** - * Simply.js event. See all the possible event types. Subscribe to events using {@link simply.on}. - * @typedef simply.event - * @see simply.clickEvent - * @see simply.accelTapEvent - * @see simply.accelDataEvent - */ - -/** - * Simply.js button click event. This can either be a single click or long click. - * Use the event type 'singleClick' or 'longClick' to subscribe to these events. - * @typedef simply.clickEvent - * @property {string} button - The button that was pressed: 'back', 'up', 'select', or 'down'. This is also the event subtype. - */ - -simply.emitClick = function(type, button) { - simply.emit(type, button, { - button: button, - }); -}; - -simply.emitCardExit = function() { - var cardDef = state.card; - simply.emit('cardExit', util2.copy(cardDef, { - card: cardDef - })); -}; - -/** - * Simply.js accel tap event. - * Use the event type 'accelTap' to subscribe to these events. - * @typedef simply.accelTapEvent - * @property {string} axis - The axis the tap event occurred on: 'x', 'y', or 'z'. This is also the event subtype. - * @property {number} direction - The direction of the tap along the axis: 1 or -1. - */ - -simply.emitAccelTap = function(axis, direction) { - simply.emit('accelTap', axis, { - axis: axis, - direction: direction, - }); -}; - -/** - * Simply.js accel data point. - * Typical values for gravity is around -1000 on the z axis. - * @typedef simply.accelPoint - * @property {number} x - The acceleration across the x-axis. - * @property {number} y - The acceleration across the y-axis. - * @property {number} z - The acceleration across the z-axis. - * @property {boolean} vibe - Whether the watch was vibrating when measuring this point. - * @property {number} time - The amount of ticks in millisecond resolution when measuring this point. - */ - -/** - * Simply.js accel data event. - * Use the event type 'accelData' to subscribe to these events. - * @typedef simply.accelDataEvent - * @property {number} samples - The number of accelerometer samples in this event. - * @property {simply.accelPoint} accel - The first accel in the batch. This is provided for convenience. - * @property {simply.accelPoint[]} accels - The accelerometer samples in an array. - */ - -simply.emitAccelData = function(accels, callback) { - var e = { - samples: accels.length, - accel: accels[0], - accels: accels, - }; - if (callback) { - return callback(e); - } - simply.emit('accelData', e); -}; - -simply.emitMenuSection = function(section) { - var e = { - menu: state.menu, - section: section - }; - if (simply.emit('menuSection', e)) { return; } - simply.onMenuSection(e); -}; - -simply.emitMenuItem = function(section, item) { - var e = { - menu: state.menu, - section: section, - item: item, - }; - if (simply.emit('menuItem', e)) { return; } - simply.onMenuItem(e); -}; - -simply.emitMenuSelect = function(type, section, item) { - var e = { - menu: state.menu, - section: section, - item: item, - }; - if (simply.emit(type, e)) { return; } - simply.onMenuSelect(e); -}; - -Pebble.require = require; -window.require = simply.require; - -}); -__loader.define("util2.js", function(module, require) { -/* - * util2.js by Meiguro - MIT License - */ - -var util2 = (function(){ - -var util2 = {}; - -util2.noop = function() {}; - -util2.copy = function(a, b) { - b = b || (a instanceof Array ? [] : {}); - for (var k in a) { b[k] = a[k]; } - return b; -}; - -util2.toInteger = function(x) { - if (!isNaN(x = parseInt(x))) { return x; } -}; - -util2.toNumber = function(x) { - if (!isNaN(x = parseFloat(x))) { return x; } -}; - -util2.toString = function(x) { - return typeof x === 'object' ? JSON.stringify.apply(this, arguments) : '' + x; -}; - -util2.toArray = function(x) { - if (x instanceof Array) { return x; } - if (x[0]) { return util2.copy(x, []); } - return [x]; -}; - -util2.trim = function(s) { - return s ? s.toString().trim() : s; -}; - -util2.last = function(a) { - return a[a.length-1]; -}; - -var chunkSize = 128; - -var randomBytes = function(chunkSize) { - var z = []; - for (var i = 0; i < chunkSize; ++i) { - z[i] = String.fromCharCode(Math.random() * 256); - } - return z.join(''); -}; - -util2.randomString = function(regex, size, acc) { - if (!size) { - return ''; - } - if (typeof regex === 'string') { - regex = new RegExp('(?!'+regex+')[\\s\\S]', 'g'); - } - acc = acc || ''; - var buf = randomBytes(chunkSize); - if (buf) { - acc += buf.replace(regex, ''); - } - if (acc.length >= size) { - return acc.substr(0, size); - } else { - return util2.randomString(regex, size, acc); - } -}; - -var varpat = new RegExp("^([\\s\\S]*?)\\$([_a-zA-Z0-9]+)", "m"); - -util2.format = function(text, table) { - var m, z = ''; - while ((m = text.match(varpat))) { - var subtext = m[0], value = table[m[2]]; - if (typeof value === 'function') { value = value(); } - z += value !== undefined ? m[1] + value.toString() : subtext; - text = text.substring(subtext.length); - } - z += text; - return z; -}; - -if (typeof module !== 'undefined') { - module.exports = util2; -} - -return util2; - -})(); - -}); -//! moment.js -//! version : 2.6.0 -//! authors : Tim Wood, Iskren Chernev, Moment.js contributors -//! license : MIT -//! momentjs.com - -(function (undefined) { - - /************************************ - Constants - ************************************/ - - var moment, - VERSION = "2.6.0", - // the global-scope this is NOT the global object in Node.js - globalScope = typeof global !== 'undefined' ? global : this, - oldGlobalMoment, - round = Math.round, - i, - - YEAR = 0, - MONTH = 1, - DATE = 2, - HOUR = 3, - MINUTE = 4, - SECOND = 5, - MILLISECOND = 6, - - // internal storage for language config files - languages = {}, - - // moment internal properties - momentProperties = { - _isAMomentObject: null, - _i : null, - _f : null, - _l : null, - _strict : null, - _isUTC : null, - _offset : null, // optional. Combine with _isUTC - _pf : null, - _lang : null // optional - }, - - // check for nodeJS - hasModule = (typeof module !== 'undefined' && module.exports), - - // ASP.NET json date format regex - aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, - aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/, - - // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html - // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere - isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, - - // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, - localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, - - // parsing token regexes - parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 - parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 - parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999 - parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 - parseTokenDigits = /\d+/, // nonzero number of digits - parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. - parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z - parseTokenT = /T/i, // T (ISO separator) - parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 - parseTokenOrdinal = /\d{1,2}/, - - //strict parsing regexes - parseTokenOneDigit = /\d/, // 0 - 9 - parseTokenTwoDigits = /\d\d/, // 00 - 99 - parseTokenThreeDigits = /\d{3}/, // 000 - 999 - parseTokenFourDigits = /\d{4}/, // 0000 - 9999 - parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999 - parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf - - // iso 8601 regex - // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) - isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/, - - isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', - - isoDates = [ - ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], - ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], - ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], - ['GGGG-[W]WW', /\d{4}-W\d{2}/], - ['YYYY-DDD', /\d{4}-\d{3}/] - ], - - // iso time formats and regexes - isoTimes = [ - ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], - ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], - ['HH:mm', /(T| )\d\d:\d\d/], - ['HH', /(T| )\d\d/] - ], - - // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] - parseTimezoneChunker = /([\+\-]|\d\d)/gi, - - // getter and setter names - proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), - unitMillisecondFactors = { - 'Milliseconds' : 1, - 'Seconds' : 1e3, - 'Minutes' : 6e4, - 'Hours' : 36e5, - 'Days' : 864e5, - 'Months' : 2592e6, - 'Years' : 31536e6 - }, - - unitAliases = { - ms : 'millisecond', - s : 'second', - m : 'minute', - h : 'hour', - d : 'day', - D : 'date', - w : 'week', - W : 'isoWeek', - M : 'month', - Q : 'quarter', - y : 'year', - DDD : 'dayOfYear', - e : 'weekday', - E : 'isoWeekday', - gg: 'weekYear', - GG: 'isoWeekYear' - }, - - camelFunctions = { - dayofyear : 'dayOfYear', - isoweekday : 'isoWeekday', - isoweek : 'isoWeek', - weekyear : 'weekYear', - isoweekyear : 'isoWeekYear' - }, - - // format function strings - formatFunctions = {}, - - // tokens to ordinalize and pad - ordinalizeTokens = 'DDD w W M D d'.split(' '), - paddedTokens = 'M D H h m s w W'.split(' '), - - formatTokenFunctions = { - M : function () { - return this.month() + 1; - }, - MMM : function (format) { - return this.lang().monthsShort(this, format); - }, - MMMM : function (format) { - return this.lang().months(this, format); - }, - D : function () { - return this.date(); - }, - DDD : function () { - return this.dayOfYear(); - }, - d : function () { - return this.day(); - }, - dd : function (format) { - return this.lang().weekdaysMin(this, format); - }, - ddd : function (format) { - return this.lang().weekdaysShort(this, format); - }, - dddd : function (format) { - return this.lang().weekdays(this, format); - }, - w : function () { - return this.week(); - }, - W : function () { - return this.isoWeek(); - }, - YY : function () { - return leftZeroFill(this.year() % 100, 2); - }, - YYYY : function () { - return leftZeroFill(this.year(), 4); - }, - YYYYY : function () { - return leftZeroFill(this.year(), 5); - }, - YYYYYY : function () { - var y = this.year(), sign = y >= 0 ? '+' : '-'; - return sign + leftZeroFill(Math.abs(y), 6); - }, - gg : function () { - return leftZeroFill(this.weekYear() % 100, 2); - }, - gggg : function () { - return leftZeroFill(this.weekYear(), 4); - }, - ggggg : function () { - return leftZeroFill(this.weekYear(), 5); - }, - GG : function () { - return leftZeroFill(this.isoWeekYear() % 100, 2); - }, - GGGG : function () { - return leftZeroFill(this.isoWeekYear(), 4); - }, - GGGGG : function () { - return leftZeroFill(this.isoWeekYear(), 5); - }, - e : function () { - return this.weekday(); - }, - E : function () { - return this.isoWeekday(); - }, - a : function () { - return this.lang().meridiem(this.hours(), this.minutes(), true); - }, - A : function () { - return this.lang().meridiem(this.hours(), this.minutes(), false); - }, - H : function () { - return this.hours(); - }, - h : function () { - return this.hours() % 12 || 12; - }, - m : function () { - return this.minutes(); - }, - s : function () { - return this.seconds(); - }, - S : function () { - return toInt(this.milliseconds() / 100); - }, - SS : function () { - return leftZeroFill(toInt(this.milliseconds() / 10), 2); - }, - SSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - SSSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - Z : function () { - var a = -this.zone(), - b = "+"; - if (a < 0) { - a = -a; - b = "-"; - } - return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2); - }, - ZZ : function () { - var a = -this.zone(), - b = "+"; - if (a < 0) { - a = -a; - b = "-"; - } - return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); - }, - z : function () { - return this.zoneAbbr(); - }, - zz : function () { - return this.zoneName(); - }, - X : function () { - return this.unix(); - }, - Q : function () { - return this.quarter(); - } - }, - - lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; - - function defaultParsingFlags() { - // We need to deep clone this object, and es5 standard is not very - // helpful. - return { - empty : false, - unusedTokens : [], - unusedInput : [], - overflow : -2, - charsLeftOver : 0, - nullInput : false, - invalidMonth : null, - invalidFormat : false, - userInvalidated : false, - iso: false - }; - } - - function deprecate(msg, fn) { - var firstTime = true; - function printMsg() { - if (moment.suppressDeprecationWarnings === false && - typeof console !== 'undefined' && console.warn) { - console.warn("Deprecation warning: " + msg); - } - } - return extend(function () { - if (firstTime) { - printMsg(); - firstTime = false; - } - return fn.apply(this, arguments); - }, fn); - } - - function padToken(func, count) { - return function (a) { - return leftZeroFill(func.call(this, a), count); - }; - } - function ordinalizeToken(func, period) { - return function (a) { - return this.lang().ordinal(func.call(this, a), period); - }; - } - - while (ordinalizeTokens.length) { - i = ordinalizeTokens.pop(); - formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); - } - while (paddedTokens.length) { - i = paddedTokens.pop(); - formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); - } - formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); - - - /************************************ - Constructors - ************************************/ - - function Language() { - - } - - // Moment prototype object - function Moment(config) { - checkOverflow(config); - extend(this, config); - } - - // Duration Constructor - function Duration(duration) { - var normalizedInput = normalizeObjectUnits(duration), - years = normalizedInput.year || 0, - quarters = normalizedInput.quarter || 0, - months = normalizedInput.month || 0, - weeks = normalizedInput.week || 0, - days = normalizedInput.day || 0, - hours = normalizedInput.hour || 0, - minutes = normalizedInput.minute || 0, - seconds = normalizedInput.second || 0, - milliseconds = normalizedInput.millisecond || 0; - - // representation for dateAddRemove - this._milliseconds = +milliseconds + - seconds * 1e3 + // 1000 - minutes * 6e4 + // 1000 * 60 - hours * 36e5; // 1000 * 60 * 60 - // Because of dateAddRemove treats 24 hours as different from a - // day when working around DST, we need to store them separately - this._days = +days + - weeks * 7; - // It is impossible translate months into days without knowing - // which months you are are talking about, so we have to store - // it separately. - this._months = +months + - quarters * 3 + - years * 12; - - this._data = {}; - - this._bubble(); - } - - /************************************ - Helpers - ************************************/ - - - function extend(a, b) { - for (var i in b) { - if (b.hasOwnProperty(i)) { - a[i] = b[i]; - } - } - - if (b.hasOwnProperty("toString")) { - a.toString = b.toString; - } - - if (b.hasOwnProperty("valueOf")) { - a.valueOf = b.valueOf; - } - - return a; - } - - function cloneMoment(m) { - var result = {}, i; - for (i in m) { - if (m.hasOwnProperty(i) && momentProperties.hasOwnProperty(i)) { - result[i] = m[i]; - } - } - - return result; - } - - function absRound(number) { - if (number < 0) { - return Math.ceil(number); - } else { - return Math.floor(number); - } - } - - // left zero fill a number - // see http://jsperf.com/left-zero-filling for performance comparison - function leftZeroFill(number, targetLength, forceSign) { - var output = '' + Math.abs(number), - sign = number >= 0; - - while (output.length < targetLength) { - output = '0' + output; - } - return (sign ? (forceSign ? '+' : '') : '-') + output; - } - - // helper function for _.addTime and _.subtractTime - function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) { - var milliseconds = duration._milliseconds, - days = duration._days, - months = duration._months; - updateOffset = updateOffset == null ? true : updateOffset; - - if (milliseconds) { - mom._d.setTime(+mom._d + milliseconds * isAdding); - } - if (days) { - rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding); - } - if (months) { - rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding); - } - if (updateOffset) { - moment.updateOffset(mom, days || months); - } - } - - // check if is an array - function isArray(input) { - return Object.prototype.toString.call(input) === '[object Array]'; - } - - function isDate(input) { - return Object.prototype.toString.call(input) === '[object Date]' || - input instanceof Date; - } - - // compare two arrays, return the number of differences - function compareArrays(array1, array2, dontConvert) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if ((dontConvert && array1[i] !== array2[i]) || - (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { - diffs++; - } - } - return diffs + lengthDiff; - } - - function normalizeUnits(units) { - if (units) { - var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); - units = unitAliases[units] || camelFunctions[lowered] || lowered; - } - return units; - } - - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; - - for (prop in inputObject) { - if (inputObject.hasOwnProperty(prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } - } - } - - return normalizedInput; - } - - function makeList(field) { - var count, setter; - - if (field.indexOf('week') === 0) { - count = 7; - setter = 'day'; - } - else if (field.indexOf('month') === 0) { - count = 12; - setter = 'month'; - } - else { - return; - } - - moment[field] = function (format, index) { - var i, getter, - method = moment.fn._lang[field], - results = []; - - if (typeof format === 'number') { - index = format; - format = undefined; - } - - getter = function (i) { - var m = moment().utc().set(setter, i); - return method.call(moment.fn._lang, m, format || ''); - }; - - if (index != null) { - return getter(index); - } - else { - for (i = 0; i < count; i++) { - results.push(getter(i)); - } - return results; - } - }; - } - - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; - - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - if (coercedNumber >= 0) { - value = Math.floor(coercedNumber); - } else { - value = Math.ceil(coercedNumber); - } - } - - return value; - } - - function daysInMonth(year, month) { - return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); - } - - function weeksInYear(year, dow, doy) { - return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week; - } - - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; - } - - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - } - - function checkOverflow(m) { - var overflow; - if (m._a && m._pf.overflow === -2) { - overflow = - m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : - m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : - m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR : - m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : - m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : - m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : - -1; - - if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { - overflow = DATE; - } - - m._pf.overflow = overflow; - } - } - - function isValid(m) { - if (m._isValid == null) { - m._isValid = !isNaN(m._d.getTime()) && - m._pf.overflow < 0 && - !m._pf.empty && - !m._pf.invalidMonth && - !m._pf.nullInput && - !m._pf.invalidFormat && - !m._pf.userInvalidated; - - if (m._strict) { - m._isValid = m._isValid && - m._pf.charsLeftOver === 0 && - m._pf.unusedTokens.length === 0; - } - } - return m._isValid; - } - - function normalizeLanguage(key) { - return key ? key.toLowerCase().replace('_', '-') : key; - } - - // Return a moment from input, that is local/utc/zone equivalent to model. - function makeAs(input, model) { - return model._isUTC ? moment(input).zone(model._offset || 0) : - moment(input).local(); - } - - /************************************ - Languages - ************************************/ - - - extend(Language.prototype, { - - set : function (config) { - var prop, i; - for (i in config) { - prop = config[i]; - if (typeof prop === 'function') { - this[i] = prop; - } else { - this['_' + i] = prop; - } - } - }, - - _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), - months : function (m) { - return this._months[m.month()]; - }, - - _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), - monthsShort : function (m) { - return this._monthsShort[m.month()]; - }, - - monthsParse : function (monthName) { - var i, mom, regex; - - if (!this._monthsParse) { - this._monthsParse = []; - } - - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - if (!this._monthsParse[i]) { - mom = moment.utc([2000, i]); - regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); - this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._monthsParse[i].test(monthName)) { - return i; - } - } - }, - - _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), - weekdays : function (m) { - return this._weekdays[m.day()]; - }, - - _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), - weekdaysShort : function (m) { - return this._weekdaysShort[m.day()]; - }, - - _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), - weekdaysMin : function (m) { - return this._weekdaysMin[m.day()]; - }, - - weekdaysParse : function (weekdayName) { - var i, mom, regex; - - if (!this._weekdaysParse) { - this._weekdaysParse = []; - } - - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - if (!this._weekdaysParse[i]) { - mom = moment([2000, 1]).day(i); - regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); - this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._weekdaysParse[i].test(weekdayName)) { - return i; - } - } - }, - - _longDateFormat : { - LT : "h:mm A", - L : "MM/DD/YYYY", - LL : "MMMM D YYYY", - LLL : "MMMM D YYYY LT", - LLLL : "dddd, MMMM D YYYY LT" - }, - longDateFormat : function (key) { - var output = this._longDateFormat[key]; - if (!output && this._longDateFormat[key.toUpperCase()]) { - output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { - return val.slice(1); - }); - this._longDateFormat[key] = output; - } - return output; - }, - - isPM : function (input) { - // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays - // Using charAt should be more compatible. - return ((input + '').toLowerCase().charAt(0) === 'p'); - }, - - _meridiemParse : /[ap]\.?m?\.?/i, - meridiem : function (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'pm' : 'PM'; - } else { - return isLower ? 'am' : 'AM'; - } - }, - - _calendar : { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }, - calendar : function (key, mom) { - var output = this._calendar[key]; - return typeof output === 'function' ? output.apply(mom) : output; - }, - - _relativeTime : { - future : "in %s", - past : "%s ago", - s : "a few seconds", - m : "a minute", - mm : "%d minutes", - h : "an hour", - hh : "%d hours", - d : "a day", - dd : "%d days", - M : "a month", - MM : "%d months", - y : "a year", - yy : "%d years" - }, - relativeTime : function (number, withoutSuffix, string, isFuture) { - var output = this._relativeTime[string]; - return (typeof output === 'function') ? - output(number, withoutSuffix, string, isFuture) : - output.replace(/%d/i, number); - }, - pastFuture : function (diff, output) { - var format = this._relativeTime[diff > 0 ? 'future' : 'past']; - return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); - }, - - ordinal : function (number) { - return this._ordinal.replace("%d", number); - }, - _ordinal : "%d", - - preparse : function (string) { - return string; - }, - - postformat : function (string) { - return string; - }, - - week : function (mom) { - return weekOfYear(mom, this._week.dow, this._week.doy).week; - }, - - _week : { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - }, - - _invalidDate: 'Invalid date', - invalidDate: function () { - return this._invalidDate; - } - }); - - // Loads a language definition into the `languages` cache. The function - // takes a key and optionally values. If not in the browser and no values - // are provided, it will load the language file module. As a convenience, - // this function also returns the language values. - function loadLang(key, values) { - values.abbr = key; - if (!languages[key]) { - languages[key] = new Language(); - } - languages[key].set(values); - return languages[key]; - } - - // Remove a language from the `languages` cache. Mostly useful in tests. - function unloadLang(key) { - delete languages[key]; - } - - // Determines which language definition to use and returns it. - // - // With no parameters, it will return the global language. If you - // pass in a language key, such as 'en', it will return the - // definition for 'en', so long as 'en' has already been loaded using - // moment.lang. - function getLangDefinition(key) { - var i = 0, j, lang, next, split, - get = function (k) { - if (!languages[k] && hasModule) { - try { - require('./lang/' + k); - } catch (e) { } - } - return languages[k]; - }; - - if (!key) { - return moment.fn._lang; - } - - if (!isArray(key)) { - //short-circuit everything else - lang = get(key); - if (lang) { - return lang; - } - key = [key]; - } - - //pick the language from the array - //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - while (i < key.length) { - split = normalizeLanguage(key[i]).split('-'); - j = split.length; - next = normalizeLanguage(key[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - lang = get(split.slice(0, j).join('-')); - if (lang) { - return lang; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; - } - return moment.fn._lang; - } - - /************************************ - Formatting - ************************************/ - - - function removeFormattingTokens(input) { - if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ""); - } - return input.replace(/\\/g, ""); - } - - function makeFormatFunction(format) { - var array = format.match(formattingTokens), i, length; - - for (i = 0, length = array.length; i < length; i++) { - if (formatTokenFunctions[array[i]]) { - array[i] = formatTokenFunctions[array[i]]; - } else { - array[i] = removeFormattingTokens(array[i]); - } - } - - return function (mom) { - var output = ""; - for (i = 0; i < length; i++) { - output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; - } - return output; - }; - } - - // format date using native date object - function formatMoment(m, format) { - - if (!m.isValid()) { - return m.lang().invalidDate(); - } - - format = expandFormat(format, m.lang()); - - if (!formatFunctions[format]) { - formatFunctions[format] = makeFormatFunction(format); - } - - return formatFunctions[format](m); - } - - function expandFormat(format, lang) { - var i = 5; - - function replaceLongDateFormatTokens(input) { - return lang.longDateFormat(input) || input; - } - - localFormattingTokens.lastIndex = 0; - while (i >= 0 && localFormattingTokens.test(format)) { - format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); - localFormattingTokens.lastIndex = 0; - i -= 1; - } - - return format; - } - - - /************************************ - Parsing - ************************************/ - - - // get the regex to find the next token - function getParseRegexForToken(token, config) { - var a, strict = config._strict; - switch (token) { - case 'Q': - return parseTokenOneDigit; - case 'DDDD': - return parseTokenThreeDigits; - case 'YYYY': - case 'GGGG': - case 'gggg': - return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; - case 'Y': - case 'G': - case 'g': - return parseTokenSignedNumber; - case 'YYYYYY': - case 'YYYYY': - case 'GGGGG': - case 'ggggg': - return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; - case 'S': - if (strict) { return parseTokenOneDigit; } - /* falls through */ - case 'SS': - if (strict) { return parseTokenTwoDigits; } - /* falls through */ - case 'SSS': - if (strict) { return parseTokenThreeDigits; } - /* falls through */ - case 'DDD': - return parseTokenOneToThreeDigits; - case 'MMM': - case 'MMMM': - case 'dd': - case 'ddd': - case 'dddd': - return parseTokenWord; - case 'a': - case 'A': - return getLangDefinition(config._l)._meridiemParse; - case 'X': - return parseTokenTimestampMs; - case 'Z': - case 'ZZ': - return parseTokenTimezone; - case 'T': - return parseTokenT; - case 'SSSS': - return parseTokenDigits; - case 'MM': - case 'DD': - case 'YY': - case 'GG': - case 'gg': - case 'HH': - case 'hh': - case 'mm': - case 'ss': - case 'ww': - case 'WW': - return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; - case 'M': - case 'D': - case 'd': - case 'H': - case 'h': - case 'm': - case 's': - case 'w': - case 'W': - case 'e': - case 'E': - return parseTokenOneOrTwoDigits; - case 'Do': - return parseTokenOrdinal; - default : - a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i")); - return a; - } - } - - function timezoneMinutesFromString(string) { - string = string || ""; - var possibleTzMatches = (string.match(parseTokenTimezone) || []), - tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], - parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], - minutes = +(parts[1] * 60) + toInt(parts[2]); - - return parts[0] === '+' ? -minutes : minutes; - } - - // function to convert string input to date - function addTimeToArrayFromToken(token, input, config) { - var a, datePartArray = config._a; - - switch (token) { - // QUARTER - case 'Q': - if (input != null) { - datePartArray[MONTH] = (toInt(input) - 1) * 3; - } - break; - // MONTH - case 'M' : // fall through to MM - case 'MM' : - if (input != null) { - datePartArray[MONTH] = toInt(input) - 1; - } - break; - case 'MMM' : // fall through to MMMM - case 'MMMM' : - a = getLangDefinition(config._l).monthsParse(input); - // if we didn't find a month name, mark the date as invalid. - if (a != null) { - datePartArray[MONTH] = a; - } else { - config._pf.invalidMonth = input; - } - break; - // DAY OF MONTH - case 'D' : // fall through to DD - case 'DD' : - if (input != null) { - datePartArray[DATE] = toInt(input); - } - break; - case 'Do' : - if (input != null) { - datePartArray[DATE] = toInt(parseInt(input, 10)); - } - break; - // DAY OF YEAR - case 'DDD' : // fall through to DDDD - case 'DDDD' : - if (input != null) { - config._dayOfYear = toInt(input); - } - - break; - // YEAR - case 'YY' : - datePartArray[YEAR] = moment.parseTwoDigitYear(input); - break; - case 'YYYY' : - case 'YYYYY' : - case 'YYYYYY' : - datePartArray[YEAR] = toInt(input); - break; - // AM / PM - case 'a' : // fall through to A - case 'A' : - config._isPm = getLangDefinition(config._l).isPM(input); - break; - // 24 HOUR - case 'H' : // fall through to hh - case 'HH' : // fall through to hh - case 'h' : // fall through to hh - case 'hh' : - datePartArray[HOUR] = toInt(input); - break; - // MINUTE - case 'm' : // fall through to mm - case 'mm' : - datePartArray[MINUTE] = toInt(input); - break; - // SECOND - case 's' : // fall through to ss - case 'ss' : - datePartArray[SECOND] = toInt(input); - break; - // MILLISECOND - case 'S' : - case 'SS' : - case 'SSS' : - case 'SSSS' : - datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); - break; - // UNIX TIMESTAMP WITH MS - case 'X': - config._d = new Date(parseFloat(input) * 1000); - break; - // TIMEZONE - case 'Z' : // fall through to ZZ - case 'ZZ' : - config._useUTC = true; - config._tzm = timezoneMinutesFromString(input); - break; - case 'w': - case 'ww': - case 'W': - case 'WW': - case 'd': - case 'dd': - case 'ddd': - case 'dddd': - case 'e': - case 'E': - token = token.substr(0, 1); - /* falls through */ - case 'gg': - case 'gggg': - case 'GG': - case 'GGGG': - case 'GGGGG': - token = token.substr(0, 2); - if (input) { - config._w = config._w || {}; - config._w[token] = input; - } - break; - } - } - - // convert an array to a date. - // the array should mirror the parameters below - // note: all values past the year are optional and will default to the lowest possible value. - // [year, month, day , hour, minute, second, millisecond] - function dateFromConfig(config) { - var i, date, input = [], currentDate, - yearToUse, fixYear, w, temp, lang, weekday, week; - - if (config._d) { - return; - } - - currentDate = currentDateArray(config); - - //compute day of the year from weeks and weekdays - if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - fixYear = function (val) { - var intVal = parseInt(val, 10); - return val ? - (val.length < 3 ? (intVal > 68 ? 1900 + intVal : 2000 + intVal) : intVal) : - (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]); - }; - - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1); - } - else { - lang = getLangDefinition(config._l); - weekday = w.d != null ? parseWeekday(w.d, lang) : - (w.e != null ? parseInt(w.e, 10) + lang._week.dow : 0); - - week = parseInt(w.w, 10) || 1; - - //if we're parsing 'd', then the low day numbers may be next week - if (w.d != null && weekday < lang._week.dow) { - week++; - } - - temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow); - } - - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; - } - - //if the day of the year is set, figure out what it is - if (config._dayOfYear) { - yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR]; - - if (config._dayOfYear > daysInYear(yearToUse)) { - config._pf._overflowDayOfYear = true; - } - - date = makeUTCDate(yearToUse, 0, config._dayOfYear); - config._a[MONTH] = date.getUTCMonth(); - config._a[DATE] = date.getUTCDate(); - } - - // Default to current date. - // * if no year, month, day of month are given, default to today - // * if day of month is given, default month and year - // * if month is given, default only year - // * if year is given, don't default anything - for (i = 0; i < 3 && config._a[i] == null; ++i) { - config._a[i] = input[i] = currentDate[i]; - } - - // Zero out whatever was not defaulted, including time - for (; i < 7; i++) { - config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; - } - - // add the offsets to the time to be parsed so that we can have a clean array for checking isValid - input[HOUR] += toInt((config._tzm || 0) / 60); - input[MINUTE] += toInt((config._tzm || 0) % 60); - - config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); - } - - function dateFromObject(config) { - var normalizedInput; - - if (config._d) { - return; - } - - normalizedInput = normalizeObjectUnits(config._i); - config._a = [ - normalizedInput.year, - normalizedInput.month, - normalizedInput.day, - normalizedInput.hour, - normalizedInput.minute, - normalizedInput.second, - normalizedInput.millisecond - ]; - - dateFromConfig(config); - } - - function currentDateArray(config) { - var now = new Date(); - if (config._useUTC) { - return [ - now.getUTCFullYear(), - now.getUTCMonth(), - now.getUTCDate() - ]; - } else { - return [now.getFullYear(), now.getMonth(), now.getDate()]; - } - } - - // date from string and format string - function makeDateFromStringAndFormat(config) { - - config._a = []; - config._pf.empty = true; - - // This array is used to make a Date, either with `new Date` or `Date.UTC` - var lang = getLangDefinition(config._l), - string = '' + config._i, - i, parsedInput, tokens, token, skipped, - stringLength = string.length, - totalParsedInputLength = 0; - - tokens = expandFormat(config._f, lang).match(formattingTokens) || []; - - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; - if (parsedInput) { - skipped = string.substr(0, string.indexOf(parsedInput)); - if (skipped.length > 0) { - config._pf.unusedInput.push(skipped); - } - string = string.slice(string.indexOf(parsedInput) + parsedInput.length); - totalParsedInputLength += parsedInput.length; - } - // don't parse if it's not a known token - if (formatTokenFunctions[token]) { - if (parsedInput) { - config._pf.empty = false; - } - else { - config._pf.unusedTokens.push(token); - } - addTimeToArrayFromToken(token, parsedInput, config); - } - else if (config._strict && !parsedInput) { - config._pf.unusedTokens.push(token); - } - } - - // add remaining unparsed input length to the string - config._pf.charsLeftOver = stringLength - totalParsedInputLength; - if (string.length > 0) { - config._pf.unusedInput.push(string); - } - - // handle am pm - if (config._isPm && config._a[HOUR] < 12) { - config._a[HOUR] += 12; - } - // if is 12 am, change hours to 0 - if (config._isPm === false && config._a[HOUR] === 12) { - config._a[HOUR] = 0; - } - - dateFromConfig(config); - checkOverflow(config); - } - - function unescapeFormat(s) { - return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { - return p1 || p2 || p3 || p4; - }); - } - - // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - function regexpEscape(s) { - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - } - - // date from string and array of format strings - function makeDateFromStringAndArray(config) { - var tempConfig, - bestMoment, - - scoreToBeat, - i, - currentScore; - - if (config._f.length === 0) { - config._pf.invalidFormat = true; - config._d = new Date(NaN); - return; - } - - for (i = 0; i < config._f.length; i++) { - currentScore = 0; - tempConfig = extend({}, config); - tempConfig._pf = defaultParsingFlags(); - tempConfig._f = config._f[i]; - makeDateFromStringAndFormat(tempConfig); - - if (!isValid(tempConfig)) { - continue; - } - - // if there is any input that was not parsed add a penalty for that format - currentScore += tempConfig._pf.charsLeftOver; - - //or tokens - currentScore += tempConfig._pf.unusedTokens.length * 10; - - tempConfig._pf.score = currentScore; - - if (scoreToBeat == null || currentScore < scoreToBeat) { - scoreToBeat = currentScore; - bestMoment = tempConfig; - } - } - - extend(config, bestMoment || tempConfig); - } - - // date from iso format - function makeDateFromString(config) { - var i, l, - string = config._i, - match = isoRegex.exec(string); - - if (match) { - config._pf.iso = true; - for (i = 0, l = isoDates.length; i < l; i++) { - if (isoDates[i][1].exec(string)) { - // match[5] should be "T" or undefined - config._f = isoDates[i][0] + (match[6] || " "); - break; - } - } - for (i = 0, l = isoTimes.length; i < l; i++) { - if (isoTimes[i][1].exec(string)) { - config._f += isoTimes[i][0]; - break; - } - } - if (string.match(parseTokenTimezone)) { - config._f += "Z"; - } - makeDateFromStringAndFormat(config); - } - else { - moment.createFromInputFallback(config); - } - } - - function makeDateFromInput(config) { - var input = config._i, - matched = aspNetJsonRegex.exec(input); - - if (input === undefined) { - config._d = new Date(); - } else if (matched) { - config._d = new Date(+matched[1]); - } else if (typeof input === 'string') { - makeDateFromString(config); - } else if (isArray(input)) { - config._a = input.slice(0); - dateFromConfig(config); - } else if (isDate(input)) { - config._d = new Date(+input); - } else if (typeof(input) === 'object') { - dateFromObject(config); - } else if (typeof(input) === 'number') { - // from milliseconds - config._d = new Date(input); - } else { - moment.createFromInputFallback(config); - } - } - - function makeDate(y, m, d, h, M, s, ms) { - //can't just apply() to create a date: - //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply - var date = new Date(y, m, d, h, M, s, ms); - - //the date constructor doesn't accept years < 1970 - if (y < 1970) { - date.setFullYear(y); - } - return date; - } - - function makeUTCDate(y) { - var date = new Date(Date.UTC.apply(null, arguments)); - if (y < 1970) { - date.setUTCFullYear(y); - } - return date; - } - - function parseWeekday(input, language) { - if (typeof input === 'string') { - if (!isNaN(input)) { - input = parseInt(input, 10); - } - else { - input = language.weekdaysParse(input); - if (typeof input !== 'number') { - return null; - } - } - } - return input; - } - - /************************************ - Relative Time - ************************************/ - - - // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { - return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); - } - - function relativeTime(milliseconds, withoutSuffix, lang) { - var seconds = round(Math.abs(milliseconds) / 1000), - minutes = round(seconds / 60), - hours = round(minutes / 60), - days = round(hours / 24), - years = round(days / 365), - args = seconds < 45 && ['s', seconds] || - minutes === 1 && ['m'] || - minutes < 45 && ['mm', minutes] || - hours === 1 && ['h'] || - hours < 22 && ['hh', hours] || - days === 1 && ['d'] || - days <= 25 && ['dd', days] || - days <= 45 && ['M'] || - days < 345 && ['MM', round(days / 30)] || - years === 1 && ['y'] || ['yy', years]; - args[2] = withoutSuffix; - args[3] = milliseconds > 0; - args[4] = lang; - return substituteTimeAgo.apply({}, args); - } - - - /************************************ - Week of Year - ************************************/ - - - // firstDayOfWeek 0 = sun, 6 = sat - // the day of the week that starts the week - // (usually sunday or monday) - // firstDayOfWeekOfYear 0 = sun, 6 = sat - // the first week is the week that contains the first - // of this day of the week - // (eg. ISO weeks use thursday (4)) - function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { - var end = firstDayOfWeekOfYear - firstDayOfWeek, - daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), - adjustedMoment; - - - if (daysToDayOfWeek > end) { - daysToDayOfWeek -= 7; - } - - if (daysToDayOfWeek < end - 7) { - daysToDayOfWeek += 7; - } - - adjustedMoment = moment(mom).add('d', daysToDayOfWeek); - return { - week: Math.ceil(adjustedMoment.dayOfYear() / 7), - year: adjustedMoment.year() - }; - } - - //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday - function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { - var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; - - weekday = weekday != null ? weekday : firstDayOfWeek; - daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); - dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; - - return { - year: dayOfYear > 0 ? year : year - 1, - dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear - }; - } - - /************************************ - Top Level Functions - ************************************/ - - function makeMoment(config) { - var input = config._i, - format = config._f; - - if (input === null || (format === undefined && input === '')) { - return moment.invalid({nullInput: true}); - } - - if (typeof input === 'string') { - config._i = input = getLangDefinition().preparse(input); - } - - if (moment.isMoment(input)) { - config = cloneMoment(input); - - config._d = new Date(+input._d); - } else if (format) { - if (isArray(format)) { - makeDateFromStringAndArray(config); - } else { - makeDateFromStringAndFormat(config); - } - } else { - makeDateFromInput(config); - } - - return new Moment(config); - } - - moment = function (input, format, lang, strict) { - var c; - - if (typeof(lang) === "boolean") { - strict = lang; - lang = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c = {}; - c._isAMomentObject = true; - c._i = input; - c._f = format; - c._l = lang; - c._strict = strict; - c._isUTC = false; - c._pf = defaultParsingFlags(); - - return makeMoment(c); - }; - - moment.suppressDeprecationWarnings = false; - - moment.createFromInputFallback = deprecate( - "moment construction falls back to js Date. This is " + - "discouraged and will be removed in upcoming major " + - "release. Please refer to " + - "https://github.com/moment/moment/issues/1407 for more info.", - function (config) { - config._d = new Date(config._i); - }); - - // creating with utc - moment.utc = function (input, format, lang, strict) { - var c; - - if (typeof(lang) === "boolean") { - strict = lang; - lang = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c = {}; - c._isAMomentObject = true; - c._useUTC = true; - c._isUTC = true; - c._l = lang; - c._i = input; - c._f = format; - c._strict = strict; - c._pf = defaultParsingFlags(); - - return makeMoment(c).utc(); - }; - - // creating with unix timestamp (in seconds) - moment.unix = function (input) { - return moment(input * 1000); - }; - - // duration - moment.duration = function (input, key) { - var duration = input, - // matching against regexp is expensive, do it on demand - match = null, - sign, - ret, - parseIso; - - if (moment.isDuration(input)) { - duration = { - ms: input._milliseconds, - d: input._days, - M: input._months - }; - } else if (typeof input === 'number') { - duration = {}; - if (key) { - duration[key] = input; - } else { - duration.milliseconds = input; - } - } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { - sign = (match[1] === "-") ? -1 : 1; - duration = { - y: 0, - d: toInt(match[DATE]) * sign, - h: toInt(match[HOUR]) * sign, - m: toInt(match[MINUTE]) * sign, - s: toInt(match[SECOND]) * sign, - ms: toInt(match[MILLISECOND]) * sign - }; - } else if (!!(match = isoDurationRegex.exec(input))) { - sign = (match[1] === "-") ? -1 : 1; - parseIso = function (inp) { - // We'd normally use ~~inp for this, but unfortunately it also - // converts floats to ints. - // inp may be undefined, so careful calling replace on it. - var res = inp && parseFloat(inp.replace(',', '.')); - // apply sign while we're at it - return (isNaN(res) ? 0 : res) * sign; - }; - duration = { - y: parseIso(match[2]), - M: parseIso(match[3]), - d: parseIso(match[4]), - h: parseIso(match[5]), - m: parseIso(match[6]), - s: parseIso(match[7]), - w: parseIso(match[8]) - }; - } - - ret = new Duration(duration); - - if (moment.isDuration(input) && input.hasOwnProperty('_lang')) { - ret._lang = input._lang; - } - - return ret; - }; - - // version number - moment.version = VERSION; - - // default format - moment.defaultFormat = isoFormat; - - // Plugins that add properties should also add the key here (null value), - // so we can properly clone ourselves. - moment.momentProperties = momentProperties; - - // This function will be called whenever a moment is mutated. - // It is intended to keep the offset in sync with the timezone. - moment.updateOffset = function () {}; - - // This function will load languages and then set the global language. If - // no arguments are passed in, it will simply return the current global - // language key. - moment.lang = function (key, values) { - var r; - if (!key) { - return moment.fn._lang._abbr; - } - if (values) { - loadLang(normalizeLanguage(key), values); - } else if (values === null) { - unloadLang(key); - key = 'en'; - } else if (!languages[key]) { - getLangDefinition(key); - } - r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); - return r._abbr; - }; - - // returns language data - moment.langData = function (key) { - if (key && key._lang && key._lang._abbr) { - key = key._lang._abbr; - } - return getLangDefinition(key); - }; - - // compare moment object - moment.isMoment = function (obj) { - return obj instanceof Moment || - (obj != null && obj.hasOwnProperty('_isAMomentObject')); - }; - - // for typechecking Duration objects - moment.isDuration = function (obj) { - return obj instanceof Duration; - }; - - for (i = lists.length - 1; i >= 0; --i) { - makeList(lists[i]); - } - - moment.normalizeUnits = function (units) { - return normalizeUnits(units); - }; - - moment.invalid = function (flags) { - var m = moment.utc(NaN); - if (flags != null) { - extend(m._pf, flags); - } - else { - m._pf.userInvalidated = true; - } - - return m; - }; - - moment.parseZone = function () { - return moment.apply(null, arguments).parseZone(); - }; - - moment.parseTwoDigitYear = function (input) { - return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); - }; - - /************************************ - Moment Prototype - ************************************/ - - - extend(moment.fn = Moment.prototype, { - - clone : function () { - return moment(this); - }, - - valueOf : function () { - return +this._d + ((this._offset || 0) * 60000); - }, - - unix : function () { - return Math.floor(+this / 1000); - }, - - toString : function () { - return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); - }, - - toDate : function () { - return this._offset ? new Date(+this) : this._d; - }, - - toISOString : function () { - var m = moment(this).utc(); - if (0 < m.year() && m.year() <= 9999) { - return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } else { - return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } - }, - - toArray : function () { - var m = this; - return [ - m.year(), - m.month(), - m.date(), - m.hours(), - m.minutes(), - m.seconds(), - m.milliseconds() - ]; - }, - - isValid : function () { - return isValid(this); - }, - - isDSTShifted : function () { - - if (this._a) { - return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; - } - - return false; - }, - - parsingFlags : function () { - return extend({}, this._pf); - }, - - invalidAt: function () { - return this._pf.overflow; - }, - - utc : function () { - return this.zone(0); - }, - - local : function () { - this.zone(0); - this._isUTC = false; - return this; - }, - - format : function (inputString) { - var output = formatMoment(this, inputString || moment.defaultFormat); - return this.lang().postformat(output); - }, - - add : function (input, val) { - var dur; - // switch args to support add('s', 1) and add(1, 's') - if (typeof input === 'string') { - dur = moment.duration(+val, input); - } else { - dur = moment.duration(input, val); - } - addOrSubtractDurationFromMoment(this, dur, 1); - return this; - }, - - subtract : function (input, val) { - var dur; - // switch args to support subtract('s', 1) and subtract(1, 's') - if (typeof input === 'string') { - dur = moment.duration(+val, input); - } else { - dur = moment.duration(input, val); - } - addOrSubtractDurationFromMoment(this, dur, -1); - return this; - }, - - diff : function (input, units, asFloat) { - var that = makeAs(input, this), - zoneDiff = (this.zone() - that.zone()) * 6e4, - diff, output; - - units = normalizeUnits(units); - - if (units === 'year' || units === 'month') { - // average number of days in the months in the given dates - diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 - // difference in months - output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); - // adjust by taking difference in days, average number of days - // and dst in the given months. - output += ((this - moment(this).startOf('month')) - - (that - moment(that).startOf('month'))) / diff; - // same as above but with zones, to negate all dst - output -= ((this.zone() - moment(this).startOf('month').zone()) - - (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff; - if (units === 'year') { - output = output / 12; - } - } else { - diff = (this - that); - output = units === 'second' ? diff / 1e3 : // 1000 - units === 'minute' ? diff / 6e4 : // 1000 * 60 - units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 - units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst - units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst - diff; - } - return asFloat ? output : absRound(output); - }, - - from : function (time, withoutSuffix) { - return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); - }, - - fromNow : function (withoutSuffix) { - return this.from(moment(), withoutSuffix); - }, - - calendar : function () { - // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're zone'd or not. - var sod = makeAs(moment(), this).startOf('day'), - diff = this.diff(sod, 'days', true), - format = diff < -6 ? 'sameElse' : - diff < -1 ? 'lastWeek' : - diff < 0 ? 'lastDay' : - diff < 1 ? 'sameDay' : - diff < 2 ? 'nextDay' : - diff < 7 ? 'nextWeek' : 'sameElse'; - return this.format(this.lang().calendar(format, this)); - }, - - isLeapYear : function () { - return isLeapYear(this.year()); - }, - - isDST : function () { - return (this.zone() < this.clone().month(0).zone() || - this.zone() < this.clone().month(5).zone()); - }, - - day : function (input) { - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); - if (input != null) { - input = parseWeekday(input, this.lang()); - return this.add({ d : input - day }); - } else { - return day; - } - }, - - month : makeAccessor('Month', true), - - startOf: function (units) { - units = normalizeUnits(units); - // the following switch intentionally omits break keywords - // to utilize falling through the cases. - switch (units) { - case 'year': - this.month(0); - /* falls through */ - case 'quarter': - case 'month': - this.date(1); - /* falls through */ - case 'week': - case 'isoWeek': - case 'day': - this.hours(0); - /* falls through */ - case 'hour': - this.minutes(0); - /* falls through */ - case 'minute': - this.seconds(0); - /* falls through */ - case 'second': - this.milliseconds(0); - /* falls through */ - } - - // weeks are a special case - if (units === 'week') { - this.weekday(0); - } else if (units === 'isoWeek') { - this.isoWeekday(1); - } - - // quarters are also special - if (units === 'quarter') { - this.month(Math.floor(this.month() / 3) * 3); - } - - return this; - }, - - endOf: function (units) { - units = normalizeUnits(units); - return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1); - }, - - isAfter: function (input, units) { - units = typeof units !== 'undefined' ? units : 'millisecond'; - return +this.clone().startOf(units) > +moment(input).startOf(units); - }, - - isBefore: function (input, units) { - units = typeof units !== 'undefined' ? units : 'millisecond'; - return +this.clone().startOf(units) < +moment(input).startOf(units); - }, - - isSame: function (input, units) { - units = units || 'ms'; - return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); - }, - - min: function (other) { - other = moment.apply(null, arguments); - return other < this ? this : other; - }, - - max: function (other) { - other = moment.apply(null, arguments); - return other > this ? this : other; - }, - - // keepTime = true means only change the timezone, without affecting - // the local hour. So 5:31:26 +0300 --[zone(2, true)]--> 5:31:26 +0200 - // It is possible that 5:31:26 doesn't exist int zone +0200, so we - // adjust the time as needed, to be valid. - // - // Keeping the time actually adds/subtracts (one hour) - // from the actual represented time. That is why we call updateOffset - // a second time. In case it wants us to change the offset again - // _changeInProgress == true case, then we have to adjust, because - // there is no such time in the given timezone. - zone : function (input, keepTime) { - var offset = this._offset || 0; - if (input != null) { - if (typeof input === "string") { - input = timezoneMinutesFromString(input); - } - if (Math.abs(input) < 16) { - input = input * 60; - } - this._offset = input; - this._isUTC = true; - if (offset !== input) { - if (!keepTime || this._changeInProgress) { - addOrSubtractDurationFromMoment(this, - moment.duration(offset - input, 'm'), 1, false); - } else if (!this._changeInProgress) { - this._changeInProgress = true; - moment.updateOffset(this, true); - this._changeInProgress = null; - } - } - } else { - return this._isUTC ? offset : this._d.getTimezoneOffset(); - } - return this; - }, - - zoneAbbr : function () { - return this._isUTC ? "UTC" : ""; - }, - - zoneName : function () { - return this._isUTC ? "Coordinated Universal Time" : ""; - }, - - parseZone : function () { - if (this._tzm) { - this.zone(this._tzm); - } else if (typeof this._i === 'string') { - this.zone(this._i); - } - return this; - }, - - hasAlignedHourOffset : function (input) { - if (!input) { - input = 0; - } - else { - input = moment(input).zone(); - } - - return (this.zone() - input) % 60 === 0; - }, - - daysInMonth : function () { - return daysInMonth(this.year(), this.month()); - }, - - dayOfYear : function (input) { - var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); - }, - - quarter : function (input) { - return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); - }, - - weekYear : function (input) { - var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year; - return input == null ? year : this.add("y", (input - year)); - }, - - isoWeekYear : function (input) { - var year = weekOfYear(this, 1, 4).year; - return input == null ? year : this.add("y", (input - year)); - }, - - week : function (input) { - var week = this.lang().week(this); - return input == null ? week : this.add("d", (input - week) * 7); - }, - - isoWeek : function (input) { - var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add("d", (input - week) * 7); - }, - - weekday : function (input) { - var weekday = (this.day() + 7 - this.lang()._week.dow) % 7; - return input == null ? weekday : this.add("d", input - weekday); - }, - - isoWeekday : function (input) { - // behaves the same as moment#day except - // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) - // as a setter, sunday should belong to the previous week. - return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); - }, - - isoWeeksInYear : function () { - return weeksInYear(this.year(), 1, 4); - }, - - weeksInYear : function () { - var weekInfo = this._lang._week; - return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); - }, - - get : function (units) { - units = normalizeUnits(units); - return this[units](); - }, - - set : function (units, value) { - units = normalizeUnits(units); - if (typeof this[units] === 'function') { - this[units](value); - } - return this; - }, - - // If passed a language key, it will set the language for this - // instance. Otherwise, it will return the language configuration - // variables for this instance. - lang : function (key) { - if (key === undefined) { - return this._lang; - } else { - this._lang = getLangDefinition(key); - return this; - } - } - }); - - function rawMonthSetter(mom, value) { - var dayOfMonth; - - // TODO: Move this out of here! - if (typeof value === 'string') { - value = mom.lang().monthsParse(value); - // TODO: Another silent failure? - if (typeof value !== 'number') { - return mom; - } - } - - dayOfMonth = Math.min(mom.date(), - daysInMonth(mom.year(), value)); - mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); - return mom; - } - - function rawGetter(mom, unit) { - return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); - } - - function rawSetter(mom, unit, value) { - if (unit === 'Month') { - return rawMonthSetter(mom, value); - } else { - return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); - } - } - - function makeAccessor(unit, keepTime) { - return function (value) { - if (value != null) { - rawSetter(this, unit, value); - moment.updateOffset(this, keepTime); - return this; - } else { - return rawGetter(this, unit); - } - }; - } - - moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false); - moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false); - moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false); - // Setting the hour should keep the time, because the user explicitly - // specified which hour he wants. So trying to maintain the same hour (in - // a new timezone) makes sense. Adding/subtracting hours does not follow - // this rule. - moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true); - // moment.fn.month is defined separately - moment.fn.date = makeAccessor('Date', true); - moment.fn.dates = deprecate("dates accessor is deprecated. Use date instead.", makeAccessor('Date', true)); - moment.fn.year = makeAccessor('FullYear', true); - moment.fn.years = deprecate("years accessor is deprecated. Use year instead.", makeAccessor('FullYear', true)); - - // add plural methods - moment.fn.days = moment.fn.day; - moment.fn.months = moment.fn.month; - moment.fn.weeks = moment.fn.week; - moment.fn.isoWeeks = moment.fn.isoWeek; - moment.fn.quarters = moment.fn.quarter; - - // add aliased format methods - moment.fn.toJSON = moment.fn.toISOString; - - /************************************ - Duration Prototype - ************************************/ - - - extend(moment.duration.fn = Duration.prototype, { - - _bubble : function () { - var milliseconds = this._milliseconds, - days = this._days, - months = this._months, - data = this._data, - seconds, minutes, hours, years; - - // The following code bubbles up values, see the tests for - // examples of what that means. - data.milliseconds = milliseconds % 1000; - - seconds = absRound(milliseconds / 1000); - data.seconds = seconds % 60; - - minutes = absRound(seconds / 60); - data.minutes = minutes % 60; - - hours = absRound(minutes / 60); - data.hours = hours % 24; - - days += absRound(hours / 24); - data.days = days % 30; - - months += absRound(days / 30); - data.months = months % 12; - - years = absRound(months / 12); - data.years = years; - }, - - weeks : function () { - return absRound(this.days() / 7); - }, - - valueOf : function () { - return this._milliseconds + - this._days * 864e5 + - (this._months % 12) * 2592e6 + - toInt(this._months / 12) * 31536e6; - }, - - humanize : function (withSuffix) { - var difference = +this, - output = relativeTime(difference, !withSuffix, this.lang()); - - if (withSuffix) { - output = this.lang().pastFuture(difference, output); - } - - return this.lang().postformat(output); - }, - - add : function (input, val) { - // supports only 2.0-style add(1, 's') or add(moment) - var dur = moment.duration(input, val); - - this._milliseconds += dur._milliseconds; - this._days += dur._days; - this._months += dur._months; - - this._bubble(); - - return this; - }, - - subtract : function (input, val) { - var dur = moment.duration(input, val); - - this._milliseconds -= dur._milliseconds; - this._days -= dur._days; - this._months -= dur._months; - - this._bubble(); - - return this; - }, - - get : function (units) { - units = normalizeUnits(units); - return this[units.toLowerCase() + 's'](); - }, - - as : function (units) { - units = normalizeUnits(units); - return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's'](); - }, - - lang : moment.fn.lang, - - toIsoString : function () { - // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js - var years = Math.abs(this.years()), - months = Math.abs(this.months()), - days = Math.abs(this.days()), - hours = Math.abs(this.hours()), - minutes = Math.abs(this.minutes()), - seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); - - if (!this.asSeconds()) { - // this is the same as C#'s (Noda) and python (isodate)... - // but not other JS (goog.date) - return 'P0D'; - } - - return (this.asSeconds() < 0 ? '-' : '') + - 'P' + - (years ? years + 'Y' : '') + - (months ? months + 'M' : '') + - (days ? days + 'D' : '') + - ((hours || minutes || seconds) ? 'T' : '') + - (hours ? hours + 'H' : '') + - (minutes ? minutes + 'M' : '') + - (seconds ? seconds + 'S' : ''); - } - }); - - function makeDurationGetter(name) { - moment.duration.fn[name] = function () { - return this._data[name]; - }; - } - - function makeDurationAsGetter(name, factor) { - moment.duration.fn['as' + name] = function () { - return +this / factor; - }; - } - - for (i in unitMillisecondFactors) { - if (unitMillisecondFactors.hasOwnProperty(i)) { - makeDurationAsGetter(i, unitMillisecondFactors[i]); - makeDurationGetter(i.toLowerCase()); - } - } - - makeDurationAsGetter('Weeks', 6048e5); - moment.duration.fn.asMonths = function () { - return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12; - }; - - - /************************************ - Default Lang - ************************************/ - - - // Set default language, other languages will inherit from English. - moment.lang('en', { - ordinal : function (number) { - var b = number % 10, - output = (toInt(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - } - }); - - /* EMBED_LANGUAGES */ - - /************************************ - Exposing Moment - ************************************/ - - function makeGlobal(shouldDeprecate) { - /*global ender:false */ - if (typeof ender !== 'undefined') { - return; - } - oldGlobalMoment = globalScope.moment; - if (shouldDeprecate) { - globalScope.moment = deprecate( - "Accessing Moment through the global scope is " + - "deprecated, and will be removed in an upcoming " + - "release.", - moment); - } else { - globalScope.moment = moment; - } - } - - // CommonJS module is defined - if (hasModule) { - module.exports = moment; - } else if (typeof define === "function" && define.amd) { - define("moment", function (require, exports, module) { - if (module.config && module.config() && module.config().noGlobal === true) { - // release the global variable - globalScope.moment = oldGlobalMoment; - } - - return moment; - }); - makeGlobal(true); - } else { - makeGlobal(); - } -}).call(this); - -// Generated by CoffeeScript 1.4.0 - -/* -# MIT LICENSE -# Copyright (c) 2011 Devon Govett -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this -# software and associated documentation files (the "Software"), to deal in the Software -# without restriction, including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -# to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or -# substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - - -(function() { - var PNG; - - PNG = (function() { - var APNG_BLEND_OP_OVER, APNG_BLEND_OP_SOURCE, APNG_DISPOSE_OP_BACKGROUND, APNG_DISPOSE_OP_NONE, APNG_DISPOSE_OP_PREVIOUS, makeImage, scratchCanvas, scratchCtx; - - PNG.load = function(url, canvas, callback) { - var xhr, - _this = this; - if (typeof canvas === 'function') { - callback = canvas; - } - xhr = new XMLHttpRequest; - xhr.open("GET", url, true); - xhr.responseType = "arraybuffer"; - xhr.onload = function() { - var data, png; - data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer); - png = new PNG(data); - if (typeof (canvas != null ? canvas.getContext : void 0) === 'function') { - png.render(canvas); - } - return typeof callback === "function" ? callback(png) : void 0; - }; - return xhr.send(null); - }; - - APNG_DISPOSE_OP_NONE = 0; - - APNG_DISPOSE_OP_BACKGROUND = 1; - - APNG_DISPOSE_OP_PREVIOUS = 2; - - APNG_BLEND_OP_SOURCE = 0; - - APNG_BLEND_OP_OVER = 1; - - function PNG(data) { - var chunkSize, colors, delayDen, delayNum, frame, i, index, key, section, short, text, _i, _j, _ref; - this.data = data; - this.pos = 8; - this.palette = []; - this.imgData = []; - this.transparency = {}; - this.animation = null; - this.text = {}; - frame = null; - while (true) { - chunkSize = this.readUInt32(); - section = ((function() { - var _i, _results; - _results = []; - for (i = _i = 0; _i < 4; i = ++_i) { - _results.push(String.fromCharCode(this.data[this.pos++])); - } - return _results; - }).call(this)).join(''); - switch (section) { - case 'IHDR': - this.width = this.readUInt32(); - this.height = this.readUInt32(); - this.bits = this.data[this.pos++]; - this.colorType = this.data[this.pos++]; - this.compressionMethod = this.data[this.pos++]; - this.filterMethod = this.data[this.pos++]; - this.interlaceMethod = this.data[this.pos++]; - break; - case 'acTL': - this.animation = { - numFrames: this.readUInt32(), - numPlays: this.readUInt32() || Infinity, - frames: [] - }; - break; - case 'PLTE': - this.palette = this.read(chunkSize); - break; - case 'fcTL': - if (frame) { - this.animation.frames.push(frame); - } - this.pos += 4; - frame = { - width: this.readUInt32(), - height: this.readUInt32(), - xOffset: this.readUInt32(), - yOffset: this.readUInt32() - }; - delayNum = this.readUInt16(); - delayDen = this.readUInt16() || 100; - frame.delay = 1000 * delayNum / delayDen; - frame.disposeOp = this.data[this.pos++]; - frame.blendOp = this.data[this.pos++]; - frame.data = []; - break; - case 'IDAT': - case 'fdAT': - if (section === 'fdAT') { - this.pos += 4; - chunkSize -= 4; - } - data = (frame != null ? frame.data : void 0) || this.imgData; - for (i = _i = 0; 0 <= chunkSize ? _i < chunkSize : _i > chunkSize; i = 0 <= chunkSize ? ++_i : --_i) { - data.push(this.data[this.pos++]); - } - break; - case 'tRNS': - this.transparency = {}; - switch (this.colorType) { - case 3: - this.transparency.indexed = this.read(chunkSize); - short = 255 - this.transparency.indexed.length; - if (short > 0) { - for (i = _j = 0; 0 <= short ? _j < short : _j > short; i = 0 <= short ? ++_j : --_j) { - this.transparency.indexed.push(255); - } - } - break; - case 0: - this.transparency.grayscale = this.read(chunkSize)[0]; - break; - case 2: - this.transparency.rgb = this.read(chunkSize); - } - break; - case 'tEXt': - text = this.read(chunkSize); - index = text.indexOf(0); - key = String.fromCharCode.apply(String, text.slice(0, index)); - this.text[key] = String.fromCharCode.apply(String, text.slice(index + 1)); - break; - case 'IEND': - if (frame) { - this.animation.frames.push(frame); - } - this.colors = (function() { - switch (this.colorType) { - case 0: - case 3: - case 4: - return 1; - case 2: - case 6: - return 3; - } - }).call(this); - this.hasAlphaChannel = (_ref = this.colorType) === 4 || _ref === 6; - colors = this.colors + (this.hasAlphaChannel ? 1 : 0); - this.pixelBitlength = this.bits * colors; - this.colorSpace = (function() { - switch (this.colors) { - case 1: - return 'DeviceGray'; - case 3: - return 'DeviceRGB'; - } - }).call(this); - this.imgData = new Uint8Array(this.imgData); - return; - default: - this.pos += chunkSize; - } - this.pos += 4; - if (this.pos > this.data.length) { - throw new Error("Incomplete or corrupt PNG file"); - } - } - return; - } - - PNG.prototype.read = function(bytes) { - var i, _i, _results; - _results = []; - for (i = _i = 0; 0 <= bytes ? _i < bytes : _i > bytes; i = 0 <= bytes ? ++_i : --_i) { - _results.push(this.data[this.pos++]); - } - return _results; - }; - - PNG.prototype.readUInt32 = function() { - var b1, b2, b3, b4; - b1 = this.data[this.pos++] << 24; - b2 = this.data[this.pos++] << 16; - b3 = this.data[this.pos++] << 8; - b4 = this.data[this.pos++]; - return b1 | b2 | b3 | b4; - }; - - PNG.prototype.readUInt16 = function() { - var b1, b2; - b1 = this.data[this.pos++] << 8; - b2 = this.data[this.pos++]; - return b1 | b2; - }; - - PNG.prototype.decodePixels = function(data) { - var byte, c, col, i, left, length, p, pa, paeth, pb, pc, pixelBytes, pixels, pos, row, scanlineLength, upper, upperLeft, _i, _j, _k, _l, _m; - if (data == null) { - data = this.imgData; - } - if (data.length === 0) { - return new Uint8Array(0); - } - data = new FlateStream(data); - data = data.getBytes(); - pixelBytes = this.pixelBitlength / 8; - scanlineLength = pixelBytes * this.width; - pixels = new Uint8Array(scanlineLength * this.height); - length = data.length; - row = 0; - pos = 0; - c = 0; - while (pos < length) { - switch (data[pos++]) { - case 0: - for (i = _i = 0; _i < scanlineLength; i = _i += 1) { - pixels[c++] = data[pos++]; - } - break; - case 1: - for (i = _j = 0; _j < scanlineLength; i = _j += 1) { - byte = data[pos++]; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - pixels[c++] = (byte + left) % 256; - } - break; - case 2: - for (i = _k = 0; _k < scanlineLength; i = _k += 1) { - byte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; - pixels[c++] = (upper + byte) % 256; - } - break; - case 3: - for (i = _l = 0; _l < scanlineLength; i = _l += 1) { - byte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; - pixels[c++] = (byte + Math.floor((left + upper) / 2)) % 256; - } - break; - case 4: - for (i = _m = 0; _m < scanlineLength; i = _m += 1) { - byte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - if (row === 0) { - upper = upperLeft = 0; - } else { - upper = pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; - upperLeft = col && pixels[(row - 1) * scanlineLength + (col - 1) * pixelBytes + (i % pixelBytes)]; - } - p = left + upper - upperLeft; - pa = Math.abs(p - left); - pb = Math.abs(p - upper); - pc = Math.abs(p - upperLeft); - if (pa <= pb && pa <= pc) { - paeth = left; - } else if (pb <= pc) { - paeth = upper; - } else { - paeth = upperLeft; - } - pixels[c++] = (byte + paeth) % 256; - } - break; - default: - throw new Error("Invalid filter algorithm: " + data[pos - 1]); - } - row++; - } - return pixels; - }; - - PNG.prototype.decodePalette = function() { - var c, i, length, palette, pos, ret, transparency, _i, _ref, _ref1; - palette = this.palette; - transparency = this.transparency.indexed || []; - ret = new Uint8Array((transparency.length || 0) + palette.length); - pos = 0; - length = palette.length; - c = 0; - for (i = _i = 0, _ref = palette.length; _i < _ref; i = _i += 3) { - ret[pos++] = palette[i]; - ret[pos++] = palette[i + 1]; - ret[pos++] = palette[i + 2]; - ret[pos++] = (_ref1 = transparency[c++]) != null ? _ref1 : 255; - } - return ret; - }; - - PNG.prototype.copyToImageData = function(imageData, pixels) { - var alpha, colors, data, i, input, j, k, length, palette, v, _ref; - colors = this.colors; - palette = null; - alpha = this.hasAlphaChannel; - if (this.palette.length) { - palette = (_ref = this._decodedPalette) != null ? _ref : this._decodedPalette = this.decodePalette(); - colors = 4; - alpha = true; - } - data = imageData.data || imageData; - length = data.length; - input = palette || pixels; - i = j = 0; - if (colors === 1) { - while (i < length) { - k = palette ? pixels[i / 4] * 4 : j; - v = input[k++]; - data[i++] = v; - data[i++] = v; - data[i++] = v; - data[i++] = alpha ? input[k++] : 255; - j = k; - } - } else { - while (i < length) { - k = palette ? pixels[i / 4] * 4 : j; - data[i++] = input[k++]; - data[i++] = input[k++]; - data[i++] = input[k++]; - data[i++] = alpha ? input[k++] : 255; - j = k; - } - } - }; - - PNG.prototype.decode = function() { - var ret; - ret = new Uint8Array(this.width * this.height * 4); - this.copyToImageData(ret, this.decodePixels()); - return ret; - }; - - makeImage = function(imageData) { - var img; - scratchCtx.width = imageData.width; - scratchCtx.height = imageData.height; - scratchCtx.clearRect(0, 0, imageData.width, imageData.height); - scratchCtx.putImageData(imageData, 0, 0); - img = new Image; - img.src = scratchCanvas.toDataURL(); - return img; - }; - - PNG.prototype.decodeFrames = function(ctx) { - var frame, i, imageData, pixels, _i, _len, _ref, _results; - if (!this.animation) { - return; - } - _ref = this.animation.frames; - _results = []; - for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { - frame = _ref[i]; - imageData = ctx.createImageData(frame.width, frame.height); - pixels = this.decodePixels(new Uint8Array(frame.data)); - this.copyToImageData(imageData, pixels); - frame.imageData = imageData; - _results.push(frame.image = makeImage(imageData)); - } - return _results; - }; - - PNG.prototype.renderFrame = function(ctx, number) { - var frame, frames, prev; - frames = this.animation.frames; - frame = frames[number]; - prev = frames[number - 1]; - if (number === 0) { - ctx.clearRect(0, 0, this.width, this.height); - } - if ((prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_BACKGROUND) { - ctx.clearRect(prev.xOffset, prev.yOffset, prev.width, prev.height); - } else if ((prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_PREVIOUS) { - ctx.putImageData(prev.imageData, prev.xOffset, prev.yOffset); - } - if (frame.blendOp === APNG_BLEND_OP_SOURCE) { - ctx.clearRect(frame.xOffset, frame.yOffset, frame.width, frame.height); - } - return ctx.drawImage(frame.image, frame.xOffset, frame.yOffset); - }; - - PNG.prototype.animate = function(ctx) { - var doFrame, frameNumber, frames, numFrames, numPlays, _ref, - _this = this; - frameNumber = 0; - _ref = this.animation, numFrames = _ref.numFrames, frames = _ref.frames, numPlays = _ref.numPlays; - return (doFrame = function() { - var f, frame; - f = frameNumber++ % numFrames; - frame = frames[f]; - _this.renderFrame(ctx, f); - if (numFrames > 1 && frameNumber / numFrames < numPlays) { - return _this.animation._timeout = setTimeout(doFrame, frame.delay); - } - })(); - }; - - PNG.prototype.stopAnimation = function() { - var _ref; - return clearTimeout((_ref = this.animation) != null ? _ref._timeout : void 0); - }; - - PNG.prototype.render = function(canvas) { - var ctx, data; - if (canvas._png) { - canvas._png.stopAnimation(); - } - canvas._png = this; - canvas.width = this.width; - canvas.height = this.height; - ctx = canvas.getContext("2d"); - if (this.animation) { - this.decodeFrames(ctx); - return this.animate(ctx); - } else { - data = ctx.createImageData(this.width, this.height); - this.copyToImageData(data, this.decodePixels()); - return ctx.putImageData(data, 0, 0); - } - }; - - return PNG; - - })(); - - window.PNG = PNG; - -}).call(this); - -/* - * Extracted from pdf.js - * https://github.com/andreasgal/pdf.js - * - * Copyright (c) 2011 Mozilla Foundation - * - * Contributors: Andreas Gal - * Chris G Jones - * Shaon Barman - * Vivien Nicolas <21@vingtetun.org> - * Justin D'Arcangelo - * Yury Delendik - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -var DecodeStream = (function() { - function constructor() { - this.pos = 0; - this.bufferLength = 0; - this.eof = false; - this.buffer = null; - } - - constructor.prototype = { - ensureBuffer: function decodestream_ensureBuffer(requested) { - var buffer = this.buffer; - var current = buffer ? buffer.byteLength : 0; - if (requested < current) - return buffer; - var size = 512; - while (size < requested) - size <<= 1; - var buffer2 = new Uint8Array(size); - for (var i = 0; i < current; ++i) - buffer2[i] = buffer[i]; - return this.buffer = buffer2; - }, - getByte: function decodestream_getByte() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) - return null; - this.readBlock(); - } - return this.buffer[this.pos++]; - }, - getBytes: function decodestream_getBytes(length) { - var pos = this.pos; - - if (length) { - this.ensureBuffer(pos + length); - var end = pos + length; - - while (!this.eof && this.bufferLength < end) - this.readBlock(); - - var bufEnd = this.bufferLength; - if (end > bufEnd) - end = bufEnd; - } else { - while (!this.eof) - this.readBlock(); - - var end = this.bufferLength; - } - - this.pos = end; - return this.buffer.subarray(pos, end); - }, - lookChar: function decodestream_lookChar() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) - return null; - this.readBlock(); - } - return String.fromCharCode(this.buffer[this.pos]); - }, - getChar: function decodestream_getChar() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) - return null; - this.readBlock(); - } - return String.fromCharCode(this.buffer[this.pos++]); - }, - makeSubStream: function decodestream_makeSubstream(start, length, dict) { - var end = start + length; - while (this.bufferLength <= end && !this.eof) - this.readBlock(); - return new Stream(this.buffer, start, length, dict); - }, - skip: function decodestream_skip(n) { - if (!n) - n = 1; - this.pos += n; - }, - reset: function decodestream_reset() { - this.pos = 0; - } - }; - - return constructor; -})(); - -var FlateStream = (function() { - var codeLenCodeMap = new Uint32Array([ - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 - ]); - - var lengthDecode = new Uint32Array([ - 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, - 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, - 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, - 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102 - ]); - - var distDecode = new Uint32Array([ - 0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, - 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, - 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, - 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001 - ]); - - var fixedLitCodeTab = [new Uint32Array([ - 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, - 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, - 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, - 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, - 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, - 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, - 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, - 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, - 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, - 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, - 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, - 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, - 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, - 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, - 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, - 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, - 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, - 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, - 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, - 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, - 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, - 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, - 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, - 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, - 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, - 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, - 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, - 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, - 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, - 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, - 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, - 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, - 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, - 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, - 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, - 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, - 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, - 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, - 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, - 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, - 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, - 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, - 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, - 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, - 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, - 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, - 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, - 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, - 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, - 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, - 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, - 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, - 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, - 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, - 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, - 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, - 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, - 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, - 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, - 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, - 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, - 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, - 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, - 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff - ]), 9]; - - var fixedDistCodeTab = [new Uint32Array([ - 0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, - 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, - 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, - 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000 - ]), 5]; - - function error(e) { - throw new Error(e) - } - - function constructor(bytes) { - //var bytes = stream.getBytes(); - var bytesPos = 0; - - var cmf = bytes[bytesPos++]; - var flg = bytes[bytesPos++]; - if (cmf == -1 || flg == -1) - error('Invalid header in flate stream'); - if ((cmf & 0x0f) != 0x08) - error('Unknown compression method in flate stream'); - if ((((cmf << 8) + flg) % 31) != 0) - error('Bad FCHECK in flate stream'); - if (flg & 0x20) - error('FDICT bit set in flate stream'); - - this.bytes = bytes; - this.bytesPos = bytesPos; - - this.codeSize = 0; - this.codeBuf = 0; - - DecodeStream.call(this); - } - - constructor.prototype = Object.create(DecodeStream.prototype); - - constructor.prototype.getBits = function(bits) { - var codeSize = this.codeSize; - var codeBuf = this.codeBuf; - var bytes = this.bytes; - var bytesPos = this.bytesPos; - - var b; - while (codeSize < bits) { - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad encoding in flate stream'); - codeBuf |= b << codeSize; - codeSize += 8; - } - b = codeBuf & ((1 << bits) - 1); - this.codeBuf = codeBuf >> bits; - this.codeSize = codeSize -= bits; - this.bytesPos = bytesPos; - return b; - }; - - constructor.prototype.getCode = function(table) { - var codes = table[0]; - var maxLen = table[1]; - var codeSize = this.codeSize; - var codeBuf = this.codeBuf; - var bytes = this.bytes; - var bytesPos = this.bytesPos; - - while (codeSize < maxLen) { - var b; - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad encoding in flate stream'); - codeBuf |= (b << codeSize); - codeSize += 8; - } - var code = codes[codeBuf & ((1 << maxLen) - 1)]; - var codeLen = code >> 16; - var codeVal = code & 0xffff; - if (codeSize == 0 || codeSize < codeLen || codeLen == 0) - error('Bad encoding in flate stream'); - this.codeBuf = (codeBuf >> codeLen); - this.codeSize = (codeSize - codeLen); - this.bytesPos = bytesPos; - return codeVal; - }; - - constructor.prototype.generateHuffmanTable = function(lengths) { - var n = lengths.length; - - // find max code length - var maxLen = 0; - for (var i = 0; i < n; ++i) { - if (lengths[i] > maxLen) - maxLen = lengths[i]; - } - - // build the table - var size = 1 << maxLen; - var codes = new Uint32Array(size); - for (var len = 1, code = 0, skip = 2; - len <= maxLen; - ++len, code <<= 1, skip <<= 1) { - for (var val = 0; val < n; ++val) { - if (lengths[val] == len) { - // bit-reverse the code - var code2 = 0; - var t = code; - for (var i = 0; i < len; ++i) { - code2 = (code2 << 1) | (t & 1); - t >>= 1; - } - - // fill the table entries - for (var i = code2; i < size; i += skip) - codes[i] = (len << 16) | val; - - ++code; - } - } - } - - return [codes, maxLen]; - }; - - constructor.prototype.readBlock = function() { - function repeat(stream, array, len, offset, what) { - var repeat = stream.getBits(len) + offset; - while (repeat-- > 0) - array[i++] = what; - } - - // read block header - var hdr = this.getBits(3); - if (hdr & 1) - this.eof = true; - hdr >>= 1; - - if (hdr == 0) { // uncompressed block - var bytes = this.bytes; - var bytesPos = this.bytesPos; - var b; - - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad block header in flate stream'); - var blockLen = b; - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad block header in flate stream'); - blockLen |= (b << 8); - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad block header in flate stream'); - var check = b; - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad block header in flate stream'); - check |= (b << 8); - if (check != (~blockLen & 0xffff)) - error('Bad uncompressed block length in flate stream'); - - this.codeBuf = 0; - this.codeSize = 0; - - var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + blockLen); - var end = bufferLength + blockLen; - this.bufferLength = end; - for (var n = bufferLength; n < end; ++n) { - if (typeof (b = bytes[bytesPos++]) == 'undefined') { - this.eof = true; - break; - } - buffer[n] = b; - } - this.bytesPos = bytesPos; - return; - } - - var litCodeTable; - var distCodeTable; - if (hdr == 1) { // compressed block, fixed codes - litCodeTable = fixedLitCodeTab; - distCodeTable = fixedDistCodeTab; - } else if (hdr == 2) { // compressed block, dynamic codes - var numLitCodes = this.getBits(5) + 257; - var numDistCodes = this.getBits(5) + 1; - var numCodeLenCodes = this.getBits(4) + 4; - - // build the code lengths code table - var codeLenCodeLengths = Array(codeLenCodeMap.length); - var i = 0; - while (i < numCodeLenCodes) - codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3); - var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); - - // build the literal and distance code tables - var len = 0; - var i = 0; - var codes = numLitCodes + numDistCodes; - var codeLengths = new Array(codes); - while (i < codes) { - var code = this.getCode(codeLenCodeTab); - if (code == 16) { - repeat(this, codeLengths, 2, 3, len); - } else if (code == 17) { - repeat(this, codeLengths, 3, 3, len = 0); - } else if (code == 18) { - repeat(this, codeLengths, 7, 11, len = 0); - } else { - codeLengths[i++] = len = code; - } - } - - litCodeTable = - this.generateHuffmanTable(codeLengths.slice(0, numLitCodes)); - distCodeTable = - this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes)); - } else { - error('Unknown block type in flate stream'); - } - - var buffer = this.buffer; - var limit = buffer ? buffer.length : 0; - var pos = this.bufferLength; - while (true) { - var code1 = this.getCode(litCodeTable); - if (code1 < 256) { - if (pos + 1 >= limit) { - buffer = this.ensureBuffer(pos + 1); - limit = buffer.length; - } - buffer[pos++] = code1; - continue; - } - if (code1 == 256) { - this.bufferLength = pos; - return; - } - code1 -= 257; - code1 = lengthDecode[code1]; - var code2 = code1 >> 16; - if (code2 > 0) - code2 = this.getBits(code2); - var len = (code1 & 0xffff) + code2; - code1 = this.getCode(distCodeTable); - code1 = distDecode[code1]; - code2 = code1 >> 16; - if (code2 > 0) - code2 = this.getBits(code2); - var dist = (code1 & 0xffff) + code2; - if (pos + len >= limit) { - buffer = this.ensureBuffer(pos + len); - limit = buffer.length; - } - for (var k = 0; k < len; ++k, ++pos) - buffer[pos] = buffer[pos - dist]; - } - }; - - return constructor; -})(); -__loader.require("main"); \ No newline at end of file diff --git a/src/js/simply.js b/src/js/simply.js index 1348279c..defe034d 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -14,9 +14,9 @@ var simply = module.exports; simply.ui = {}; -var Window = require('simply/window'); -var Card = simply.ui.Card = require('simply/card'); -var Menu = simply.ui.Menu = require('simply/menu'); +var Window = require('ui/window'); +var Card = simply.ui.Card = require('ui/card'); +var Menu = simply.ui.Menu = require('ui/menu'); var buttons = [ 'back', diff --git a/src/js/simply/card.js b/src/js/ui/card.js similarity index 96% rename from src/js/simply/card.js rename to src/js/ui/card.js index 829f1284..aef7074d 100644 --- a/src/js/simply/card.js +++ b/src/js/ui/card.js @@ -3,7 +3,7 @@ var myutil = require('myutil'); var simply = require('simply'); var Emitter = require('emitter'); -var Window = require('simply/window'); +var Window = require('ui/window'); var textProps = [ 'title', diff --git a/src/js/simply/menu.js b/src/js/ui/menu.js similarity index 99% rename from src/js/simply/menu.js rename to src/js/ui/menu.js index 196b106f..dedd7209 100644 --- a/src/js/simply/menu.js +++ b/src/js/ui/menu.js @@ -3,7 +3,7 @@ var myutil = require('myutil'); var simply = require('simply'); var Emitter = require('emitter'); -var Window = require('simply/window'); +var Window = require('ui/window'); var Menu = function(menuDef) { this._sections = {}; diff --git a/src/js/simply/window.js b/src/js/ui/window.js similarity index 100% rename from src/js/simply/window.js rename to src/js/ui/window.js From 61cdf97c48684af4a3c19ca0e1abd9fcf5594d34 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 24 May 2014 18:11:25 -0700 Subject: [PATCH 120/791] Move button logic to ui window --- src/js/emitter.js | 11 ++++++ src/js/simply.js | 85 ++---------------------------------------- src/js/ui/menu.js | 8 ++++ src/js/ui/window.js | 91 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 82 deletions(-) diff --git a/src/js/emitter.js b/src/js/emitter.js index 69b4c082..26b4efd4 100644 --- a/src/js/emitter.js +++ b/src/js/emitter.js @@ -15,6 +15,9 @@ Emitter.prototype.on = function(type, subtype, handler) { if (Emitter.onAddHandler) { Emitter.onAddHandler(type, subtype, handler); } + if (this.onAddHandler) { + this.onAddHandler(type, subtype, handler); + } var typeMap = this._events || ( this._events = {} ); var subtypeMap = typeMap[type] || ( typeMap[type] = {} ); (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ @@ -31,6 +34,9 @@ Emitter.prototype.off = function(type, subtype, handler) { if (Emitter.onRemoveHandler) { Emitter.onRemoveHandler(type, subtype, handler); } + if (this.onRemoveHandler) { + this.onRemoveHandler(type, subtype, handler); + } if (!type) { this._events = {}; return; @@ -70,6 +76,11 @@ Emitter.prototype.listeners = function(type, subtype) { return subtypeMap[subtype]; }; +Emitter.prototype.listenerCount = function(type, subtype) { + var listeners = this.listeners(type, subtype); + return listeners ? listeners.length : 0; +}; + Emitter.prototype.forEachListener = function(type, subtype, f) { var typeMap = this._events; if (!typeMap) { return; } diff --git a/src/js/simply.js b/src/js/simply.js index defe034d..dfece1cc 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -18,13 +18,6 @@ var Window = require('ui/window'); var Card = simply.ui.Card = require('ui/card'); var Menu = simply.ui.Menu = require('ui/menu'); -var buttons = [ - 'back', - 'up', - 'select', - 'down', -]; - var eventTypes = [ 'accelTap', 'accelData', @@ -75,17 +68,6 @@ simply.reset = function() { state.card = new Card(); - state.button = { - config: {}, - configMode: 'auto', - }; - for (var i = 0, ii = buttons.length; i < ii; i++) { - var button = buttons[i]; - if (button !== 'back') { - state.button.config[buttons[i]] = true; - } - } - state.image = { cache: {}, nextId: 1, @@ -104,22 +86,13 @@ simply.reset = function() { * @param {simply.event} event - The event object with event specific information. */ -var isBackEvent = function(type, subtype) { - return ((type === 'singleClick' || type === 'longClick') && subtype === 'back'); -}; - simply.onAddHandler = function(type, subtype) { - if (isBackEvent(type, subtype)) { - simply.buttonAutoConfig(); - } else if (type === 'accelData') { + if (type === 'accelData') { simply.accelAutoSubscribe(); } }; simply.onRemoveHandler = function(type, subtype) { - if (!type || isBackEvent(type, subtype)) { - simply.buttonAutoConfig(); - } if (!type || type === 'accelData') { simply.accelAutoSubscribe(); } @@ -138,10 +111,12 @@ var setWindow = function(wind, field) { field = field || 'window'; var other = state[field]; if (other) { + other.forEachListener(other.onRemoveHandler); other.forEachListener(simply.onRemoveHandler); } state[field] = wind; if (wind) { + wind.forEachListener(wind.onAddHandler); wind.forEachListener(simply.onAddHandler); } return wind; @@ -365,60 +340,6 @@ simply.require = function(path) { return simply.loadScript(path, false); }; -/** - * The button configuration parameter for {@link simply.buttonConfig}. - * The button configuration allows you to enable to disable buttons without having to register or unregister handlers if that is your preferred style. - * You may also enable the back button manually as an alternative to registering a click handler with 'back' as its subtype using {@link simply.on}. - * @typedef {object} simply.buttonConf - * @property {boolean} [back] - Whether to enable the back button. Initializes as false. Simply.js can also automatically register this for you based on the amount of click handlers with subtype 'back'. - * @property {boolean} [up] - Whether to enable the up button. Initializes as true. Note that this is disabled when using {@link simply.scrollable}. - * @property {boolean} [select] - Whether to enable the select button. Initializes as true. - * @property {boolean} [down] - Whether to enable the down button. Initializes as true. Note that this is disabled when using {@link simply.scrollable}. - */ - -/** - * Changes the button configuration. - * See {@link simply.buttonConfig} - * @memberOf simply - * @param {simply.buttonConfig} buttonConf - An object defining the button configuration. - */ -simply.buttonConfig = function(buttonConf, auto) { - var buttonState = state.button; - if (typeof buttonConf === 'undefined') { - var config = {}; - for (var i = 0, ii = buttons.length; i < ii; ++i) { - var name = buttons[i]; - config[name] = buttonConf.config[name]; - } - return config; - } - for (var k in buttonConf) { - if (buttons.indexOf(k) !== -1) { - if (k === 'back') { - buttonState.configMode = buttonConf.back && !auto ? 'manual' : 'auto'; - } - buttonState.config[k] = buttonConf[k]; - } - } - if (simply.impl.buttonConfig) { - return simply.impl.buttonConfig(buttonState.config); - } -}; - -simply.buttonAutoConfig = function() { - var buttonState = state.button; - if (!buttonState || buttonState.configMode !== 'auto') { - return; - } - var singleBackCount = simply.countHandlers('singleClick', 'back'); - var longBackCount = simply.countHandlers('longClick', 'back'); - var useBack = singleBackCount + longBackCount > 0; - if (useBack !== buttonState.config.back) { - buttonState.config.back = useBack; - return simply.buttonConfig(buttonState.config, true); - } -}; - simply.window = function(field, value, clear) { var wind = state.window; var result; diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index dedd7209..6d5bea4f 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -22,6 +22,14 @@ Menu.prototype.action = function() { throw new Error("Menus don't support action bars."); }; +Menu.prototype._buttonInit = function() {}; + +Menu.prototype.buttonConfig = function() { + throw new Error("Menus don't support changing button configurations."); +}; + +Menu.prototype._buttonAutoConfig = function() {}; + var getMetaSection = function(sectionIndex) { return (this._sections[sectionIndex] || ( this._sections[sectionIndex] = {} )); }; diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 020f1672..18ea62b9 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -4,6 +4,13 @@ var simply = require('simply'); var Emitter = require('emitter'); +var buttons = [ + 'back', + 'up', + 'select', + 'down', +]; + var configProps = [ 'fullscreen', 'style', @@ -23,6 +30,7 @@ var nextId = 1; var Window = function(windowDef) { this.state = windowDef || {}; this._id = nextId++; + this._buttonInit(); }; util2.copy(Emitter.prototype, Window.prototype); @@ -96,4 +104,87 @@ Window.prototype.action = function(field, value, clear) { return this; }; +var isBackEvent = function(type, subtype) { + return ((type === 'singleClick' || type === 'longClick') && subtype === 'back'); +}; + +Window.prototype.onAddHandler = function(type, subtype) { + if (isBackEvent(type, subtype)) { + this._buttonAutoConfig(); + } +}; + +Window.prototype.onRemoveHandler = function(type, subtype) { + if (!type || isBackEvent(type, subtype)) { + this._buttonAutoConfig(); + } +}; + +Window.prototype._buttonInit = function() { + this.state.button = { + config: {}, + configMode: 'auto', + }; + for (var i = 0, ii = buttons.length; i < ii; i++) { + var button = buttons[i]; + if (button !== 'back') { + this.state.button.config[buttons[i]] = true; + } + } +}; + +/** + * The button configuration parameter for {@link simply.buttonConfig}. + * The button configuration allows you to enable to disable buttons without having to register or unregister handlers if that is your preferred style. + * You may also enable the back button manually as an alternative to registering a click handler with 'back' as its subtype using {@link simply.on}. + * @typedef {object} simply.buttonConf + * @property {boolean} [back] - Whether to enable the back button. Initializes as false. Simply.js can also automatically register this for you based on the amount of click handlers with subtype 'back'. + * @property {boolean} [up] - Whether to enable the up button. Initializes as true. Note that this is disabled when using {@link simply.scrollable}. + * @property {boolean} [select] - Whether to enable the select button. Initializes as true. + * @property {boolean} [down] - Whether to enable the down button. Initializes as true. Note that this is disabled when using {@link simply.scrollable}. + */ + +/** + * Changes the button configuration. + * See {@link simply.buttonConfig} + * @memberOf simply + * @param {simply.buttonConfig} buttonConf - An object defining the button configuration. + */ +Window.prototype.buttonConfig = function(buttonConf, auto) { + var buttonState = this.state.button; + if (typeof buttonConf === 'undefined') { + var config = {}; + for (var i = 0, ii = buttons.length; i < ii; ++i) { + var name = buttons[i]; + config[name] = buttonConf.config[name]; + } + return config; + } + for (var k in buttonConf) { + if (buttons.indexOf(k) !== -1) { + if (k === 'back') { + buttonState.configMode = buttonConf.back && !auto ? 'manual' : 'auto'; + } + buttonState.config[k] = buttonConf[k]; + } + } + if (simply.impl.buttonConfig) { + return simply.impl.buttonConfig(buttonState.config); + } +}; + +Window.prototype._buttonAutoConfig = function() { + var buttonState = this.state.button; + if (!buttonState || buttonState.configMode !== 'auto') { + return; + } + var singleBackCount = this.listenerCount('singleClick', 'back'); + var longBackCount = this.listenerCount('longClick', 'back'); + var useBack = singleBackCount + longBackCount > 0; + if (useBack !== buttonState.config.back) { + buttonState.config.back = useBack; + return this.buttonConfig(buttonState.config, true); + } +}; + module.exports = Window; From c1b953460350a64104eb39abd75eeffaa6386b48 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 24 May 2014 18:19:34 -0700 Subject: [PATCH 121/791] Remove global window and card accessors --- src/js/simply.js | 107 -------------------------------------------- src/js/ui/card.js | 37 ++++++++++++++- src/js/ui/window.js | 15 +++++++ 3 files changed, 51 insertions(+), 108 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index dfece1cc..e2946f41 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -394,70 +394,6 @@ simply.card = function(field, value, clear) { return result; }; -/** - * The text definition parameter for {@link simply.text}. - * @typedef {object} simply.textDef - * @property {string} [title] - A new title for the first and largest text field. - * @property {string} [subtitle] - A new subtitle for the second large text field. - * @property {string} [body] - A new body for the last text field meant to display large bodies of text. - */ - -/** - * Sets a group of text fields at once. - * For example, passing a text definition { title: 'A', subtitle: 'B', body: 'C' } - * will set the title, subtitle, and body simultaneously. Not all fields need to be specified. - * When setting a single field, consider using the specific text setters simply.title, simply.subtitle, simply.body. - * @memberOf simply - * @param {simply.textDef} textDef - An object defining new text values. - * @param {boolean} [clear] - If true, all other text fields will be cleared. - */ -simply.text = simply.card; - -simply.setText = simply.text; - -var textfield = function(field, text, clear) { - if (arguments.length <= 1) { - return state.card[field]; - } - if (clear) { - state.card = {}; - } - state.card[field] = text; - return simply.impl.textfield(field, text, clear); -}; - -/** - * Sets the title field. The title field is the first and largest text field available. - * @memberOf simply - * @param {string} text - The desired text to display. - * @param {boolean} [clear] - If true, all other text fields will be cleared. - */ -simply.title = function(text, clear) { - return textfield('title', text, clear); -}; - -/** - * Sets the subtitle field. The subtitle field is the second large text field available. - * @memberOf simply - * @param {string} text - The desired text to display. - * @param {boolean} [clear] - If true, all other text fields will be cleared. - */ -simply.subtitle = function(text, clear) { - return textfield('subtitle', text, clear); -}; - -/** - * Sets the body field. The body field is the last text field available meant to display large bodies of text. - * This can be used to display entire text interfaces. - * You may even clear the title and subtitle fields in order to display more in the body field. - * @memberOf simply - * @param {string} text - The desired text to display. - * @param {boolean} [clear] - If true, all other text fields will be cleared. - */ -simply.body = function(text, clear) { - return textfield('body', text, clear); -}; - simply.action = function(field, image, clear) { var card = state.card; var result = this instanceof Card ? this : card.action(field, image, clear); @@ -477,49 +413,6 @@ simply.vibe = function() { return simply.impl.vibe.apply(this, arguments); }; -/** - * Enable scrolling in the Pebble UI. - * When scrolling is enabled, up and down button presses are no longer forwarded to JavaScript handlers. - * Single select, long select, and accel tap events are still available to you however. - * @memberOf simply - * @param {boolean} scrollable - Whether to enable a scrollable view. - */ - -simply.scrollable = function(scrollable) { - if (typeof scrollable === 'undefined') { - return state.scrollable === true; - } - state.scrollable = scrollable; - return simply.impl.scrollable.apply(this, arguments); -}; - -/** - * Enable fullscreen in the Pebble UI. - * Fullscreen removes the Pebble status bar, giving slightly more vertical display height. - * @memberOf simply - * @param {boolean} fullscreen - Whether to enable fullscreen mode. - */ - -simply.fullscreen = function(fullscreen) { - if (typeof fullscreen === 'undefined') { - return state.fullscreen === true; - } - state.fullscreen = fullscreen; - return simply.impl.fullscreen.apply(this, arguments); -}; - -/** - * Set the Pebble UI style. - * The available styles are 'small', 'large', and 'mono'. Small and large correspond to the system notification styles. - * Mono sets a monospace font for the body textfield, enabling more complex text UIs or ASCII art. - * @memberOf simply - * @param {string} type - The type of style to set: 'small', 'large', or 'mono'. - */ - -simply.style = function(type) { - return simply.impl.style.apply(this, arguments); -}; - var makeImageHash = function(image) { var url = image.url; var hashPart = ''; diff --git a/src/js/ui/card.js b/src/js/ui/card.js index aef7074d..9af36bb8 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -5,6 +5,29 @@ var simply = require('simply'); var Emitter = require('emitter'); var Window = require('ui/window'); +/** + * Sets the title field. The title field is the first and largest text field available. + * @memberOf simply + * @param {string} text - The desired text to display. + * @param {boolean} [clear] - If true, all other text fields will be cleared. + */ + +/** + * Sets the subtitle field. The subtitle field is the second large text field available. + * @memberOf simply + * @param {string} text - The desired text to display. + * @param {boolean} [clear] - If true, all other text fields will be cleared. + */ + +/** + * Sets the body field. The body field is the last text field available meant to display large bodies of text. + * This can be used to display entire text interfaces. + * You may even clear the title and subtitle fields in order to display more in the body field. + * @memberOf simply + * @param {string} text - The desired text to display. + * @param {boolean} [clear] - If true, all other text fields will be cleared. + */ + var textProps = [ 'title', 'subtitle', @@ -23,7 +46,19 @@ var actionProps = [ 'back', ]; -var accessorProps = textProps.concat(imageProps); +/** + * Set the Pebble UI style. + * The available styles are 'small', 'large', and 'mono'. Small and large correspond to the system notification styles. + * Mono sets a monospace font for the body textfield, enabling more complex text UIs or ASCII art. + * @memberOf simply + * @param {string} type - The type of style to set: 'small', 'large', or 'mono'. + */ + +var configProps = [ + 'style', +]; + +var accessorProps = textProps.concat(imageProps).concat(configProps); var clearableProps = textProps.concat(imageProps); var Card = function(cardDef) { diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 18ea62b9..cd7162cf 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -11,6 +11,21 @@ var buttons = [ 'down', ]; +/** + * Enable fullscreen in the Pebble UI. + * Fullscreen removes the Pebble status bar, giving slightly more vertical display height. + * @memberOf simply + * @param {boolean} fullscreen - Whether to enable fullscreen mode. + */ + +/** + * Enable scrolling in the Pebble UI. + * When scrolling is enabled, up and down button presses are no longer forwarded to JavaScript handlers. + * Single select, long select, and accel tap events are still available to you however. + * @memberOf simply + * @param {boolean} scrollable - Whether to enable a scrollable view. + */ + var configProps = [ 'fullscreen', 'style', From 699fb57c7af4fc62d723038647ac1d8d035f1a35 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 24 May 2014 18:23:59 -0700 Subject: [PATCH 122/791] Rename countHandlers to listenerCount --- src/js/simply.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index e2946f41..68e7efae 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -98,7 +98,7 @@ simply.onRemoveHandler = function(type, subtype) { } }; -simply.countHandlers = function(type, subtype) { +simply.listenerCount = function(type, subtype) { var count = 0; var listeners = state.emitter.listeners(type, subtype); count += (listeners ? listeners.length : 0); @@ -623,7 +623,7 @@ simply.accelAutoSubscribe = function() { if (!accelState || accelState.subscribeMode !== 'auto') { return; } - var subscribe = simply.countHandlers('accelData') > 0; + var subscribe = simply.listenerCount('accelData') > 0; if (subscribe !== state.accel.subscribe) { return simply.accelConfig(subscribe, true); } From 740294c0f3ec157cc4abceffbecbfe69f850a4dd Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 24 May 2014 18:27:22 -0700 Subject: [PATCH 123/791] Remove redundant settings messengers --- src/js/simply-pebble.js | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index 584674ad..165edd1d 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -463,19 +463,6 @@ SimplyPebble.card = function(cardDef, clear) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.textfield = function(field, text, clear) { - var command = commandMap.setCard; - var packet = makePacket(command); - var param = command.paramMap[field]; - if (param) { - packet[param.id] = text.toString(); - } - if (clear) { - packet[command.paramMap.clear.id] = true; - } - SimplyPebble.sendPacket(packet); -}; - SimplyPebble.vibe = function(type) { var command = commandMap.vibe; var packet = makePacket(command); @@ -484,28 +471,6 @@ SimplyPebble.vibe = function(type) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.scrollable = function(scrollable) { - var command = commandMap.window; - var packet = makePacket(command); - packet[command.paramMap.scrollable.id] = scrollable ? 1 : 0; - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.fullscreen = function(fullscreen) { - var command = commandMap.window; - var packet = makePacket(command); - packet[command.paramMap.fullscreen.id] = fullscreen ? 1 : 0; - SimplyPebble.sendPacket(packet); -}; - -SimplyPebble.style = function(type) { - var command = commandMap.card; - var packet = makePacket(command); - var styleIndex = styleTypes.indexOf(type); - packet[command.paramMap.type.id] = styleIndex !== -1 ? styleIndex : 1; - SimplyPebble.sendPacket(packet); -}; - SimplyPebble.accelConfig = function(configDef) { var command = commandMap.configAccelData; var packet = makePacket(command, configDef); From fcfea5184d87b99b17013d9878e023059b3aca6c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 24 May 2014 20:24:25 -0700 Subject: [PATCH 124/791] Remove global menu section and item accessors and events --- src/js/simply.js | 102 ++++++++-------------------------------------- src/js/ui/menu.js | 39 ++++++++++++++---- 2 files changed, 48 insertions(+), 93 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index 68e7efae..d03d4679 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -677,59 +677,6 @@ simply.accelPeek = function(callback) { return simply.impl.accelPeek.apply(this, arguments); }; -var getMenuSection = function(e) { - var menu = e.menu || state.menu; - if (!menu) { return; } - return menu._section(e); -}; - -var getMenuItem = function(e) { - var menu = e.menu || state.menu; - if (!menu) { return; } - return menu._item(e); -}; - -simply.onMenuSection = function(e) { - var section = getMenuSection(e); - if (!section) { return; } - simply.menuSection(e.section, section); -}; - -simply.onMenuItem = function(e) { - var item = getMenuItem(e); - if (!item) { return; } - simply.menuItem(e.section, e.item, item); -}; - -simply.onMenuSelect = function(e) { - var menu = e.menu; - var item = getMenuItem(e); - if (!item) { return; } - switch (e.type) { - case 'menuSelect': - if (typeof item.select === 'function') { - if (item.select(e) === false) { - return false; - } - } - break; - case 'menuLongSelect': - if (typeof item.longSelect === 'function') { - if (item.longSelect(e) === false) { - return false; - } - } - break; - case 'menuHide': - if (typeof item.hide === 'function') { - if (item.hide(e) === false) { - return false; - } - } - break; - } -}; - simply.menu = function(menuDef) { var menu = state.menu; if (arguments.length === 0) { @@ -746,25 +693,11 @@ simply.menu = function(menuDef) { menu = setWindow(menu, 'menu'); util2.copy(menu.state, menuDef); } - menu._menu(); + menu._resolveMenu(); state.menu = menu; return simply.impl.menu(menu.state); }; -simply.menuSection = function(sectionIndex, sectionDef) { - if (typeof sectionIndex === 'undefined') { - return getMenuSection({ section: sectionIndex }); - } - return simply.impl.menuSection.apply(this, arguments); -}; - -simply.menuItem = function(sectionIndex, itemIndex, itemDef) { - if (typeof sectionIndex === 'undefined') { - return getMenuItem({ section: sectionIndex, item: itemIndex }); - } - return simply.impl.menuItem.apply(this, arguments); -}; - /** * Simply.js event. See all the possible event types. Subscribe to events using {@link simply.on}. * @typedef simply.event @@ -797,14 +730,6 @@ simply.emitClick = function(type, button) { return simply.emitCard(type, button, e); }; -simply.emitCardShow = function() { - return simply.emitCard('show', null, {}); -}; - -simply.emitCardHide = function() { - return simply.emitCard('hide', null, {}); -}; - /** * Simply.js accel tap event. * Use the event type 'accelTap' to subscribe to these events. @@ -864,31 +789,38 @@ simply.emitMenu = function(type, subtype, e, globalType) { }; simply.emitMenuSection = function(section) { + var menu = state.menu; var e = { - menu: state.menu, + menu: menu, section: section }; - if (simply.emitMenu('section', null, e, 'menuSection') === false) { + if (simply.emitMenu('section', null, e) === false) { return false; } - simply.onMenuSection(e); + if (menu) { + menu._resolveSection(e); + } }; simply.emitMenuItem = function(section, item) { + var menu = state.menu; var e = { - menu: state.menu, + menu: menu, section: section, item: item, }; - if (simply.emitMenu('item', null, e, 'menuItem') === false) { + if (simply.emitMenu('item', null, e) === false) { return false; } - simply.onMenuItem(e); + if (menu) { + menu._resolveItem(e); + } }; simply.emitMenuSelect = function(type, section, item) { + var menu = state.menu; var e = { - menu: state.menu, + menu: menu, section: section, item: item, }; @@ -899,7 +831,9 @@ simply.emitMenuSelect = function(type, section, item) { if (simply.emitMenu(type, null, e) === false) { return false; } - simply.onMenuSelect(e); + if (menu) { + menu._emitSelect(e); + } }; Pebble.require = require; diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 6d5bea4f..42e34521 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -126,7 +126,7 @@ var getItem = function(e) { } }; -Menu.prototype._menu = function() { +Menu.prototype._resolveMenu = function() { var sections = getSections.call(this, targetType.menu); this.state.sections = sections; if (isEnumerable(sections)) { @@ -137,7 +137,7 @@ Menu.prototype._menu = function() { } }; -Menu.prototype._section = function(e) { +Menu.prototype._resolveSection = function(e) { var section = getSection.call(this, e); if (section) { if (!(section.items instanceof Array)) { @@ -147,15 +147,36 @@ Menu.prototype._section = function(e) { if (typeof section.items === 'number') { section.items = new Array(section.items); } - return simply.menuSection.call(this, e.section, section); + return simply.impl.menuSection.call(this, e.section, section); } } }; -Menu.prototype._item = function(e) { +Menu.prototype._resolveItem = function(e) { var item = getItem.call(this, e); if (item) { - return simply.menuItem.call(this, e.section, e.item, item); + return simply.impl.menuItem.call(this, e.section, e.item, item); + } +}; + +Menu.prototype._emitSelect = function(e) { + var item = getItem.call(this, e); + if (!item) { return; } + switch (e.type) { + case 'select': + if (typeof item.select === 'function') { + if (item.select(e) === false) { + return false; + } + } + break; + case 'longSelect': + if (typeof item.longSelect === 'function') { + if (item.longSelect(e) === false) { + return false; + } + } + break; } }; @@ -165,7 +186,7 @@ Menu.prototype.sections = function(sections) { return this; } this.state.sections = sections; - this._menu(); + this._resolveMenu(); return this; }; @@ -180,7 +201,7 @@ Menu.prototype.section = function(sectionIndex, section) { if (sections instanceof Array) { sections[sectionIndex] = section; } - this._section({ section: sectionIndex }); + this._resolveSection({ section: sectionIndex }); return this; }; @@ -203,7 +224,7 @@ Menu.prototype.items = function(sectionIndex, items) { return section.items; } } - this._section({ section: sectionIndex }); + this._resolveSection({ section: sectionIndex }); return this; }; @@ -232,7 +253,7 @@ Menu.prototype.item = function(sectionIndex, itemIndex, item) { } else { return getItem.call(this, { section: sectionIndex, item: itemIndex }); } - this._item({ section: sectionIndex, item: itemIndex }); + this._resolveItem({ section: sectionIndex, item: itemIndex }); return this; }; From 65496763fbbf7d69934ba7fff4d5e1de36433bb0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 24 May 2014 20:55:58 -0700 Subject: [PATCH 125/791] Remove require overwrite --- src/js/simply.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/js/simply.js b/src/js/simply.js index d03d4679..58e256aa 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -835,6 +835,3 @@ simply.emitMenuSelect = function(type, section, item) { menu._emitSelect(e); } }; - -Pebble.require = require; -window.require = simply.require; From 33438bdb50a65ef201afe07c956a5e64e3e80597 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 00:52:53 -0700 Subject: [PATCH 126/791] Change menu to inherit window parameters --- src/js/simply-pebble.js | 15 ++++++--------- src/simply_msg.c | 23 ++++++++++++++++++----- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index 165edd1d..d3d31820 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -55,6 +55,11 @@ var setCardParams = setWindowParams.concat([{ type: Boolean, }]); +var setMenuParams = setWindowParams.concat([{ + name: 'sections', + type: Number, +}]); + var commands = [{ name: 'setWindow', params: setWindowParams, @@ -129,11 +134,7 @@ var commands = [{ }], }, { name: 'setMenu', - params: [{ - name: 'id', - }, { - name: 'sections', - }], + params: setMenuParams, }, { name: 'setMenuSection', params: [{ @@ -402,10 +403,6 @@ SimplyPebble.sendPacket = function(packet) { send(); }; -SimplyPebble.setMenu = function() { - SimplyPebble.sendPacket(makePacket(commandMap.setMenu)); -}; - SimplyPebble.buttonConfig = function(buttonConf) { var command = commandMap.configButtons; var packet = makePacket(command, buttonConf); diff --git a/src/simply_msg.c b/src/simply_msg.c index 833f0297..9a22b74d 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -62,6 +62,12 @@ enum SimplySetUiParam { SetUi_style, }; +enum SimplySetMenuParam { + SetMenu_clear = SetWindow_clear, + SetMenu_id, + SetMenu_sections = SetWindowLast, +}; + typedef enum VibeType VibeType; enum VibeType { @@ -91,6 +97,7 @@ static void handle_set_window(DictionaryIterator *iter, Simply *simply) { switch (tuple->key) { case SetWindow_id: window->id = tuple->value->uint32; + break; case SetWindow_action: simply_window_set_action_bar(window, tuple->value->int32); break; @@ -119,6 +126,7 @@ static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { switch (tuple->key) { case SetUi_id: ui->window.id = tuple->value->uint32; + break; case SetUi_title: case SetUi_subtitle: case SetUi_body: @@ -188,13 +196,18 @@ static void handle_set_accel_config(DictionaryIterator *iter, Simply *simply) { static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { SimplyMenu *menu = simply->menu; Tuple *tuple; - if ((tuple = dict_find(iter, 1))) { - menu->window.id = tuple->value->uint32; - } - if ((tuple = dict_find(iter, 2))) { - simply_menu_set_num_sections(menu, tuple->value->int32); + for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { + switch (tuple->key) { + case SetMenu_id: + menu->window.id = tuple->value->uint32; + break; + case SetMenu_sections: + simply_menu_set_num_sections(menu, tuple->value->int32); + break; + } } simply_menu_show(menu); + handle_set_window(iter, simply); } static void handle_set_menu_section(DictionaryIterator *iter, Simply *simply) { From c4d2a08bd6c786d875eb710b2f7659983bb45cfa Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 00:53:40 -0700 Subject: [PATCH 127/791] Add window stack class --- src/js/ui/windowstack.js | 80 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/js/ui/windowstack.js diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js new file mode 100644 index 00000000..088270c2 --- /dev/null +++ b/src/js/ui/windowstack.js @@ -0,0 +1,80 @@ +var util2 = require('util2'); +var myutil = require('myutil'); +var simply = require('simply'); + +var Emitter = require('emitter'); + +var WindowStack = function() { + this.state = {}; + this.state.items = []; +}; + +util2.copy(Emitter.prototype, WindowStack.prototype); + +WindowStack.prototype.top = function() { + return util2.last(this.state.items); +}; + +WindowStack.prototype._emitShow = function(item) { + var e = { + window: item + }; + this.emit('show', e); +}; + +WindowStack.prototype._emitHide = function(item) { + var e = { + window: item + }; + this.emit('hide', e); +}; + +WindowStack.prototype._show = function(item) { + if (!item) { return; } + this._emitShow(item); + item._show(); +}; + +WindowStack.prototype._hide = function(item) { + if (!item) { return; } + this._emitHide(item); + item._hide(); +}; + +WindowStack.prototype.push = function(item) { + if (item === this.top()) { return; } + this.remove(item); + this._hide(this.top()); + this.state.items.push(item); + this._show(item); +}; + +WindowStack.prototype.pop = function(item) { + this.remove(this.top()); +}; + +WindowStack.prototype.remove = function(item) { + if (!item) { return; } + var index = this.state.items.indexOf(item); + if (index === -1) { return; } + var wasTop = (item === this.top()); + if (wasTop) { + this._hide(item); + } + this.state.items.splice(index, 1); + if (wasTop) { + this._show(this.top()); + } +}; + +WindowStack.prototype.get = function(windowId) { + var items = this.state.items; + for (var i = 0, ii = items.length; i < ii; ++i) { + var wind = items[i]; + if (wind._id() === windowId) { + return wind; + } + } +}; + +module.exports = WindowStack; From 477517ab99e5a0c74520e17724531f32e5abece3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 00:54:25 -0700 Subject: [PATCH 128/791] Change windows to hide and show using the window stack --- src/js/simply-pebble.js | 3 + src/js/simply.js | 197 +++++++++++++++++----------------------- src/js/ui/card.js | 4 +- src/js/ui/menu.js | 11 ++- src/js/ui/window.js | 26 +++++- 5 files changed, 121 insertions(+), 120 deletions(-) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index d3d31820..07deedc6 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -540,6 +540,9 @@ SimplyPebble.onAppMessage = function(e) { var command = commands[code]; switch (command.name) { + case 'windowHide': + simply.hideWindowById(payload[1]); + break; case 'singleClick': case 'longClick': var button = buttons[payload[1]]; diff --git a/src/js/simply.js b/src/js/simply.js index 58e256aa..8bf5b09c 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -14,6 +14,8 @@ var simply = module.exports; simply.ui = {}; +var WindowStack = require('ui/windowstack'); + var Window = require('ui/window'); var Card = simply.ui.Card = require('ui/card'); var Menu = simply.ui.Menu = require('ui/menu'); @@ -66,7 +68,10 @@ simply.reset = function() { emitter.wrapHandler = simply.wrapHandler; state.emitter = emitter; - state.card = new Card(); + var windowStack = new WindowStack(); + windowStack.on('show', simply.onShowWindow); + windowStack.on('hide', simply.onHideWindow); + state.windowStack = windowStack; state.image = { cache: {}, @@ -102,24 +107,45 @@ simply.listenerCount = function(type, subtype) { var count = 0; var listeners = state.emitter.listeners(type, subtype); count += (listeners ? listeners.length : 0); - listeners = state.card && state.card.emitter.listeners(type, subtype); + var wind = simply.topWindow(); + listeners = wind && wind.emitter.listeners(type, subtype); count += (listeners ? listeners.length : 0); return count; }; -var setWindow = function(wind, field) { - field = field || 'window'; - var other = state[field]; - if (other) { - other.forEachListener(other.onRemoveHandler); - other.forEachListener(simply.onRemoveHandler); - } - state[field] = wind; +simply.onShowWindow = function(e) { + var wind = e.window; + wind.forEachListener(wind.onAddHandler); + wind.forEachListener(simply.onAddHandler); +}; + +simply.onHideWindow = function(e) { + var wind = e.window; + wind.forEachListener(wind.onAddHandler); + wind.forEachListener(simply.onRemoveHandler); +}; + +simply.topWindow = function() { + return state.windowStack.top(); +}; + +simply.getWindow = function(windowId) { + return state.windowStack.get(windowId); +}; + +simply.showWindow = function(wind) { + state.windowStack.push(wind); +}; + +simply.hideWindow = function(wind) { + state.windowStack.remove(wind); +}; + +simply.hideWindowById = function(windowId) { + var wind = simply.getWindow(windowId); if (wind) { - wind.forEachListener(wind.onAddHandler); - wind.forEachListener(simply.onAddHandler); + simply.hideWindow(wind); } - return wind; }; var checkEventType = function(type) { @@ -340,67 +366,42 @@ simply.require = function(path) { return simply.loadScript(path, false); }; -simply.window = function(field, value, clear) { - var wind = state.window; - var result; - var windowDef = myutil.toObject(field, value); - if (typeof clear === 'undefined') { - clear = value; - } - clear = clear ? 'all' : clear; - if (this instanceof Window) { - result = this; - if (wind !== this) { - wind = this; - } +simply.text = function(textDef) { + var wind = simply.topWindow(); + if (!wind || !(wind instanceof Card)) { + wind = new Card(textDef); + wind.show(); } else { - result = wind.prop(windowDef, clear); + wind.prop(textDef, true); } - if (wind !== state.window) { - wind = setWindow(wind); - util2.copy(wind.state, windowDef); - clear = 'all'; - } - if (wind === result) { - simply.impl.window(windowDef, clear); - } - return result; }; -simply.card = function(field, value, clear) { - var card = state.card; - var result; - var cardDef = myutil.toObject(field, value); - if (typeof clear === 'undefined') { - clear = value; - } - clear = clear ? 'all' : clear; - if (this instanceof Card) { - result = this; - if (card !== this) { - card = this; - } - } else { - result = card.prop(cardDef, clear); +simply.window = function(windowDef) { + var wind = simply.topWindow(); + if (wind === this) { + simply.impl.window.apply(this, arguments); } - if (card !== state.card) { - card = setWindow(card, 'card'); - util2.copy(card.state, cardDef); - clear = 'all'; +}; + +simply.card = function(windowDef) { + var wind = simply.topWindow(); + if (wind === this) { + simply.impl.card.apply(this, arguments); } - if (card === result) { - simply.impl.card(cardDef, clear); +}; + +simply.menu = function(menuDef) { + var wind = simply.topWindow(); + if (wind === this) { + simply.impl.menu.apply(this, arguments); } - return result; }; -simply.action = function(field, image, clear) { - var card = state.card; - var result = this instanceof Card ? this : card.action(field, image, clear); - if (card === result) { - simply.impl.card({ action: typeof field === 'boolean' ? field : card.state.action }, 'action'); +simply.action = function(actionDef) { + var wind = simply.topWindow(); + if (wind === this) { + simply.impl.window({ action: typeof actionDef === 'boolean' ? actionDef : this.state.action }, 'action'); } - return result; }; /** @@ -677,27 +678,6 @@ simply.accelPeek = function(callback) { return simply.impl.accelPeek.apply(this, arguments); }; -simply.menu = function(menuDef) { - var menu = state.menu; - if (arguments.length === 0) { - return menu; - } - if (this instanceof Menu) { - menu = this; - } else if (menuDef instanceof Menu) { - menu = menuDef; - } else { - menu = new Menu(menuDef); - } - if (menu !== state.menu) { - menu = setWindow(menu, 'menu'); - util2.copy(menu.state, menuDef); - } - menu._resolveMenu(); - state.menu = menu; - return simply.impl.menu(menu.state); -}; - /** * Simply.js event. See all the possible event types. Subscribe to events using {@link simply.on}. * @typedef simply.event @@ -713,9 +693,12 @@ simply.menu = function(menuDef) { * @property {string} button - The button that was pressed: 'back', 'up', 'select', or 'down'. This is also the event subtype. */ -simply.emitCard = function(type, subtype, e, globalType) { - var card = e.card = state.card; - if (card && card.emit(type, subtype, e) === false) { +simply.emitWindow = function(type, subtype, e, globalType, klass) { + var wind = e.window = simply.topWindow(); + if (klass) { + e[klass._codeName] = wind; + } + if (wind && wind.emit(type, subtype, e) === false) { return false; } if (globalType) { @@ -727,7 +710,7 @@ simply.emitClick = function(type, button) { var e = { button: button, }; - return simply.emitCard(type, button, e); + return simply.emitWindow(type, button, e); }; /** @@ -743,7 +726,7 @@ simply.emitAccelTap = function(axis, direction) { axis: axis, direction: direction, }; - return simply.emitCard('accelTap', axis, e, 'accelTap'); + return simply.emitWindow('accelTap', axis, e, 'accelTap'); }; /** @@ -775,52 +758,42 @@ simply.emitAccelData = function(accels, callback) { if (callback) { return callback(e); } - return simply.emitCard('accelData', null, e, 'accelData'); + return simply.emitWindow('accelData', null, e, 'accelData'); }; simply.emitMenu = function(type, subtype, e, globalType) { - var menu = e.menu = state.menu; - if (menu && menu.emit(type, subtype, e) === false) { - return false; - } - if (globalType) { - return simply.emit(globalType, subtype, e); - } + simply.emitWindow(type, subtype, e, globalType, Menu); }; simply.emitMenuSection = function(section) { - var menu = state.menu; + var menu = simply.topWindow(); + if (!(menu instanceof Menu)) { return; } var e = { - menu: menu, section: section }; if (simply.emitMenu('section', null, e) === false) { return false; } - if (menu) { - menu._resolveSection(e); - } + menu._resolveSection(e); }; simply.emitMenuItem = function(section, item) { - var menu = state.menu; + var menu = simply.topWindow(); + if (!(menu instanceof Menu)) { return; } var e = { - menu: menu, section: section, item: item, }; if (simply.emitMenu('item', null, e) === false) { return false; } - if (menu) { - menu._resolveItem(e); - } + menu._resolveItem(e); }; simply.emitMenuSelect = function(type, section, item) { - var menu = state.menu; + var menu = simply.topWindow(); + if (!(menu instanceof Menu)) { return; } var e = { - menu: menu, section: section, item: item, }; @@ -831,7 +804,5 @@ simply.emitMenuSelect = function(type, section, item) { if (simply.emitMenu(type, null, e) === false) { return false; } - if (menu) { - menu._emitSelect(e); - } + menu._emitSelect(e); }; diff --git a/src/js/ui/card.js b/src/js/ui/card.js index 9af36bb8..61f51f51 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -62,9 +62,11 @@ var accessorProps = textProps.concat(imageProps).concat(configProps); var clearableProps = textProps.concat(imageProps); var Card = function(cardDef) { - this.state = cardDef || {}; + Window.call(this, cardDef); }; +Card._codeName = 'card'; + util2.inherit(Card, Window); util2.copy(Emitter.prototype, Card.prototype); diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 42e34521..60f83b75 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -6,15 +6,22 @@ var Emitter = require('emitter'); var Window = require('ui/window'); var Menu = function(menuDef) { + Window.call(this, menuDef); this._sections = {}; - this.state = menuDef || {}; }; +Menu.prototype._codeName = 'menu'; + util2.inherit(Menu, Window); util2.copy(Emitter.prototype, Menu.prototype); -Menu.prototype_prop = function() { +Menu.prototype._show = function() { + this._resolveMenu(); + return Window.prototype._show.apply(this, arguments); +}; + +Menu.prototype._prop = function() { return simply.menu.apply(this, arguments); }; diff --git a/src/js/ui/window.js b/src/js/ui/window.js index cd7162cf..fb221883 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -44,22 +44,40 @@ var nextId = 1; var Window = function(windowDef) { this.state = windowDef || {}; - this._id = nextId++; + this.state.id = nextId++; this._buttonInit(); }; +Window._codeName = 'window'; + util2.copy(Emitter.prototype, Window.prototype); accessorProps.forEach(function(k) { Window.prototype[k] = myutil.makeAccessor(k); }); +Window.prototype._id = function() { + return this.state.id; +}; + Window.prototype._prop = function() { return simply.window.apply(this, arguments); }; +Window.prototype._hide = function() { +}; + +Window.prototype.hide = function() { + simply.hideWindow(this); + return this; +}; + +Window.prototype._show = function() { + this._prop(this.state); +}; + Window.prototype.show = function() { - this._prop({}); + simply.showWindow(this); return this; }; @@ -81,7 +99,7 @@ Window.prototype.prop = function(field, value, clear) { if (arguments.length === 1 && typeof field !== 'object') { return this.state[field]; } - if (typeof clear === 'undefined') { + if (typeof field === 'object') { clear = value; } if (clear) { @@ -106,7 +124,7 @@ Window.prototype.action = function(field, value, clear) { if (arguments.length === 1 && typeof field !== 'object') { return action[field]; } - if (typeof clear === 'undefined') { + if (typeof field !== 'string') { clear = value; } if (clear) { From f0bd9a5e703d90fce399914dd7205b3b1e9826d3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 01:13:38 -0700 Subject: [PATCH 129/791] Add simply window hide --- src/simply_msg.c | 19 +++++++++++++++++++ src/simply_window.c | 7 +++++++ src/simply_window.h | 1 + 3 files changed, 27 insertions(+) diff --git a/src/simply_msg.c b/src/simply_msg.c index 9a22b74d..b7f7cb33 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -116,6 +116,22 @@ static void handle_set_window(DictionaryIterator *iter, Simply *simply) { } } +static void handle_hide_window(DictionaryIterator *iter, Simply *simply) { + Window *base_window = window_stack_get_top_window(); + SimplyWindow *window = window_get_user_data(base_window); + if (!window || (void*) window == simply->splash) { + return; + } + Tuple *tuple; + uint32_t window_id = 0; + if ((tuple = dict_find(iter, 1))) { + window_id = tuple->value->uint32; + } + if (window->id == window_id) { + simply_window_hide(window); + } +} + static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { SimplyUi *ui = simply->ui; Tuple *tuple; @@ -297,6 +313,9 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_setWindow: handle_set_window(iter, context); break; + case SimplyACmd_windowHide: + handle_hide_window(iter, context); + break; case SimplyACmd_setUi: handle_set_ui(iter, context); break; diff --git a/src/simply_window.c b/src/simply_window.c index ee2de81c..1530849c 100644 --- a/src/simply_window.c +++ b/src/simply_window.c @@ -152,6 +152,13 @@ void simply_window_unload(SimplyWindow *self) { self->scroll_layer = NULL; } +void simply_window_hide(SimplyWindow *self) { + if (self->window == window_stack_get_top_window()) { + bool animated = true; + window_stack_pop(animated); + } +} + void simply_window_show(SimplyWindow *self) { if (!self->window) { return; diff --git a/src/simply_window.h b/src/simply_window.h index 490dc687..9df17dab 100644 --- a/src/simply_window.h +++ b/src/simply_window.h @@ -21,6 +21,7 @@ struct SimplyWindow { SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply); void simply_window_deinit(SimplyWindow *self); void simply_window_show(SimplyWindow *self); +void simply_window_hide(SimplyWindow *self); void simply_window_load(SimplyWindow *self); void simply_window_unload(SimplyWindow *self); From f0a9657dba2ffb6042ee1a5e39aabd15a0a33045 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 01:14:13 -0700 Subject: [PATCH 130/791] Add window hide method implementation --- src/js/simply-pebble.js | 9 ++++++++- src/js/simply.js | 8 ++++---- src/js/ui/window.js | 8 +++++--- src/js/ui/windowstack.js | 17 ++++++++--------- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index 07deedc6..41a55549 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -443,6 +443,13 @@ SimplyPebble.window = function(windowDef, clear) { SimplyPebble.sendPacket(packet); }; +SimplyPebble.windowHide = function(windowId) { + var command = commandMap.windowHide; + var packet = makePacket(command); + packet[command.paramMap.id.id] = windowId; + SimplyPebble.sendPacket(packet); +}; + SimplyPebble.card = function(cardDef, clear) { clear = toClearFlags(clear); var command = commandMap.setCard; @@ -541,7 +548,7 @@ SimplyPebble.onAppMessage = function(e) { switch (command.name) { case 'windowHide': - simply.hideWindowById(payload[1]); + simply.hideWindowById(payload[1], false); break; case 'singleClick': case 'longClick': diff --git a/src/js/simply.js b/src/js/simply.js index 8bf5b09c..ceb8017a 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -137,14 +137,14 @@ simply.showWindow = function(wind) { state.windowStack.push(wind); }; -simply.hideWindow = function(wind) { - state.windowStack.remove(wind); +simply.hideWindow = function(wind, broadcast) { + state.windowStack.remove(wind, broadcast); }; -simply.hideWindowById = function(windowId) { +simply.hideWindowById = function(windowId, broadcast) { var wind = simply.getWindow(windowId); if (wind) { - simply.hideWindow(wind); + simply.hideWindow(wind, broadcast); } }; diff --git a/src/js/ui/window.js b/src/js/ui/window.js index fb221883..898d3d0b 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -61,14 +61,16 @@ Window.prototype._id = function() { }; Window.prototype._prop = function() { - return simply.window.apply(this, arguments); + simply.window.apply(this, arguments); }; -Window.prototype._hide = function() { +Window.prototype._hide = function(broadcast) { + if (broadcast === false) { return; } + simply.impl.windowHide(this.state.id); }; Window.prototype.hide = function() { - simply.hideWindow(this); + simply.hideWindow(this, true); return this; }; diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index 088270c2..1b4215f9 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -35,35 +35,34 @@ WindowStack.prototype._show = function(item) { item._show(); }; -WindowStack.prototype._hide = function(item) { +WindowStack.prototype._hide = function(item, broadcast) { if (!item) { return; } this._emitHide(item); - item._hide(); + item._hide(broadcast); }; WindowStack.prototype.push = function(item) { if (item === this.top()) { return; } this.remove(item); - this._hide(this.top()); + var prevTop = this.top(); this.state.items.push(item); this._show(item); + this._hide(prevTop, false); }; -WindowStack.prototype.pop = function(item) { - this.remove(this.top()); +WindowStack.prototype.pop = function(broadcast) { + this.remove(this.top(), broadcast); }; -WindowStack.prototype.remove = function(item) { +WindowStack.prototype.remove = function(item, broadcast) { if (!item) { return; } var index = this.state.items.indexOf(item); if (index === -1) { return; } var wasTop = (item === this.top()); - if (wasTop) { - this._hide(item); - } this.state.items.splice(index, 1); if (wasTop) { this._show(this.top()); + this._hide(item, broadcast); } }; From ce9d80e06dadf926172d06ba00a218b2854a3b55 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 03:17:48 -0700 Subject: [PATCH 131/791] Move script loading to package --- src/js/base/package-pebble.js | 104 ++++++++++++++++++++ src/js/base/package.js | 153 ++++++++++++++++++++++++++++++ src/js/myutil.js | 27 ++++-- src/js/simply-pebble.js | 91 +----------------- src/js/simply.js | 173 ++++------------------------------ 5 files changed, 295 insertions(+), 253 deletions(-) create mode 100644 src/js/base/package-pebble.js create mode 100644 src/js/base/package.js diff --git a/src/js/base/package-pebble.js b/src/js/base/package-pebble.js new file mode 100644 index 00000000..142c4d2b --- /dev/null +++ b/src/js/base/package-pebble.js @@ -0,0 +1,104 @@ + +var simply = require('simply'); +var myutil = require('myutil'); + +var package = require('base/package'); + +var packageImpl = module.exports; + +var getExecPackage = function(execname) { + var packages = package.packages; + for (var path in packages) { + var pkg = packages[path]; + if (pkg && pkg.execname === execname) { + return path; + } + } +}; + +var getExceptionFile = function(e, level) { + var stack = e.stack.split('\n'); + for (var i = level || 0, ii = stack.length; i < ii; ++i) { + var line = stack[i]; + if (line.match(/^\$\d/)) { + var path = getExecPackage(line); + if (path) { + return path; + } + } + } + return stack[level]; +}; + +var getExceptionScope = function(e, level) { + var stack = e.stack.split('\n'); + for (var i = level || 0, ii = stack.length; i < ii; ++i) { + var line = stack[i]; + if (!line || line.match('native code')) { continue; } + return line.match(/^\$\d/) && getExecPackage(line) || line; + } + return stack[level]; +}; + +var setHandlerPath = function(handler, path, level) { + var level0 = 4; // caller -> wrap -> apply -> wrap -> set + handler.path = path || + getExceptionScope(new Error(), (level || 0) + level0) || + package.basename(package.module.filename); + return handler; +}; + +var papply = packageImpl.papply = function(f, args, path) { + try { + return f.apply(this, args); + } catch (e) { + var scope = package.name(!path && getExceptionFile(e) || getExecPackage(path) || path); + console.log(scope + ':' + e.line + ': ' + e + '\n' + e.stack); + simply.text({ + subtitle: scope, + body: e.line + ' ' + e.message, + }, true); + simply.state.run = false; + } +}; + +var protect = packageImpl.protect = function(f, path) { + return function() { + return papply(f, arguments, path); + }; +}; + +packageImpl.wrapHandler = function(handler, level) { + if (!handler) { return; } + setHandlerPath(handler, null, level || 1); + var pkg = package.packages[handler.path]; + if (pkg) { + return protect(pkg.fwrap(handler), handler.path); + } else { + return protect(handler, handler.path); + } +}; + +var toSafeName = function(name) { + name = name.replace(/[^0-9A-Za-z_$]/g, '_'); + if (name.match(/^[0-9]/)) { + name = '_' + name; + } + return name; +}; + +var nextId = 1; + +packageImpl.loadPackage = function(pkg, loader) { + pkg.execname = toSafeName(pkg.name) + '$' + nextId++; + pkg.fapply = myutil.defun(pkg.execname, ['f', 'args'], + 'return f.apply(this, args)' + ); + pkg.fwrap = function(f) { + return function() { + return pkg.fapply(f, arguments); + }; + }; + return papply(loader, null, pkg.name); +}; + diff --git a/src/js/base/package.js b/src/js/base/package.js new file mode 100644 index 00000000..8693a4d5 --- /dev/null +++ b/src/js/base/package.js @@ -0,0 +1,153 @@ +var ajax = require('ajax'); +var util2 = require('util2'); +var myutil = require('myutil'); + +var simply = require('simply'); + +var package = module.exports; + +package.packages = {}; + +package.basepath = function(path) { + return path.replace(/[^\/]*$/, ''); +}; + +package.basename = function(path) { + return path.match(/[^\/]*$/)[0]; +}; + +package.abspath = function(root, path) { + if (!path) { + path = root; + root = null; + } + if (!root && package.module) { + root = package.basepath(package.module.filename); + } + if (!path) { + path = root; + } + if (path.match(/^\/\//)) { + var m = root && root.match(/^(\w+:)\/\//); + path = (m ? m[1] : 'http:') + path; + } + if (root && !path.match(/^\w+:\/\//)) { + path = root + path; + } + return path; +}; + +package.name = function(rootfile, path) { + if (!path) { + path = rootfile; + rootfile = null; + } + if (!rootfile && package.module) { + rootfile = package.basepath(package.module.filename); + } + var name = path; + if (typeof name === 'string') { + name = name.replace(package.basepath(rootfile), ''); + } + return name || package.basename(rootfile); +}; + +package.get = function(root, path) { + return package.packages[package.abspath(root, path)]; +}; + +package.make = function(path) { + var pkg = package.packages[path]; + if (pkg) { return; } + pkg = package.packages[path] = { + name: package.basename(path), + savename: 'script:' + path, + filename: path + }; + return pkg; +}; + +package.loader = function(pkg, script) { + // console shim + var console2 = util2.copy(console); + + console2.log = function() { + var msg = pkg.name + ': ' + myutil.slog.apply(this, arguments); + var width = 45; + var prefix = (new Array(width + 1)).join('\b'); // erase source line + var suffix = msg.length < width ? (new Array(width - msg.length + 1)).join(' ') : 0; + console.log(prefix + msg + suffix); + }; + + // loader + return function() { + var exports = pkg.exports; + var result = myutil.defun(pkg.execName, + ['module', 'require', 'console', 'Pebble', 'simply'], script) + (pkg, package.require, console2, Pebble, simply); + + // backwards compatibility for return-style modules + if (pkg.exports === exports && result) { + pkg.exports = result; + } + + return pkg.exports; + }; +}; + +package.loadScript = function(url, async) { + console.log('loading: ' + url); + + var pkg = package.make(url); + + if (!package.module) { + package.module = pkg; + } + + pkg.exports = {}; + + var loader = util2.noop; + var makeLoader = function(script) { + return package.loader(pkg, script); + }; + + ajax({ url: url, cache: false, async: async }, + function(data) { + if (data && data.length) { + localStorage.setItem(pkg.savename, data); + loader = makeLoader(data); + } + }, + function(data, status) { + data = localStorage.getItem(pkg.savename); + if (data && data.length) { + console.log(status + ': failed, loading saved script instead'); + loader = makeLoader(data); + } + } + ); + + return package.impl.loadPackage(pkg, loader); +}; + +/** + * Loads external dependencies, allowing you to write a multi-file project. + * Package loading loosely follows the CommonJS format. + * Exporting is possible by modifying or setting module.exports within the required file. + * The module path is also available as module.path. + * This currently only supports a relative path to another JavaScript file. + * @global + * @param {string} path - The path to the dependency. + */ + +package.require = function(path) { + if (!path.match(/\.js$/)) { + path += '.js'; + } + var pkg = package.get(path); + if (pkg) { + return pkg.exports; + } + path = package.abspath(path); + return package.loadScript(path, false); +}; diff --git a/src/js/myutil.js b/src/js/myutil.js index 17b168ca..54379e5e 100644 --- a/src/js/myutil.js +++ b/src/js/myutil.js @@ -1,8 +1,23 @@ - -var myutil = (function(){ +var util2 = require('util2'); var myutil = {}; +myutil.defun = function(fn, fargs, fbody) { + if (!fbody) { + fbody = fargs; + fargs = []; + } + return new Function('return function ' + fn + '(' + fargs.join(', ') + ') {' + fbody + '}')(); +}; + +myutil.slog = function() { + var args = []; + for (var i = 0, ii = arguments.length; i < ii; ++i) { + args[i] = util2.toString(arguments[i]); + } + return args.join(' '); +}; + myutil.toObject = function(key, value) { if (typeof key === 'object') { return key; @@ -57,10 +72,4 @@ myutil.makeAccessor = function(k) { }; }; -if (typeof module !== 'undefined') { - module.exports = myutil; -} - -return myutil; - -})(); +module.exports = myutil; diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index 41a55549..a2059078 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -3,6 +3,10 @@ var util2 = require('util2'); var SimplyPebble = {}; +var package = require('base/package'); +var packageImpl = require('base/package-pebble'); +package.impl = packageImpl; + if (typeof Image === 'undefined') { window.Image = function(){}; } @@ -264,93 +268,6 @@ SimplyPebble.init = function() { simply.init(); }; -var getExecPackage = function(execName) { - var packages = simply.packages; - for (var path in packages) { - var package = packages[path]; - if (package && package.execName === execName) { - return path; - } - } -}; - -var getExceptionFile = function(e, level) { - var stack = e.stack.split('\n'); - for (var i = level || 0, ii = stack.length; i < ii; ++i) { - var line = stack[i]; - if (line.match(/^\$\d/)) { - var path = getExecPackage(line); - if (path) { - return path; - } - } - } - return stack[level]; -}; - -var getExceptionScope = function(e, level) { - var stack = e.stack.split('\n'); - for (var i = level || 0, ii = stack.length; i < ii; ++i) { - var line = stack[i]; - if (!line || line.match('native code')) { continue; } - return line.match(/^\$\d/) && getExecPackage(line) || line; - } - return stack[level]; -}; - -var setHandlerPath = function(handler, path, level) { - var level0 = 4; // caller -> wrap -> apply -> wrap -> set - handler.path = path || getExceptionScope(new Error(), (level || 0) + level0) || simply.basename(); - return handler; -}; - -var papply = function(f, args, path) { - try { - return f.apply(this, args); - } catch (e) { - var scope = !path && getExceptionFile(e) || getExecPackage(path) || path; - console.log(scope + ':' + e.line + ': ' + e + '\n' + e.stack); - simply.text({ - subtitle: scope, - body: e.line + ' ' + e.message, - }, true); - simply.state.run = false; - } -}; - -var protect = function(f, path) { - return function() { - return papply(f, arguments, path); - }; -}; - -SimplyPebble.wrapHandler = function(handler, level) { - if (!handler) { return; } - setHandlerPath(handler, null, level || 1); - var package = simply.packages[handler.path]; - if (package) { - return protect(package.fwrap(handler), handler.path); - } else { - return protect(handler, handler.path); - } -}; - -var toSafeName = function(name) { - name = name.replace(/[^0-9A-Za-z_$]/g, '_'); - if (name.match(/^[0-9]/)) { - name = '_' + name; - } - return name; -}; - -SimplyPebble.loadPackage = function(pkg, loader) { - pkg.execName = '$' + simply.state.numPackages++ + toSafeName(pkg.name); - pkg.fapply = simply.defun(pkg.execName, ['f', 'args'], 'return f.apply(this, args)'); - pkg.fwrap = function(f) { return function() { return pkg.fapply(f, arguments); }; }; - - return papply(loader, null, pkg.name); -}; - SimplyPebble.onShowConfiguration = function(e) { simply.openSettings(e); }; diff --git a/src/js/simply.js b/src/js/simply.js index ceb8017a..6400309a 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -14,6 +14,8 @@ var simply = module.exports; simply.ui = {}; +var package = require('base/package'); + var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); @@ -49,7 +51,7 @@ simply.init = function() { }; simply.wrapHandler = function(handler) { - return simply.impl.wrapHandler.apply(this, arguments); + return package.impl.wrapHandler.apply(this, arguments); }; simply.reset = function() { @@ -61,7 +63,6 @@ simply.reset = function() { simply.packages = {}; state.run = true; - state.numPackages = 0; state.options = {}; var emitter = new Emitter(); @@ -201,108 +202,6 @@ simply.emit = function(type, subtype, handler) { state.emitter.emit(type, subtype, handler); }; -var pathToName = function(path) { - var name = path; - if (typeof name === 'string') { - name = name.replace(simply.basepath(), ''); - } - return name || simply.basename(); -}; - -simply.getPackageByPath = function(path) { - return simply.packages[pathToName(path)]; -}; - -simply.makePackage = function(path) { - var name = pathToName(path); - var saveName = 'script:' + path; - var pkg = simply.packages[name]; - - if (!pkg) { - pkg = simply.packages[name] = { - name: name, - saveName: saveName, - filename: path - }; - } - - return pkg; -}; - -simply.defun = function(fn, fargs, fbody) { - if (!fbody) { - fbody = fargs; - fargs = []; - } - return new Function('return function ' + fn + '(' + fargs.join(', ') + ') {' + fbody + '}')(); -}; - -var slog = function() { - var args = []; - for (var i = 0, ii = arguments.length; i < ii; ++i) { - args[i] = util2.toString(arguments[i]); - } - return args.join(' '); -}; - -simply.fexecPackage = function(script, pkg) { - // console shim - var console2 = simply.console2 = {}; - for (var k in console) { - console2[k] = console[k]; - } - - console2.log = function() { - var msg = pkg.name + ': ' + slog.apply(this, arguments); - var width = 45; - var prefix = (new Array(width + 1)).join('\b'); // erase Simply.js source line - var suffix = msg.length < width ? (new Array(width - msg.length + 1)).join(' ') : 0; - console.log(prefix + msg + suffix); - }; - - // loader - return function() { - var exports = pkg.exports; - var result = simply.defun(pkg.execName, - ['module', 'require', 'console', 'Pebble', 'simply'], script) - (pkg, simply.require, console2, Pebble, simply); - - // backwards compatibility for return-style modules - if (pkg.exports === exports && result) { - pkg.exports = result; - } - - return pkg.exports; - }; -}; - -simply.loadScript = function(scriptUrl, async) { - console.log('loading: ' + scriptUrl); - - var pkg = simply.makePackage(scriptUrl); - pkg.exports = {}; - - var loader = util2.noop; - var useScript = function(script) { - loader = simply.fexecPackage(script, pkg); - }; - - ajax({ url: scriptUrl, cache: false, async: async }, function(data) { - if (data && data.length) { - localStorage.setItem(pkg.saveName, data); - useScript(data); - } - }, function(data, status) { - data = localStorage.getItem(pkg.saveName); - if (data && data.length) { - console.log(status + ': failed, loading saved script instead'); - useScript(data); - } - }); - - return simply.impl.loadPackage.call(this, pkg, loader); -}; - simply.loadMainScriptUrl = function(scriptUrl) { if (typeof scriptUrl === 'string' && scriptUrl.length && !scriptUrl.match(/^(\w+:)?\/\//)) { scriptUrl = 'http://' + scriptUrl; @@ -323,9 +222,9 @@ simply.loadMainScript = function(scriptUrl) { if (!scriptUrl) { return; } - simply.loadOptions(); + simply.loadOptions(scriptUrl); try { - simply.loadScript(scriptUrl, false); + package.loadScript(scriptUrl, false); } catch (e) { simply.text({ title: 'Failed to load', @@ -335,37 +234,6 @@ simply.loadMainScript = function(scriptUrl) { } }; -simply.basepath = function(path) { - path = path || localStorage.getItem('mainJsUrl'); - return path.replace(/[^\/]*$/, ''); -}; - -simply.basename = function(path) { - path = path || localStorage.getItem('mainJsUrl'); - return path.match(/[^\/]*$/)[0]; -}; - -/** - * Loads external dependencies, allowing you to write a multi-file project. - * Package loading loosely follows the CommonJS format. - * Exporting is possible by modifying or setting module.exports within the required file. - * The module path is also available as module.path. - * This currently only supports a relative path to another JavaScript file. - * @global - * @param {string} path - The path to the dependency. - */ -simply.require = function(path) { - if (!path.match(/\.js$/)) { - path += '.js'; - } - var package = simply.packages[path]; - if (package) { - return package.value; - } - path = baseTransformPath(path); - return simply.loadScript(path, false); -}; - simply.text = function(textDef) { var wind = simply.topWindow(); if (!wind || !(wind instanceof Card)) { @@ -452,18 +320,6 @@ var parseImageHash = function(hash) { return image; }; -var baseTransformPath = function(path) { - var basepath = simply.basepath(); - if (path.match(/^\/\//)) { - var m = basepath.match(/^(\w+:)\/\//); - path = (m ? m[1] : 'http:') + path; - } - if (!path.match(/^\w+:\/\//)) { - path = basepath + path; - } - return path; -}; - simply.image = function(opt, reset, callback) { if (typeof opt === 'string') { opt = parseImageHash(opt); @@ -472,7 +328,7 @@ simply.image = function(opt, reset, callback) { callback = reset; reset = null; } - var url = baseTransformPath(opt.url); + var url = package.abspath(opt.url); var hash = makeImageHash(opt); var image = state.image.cache[hash]; if (image) { @@ -507,21 +363,24 @@ simply.image = function(opt, reset, callback) { return image.id; }; -var getOptionsKey = function() { - return 'options:' + simply.basename(); +var getOptionsKey = function(path) { + return 'options:' + (path || package.module.filename); }; -simply.saveOptions = function() { +simply.saveOptions = function(path) { var options = state.options; - localStorage.setItem(getOptionsKey(), JSON.stringify(options)); + localStorage.setItem(getOptionsKey(path), JSON.stringify(options)); }; -simply.loadOptions = function() { +simply.loadOptions = function(path) { state.options = {}; - var options = localStorage.getItem(getOptionsKey()); + var options = localStorage.getItem(getOptionsKey(path)); try { - state.options = JSON.parse(options); + options = JSON.parse(options); } catch (e) {} + if (typeof options === 'object' && options !== null) { + state.options = options; + } }; simply.option = function(key, value) { From 9618b668c8e242182ac8ada36d35742542042dbe Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 03:41:08 -0700 Subject: [PATCH 132/791] Move libraries to lib and base modules to base --- src/js/{ => base}/emitter.js | 0 src/js/{ => base}/myutil.js | 2 +- src/js/base/package-pebble.js | 6 ++---- src/js/base/package.js | 7 +++---- src/js/{ => lib}/ajax.js | 0 src/js/{ => lib}/image.js | 0 src/js/{ => lib}/util2.js | 0 src/js/simply-pebble.js | 3 ++- src/js/simply.js | 26 +++++++++++++------------- src/js/ui/card.js | 9 ++++----- src/js/ui/menu.js | 9 ++++----- src/js/ui/window.js | 7 +++---- src/js/ui/windowstack.js | 7 +++---- 13 files changed, 35 insertions(+), 41 deletions(-) rename src/js/{ => base}/emitter.js (100%) rename src/js/{ => base}/myutil.js (97%) rename src/js/{ => lib}/ajax.js (100%) rename src/js/{ => lib}/image.js (100%) rename src/js/{ => lib}/util2.js (100%) diff --git a/src/js/emitter.js b/src/js/base/emitter.js similarity index 100% rename from src/js/emitter.js rename to src/js/base/emitter.js diff --git a/src/js/myutil.js b/src/js/base/myutil.js similarity index 97% rename from src/js/myutil.js rename to src/js/base/myutil.js index 54379e5e..d539ebc0 100644 --- a/src/js/myutil.js +++ b/src/js/base/myutil.js @@ -1,4 +1,4 @@ -var util2 = require('util2'); +var util2 = require('lib/util2'); var myutil = {}; diff --git a/src/js/base/package-pebble.js b/src/js/base/package-pebble.js index 142c4d2b..46702729 100644 --- a/src/js/base/package-pebble.js +++ b/src/js/base/package-pebble.js @@ -1,8 +1,6 @@ - -var simply = require('simply'); -var myutil = require('myutil'); - +var myutil = require('base/myutil'); var package = require('base/package'); +var simply = require('simply'); var packageImpl = module.exports; diff --git a/src/js/base/package.js b/src/js/base/package.js index 8693a4d5..3ecaaed9 100644 --- a/src/js/base/package.js +++ b/src/js/base/package.js @@ -1,7 +1,6 @@ -var ajax = require('ajax'); -var util2 = require('util2'); -var myutil = require('myutil'); - +var ajax = require('lib/ajax'); +var util2 = require('lib/util2'); +var myutil = require('base/myutil'); var simply = require('simply'); var package = module.exports; diff --git a/src/js/ajax.js b/src/js/lib/ajax.js similarity index 100% rename from src/js/ajax.js rename to src/js/lib/ajax.js diff --git a/src/js/image.js b/src/js/lib/image.js similarity index 100% rename from src/js/image.js rename to src/js/lib/image.js diff --git a/src/js/util2.js b/src/js/lib/util2.js similarity index 100% rename from src/js/util2.js rename to src/js/lib/util2.js diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index a2059078..aa41cdbf 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -1,5 +1,6 @@ +var util2 = require('lib/util2'); + var simply = require('simply'); -var util2 = require('util2'); var SimplyPebble = {}; diff --git a/src/js/simply.js b/src/js/simply.js index 6400309a..020966f0 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -3,24 +3,24 @@ * @namespace simply */ -var ajax = require('ajax'); -var util2 = require('util2'); -var myutil = require('myutil'); -var imagelib = require('image'); - -var Emitter = require('emitter'); - -var simply = module.exports; - -simply.ui = {}; +var ajax = require('lib/ajax'); +var util2 = require('lib/util2'); +var imagelib = require('lib/image'); +var myutil = require('base/myutil'); var package = require('base/package'); +var Emitter = require('base/emitter'); var WindowStack = require('ui/windowstack'); - var Window = require('ui/window'); -var Card = simply.ui.Card = require('ui/card'); -var Menu = simply.ui.Menu = require('ui/menu'); +var Card = require('ui/card'); +var Menu = require('ui/menu'); + +var simply = module.exports; + +simply.ui = {}; +simply.ui.Card = Card; +simply.ui.Menu = Menu; var eventTypes = [ 'accelTap', diff --git a/src/js/ui/card.js b/src/js/ui/card.js index 61f51f51..7f0b419d 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -1,9 +1,8 @@ -var util2 = require('util2'); -var myutil = require('myutil'); -var simply = require('simply'); - -var Emitter = require('emitter'); +var util2 = require('lib/util2'); +var myutil = require('base/myutil'); +var Emitter = require('base/emitter'); var Window = require('ui/window'); +var simply = require('simply'); /** * Sets the title field. The title field is the first and largest text field available. diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 60f83b75..ca39010b 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -1,9 +1,8 @@ -var util2 = require('util2'); -var myutil = require('myutil'); -var simply = require('simply'); - -var Emitter = require('emitter'); +var util2 = require('lib/util2'); +var myutil = require('base/myutil'); +var Emitter = require('base/emitter'); var Window = require('ui/window'); +var simply = require('simply'); var Menu = function(menuDef) { Window.call(this, menuDef); diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 898d3d0b..93324158 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -1,9 +1,8 @@ -var util2 = require('util2'); -var myutil = require('myutil'); +var util2 = require('lib/util2'); +var myutil = require('base/myutil'); +var Emitter = require('base/emitter'); var simply = require('simply'); -var Emitter = require('emitter'); - var buttons = [ 'back', 'up', diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index 1b4215f9..5df3692b 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -1,9 +1,8 @@ -var util2 = require('util2'); -var myutil = require('myutil'); +var util2 = require('lib/util2'); +var myutil = require('base/myutil'); +var Emitter = require('base/emitter'); var simply = require('simply'); -var Emitter = require('emitter'); - var WindowStack = function() { this.state = {}; this.state.items = []; From 32dcf906810f5153597d905a3d30fcec16bb811e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 03:46:04 -0700 Subject: [PATCH 133/791] Export ajax to window --- src/js/lib/ajax.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/lib/ajax.js b/src/js/lib/ajax.js index 6c963f3c..42e47fdb 100644 --- a/src/js/lib/ajax.js +++ b/src/js/lib/ajax.js @@ -98,6 +98,8 @@ ajax.formify = formify; if (typeof module !== 'undefined') { module.exports = ajax; +} else { + window.ajax = ajax; } return ajax; From 33c06b3210550417521f7baa6bb4846251d26d5e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 03:57:56 -0700 Subject: [PATCH 134/791] Move image handling to base image --- src/js/base/image.js | 95 ++++++++++++++++++++++++++++++++++++++++ src/js/simply-pebble.js | 4 +- src/js/simply.js | 97 ++--------------------------------------- 3 files changed, 101 insertions(+), 95 deletions(-) create mode 100644 src/js/base/image.js diff --git a/src/js/base/image.js b/src/js/base/image.js new file mode 100644 index 00000000..119516a7 --- /dev/null +++ b/src/js/base/image.js @@ -0,0 +1,95 @@ +var imagelib = require('lib/image'); +var package = require('base/package'); +var simply = require('simply'); + +var ImageService = module.exports; + +var state; + +ImageService.init = function() { + state = Image.state = { + cache: {}, + nextId: 1, + }; +}; + +var makeImageHash = function(image) { + var url = image.url; + var hashPart = ''; + if (image.width) { + hashPart += ',width:' + image.width; + } + if (image.height) { + hashPart += ',height:' + image.height; + } + if (image.dither) { + hashPart += ',dither:' + image.dither; + } + if (hashPart) { + url += '#' + hashPart.substr(1); + } + return url; +}; + +var parseImageHash = function(hash) { + var image = {}; + hash = hash.split('#'); + image.url = hash[0]; + hash = hash[1]; + if (!hash) { return image; } + var args = hash.split(','); + for (var i = 0, ii = args.length; i < ii; ++i) { + var arg = args[i]; + if (arg.match(':')) { + arg = arg.split(':'); + var v = arg[1]; + image[arg[0]] = !isNaN(Number(v)) ? Number(v) : v; + } else { + image[arg] = true; + } + } + return image; +}; + +ImageService.load = function(opt, reset, callback) { + if (typeof opt === 'string') { + opt = parseImageHash(opt); + } + if (typeof reset === 'function') { + callback = reset; + reset = null; + } + var url = package.abspath(opt.url); + var hash = makeImageHash(opt); + var image = state.cache[hash]; + if (image) { + if ((opt.width && image.width !== opt.width) || + (opt.height && image.height !== opt.height) || + (opt.dither && image.dither !== opt.dither)) { + reset = true; + } + if (reset !== true) { + return image.id; + } + } + image = { + id: state.nextId++, + url: url, + width: opt.width, + height: opt.height, + dither: opt.dither, + }; + state.cache[hash] = image; + imagelib.load(image, function() { + simply.impl.image(image.id, image.gbitmap); + if (callback) { + var e = { + type: 'image', + image: image.id, + url: image.url, + }; + callback(e); + } + }); + return image.id; +}; diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index aa41cdbf..334558ff 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -1,5 +1,5 @@ var util2 = require('lib/util2'); - +var ImageService = require('base/image'); var simply = require('simply'); var SimplyPebble = {}; @@ -283,7 +283,7 @@ var toParam = function(param, v) { } else if (param.type === Boolean) { v = v ? 1 : 0; } else if (param.type === Image && typeof v !== 'number') { - v = simply.image(v); + v = ImageService.load(v); } return v; }; diff --git a/src/js/simply.js b/src/js/simply.js index 020966f0..73ce4774 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -5,11 +5,11 @@ var ajax = require('lib/ajax'); var util2 = require('lib/util2'); -var imagelib = require('lib/image'); var myutil = require('base/myutil'); var package = require('base/package'); var Emitter = require('base/emitter'); +var ImageService = require('base/image'); var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); @@ -27,11 +27,7 @@ var eventTypes = [ 'accelData', ]; -var state = {}; - -simply.state = state; -simply.packages = {}; -simply.listeners = {}; +var state = simply.state = {}; simply.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; @@ -60,7 +56,6 @@ simply.reset = function() { } simply.state = state = {}; - simply.packages = {}; state.run = true; state.options = {}; @@ -74,15 +69,12 @@ simply.reset = function() { windowStack.on('hide', simply.onHideWindow); state.windowStack = windowStack; - state.image = { - cache: {}, - nextId: 1, - }; - state.webview = { listeners: [], }; + ImageService.init(); + simply.accelInit(); }; @@ -282,87 +274,6 @@ simply.vibe = function() { return simply.impl.vibe.apply(this, arguments); }; -var makeImageHash = function(image) { - var url = image.url; - var hashPart = ''; - if (image.width) { - hashPart += ',width:' + image.width; - } - if (image.height) { - hashPart += ',height:' + image.height; - } - if (image.dither) { - hashPart += ',dither:' + image.dither; - } - if (hashPart) { - url += '#' + hashPart.substr(1); - } - return url; -}; - -var parseImageHash = function(hash) { - var image = {}; - hash = hash.split('#'); - image.url = hash[0]; - hash = hash[1]; - if (!hash) { return image; } - var args = hash.split(','); - for (var i = 0, ii = args.length; i < ii; ++i) { - var arg = args[i]; - if (arg.match(':')) { - arg = arg.split(':'); - var v = arg[1]; - image[arg[0]] = !isNaN(Number(v)) ? Number(v) : v; - } else { - image[arg] = true; - } - } - return image; -}; - -simply.image = function(opt, reset, callback) { - if (typeof opt === 'string') { - opt = parseImageHash(opt); - } - if (typeof reset === 'function') { - callback = reset; - reset = null; - } - var url = package.abspath(opt.url); - var hash = makeImageHash(opt); - var image = state.image.cache[hash]; - if (image) { - if ((opt.width && image.width !== opt.width) || - (opt.height && image.height !== opt.height) || - (opt.dither && image.dither !== opt.dither)) { - reset = true; - } - if (reset !== true) { - return image.id; - } - } - image = { - id: state.image.nextId++, - url: url, - width: opt.width, - height: opt.height, - dither: opt.dither, - }; - state.image.cache[hash] = image; - imagelib.load(image, function() { - simply.impl.image(image.id, image.gbitmap); - if (callback) { - var e = { - type: 'image', - image: image.id, - url: image.url, - }; - callback(e); - } - }); - return image.id; -}; - var getOptionsKey = function(path) { return 'options:' + (path || package.module.filename); }; From a085609a2055fde9a29b8a424ef1dde94fcf6441 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 05:32:33 -0700 Subject: [PATCH 135/791] Move accel service to accel --- src/js/base/accel.js | 144 +++++++++++++++++++++++++ src/js/base/emitter.js | 66 ++++++------ src/js/simply-pebble.js | 7 +- src/js/simply.js | 226 ++-------------------------------------- src/js/ui/window.js | 7 ++ 5 files changed, 203 insertions(+), 247 deletions(-) create mode 100644 src/js/base/accel.js diff --git a/src/js/base/accel.js b/src/js/base/accel.js new file mode 100644 index 00000000..3f65efd0 --- /dev/null +++ b/src/js/base/accel.js @@ -0,0 +1,144 @@ +var Emitter = require('base/emitter'); + +var Accel = new Emitter(); + +module.exports = Accel; + +var simply = require('simply'); + +var state; + +Accel.init = function() { + if (state) { + Accel.off(); + } + + state = Accel.state = { + rate: 100, + samples: 25, + subscribe: false, + subscribeMode: 'auto', + listeners: [], + }; +}; + +Accel.onAddHandler = function(type, subtype) { + if (type === 'data') { + Accel.autoSubscribe(); + } +}; + +Accel.onRemoveHandler = function(type, subtype) { + if (!type || type === 'accelData') { + Accel.autoSubscribe(); + } +}; + +Accel.autoSubscribe = function() { + if (state.subscribeMode !== 'auto') { return; } + var subscribe = (this.listenerCount('data') + simply.listenerCount('accelData') > 0); + if (subscribe !== state.subscribe) { + return Accel.config(subscribe, true); + } +}; + +/** + * The accelerometer configuration parameter for {@link simply.accelConfig}. + * The accelerometer data stream is useful for applications such as gesture recognition when accelTap is too limited. + * However, keep in mind that smaller batch sample sizes and faster rates will drastically impact the battery life of both the Pebble and phone because of the taxing use of the processors and Bluetooth modules. + * @typedef {object} simply.accelConf + * @property {number} [rate] - The rate accelerometer data points are generated in hertz. Valid values are 10, 25, 50, and 100. Initializes as 100. + * @property {number} [samples] - The number of accelerometer data points to accumulate in a batch before calling the event handler. Valid values are 1 to 25 inclusive. Initializes as 25. + * @property {boolean} [subscribe] - Whether to subscribe to accelerometer data events. {@link simply.accelPeek} cannot be used when subscribed. Simply.js will automatically (un)subscribe for you depending on the amount of accelData handlers registered. + */ + +/** + * Changes the accelerometer configuration. + * See {@link simply.accelConfig} + * @memberOf simply + * @param {simply.accelConfig} accelConf - An object defining the accelerometer configuration. + */ +Accel.config = function(opt, auto) { + if (typeof opt === 'undefined') { + return { + rate: state.rate, + samples: state.samples, + subscribe: state.subscribe, + }; + } else if (typeof opt === 'boolean') { + opt = { subscribe: opt }; + } + for (var k in opt) { + if (k === 'subscribe') { + state.subscribeMode = opt[k] && !auto ? 'manual' : 'auto'; + } + state[k] = opt[k]; + } + return simply.impl.accelConfig.apply(this, arguments); +}; + +/** + * Peeks at the current accelerometer values. + * @memberOf simply + * @param {simply.eventHandler} callback - A callback function that will be provided the accel data point as an event. + */ +Accel.peek = function(callback) { + if (state.subscribe) { + throw new Error('Cannot use accelPeek when listening to accelData events'); + } + return simply.impl.accelPeek.apply(this, arguments); +}; + +/** + * Simply.js accel tap event. + * Use the event type 'accelTap' to subscribe to these events. + * @typedef simply.accelTapEvent + * @property {string} axis - The axis the tap event occurred on: 'x', 'y', or 'z'. This is also the event subtype. + * @property {number} direction - The direction of the tap along the axis: 1 or -1. + */ + +Accel.emitAccelTap = function(axis, direction) { + var e = { + axis: axis, + direction: direction, + }; + if (simply.emitWindow('accelTap', axis, e) === false) { + return false; + } + Accel.emit('tap', axis, e); +}; + +/** + * Simply.js accel data point. + * Typical values for gravity is around -1000 on the z axis. + * @typedef simply.accelPoint + * @property {number} x - The acceleration across the x-axis. + * @property {number} y - The acceleration across the y-axis. + * @property {number} z - The acceleration across the z-axis. + * @property {boolean} vibe - Whether the watch was vibrating when measuring this point. + * @property {number} time - The amount of ticks in millisecond resolution when measuring this point. + */ + +/** + * Simply.js accel data event. + * Use the event type 'accelData' to subscribe to these events. + * @typedef simply.accelDataEvent + * @property {number} samples - The number of accelerometer samples in this event. + * @property {simply.accelPoint} accel - The first accel in the batch. This is provided for convenience. + * @property {simply.accelPoint[]} accels - The accelerometer samples in an array. + */ + +Accel.emitAccelData = function(accels, callback) { + var e = { + samples: accels.length, + accel: accels[0], + accels: accels, + }; + if (callback) { + return callback(e); + } + if (simply.emitWindow('accelData', null, e) === false) { + return false; + } + Accel.emit('data', e); +}; diff --git a/src/js/base/emitter.js b/src/js/base/emitter.js index 26b4efd4..35b0a8c5 100644 --- a/src/js/base/emitter.js +++ b/src/js/base/emitter.js @@ -7,17 +7,7 @@ Emitter.prototype.wrapHandler = function(handler) { return handler; }; -Emitter.prototype.on = function(type, subtype, handler) { - if (!handler) { - handler = subtype; - subtype = 'all'; - } - if (Emitter.onAddHandler) { - Emitter.onAddHandler(type, subtype, handler); - } - if (this.onAddHandler) { - this.onAddHandler(type, subtype, handler); - } +Emitter.prototype._on = function(type, subtype, handler) { var typeMap = this._events || ( this._events = {} ); var subtypeMap = typeMap[type] || ( typeMap[type] = {} ); (subtypeMap[subtype] || ( subtypeMap[subtype] = [] )).push({ @@ -26,17 +16,7 @@ Emitter.prototype.on = function(type, subtype, handler) { }); }; -Emitter.prototype.off = function(type, subtype, handler) { - if (!handler) { - handler = subtype; - subtype = 'all'; - } - if (Emitter.onRemoveHandler) { - Emitter.onRemoveHandler(type, subtype, handler); - } - if (this.onRemoveHandler) { - this.onRemoveHandler(type, subtype, handler); - } +Emitter.prototype._off = function(type, subtype, handler) { if (!type) { this._events = {}; return; @@ -65,6 +45,34 @@ Emitter.prototype.off = function(type, subtype, handler) { handlers.splice(index, 1); }; +Emitter.prototype.on = function(type, subtype, handler) { + if (!handler) { + handler = subtype; + subtype = 'all'; + } + this._on(type, subtype, handler); + if (Emitter.onAddHandler) { + Emitter.onAddHandler(type, subtype, handler); + } + if (this.onAddHandler) { + this.onAddHandler(type, subtype, handler); + } +}; + +Emitter.prototype.off = function(type, subtype, handler) { + if (!handler) { + handler = subtype; + subtype = 'all'; + } + this._off(type, subtype, handler); + if (Emitter.onRemoveHandler) { + Emitter.onRemoveHandler(type, subtype, handler); + } + if (this.onRemoveHandler) { + this.onRemoveHandler(type, subtype, handler); + } +}; + Emitter.prototype.listeners = function(type, subtype) { if (!subtype) { subtype = 'all'; @@ -81,27 +89,27 @@ Emitter.prototype.listenerCount = function(type, subtype) { return listeners ? listeners.length : 0; }; -Emitter.prototype.forEachListener = function(type, subtype, f) { +Emitter.prototype.forEachListener = function(type, subtype, callback) { var typeMap = this._events; if (!typeMap) { return; } var subtypeMap; - if (typeof f === 'function') { + if (typeof callback === 'function') { var handlers = this.listeners(type, subtype); if (!handlers) { return; } for (var i = 0, ii = handlers.length; i < ii; ++i) { - f(type, subtype, handlers[i]); + callback.call(this, type, subtype, handlers[i]); } } else if (typeof subtype === 'function') { - f = subtype; + callback = subtype; subtypeMap = typeMap[type]; if (!subtypeMap) { return; } for (subtype in subtypeMap) { - this.forEachListener(type, subtype, f); + this.forEachListener(type, subtype, callback); } } else if (typeof type === 'function') { - f = type; + callback = type; for (type in typeMap) { - this.forEachListener(type, f); + this.forEachListener(type, callback); } } }; diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index 334558ff..55dde465 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -1,4 +1,5 @@ var util2 = require('lib/util2'); +var Accel = require('base/accel'); var ImageService = require('base/image'); var simply = require('simply'); @@ -475,7 +476,7 @@ SimplyPebble.onAppMessage = function(e) { break; case 'accelTap': var axis = accelAxes[payload[1]]; - simply.emitAccelTap(axis, payload[2]); + Accel.emitAccelTap(axis, payload[2]); break; case 'accelData': var transactionId = payload[1]; @@ -494,12 +495,12 @@ SimplyPebble.onAppMessage = function(e) { accels[i] = accel; } if (typeof transactionId === 'undefined') { - simply.emitAccelData(accels); + Accel.emitAccelData(accels); } else { var handlers = simply.state.accel.listeners; simply.state.accel.listeners = []; for (var j = 0, jj = handlers.length; j < jj; ++j) { - simply.emitAccelData(accels, handlers[j]); + Accel.emitAccelData(accels, handlers[j]); } } break; diff --git a/src/js/simply.js b/src/js/simply.js index 73ce4774..de61a411 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -9,6 +9,7 @@ var util2 = require('lib/util2'); var myutil = require('base/myutil'); var package = require('base/package'); var Emitter = require('base/emitter'); +var Accel = require('base/accel'); var ImageService = require('base/image'); var WindowStack = require('ui/windowstack'); @@ -18,15 +19,12 @@ var Menu = require('ui/menu'); var simply = module.exports; +simply.accel = Accel; + simply.ui = {}; simply.ui.Card = Card; simply.ui.Menu = Menu; -var eventTypes = [ - 'accelTap', - 'accelData', -]; - var state = simply.state = {}; simply.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; @@ -73,49 +71,28 @@ simply.reset = function() { listeners: [], }; - ImageService.init(); - - simply.accelInit(); -}; - -/** - * Simply.js event handler callback. - * @callback simply.eventHandler - * @param {simply.event} event - The event object with event specific information. - */ - -simply.onAddHandler = function(type, subtype) { - if (type === 'accelData') { - simply.accelAutoSubscribe(); - } -}; + Accel.init(); -simply.onRemoveHandler = function(type, subtype) { - if (!type || type === 'accelData') { - simply.accelAutoSubscribe(); - } + ImageService.init(); }; simply.listenerCount = function(type, subtype) { var count = 0; - var listeners = state.emitter.listeners(type, subtype); - count += (listeners ? listeners.length : 0); var wind = simply.topWindow(); - listeners = wind && wind.emitter.listeners(type, subtype); - count += (listeners ? listeners.length : 0); + if (wind) { + count += wind.listenerCount(type, subtype); + } return count; }; simply.onShowWindow = function(e) { var wind = e.window; wind.forEachListener(wind.onAddHandler); - wind.forEachListener(simply.onAddHandler); }; simply.onHideWindow = function(e) { var wind = e.window; wind.forEachListener(wind.onAddHandler); - wind.forEachListener(simply.onRemoveHandler); }; simply.topWindow = function() { @@ -141,59 +118,6 @@ simply.hideWindowById = function(windowId, broadcast) { } }; -var checkEventType = function(type) { - if (eventTypes.indexOf(type) === -1) { - throw new Error('Invalid event type: ' + type); - } -}; - -/** - * Subscribe to Pebble events. - * See {@link simply.event} for the possible event types to subscribe to. - * Subscribing to a Pebble event requires a handler. An event object will be passed to your handler with event information. - * Events can have a subtype which can be used to filter events before the handler is called. - * @memberOf simply - * @param {string} type - The event type. - * @param {string} [subtype] - The event subtype. - * @param {simply.eventHandler} handler - The event handler. The handler will be called with corresponding event. - * @see simply.event - */ -simply.on = function(type, subtype, handler) { - if (type) { - checkEventType(type); - } - if (!handler) { - handler = subtype; - subtype = 'all'; - } - state.emitter.on(type, subtype, handler); -}; - -/** - * Unsubscribe from Pebble events. - * When called without a handler, all handlers of the type and subtype are unsubscribe. - * When called with no parameters, all handlers are unsubscribed. - * @memberOf simply - * @param {string} type - The event type. - * @param {string} [subtype] - The event subtype. - * @param {function} [handler] - The event handler to unsubscribe. - * @see simply.on - */ -simply.off = function(type, subtype, handler) { - if (type) { - checkEventType(type); - } - if (!handler) { - handler = subtype; - subtype = 'all'; - } - state.emitter.off(type, subtype, handler); -}; - -simply.emit = function(type, subtype, handler) { - state.emitter.emit(type, subtype, handler); -}; - simply.loadMainScriptUrl = function(scriptUrl) { if (typeof scriptUrl === 'string' && scriptUrl.length && !scriptUrl.match(/^(\w+:)?\/\//)) { scriptUrl = 'http://' + scriptUrl; @@ -379,83 +303,6 @@ simply.closeSettings = function(e) { } }; -simply.accelInit = function() { - state.accel = { - rate: 100, - samples: 25, - subscribe: false, - subscribeMode: 'auto', - listeners: [], - }; -}; - -simply.accelAutoSubscribe = function() { - var accelState = state.accel; - if (!accelState || accelState.subscribeMode !== 'auto') { - return; - } - var subscribe = simply.listenerCount('accelData') > 0; - if (subscribe !== state.accel.subscribe) { - return simply.accelConfig(subscribe, true); - } -}; - -/** - * The accelerometer configuration parameter for {@link simply.accelConfig}. - * The accelerometer data stream is useful for applications such as gesture recognition when accelTap is too limited. - * However, keep in mind that smaller batch sample sizes and faster rates will drastically impact the battery life of both the Pebble and phone because of the taxing use of the processors and Bluetooth modules. - * @typedef {object} simply.accelConf - * @property {number} [rate] - The rate accelerometer data points are generated in hertz. Valid values are 10, 25, 50, and 100. Initializes as 100. - * @property {number} [samples] - The number of accelerometer data points to accumulate in a batch before calling the event handler. Valid values are 1 to 25 inclusive. Initializes as 25. - * @property {boolean} [subscribe] - Whether to subscribe to accelerometer data events. {@link simply.accelPeek} cannot be used when subscribed. Simply.js will automatically (un)subscribe for you depending on the amount of accelData handlers registered. - */ - -/** - * Changes the accelerometer configuration. - * See {@link simply.accelConfig} - * @memberOf simply - * @param {simply.accelConfig} accelConf - An object defining the accelerometer configuration. - */ -simply.accelConfig = function(opt, auto) { - var accelState = state.accel; - if (typeof opt === 'undefined') { - return { - rate: accelState.rate, - samples: accelState.samples, - subscribe: accelState.subscribe, - }; - } else if (typeof opt === 'boolean') { - opt = { subscribe: opt }; - } - for (var k in opt) { - if (k === 'subscribe') { - accelState.subscribeMode = opt[k] && !auto ? 'manual' : 'auto'; - } - accelState[k] = opt[k]; - } - return simply.impl.accelConfig.apply(this, arguments); -}; - -/** - * Peeks at the current accelerometer values. - * @memberOf simply - * @param {simply.eventHandler} callback - A callback function that will be provided the accel data point as an event. - */ -simply.accelPeek = function(callback) { - if (state.accel.subscribe) { - throw new Error('Cannot use accelPeek when listening to accelData events'); - } - return simply.impl.accelPeek.apply(this, arguments); -}; - -/** - * Simply.js event. See all the possible event types. Subscribe to events using {@link simply.on}. - * @typedef simply.event - * @see simply.clickEvent - * @see simply.accelTapEvent - * @see simply.accelDataEvent - */ - /** * Simply.js button click event. This can either be a single click or long click. * Use the event type 'singleClick' or 'longClick' to subscribe to these events. @@ -463,7 +310,7 @@ simply.accelPeek = function(callback) { * @property {string} button - The button that was pressed: 'back', 'up', 'select', or 'down'. This is also the event subtype. */ -simply.emitWindow = function(type, subtype, e, globalType, klass) { +simply.emitWindow = function(type, subtype, e, klass) { var wind = e.window = simply.topWindow(); if (klass) { e[klass._codeName] = wind; @@ -471,9 +318,6 @@ simply.emitWindow = function(type, subtype, e, globalType, klass) { if (wind && wind.emit(type, subtype, e) === false) { return false; } - if (globalType) { - return simply.emit(globalType, subtype, e); - } }; simply.emitClick = function(type, button) { @@ -483,56 +327,8 @@ simply.emitClick = function(type, button) { return simply.emitWindow(type, button, e); }; -/** - * Simply.js accel tap event. - * Use the event type 'accelTap' to subscribe to these events. - * @typedef simply.accelTapEvent - * @property {string} axis - The axis the tap event occurred on: 'x', 'y', or 'z'. This is also the event subtype. - * @property {number} direction - The direction of the tap along the axis: 1 or -1. - */ - -simply.emitAccelTap = function(axis, direction) { - var e = { - axis: axis, - direction: direction, - }; - return simply.emitWindow('accelTap', axis, e, 'accelTap'); -}; - -/** - * Simply.js accel data point. - * Typical values for gravity is around -1000 on the z axis. - * @typedef simply.accelPoint - * @property {number} x - The acceleration across the x-axis. - * @property {number} y - The acceleration across the y-axis. - * @property {number} z - The acceleration across the z-axis. - * @property {boolean} vibe - Whether the watch was vibrating when measuring this point. - * @property {number} time - The amount of ticks in millisecond resolution when measuring this point. - */ - -/** - * Simply.js accel data event. - * Use the event type 'accelData' to subscribe to these events. - * @typedef simply.accelDataEvent - * @property {number} samples - The number of accelerometer samples in this event. - * @property {simply.accelPoint} accel - The first accel in the batch. This is provided for convenience. - * @property {simply.accelPoint[]} accels - The accelerometer samples in an array. - */ - -simply.emitAccelData = function(accels, callback) { - var e = { - samples: accels.length, - accel: accels[0], - accels: accels, - }; - if (callback) { - return callback(e); - } - return simply.emitWindow('accelData', null, e, 'accelData'); -}; - -simply.emitMenu = function(type, subtype, e, globalType) { - simply.emitWindow(type, subtype, e, globalType, Menu); +simply.emitMenu = function(type, subtype, e) { + simply.emitWindow(type, subtype, e, Menu); }; simply.emitMenuSection = function(section) { diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 93324158..feb64223 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -1,6 +1,7 @@ var util2 = require('lib/util2'); var myutil = require('base/myutil'); var Emitter = require('base/emitter'); +var Accel = require('base/accel'); var simply = require('simply'); var buttons = [ @@ -146,12 +147,18 @@ Window.prototype.onAddHandler = function(type, subtype) { if (isBackEvent(type, subtype)) { this._buttonAutoConfig(); } + if (type === 'accelData') { + Accel.autoSubscribe(); + } }; Window.prototype.onRemoveHandler = function(type, subtype) { if (!type || isBackEvent(type, subtype)) { this._buttonAutoConfig(); } + if (!type || type === 'accelData') { + Accel.autoSubscribe(); + } }; Window.prototype._buttonInit = function() { From 12e78aeda7d2a1d1c8f03b42e9207f764c636d42 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 05:51:34 -0700 Subject: [PATCH 136/791] Move settings handling to settings --- src/js/base/settings.js | 119 ++++++++++++++++++++++++++++++++++++ src/js/simply-pebble.js | 5 +- src/js/simply.js | 130 +++++----------------------------------- 3 files changed, 137 insertions(+), 117 deletions(-) create mode 100644 src/js/base/settings.js diff --git a/src/js/base/settings.js b/src/js/base/settings.js new file mode 100644 index 00000000..2adbdefc --- /dev/null +++ b/src/js/base/settings.js @@ -0,0 +1,119 @@ +var util2 = require('lib/util2'); +var package = require('base/package'); +var simply = require('simply'); + +var Settings = module.exports; + +var state; + +Settings.init = function() { + state = Settings.state = { + options: {}, + listeners: [], + }; +}; + +var getOptionsKey = function(path) { + return 'options:' + (path || package.module.filename); +}; + +Settings.saveOptions = function(path) { + var options = state.options; + localStorage.setItem(getOptionsKey(path), JSON.stringify(options)); +}; + +Settings.loadOptions = function(path) { + state.options = {}; + var options = localStorage.getItem(getOptionsKey(path)); + try { + options = JSON.parse(options); + } catch (e) {} + if (typeof options === 'object' && options !== null) { + state.options = options; + } +}; + +Settings.option = function(key, value) { + var options = state.options; + if (arguments.length >= 2) { + if (typeof value === 'undefined') { + delete options[key]; + } else { + try { + value = JSON.stringify(value); + } catch (e) {} + options[key] = '' + value; + } + Settings.saveOptions(); + } + value = options[key]; + if (!isNaN(Number(value))) { + return Number(value); + } + try { + value = JSON.parse(value); + } catch (e) {} + return value; +}; + +Settings.getBaseOptions = function() { + return { + scriptUrl: simply.mainScriptUrl(), + }; +}; + +Settings.config = function(opt, open, close) { + if (typeof opt === 'string') { + opt = { url: opt }; + } + if (typeof close === 'undefined') { + close = open; + open = util2.noop; + } + var listener = { + params: opt, + open: open, + close: close, + }; + state.webview.listeners.push(listener); +}; + +Settings.onOpenConfig = function(e) { + var options; + var url; + var listener = util2.last(state.webview.listeners); + if (listener) { + url = listener.params.url; + options = state.options; + e = { + originalEvent: e, + options: options, + url: listener.params.url, + }; + listener.open(e); + } else { + url = simply.settingsUrl; + options = Settings.getBaseOptions(); + } + var hash = encodeURIComponent(JSON.stringify(options)); + Pebble.openURL(url + '#' + hash); +}; + +Settings.onCloseConfig = function(e) { + var listener = util2.last(state.webview.listeners); + var options = {}; + if (e.response) { + options = JSON.parse(decodeURIComponent(e.response)); + } + if (listener) { + e = { + originalEvent: e, + options: options, + url: listener.params.url, + }; + return listener.close(e); + } + if (options.scriptUrl) { + simply.loadMainScript(options.scriptUrl); + } +}; diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index 55dde465..3fffbe16 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -1,4 +1,5 @@ var util2 = require('lib/util2'); +var Settings = require('base/settings'); var Accel = require('base/accel'); var ImageService = require('base/image'); var simply = require('simply'); @@ -271,11 +272,11 @@ SimplyPebble.init = function() { }; SimplyPebble.onShowConfiguration = function(e) { - simply.openSettings(e); + Settings.onOpenConfig(e); }; SimplyPebble.onWebViewClosed = function(e) { - simply.closeSettings(e); + Settings.onCloseConfig(e); }; var toParam = function(param, v) { diff --git a/src/js/simply.js b/src/js/simply.js index de61a411..42468927 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -9,6 +9,7 @@ var util2 = require('lib/util2'); var myutil = require('base/myutil'); var package = require('base/package'); var Emitter = require('base/emitter'); +var Settings = require('base/settings'); var Accel = require('base/accel'); var ImageService = require('base/image'); @@ -19,6 +20,8 @@ var Menu = require('ui/menu'); var simply = module.exports; +simply.settings = Settings; + simply.accel = Accel; simply.ui = {}; @@ -56,7 +59,6 @@ simply.reset = function() { simply.state = state = {}; state.run = true; - state.options = {}; var emitter = new Emitter(); emitter.wrapHandler = simply.wrapHandler; @@ -67,9 +69,7 @@ simply.reset = function() { windowStack.on('hide', simply.onHideWindow); state.windowStack = windowStack; - state.webview = { - listeners: [], - }; + Settings.init(); Accel.init(); @@ -118,16 +118,21 @@ simply.hideWindowById = function(windowId, broadcast) { } }; -simply.loadMainScriptUrl = function(scriptUrl) { - if (typeof scriptUrl === 'string' && scriptUrl.length && !scriptUrl.match(/^(\w+:)?\/\//)) { - scriptUrl = 'http://' + scriptUrl; - } - +simply.mainScriptUrl = function(scriptUrl) { if (scriptUrl) { localStorage.setItem('mainJsUrl', scriptUrl); } else { scriptUrl = localStorage.getItem('mainJsUrl'); } + return scriptUrl; +}; + +simply.loadMainScriptUrl = function(scriptUrl) { + if (typeof scriptUrl === 'string' && scriptUrl.length && !scriptUrl.match(/^(\w+:)?\/\//)) { + scriptUrl = 'http://' + scriptUrl; + } + + scriptUrl = simply.mainScriptUrl(scriptUrl); return scriptUrl; }; @@ -138,7 +143,7 @@ simply.loadMainScript = function(scriptUrl) { if (!scriptUrl) { return; } - simply.loadOptions(scriptUrl); + Settings.loadOptions(scriptUrl); try { package.loadScript(scriptUrl, false); } catch (e) { @@ -198,111 +203,6 @@ simply.vibe = function() { return simply.impl.vibe.apply(this, arguments); }; -var getOptionsKey = function(path) { - return 'options:' + (path || package.module.filename); -}; - -simply.saveOptions = function(path) { - var options = state.options; - localStorage.setItem(getOptionsKey(path), JSON.stringify(options)); -}; - -simply.loadOptions = function(path) { - state.options = {}; - var options = localStorage.getItem(getOptionsKey(path)); - try { - options = JSON.parse(options); - } catch (e) {} - if (typeof options === 'object' && options !== null) { - state.options = options; - } -}; - -simply.option = function(key, value) { - var options = state.options; - if (arguments.length >= 2) { - if (typeof value === 'undefined') { - delete options[key]; - } else { - try { - value = JSON.stringify(value); - } catch (e) {} - options[key] = '' + value; - } - simply.saveOptions(); - } - value = options[key]; - if (!isNaN(Number(value))) { - return Number(value); - } - try { - value = JSON.parse(value); - } catch (e) {} - return value; -}; - -simply.getBaseOptions = function() { - return { - scriptUrl: localStorage.getItem('mainJsUrl'), - }; -}; - -simply.settings = function(opt, open, close) { - if (typeof opt === 'string') { - opt = { url: opt }; - } - if (typeof close === 'undefined') { - close = open; - open = util2.noop; - } - var listener = { - params: opt, - open: open, - close: close, - }; - state.webview.listeners.push(listener); -}; - -simply.openSettings = function(e) { - var options; - var url; - var listener = util2.last(state.webview.listeners); - if (listener) { - url = listener.params.url; - options = state.options; - e = { - originalEvent: e, - options: options, - url: listener.params.url, - }; - listener.open(e); - } else { - url = simply.settingsUrl; - options = simply.getBaseOptions(); - } - var hash = encodeURIComponent(JSON.stringify(options)); - Pebble.openURL(url + '#' + hash); -}; - -simply.closeSettings = function(e) { - var listener = util2.last(state.webview.listeners); - var options = {}; - if (e.response) { - options = JSON.parse(decodeURIComponent(e.response)); - } - if (listener) { - e = { - originalEvent: e, - options: options, - url: listener.params.url, - }; - return listener.close(e); - } - if (options.scriptUrl) { - simply.loadMainScript(options.scriptUrl); - } -}; - /** * Simply.js button click event. This can either be a single click or long click. * Use the event type 'singleClick' or 'longClick' to subscribe to these events. From 75448ee4becb277456dc2088f9988325acf8a095 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 06:29:05 -0700 Subject: [PATCH 137/791] Change window stack module to a singleton --- src/js/base/accel.js | 12 ++- src/js/simply-pebble.js | 13 ++-- src/js/simply.js | 158 +-------------------------------------- src/js/ui/card.js | 5 +- src/js/ui/menu.js | 63 +++++++++++++++- src/js/ui/window.js | 41 +++++++++- src/js/ui/windowstack.js | 36 ++++++--- 7 files changed, 148 insertions(+), 180 deletions(-) diff --git a/src/js/base/accel.js b/src/js/base/accel.js index 3f65efd0..55e7f8fa 100644 --- a/src/js/base/accel.js +++ b/src/js/base/accel.js @@ -1,4 +1,5 @@ var Emitter = require('base/emitter'); +var WindowStack = require('ui/windowstack'); var Accel = new Emitter(); @@ -34,9 +35,18 @@ Accel.onRemoveHandler = function(type, subtype) { } }; +var accelDataListenerCount = function() { + var count = Accel.listenerCount('data'); + var wind = WindowStack.top(); + if (wind) { + count += wind.listenerCount('accelData'); + } + return count; +}; + Accel.autoSubscribe = function() { if (state.subscribeMode !== 'auto') { return; } - var subscribe = (this.listenerCount('data') + simply.listenerCount('accelData') > 0); + var subscribe = (accelDataListenerCount > 0); if (subscribe !== state.subscribe) { return Accel.config(subscribe, true); } diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index 3fffbe16..807e1009 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -2,6 +2,9 @@ var util2 = require('lib/util2'); var Settings = require('base/settings'); var Accel = require('base/accel'); var ImageService = require('base/image'); +var WindowStack = require('ui/windowstack'); +var Window = require('ui/window'); +var Menu = require('ui/menu'); var simply = require('simply'); var SimplyPebble = {}; @@ -468,12 +471,12 @@ SimplyPebble.onAppMessage = function(e) { switch (command.name) { case 'windowHide': - simply.hideWindowById(payload[1], false); + WindowStack.remove(payload[1], false); break; case 'singleClick': case 'longClick': var button = buttons[payload[1]]; - simply.emitClick(command.name, button); + Window.emitClick(command.name, button); break; case 'accelTap': var axis = accelAxes[payload[1]]; @@ -506,14 +509,14 @@ SimplyPebble.onAppMessage = function(e) { } break; case 'getMenuSection': - simply.emitMenuSection(payload[1]); + Menu.emitSection(payload[1]); break; case 'getMenuItem': - simply.emitMenuItem(payload[1], payload[2]); + Menu.emitItem(payload[1], payload[2]); break; case 'menuSelect': case 'menuLongSelect': - simply.emitMenuSelect(command.name, payload[1], payload[2]); + Menu.emitSelect(command.name, payload[1], payload[2]); break; } }; diff --git a/src/js/simply.js b/src/js/simply.js index 42468927..be11aa7c 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -14,7 +14,6 @@ var Accel = require('base/accel'); var ImageService = require('base/image'); var WindowStack = require('ui/windowstack'); -var Window = require('ui/window'); var Card = require('ui/card'); var Menu = require('ui/menu'); @@ -40,6 +39,8 @@ simply.init = function() { return simply.wrapHandler(handler, 2); }; + Emitter.prototype.wrapHandler = simply.wrapHandler; + Emitter.onAddHandler = simply.onAddHandler; Emitter.onRemoveHandler = simply.onRemoveHandler; } @@ -52,22 +53,11 @@ simply.wrapHandler = function(handler) { }; simply.reset = function() { - if (state.emitter) { - simply.off(); - } - simply.state = state = {}; state.run = true; - var emitter = new Emitter(); - emitter.wrapHandler = simply.wrapHandler; - state.emitter = emitter; - - var windowStack = new WindowStack(); - windowStack.on('show', simply.onShowWindow); - windowStack.on('hide', simply.onHideWindow); - state.windowStack = windowStack; + WindowStack.init(); Settings.init(); @@ -76,48 +66,6 @@ simply.reset = function() { ImageService.init(); }; -simply.listenerCount = function(type, subtype) { - var count = 0; - var wind = simply.topWindow(); - if (wind) { - count += wind.listenerCount(type, subtype); - } - return count; -}; - -simply.onShowWindow = function(e) { - var wind = e.window; - wind.forEachListener(wind.onAddHandler); -}; - -simply.onHideWindow = function(e) { - var wind = e.window; - wind.forEachListener(wind.onAddHandler); -}; - -simply.topWindow = function() { - return state.windowStack.top(); -}; - -simply.getWindow = function(windowId) { - return state.windowStack.get(windowId); -}; - -simply.showWindow = function(wind) { - state.windowStack.push(wind); -}; - -simply.hideWindow = function(wind, broadcast) { - state.windowStack.remove(wind, broadcast); -}; - -simply.hideWindowById = function(windowId, broadcast) { - var wind = simply.getWindow(windowId); - if (wind) { - simply.hideWindow(wind, broadcast); - } -}; - simply.mainScriptUrl = function(scriptUrl) { if (scriptUrl) { localStorage.setItem('mainJsUrl', scriptUrl); @@ -156,7 +104,7 @@ simply.loadMainScript = function(scriptUrl) { }; simply.text = function(textDef) { - var wind = simply.topWindow(); + var wind = WindowStack.top(); if (!wind || !(wind instanceof Card)) { wind = new Card(textDef); wind.show(); @@ -165,34 +113,6 @@ simply.text = function(textDef) { } }; -simply.window = function(windowDef) { - var wind = simply.topWindow(); - if (wind === this) { - simply.impl.window.apply(this, arguments); - } -}; - -simply.card = function(windowDef) { - var wind = simply.topWindow(); - if (wind === this) { - simply.impl.card.apply(this, arguments); - } -}; - -simply.menu = function(menuDef) { - var wind = simply.topWindow(); - if (wind === this) { - simply.impl.menu.apply(this, arguments); - } -}; - -simply.action = function(actionDef) { - var wind = simply.topWindow(); - if (wind === this) { - simply.impl.window({ action: typeof actionDef === 'boolean' ? actionDef : this.state.action }, 'action'); - } -}; - /** * Vibrates the Pebble. * There are three support vibe types: short, long, and double. @@ -202,73 +122,3 @@ simply.action = function(actionDef) { simply.vibe = function() { return simply.impl.vibe.apply(this, arguments); }; - -/** - * Simply.js button click event. This can either be a single click or long click. - * Use the event type 'singleClick' or 'longClick' to subscribe to these events. - * @typedef simply.clickEvent - * @property {string} button - The button that was pressed: 'back', 'up', 'select', or 'down'. This is also the event subtype. - */ - -simply.emitWindow = function(type, subtype, e, klass) { - var wind = e.window = simply.topWindow(); - if (klass) { - e[klass._codeName] = wind; - } - if (wind && wind.emit(type, subtype, e) === false) { - return false; - } -}; - -simply.emitClick = function(type, button) { - var e = { - button: button, - }; - return simply.emitWindow(type, button, e); -}; - -simply.emitMenu = function(type, subtype, e) { - simply.emitWindow(type, subtype, e, Menu); -}; - -simply.emitMenuSection = function(section) { - var menu = simply.topWindow(); - if (!(menu instanceof Menu)) { return; } - var e = { - section: section - }; - if (simply.emitMenu('section', null, e) === false) { - return false; - } - menu._resolveSection(e); -}; - -simply.emitMenuItem = function(section, item) { - var menu = simply.topWindow(); - if (!(menu instanceof Menu)) { return; } - var e = { - section: section, - item: item, - }; - if (simply.emitMenu('item', null, e) === false) { - return false; - } - menu._resolveItem(e); -}; - -simply.emitMenuSelect = function(type, section, item) { - var menu = simply.topWindow(); - if (!(menu instanceof Menu)) { return; } - var e = { - section: section, - item: item, - }; - switch (type) { - case 'menuSelect': type = 'select'; break; - case 'menuLongSelect': type = 'longSelect'; break; - } - if (simply.emitMenu(type, null, e) === false) { - return false; - } - menu._emitSelect(e); -}; diff --git a/src/js/ui/card.js b/src/js/ui/card.js index 7f0b419d..b6c6c496 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -1,6 +1,7 @@ var util2 = require('lib/util2'); var myutil = require('base/myutil'); var Emitter = require('base/emitter'); +var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); var simply = require('simply'); @@ -75,7 +76,9 @@ accessorProps.forEach(function(k) { }); Card.prototype._prop = function() { - return simply.card.apply(this, arguments); + if (this === WindowStack.top()) { + simply.impl.card.apply(this, arguments); + } }; Card.prototype._clear = function(flags) { diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index ca39010b..8738dd0f 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -1,6 +1,7 @@ var util2 = require('lib/util2'); var myutil = require('base/myutil'); var Emitter = require('base/emitter'); +var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); var simply = require('simply'); @@ -21,7 +22,9 @@ Menu.prototype._show = function() { }; Menu.prototype._prop = function() { - return simply.menu.apply(this, arguments); + if (this === WindowStack.top()) { + simply.impl.menu.apply(this, arguments); + } }; Menu.prototype.action = function() { @@ -139,7 +142,9 @@ Menu.prototype._resolveMenu = function() { if (typeof sections === 'number') { this.state.sections = new Array(sections); } - return simply.menu.call(this); + if (this === WindowStack.top()) { + simply.impl.menu.call(this, this.state); + } } }; @@ -153,7 +158,9 @@ Menu.prototype._resolveSection = function(e) { if (typeof section.items === 'number') { section.items = new Array(section.items); } - return simply.impl.menuSection.call(this, e.section, section); + if (this === WindowStack.top()) { + simply.impl.menuSection.call(this, e.section, section); + } } } }; @@ -161,7 +168,9 @@ Menu.prototype._resolveSection = function(e) { Menu.prototype._resolveItem = function(e) { var item = getItem.call(this, e); if (item) { - return simply.impl.menuItem.call(this, e.section, e.item, item); + if (this === WindowStack.top()) { + simply.impl.menuItem.call(this, e.section, e.item, item); + } } }; @@ -263,4 +272,50 @@ Menu.prototype.item = function(sectionIndex, itemIndex, item) { return this; }; +Menu.emit = function(type, subtype, e) { + Window.emit(type, subtype, e, Menu); +}; + +Menu.emitSection = function(section) { + var menu = WindowStack.top(); + if (!(menu instanceof Menu)) { return; } + var e = { + section: section + }; + if (Menu.emit('section', null, e) === false) { + return false; + } + menu._resolveSection(e); +}; + +Menu.emitItem = function(section, item) { + var menu = WindowStack.top(); + if (!(menu instanceof Menu)) { return; } + var e = { + section: section, + item: item, + }; + if (Menu.emit('item', null, e) === false) { + return false; + } + menu._resolveItem(e); +}; + +Menu.emitSelect = function(type, section, item) { + var menu = WindowStack.top(); + if (!(menu instanceof Menu)) { return; } + var e = { + section: section, + item: item, + }; + switch (type) { + case 'menuSelect': type = 'select'; break; + case 'menuLongSelect': type = 'longSelect'; break; + } + if (Menu.emit(type, null, e) === false) { + return false; + } + menu._emitSelect(e); +}; + module.exports = Menu; diff --git a/src/js/ui/window.js b/src/js/ui/window.js index feb64223..7ad90b28 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -2,6 +2,7 @@ var util2 = require('lib/util2'); var myutil = require('base/myutil'); var Emitter = require('base/emitter'); var Accel = require('base/accel'); +var WindowStack = require('ui/windowstack'); var simply = require('simply'); var buttons = [ @@ -61,7 +62,9 @@ Window.prototype._id = function() { }; Window.prototype._prop = function() { - simply.window.apply(this, arguments); + if (this === WindowStack.top()) { + simply.impl.window.apply(this, arguments); + } }; Window.prototype._hide = function(broadcast) { @@ -70,7 +73,7 @@ Window.prototype._hide = function(broadcast) { }; Window.prototype.hide = function() { - simply.hideWindow(this, true); + WindowStack.remove(this, true); return this; }; @@ -79,7 +82,7 @@ Window.prototype._show = function() { }; Window.prototype.show = function() { - simply.showWindow(this); + WindowStack.push(this); return this; }; @@ -115,6 +118,12 @@ Window.prototype.prop = function(field, value, clear) { return this; }; +Window.prototype._action = function(actionDef) { + if (this === WindowStack.top()) { + simply.impl.window({ action: typeof actionDef === 'boolean' ? actionDef : this.state.action }, 'action'); + } +}; + Window.prototype.action = function(field, value, clear) { var action = this.state.action; if (!action) { @@ -135,7 +144,7 @@ Window.prototype.action = function(field, value, clear) { if (typeof field !== 'boolean') { util2.copy(myutil.toObject(field, value), this.state.action); } - simply.action.call(this); + this._action(); return this; }; @@ -228,4 +237,28 @@ Window.prototype._buttonAutoConfig = function() { } }; +Window.emit = function(type, subtype, e, klass) { + var wind = e.window = WindowStack.top(); + if (klass) { + e[klass._codeName] = wind; + } + if (wind && wind.emit(type, subtype, e) === false) { + return false; + } +}; + +/** + * Simply.js button click event. This can either be a single click or long click. + * Use the event type 'singleClick' or 'longClick' to subscribe to these events. + * @typedef simply.clickEvent + * @property {string} button - The button that was pressed: 'back', 'up', 'select', or 'down'. This is also the event subtype. + */ + +Window.emitClick = function(type, button) { + var e = { + button: button, + }; + return Window.emit(type, button, e); +}; + module.exports = Window; diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index 5df3692b..957007d2 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -1,17 +1,27 @@ var util2 = require('lib/util2'); var myutil = require('base/myutil'); var Emitter = require('base/emitter'); -var simply = require('simply'); var WindowStack = function() { - this.state = {}; - this.state.items = []; + this.init(); }; util2.copy(Emitter.prototype, WindowStack.prototype); +WindowStack.prototype.init = function() { + this.off(); + this._items = []; + + this.on('show', function(e) { + e.window.forEachListener(e.window.onAddHandler); + }); + this.on('hide', function(e) { + e.window.forEachListener(e.window.onRemoveHandler); + }); +}; + WindowStack.prototype.top = function() { - return util2.last(this.state.items); + return util2.last(this._items); }; WindowStack.prototype._emitShow = function(item) { @@ -44,29 +54,33 @@ WindowStack.prototype.push = function(item) { if (item === this.top()) { return; } this.remove(item); var prevTop = this.top(); - this.state.items.push(item); + this._items.push(item); this._show(item); this._hide(prevTop, false); }; WindowStack.prototype.pop = function(broadcast) { - this.remove(this.top(), broadcast); + return this.remove(this.top(), broadcast); }; WindowStack.prototype.remove = function(item, broadcast) { + if (typeof item === 'number') { + item = this.get(item); + } if (!item) { return; } - var index = this.state.items.indexOf(item); - if (index === -1) { return; } + var index = this._items.indexOf(item); + if (index === -1) { return item; } var wasTop = (item === this.top()); - this.state.items.splice(index, 1); + this._items.splice(index, 1); if (wasTop) { this._show(this.top()); this._hide(item, broadcast); } + return item; }; WindowStack.prototype.get = function(windowId) { - var items = this.state.items; + var items = this._items; for (var i = 0, ii = items.length; i < ii; ++i) { var wind = items[i]; if (wind._id() === windowId) { @@ -75,4 +89,4 @@ WindowStack.prototype.get = function(windowId) { } }; -module.exports = WindowStack; +module.exports = new WindowStack(); From 83fef46c15badbd408336a86c14bdd5d6d563779 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 06:31:22 -0700 Subject: [PATCH 138/791] Remove global state --- src/js/simply-pebble.js | 2 +- src/js/simply.js | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index 807e1009..ac538841 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -316,7 +316,7 @@ var makePacket = function(command, def) { }; SimplyPebble.sendPacket = function(packet) { - if (!simply.state.run) { + if (!simply.run) { return; } var send; diff --git a/src/js/simply.js b/src/js/simply.js index be11aa7c..26df52a5 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -27,8 +27,6 @@ simply.ui = {}; simply.ui.Card = Card; simply.ui.Menu = Menu; -var state = simply.state = {}; - simply.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; simply.init = function() { @@ -53,17 +51,12 @@ simply.wrapHandler = function(handler) { }; simply.reset = function() { - simply.state = state = {}; - - state.run = true; - - WindowStack.init(); + simply.run = true; Settings.init(); - Accel.init(); - ImageService.init(); + WindowStack.init(); }; simply.mainScriptUrl = function(scriptUrl) { From 9c3c54c4aa7009fcbf5503fc6144b21f52d54bde Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 06:47:36 -0700 Subject: [PATCH 139/791] Move main url setting, loading to settings, package --- src/js/base/package.js | 20 ++++++++++++++ src/js/base/settings.js | 21 ++++++++++++--- src/js/simply.js | 58 +++++------------------------------------ 3 files changed, 43 insertions(+), 56 deletions(-) diff --git a/src/js/base/package.js b/src/js/base/package.js index 3ecaaed9..175c7cad 100644 --- a/src/js/base/package.js +++ b/src/js/base/package.js @@ -1,6 +1,7 @@ var ajax = require('lib/ajax'); var util2 = require('lib/util2'); var myutil = require('base/myutil'); +var Settings = require('base/settings'); var simply = require('simply'); var package = module.exports; @@ -129,6 +130,25 @@ package.loadScript = function(url, async) { return package.impl.loadPackage(pkg, loader); }; +package.loadMainScript = function(scriptUrl) { + simply.reset(); + + scriptUrl = Settings.mainScriptUrl(scriptUrl); + if (!scriptUrl) { return; } + + Settings.loadOptions(scriptUrl); + + try { + package.loadScript(scriptUrl, false); + } catch (e) { + simply.text({ + title: 'Failed to load', + body: scriptUrl, + }, true); + return; + } +}; + /** * Loads external dependencies, allowing you to write a multi-file project. * Package loading loosely follows the CommonJS format. diff --git a/src/js/base/settings.js b/src/js/base/settings.js index 2adbdefc..40ea955a 100644 --- a/src/js/base/settings.js +++ b/src/js/base/settings.js @@ -1,11 +1,12 @@ var util2 = require('lib/util2'); var package = require('base/package'); -var simply = require('simply'); var Settings = module.exports; var state; +Settings.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; + Settings.init = function() { state = Settings.state = { options: {}, @@ -13,6 +14,18 @@ Settings.init = function() { }; }; +Settings.mainScriptUrl = function(scriptUrl) { + if (typeof scriptUrl === 'string' && scriptUrl.length && !scriptUrl.match(/^(\w+:)?\/\//)) { + scriptUrl = 'http://' + scriptUrl; + } + if (scriptUrl) { + localStorage.setItem('mainJsUrl', scriptUrl); + } else { + scriptUrl = localStorage.getItem('mainJsUrl'); + } + return scriptUrl; +}; + var getOptionsKey = function(path) { return 'options:' + (path || package.module.filename); }; @@ -58,7 +71,7 @@ Settings.option = function(key, value) { Settings.getBaseOptions = function() { return { - scriptUrl: simply.mainScriptUrl(), + scriptUrl: Settings.mainScriptUrl(), }; }; @@ -92,7 +105,7 @@ Settings.onOpenConfig = function(e) { }; listener.open(e); } else { - url = simply.settingsUrl; + url = Settings.settingsUrl; options = Settings.getBaseOptions(); } var hash = encodeURIComponent(JSON.stringify(options)); @@ -114,6 +127,6 @@ Settings.onCloseConfig = function(e) { return listener.close(e); } if (options.scriptUrl) { - simply.loadMainScript(options.scriptUrl); + package.loadMainScript(options.scriptUrl); } }; diff --git a/src/js/simply.js b/src/js/simply.js index 26df52a5..1cc70a13 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -27,23 +27,14 @@ simply.ui = {}; simply.ui.Card = Card; simply.ui.Menu = Menu; -simply.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; - -simply.init = function() { - if (!simply.inited) { - simply.inited = new Date().getTime(); - - ajax.onHandler = function(type, handler) { - return simply.wrapHandler(handler, 2); - }; - - Emitter.prototype.wrapHandler = simply.wrapHandler; +ajax.onHandler = function(type, handler) { + return simply.wrapHandler(handler, 2); +}; - Emitter.onAddHandler = simply.onAddHandler; - Emitter.onRemoveHandler = simply.onRemoveHandler; - } +Emitter.prototype.wrapHandler = simply.wrapHandler; - simply.loadMainScript(); +simply.init = function() { + package.loadMainScript(); }; simply.wrapHandler = function(handler) { @@ -59,43 +50,6 @@ simply.reset = function() { WindowStack.init(); }; -simply.mainScriptUrl = function(scriptUrl) { - if (scriptUrl) { - localStorage.setItem('mainJsUrl', scriptUrl); - } else { - scriptUrl = localStorage.getItem('mainJsUrl'); - } - return scriptUrl; -}; - -simply.loadMainScriptUrl = function(scriptUrl) { - if (typeof scriptUrl === 'string' && scriptUrl.length && !scriptUrl.match(/^(\w+:)?\/\//)) { - scriptUrl = 'http://' + scriptUrl; - } - - scriptUrl = simply.mainScriptUrl(scriptUrl); - - return scriptUrl; -}; - -simply.loadMainScript = function(scriptUrl) { - simply.reset(); - scriptUrl = simply.loadMainScriptUrl(scriptUrl); - if (!scriptUrl) { - return; - } - Settings.loadOptions(scriptUrl); - try { - package.loadScript(scriptUrl, false); - } catch (e) { - simply.text({ - title: 'Failed to load', - body: scriptUrl, - }, true); - return; - } -}; - simply.text = function(textDef) { var wind = WindowStack.top(); if (!wind || !(wind instanceof Card)) { From 94aaac981c1e6083ac5d545d24c5d723911722a3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 07:11:15 -0700 Subject: [PATCH 140/791] Change to export through the Pebble namespace --- src/js/base/package.js | 4 ++-- src/js/exports.js | 16 ++++++++++++++++ src/js/simply.js | 11 ++--------- 3 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 src/js/exports.js diff --git a/src/js/base/package.js b/src/js/base/package.js index 175c7cad..036eb995 100644 --- a/src/js/base/package.js +++ b/src/js/base/package.js @@ -83,8 +83,8 @@ package.loader = function(pkg, script) { return function() { var exports = pkg.exports; var result = myutil.defun(pkg.execName, - ['module', 'require', 'console', 'Pebble', 'simply'], script) - (pkg, package.require, console2, Pebble, simply); + ['module', 'require', 'console', 'Pebble'], script) + (pkg, package.require, console2, Pebble); // backwards compatibility for return-style modules if (pkg.exports === exports && result) { diff --git a/src/js/exports.js b/src/js/exports.js new file mode 100644 index 00000000..155a36aa --- /dev/null +++ b/src/js/exports.js @@ -0,0 +1,16 @@ +var Settings = require('base/settings'); +var Accel = require('base/accel'); +var Card = require('ui/card'); +var Menu = require('ui/menu'); + +Pebble.Settings = Settings; + +Pebble.Accel = Accel; + +var UI = {}; +UI.Card = Card; +UI.Menu = Menu; + +Pebble.UI = UI; + +module.exports = Pebble; diff --git a/src/js/simply.js b/src/js/simply.js index 1cc70a13..ee58a526 100644 --- a/src/js/simply.js +++ b/src/js/simply.js @@ -15,17 +15,10 @@ var ImageService = require('base/image'); var WindowStack = require('ui/windowstack'); var Card = require('ui/card'); -var Menu = require('ui/menu'); -var simply = module.exports; - -simply.settings = Settings; +require('exports'); -simply.accel = Accel; - -simply.ui = {}; -simply.ui.Card = Card; -simply.ui.Menu = Menu; +var simply = module.exports; ajax.onHandler = function(type, handler) { return simply.wrapHandler(handler, 2); From 517517b61ef6908e9470cfa115441c22828edf02 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:13:49 -0700 Subject: [PATCH 141/791] Add propable class --- src/js/base/myutil.js | 24 ------------------ src/js/ui/card.js | 5 ++-- src/js/ui/propable.js | 58 +++++++++++++++++++++++++++++++++++++++++++ src/js/ui/window.js | 13 +++++----- 4 files changed, 66 insertions(+), 34 deletions(-) create mode 100644 src/js/ui/propable.js diff --git a/src/js/base/myutil.js b/src/js/base/myutil.js index d539ebc0..4b2bd41d 100644 --- a/src/js/base/myutil.js +++ b/src/js/base/myutil.js @@ -48,28 +48,4 @@ myutil.toFlags = function(flags) { return flags; }; -myutil.unset = function(obj, k) { - if (typeof k === 'undefined') { - k = obj; - obj = this.state; - } - if (typeof obj === 'object') { - delete obj[k]; - } -}; - -myutil.makeAccessor = function(k) { - return function(value) { - if (arguments.length === 0) { - return this.state[k]; - } else { - this.state[k] = value; - if (this._prop() === this) { - this._prop(k, value); - } - return this; - } - }; -}; - module.exports = myutil; diff --git a/src/js/ui/card.js b/src/js/ui/card.js index b6c6c496..0a853b8a 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -2,6 +2,7 @@ var util2 = require('lib/util2'); var myutil = require('base/myutil'); var Emitter = require('base/emitter'); var WindowStack = require('ui/windowstack'); +var Propable = require('ui/propable'); var Window = require('ui/window'); var simply = require('simply'); @@ -71,9 +72,7 @@ util2.inherit(Card, Window); util2.copy(Emitter.prototype, Card.prototype); -accessorProps.forEach(function(k) { - Card.prototype[k] = myutil.makeAccessor(k); -}); +Propable.makeAccessors(accessorProps, Card.prototype); Card.prototype._prop = function() { if (this === WindowStack.top()) { diff --git a/src/js/ui/propable.js b/src/js/ui/propable.js new file mode 100644 index 00000000..f268356c --- /dev/null +++ b/src/js/ui/propable.js @@ -0,0 +1,58 @@ +var util2 = require('lib/util2'); +var myutil = require('base/myutil'); + +var Propable = function(def) { + this.state = def || {}; +}; + +Propable.makeAccessor = function(k) { + return function(value) { + if (arguments.length === 0) { + return this.state[k]; + } else { + this.state[k] = value; + this._prop(myutil.toObject(k, value), value); + return this; + } + }; +}; + +Propable.makeAccessors = function(props, proto) { + proto = proto || {}; + props.forEach(function(k) { + proto[k] = Propable.makeAccessor(k); + }); + return proto; +}; + +Propable.prototype.unset = function(k) { + delete this.state[k]; +}; + +Propable.prototype._clear = function() { + this.state = {}; +}; + +Propable.prototype._prop = function(def) { +}; + +Propable.prototype.prop = function(field, value, clear) { + if (arguments.length === 0) { + return util2.copy(this.state); + } + if (arguments.length === 1 && typeof field !== 'object') { + return this.state[field]; + } + if (typeof field === 'object') { + clear = value; + } + if (clear) { + this._clear('all'); + } + var def = myutil.toObject(field, value); + util2.copy(def, this.state); + this._prop(def); + return this; +}; + +module.exports = Propable; diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 7ad90b28..9bbc7a17 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -3,6 +3,7 @@ var myutil = require('base/myutil'); var Emitter = require('base/emitter'); var Accel = require('base/accel'); var WindowStack = require('ui/windowstack'); +var Propable = require('ui/propable'); var simply = require('simply'); var buttons = [ @@ -53,9 +54,9 @@ Window._codeName = 'window'; util2.copy(Emitter.prototype, Window.prototype); -accessorProps.forEach(function(k) { - Window.prototype[k] = myutil.makeAccessor(k); -}); +util2.copy(Propable.prototype, Window.prototype); + +Propable.makeAccessors(accessorProps, Window.prototype); Window.prototype._id = function() { return this.state.id; @@ -69,7 +70,7 @@ Window.prototype._prop = function() { Window.prototype._hide = function(broadcast) { if (broadcast === false) { return; } - simply.impl.windowHide(this.state.id); + simply.impl.windowHide(this._id()); }; Window.prototype.hide = function() { @@ -112,9 +113,7 @@ Window.prototype.prop = function(field, value, clear) { } var windowDef = myutil.toObject(field, value); util2.copy(windowDef, this.state); - if (this._prop() === this) { - this._prop(windowDef); - } + this._prop(windowDef); return this; }; From b019d39732fa5538f66d6eb0ab6b5878d6f1d7d3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:14:33 -0700 Subject: [PATCH 142/791] Fix accel to use window emit --- src/js/base/accel.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/base/accel.js b/src/js/base/accel.js index 55e7f8fa..cc9bcbd2 100644 --- a/src/js/base/accel.js +++ b/src/js/base/accel.js @@ -1,5 +1,6 @@ var Emitter = require('base/emitter'); var WindowStack = require('ui/windowstack'); +var Window = require('ui/window'); var Accel = new Emitter(); @@ -112,7 +113,7 @@ Accel.emitAccelTap = function(axis, direction) { axis: axis, direction: direction, }; - if (simply.emitWindow('accelTap', axis, e) === false) { + if (Window.emit('accelTap', axis, e) === false) { return false; } Accel.emit('tap', axis, e); @@ -147,7 +148,7 @@ Accel.emitAccelData = function(accels, callback) { if (callback) { return callback(e); } - if (simply.emitWindow('accelData', null, e) === false) { + if (Window.emit('accelData', null, e) === false) { return false; } Accel.emit('data', e); From 67f48a2a28b7f5b6c0d468f405246df0fefd9573 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:15:22 -0700 Subject: [PATCH 143/791] Add list1 insert --- src/util/list1.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/util/list1.h b/src/util/list1.h index 678d35bd..0b5357d3 100644 --- a/src/util/list1.h +++ b/src/util/list1.h @@ -53,6 +53,18 @@ static inline List1Node *list1_append(List1Node **head, List1Node *node) { return node; } +static inline List1Node *list1_insert(List1Node **head, int index, List1Node *node) { + List1Node **next_ref = head; + List1Node *walk = *head; + for (int i = 0; walk && i < index; ++i) { + next_ref = &walk->next; + walk = walk->next; + } + node->next = *next_ref; + *next_ref = node; + return node; +} + static inline List1Node *list1_find_prev(List1Node *node, List1FilterCallback callback, void *data, List1Node **prev_out) { for (List1Node *prev = NULL; node; node = node->next) { From b5f60dcf93f975e128a6639d051d5d9fd6482bec Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:15:58 -0700 Subject: [PATCH 144/791] Add simply stage backend --- src/simply_stage.c | 183 ++++++++++++++++++++++++++++++++++++++++++++ src/simply_stage.h | 77 +++++++++++++++++++ src/simply_ui.c | 2 +- src/simply_window.h | 4 +- 4 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 src/simply_stage.c create mode 100644 src/simply_stage.h diff --git a/src/simply_stage.c b/src/simply_stage.c new file mode 100644 index 00000000..f6de67a8 --- /dev/null +++ b/src/simply_stage.c @@ -0,0 +1,183 @@ +#include "simply_stage.h" + +#include "simply_res.h" + +#include "simply_msg.h" + +#include "simplyjs.h" + +#include + +static bool id_filter(List1Node *node, void *data) { + return (((SimplyElementCommon*) node)->id == (uint32_t)(uintptr_t) data); +} + +static void destroy_element(SimplyStage *self, SimplyElementCommon *element) { + if (!element) { return; } + list1_remove(&self->stage_layer.elements, &element->node); + free(element); +} + +static void rect_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRect* element) { + if (element->background_color != GColorClear) { + graphics_context_set_fill_color(ctx, element->background_color); + graphics_fill_rect(ctx, element->frame, element->radius, GCornersAll); + } + if (element->border_color != GColorClear) { + graphics_context_set_stroke_color(ctx, element->border_color); + graphics_draw_round_rect(ctx, element->frame, element->radius); + } +} + +static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementCircle* element) { + if (element->background_color != GColorClear) { + graphics_context_set_fill_color(ctx, element->background_color); + graphics_fill_circle(ctx, element->frame.origin, element->radius); + } + if (element->border_color != GColorClear) { + graphics_context_set_stroke_color(ctx, element->border_color); + graphics_draw_circle(ctx, element->frame.origin, element->radius); + } +} + +static void layer_update_callback(Layer *layer, GContext *ctx) { + SimplyStage *self = *(void**) layer_get_data(layer); + + SimplyElementCommon *element = (SimplyElementCommon*) self->stage_layer.elements; + while (element) { + switch (element->type) { + case SimplyElementTypeNone: + break; + case SimplyElementTypeRect: + rect_element_draw(ctx, self, (SimplyElementRect*) element); + break; + case SimplyElementTypeCircle: + circle_element_draw(ctx, self, (SimplyElementCircle*) element); + break; + } + element = (SimplyElementCommon*) element->node.next; + } +} + +static void *malloc0(size_t size) { + void *buf = malloc(size); + memset(buf, 0, size); + return buf; +} + +static SimplyElementCommon* alloc_element(SimplyElementType type) { + switch (type) { + case SimplyElementTypeNone: return NULL; + case SimplyElementTypeRect: return malloc0(sizeof(SimplyElementRect)); + case SimplyElementTypeCircle: return malloc0(sizeof(SimplyElementCircle)); + } + return NULL; +} + +SimplyElementCommon* simply_stage_auto_element(SimplyStage *self, uint32_t id, SimplyElementType type) { + if (!id) { + return NULL; + } + SimplyElementCommon* element = (SimplyElementCommon*) list1_find( + self->stage_layer.elements, id_filter, (void*)(uintptr_t) id); + if (element) { + return element; + } + element = alloc_element(type); + if (!element) { + return NULL; + } + element->id = id; + element->type = type; + return element; +} + +SimplyElementCommon* simply_stage_insert_element(SimplyStage *self, int index, SimplyElementCommon *element) { + simply_stage_remove_element(self, element); + return (SimplyElementCommon*) list1_insert(&self->stage_layer.elements, index, &element->node); +} + +SimplyElementCommon* simply_stage_remove_element(SimplyStage *self, SimplyElementCommon *element) { + return (SimplyElementCommon*) list1_remove(&self->stage_layer.elements, &element->node); +} + +static void window_load(Window *window) { + SimplyStage *self = window_get_user_data(window); + + simply_window_load(&self->window); + + Layer *window_layer = window_get_root_layer(window); + GRect frame = layer_get_frame(window_layer); + frame.origin = GPointZero; + + Layer *layer = layer_create_with_data(frame, sizeof(void*)); + self->window.layer = self->stage_layer.layer = layer; + *(void**) layer_get_data(layer) = self; + layer_set_update_proc(layer, layer_update_callback); + scroll_layer_add_child(self->window.scroll_layer, layer); + scroll_layer_set_click_config_onto_window(self->window.scroll_layer, window); +} + +static void window_appear(Window *window) { + SimplyStage *self = window_get_user_data(window); + simply_msg_window_show(self->window.id); +} + +static void window_disappear(Window *window) { + SimplyStage *self = window_get_user_data(window); + simply_msg_window_hide(self->window.id); + + while (self->stage_layer.elements) { + destroy_element(self, (SimplyElementCommon*) self->stage_layer.elements); + } +} + +static void window_unload(Window *window) { + SimplyStage *self = window_get_user_data(window); + + layer_destroy(self->stage_layer.layer); + self->window.layer = self->stage_layer.layer = NULL; + + simply_window_unload(&self->window); +} + +void simply_stage_show(SimplyStage *self) { + if (!self->window.window) { + return; + } + if (!window_stack_contains_window(self->window.window)) { + bool animated = true; + window_stack_push(self->window.window, animated); + } +} + +void simply_stage_update(SimplyStage *self) { + layer_mark_dirty(self->stage_layer.layer); +} + +SimplyStage *simply_stage_create(Simply *simply) { + SimplyStage *self = malloc(sizeof(*self)); + *self = (SimplyStage) { .window.simply = simply }; + + simply_window_init(&self->window, simply); + + window_set_user_data(self->window.window, self); + window_set_window_handlers(self->window.window, (WindowHandlers) { + .load = window_load, + .appear = window_appear, + .disappear = window_disappear, + .unload = window_unload, + }); + + return self; +} + +void simply_stage_destroy(SimplyStage *self) { + if (!self) { + return; + } + + simply_window_deinit(&self->window); + + free(self); +} diff --git a/src/simply_stage.h b/src/simply_stage.h new file mode 100644 index 00000000..df3bfb70 --- /dev/null +++ b/src/simply_stage.h @@ -0,0 +1,77 @@ +#pragma once + +#include "simply_window.h" + +#include "simplyjs.h" + +#include "util/list1.h" + +#include + +#define simply_stage_get_element(self, id) simply_stage_auto_element(self, id, SimplyElementTypeNone) + +typedef struct SimplyStageLayer SimplyStageLayer; + +typedef struct SimplyStage SimplyStage; + +typedef struct SimplyStageItem SimplyStageItem; + +typedef enum SimplyElementType SimplyElementType; + +enum SimplyElementType { + SimplyElementTypeNone = 0, + SimplyElementTypeRect = 1, + SimplyElementTypeCircle = 2, +}; + +struct SimplyStageLayer { + Layer *layer; + List1Node *elements; +}; + +struct SimplyStage { + SimplyWindow window; + SimplyStageLayer stage_layer; +}; + +typedef struct SimplyElementCommon SimplyElementCommon; + +#define SimplyElementCommonDef { \ + List1Node node; \ + uint32_t id; \ + SimplyElementType type; \ + GRect frame; \ + GColor background_color:2; \ + GColor border_color:2; \ +} + +struct SimplyElementCommon SimplyElementCommonDef; + +#define SimplyElementCommonMember \ + union { \ + struct SimplyElementCommon common; \ + struct SimplyElementCommonDef; \ + } + +typedef struct SimplyElementRect SimplyElementRect; + +struct SimplyElementRect { + SimplyElementCommonMember; + uint16_t radius; +}; + +typedef struct SimplyElementRect SimplyElementCircle; + +SimplyStage *simply_stage_create(Simply *simply); + +void simply_stage_destroy(SimplyStage *self); + +void simply_stage_show(SimplyStage *self); + +void simply_stage_update(SimplyStage *self); + +SimplyElementCommon* simply_stage_auto_element(SimplyStage *self, uint32_t id, SimplyElementType type); + +SimplyElementCommon* simply_stage_insert_element(SimplyStage *self, int index, SimplyElementCommon *element); + +SimplyElementCommon* simply_stage_remove_element(SimplyStage *self, SimplyElementCommon *element); diff --git a/src/simply_ui.c b/src/simply_ui.c index 23f3df51..443a6ae0 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -84,7 +84,7 @@ void simply_ui_set_text(SimplyUi *self, SimplyUiTextfield textfield, const char } } -void layer_update_callback(Layer *layer, GContext *ctx) { +static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyUi *self = *(void**) layer_get_data(layer); GRect window_frame = layer_get_frame(window_get_root_layer(self->window.window)); diff --git a/src/simply_window.h b/src/simply_window.h index 9df17dab..e863ebdc 100644 --- a/src/simply_window.h +++ b/src/simply_window.h @@ -14,8 +14,8 @@ struct SimplyWindow { ActionBarLayer *action_bar_layer; uint32_t id; uint32_t button_mask; - bool is_scrollable; - bool is_action_bar; + bool is_scrollable:1; + bool is_action_bar:1; }; SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply); From a73460734508996a77fadd58ba4756519714f1b5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:16:48 -0700 Subject: [PATCH 145/791] Add simply msg set stage, stage element --- src/simply_msg.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++- src/simply_msg.h | 9 ----- src/simplyjs.c | 3 ++ src/simplyjs.h | 1 + 4 files changed, 101 insertions(+), 10 deletions(-) diff --git a/src/simply_msg.c b/src/simply_msg.c index b7f7cb33..94ffb7be 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -2,6 +2,7 @@ #include "simply_accel.h" #include "simply_res.h" +#include "simply_stage.h" #include "simply_menu.h" #include "simply_ui.h" @@ -34,9 +35,12 @@ enum SimplyACmd { SimplyACmd_menuSelect, SimplyACmd_menuLongSelect, SimplyACmd_image, + SimplyACmd_setStage, + SimplyACmd_stageElement, + SimplyACmd_stageRemove, }; -typedef enum SimplySetUiParam SimplySetUiParam; +typedef enum SimplySetWindowParam SimplySetWindowParam; enum SimplySetWindowParam { SetWindow_clear = 1, @@ -50,6 +54,8 @@ enum SimplySetWindowParam { SetWindowLast, }; +typedef enum SimplySetUiParam SimplySetUiParam; + enum SimplySetUiParam { SetUi_clear = SetWindow_clear, SetUi_id, @@ -62,12 +68,21 @@ enum SimplySetUiParam { SetUi_style, }; +typedef enum SimplySetMenuParam SimplySetMenuParam; + enum SimplySetMenuParam { SetMenu_clear = SetWindow_clear, SetMenu_id, SetMenu_sections = SetWindowLast, }; +typedef enum SimplySetStageParam SimplySetStageParam; + +enum SimplySetStageParam { + SetStage_clear = SetWindow_clear, + SetStage_id, +}; + typedef enum VibeType VibeType; enum VibeType { @@ -76,6 +91,21 @@ enum VibeType { VibeDouble = 2, }; +typedef enum ElementParam ElementParam; + +enum ElementParam { + ElementId = 1, + ElementType, + ElementIndex, + ElementX, + ElementY, + ElementWidth, + ElementHeight, + ElementBackgroundColor, + ElementBorderColor, + ElementRadius, +}; + static void check_splash(Simply *simply) { if (simply->splash) { simply_ui_show(simply->ui); @@ -303,6 +333,66 @@ static void handle_set_image(DictionaryIterator *iter, Simply *simply) { simply_res_add_image(simply->res, id, width, height, pixels); } +static void handle_set_stage(DictionaryIterator *iter, Simply *simply) { + SimplyStage *stage = simply->stage; + Tuple *tuple; + for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { + switch (tuple->key) { + case SetMenu_id: + stage->window.id = tuple->value->uint32; + break; + } + } + simply_stage_show(stage); + handle_set_window(iter, simply); +} + +static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { + SimplyStage *stage = simply->stage; + Tuple *tuple; + uint32_t id = 0; + SimplyElementType type = SimplyElementTypeNone; + if ((tuple = dict_find(iter, ElementId))) { + id = tuple->value->uint32; + } + if ((tuple = dict_find(iter, ElementType))) { + type = tuple->value->int32; + } + SimplyElementCommon *element = simply_stage_auto_element(stage, id, type); + if (!element) { + return; + } + for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { + switch (tuple->key) { + case ElementIndex: + simply_stage_insert_element(stage, tuple->value->uint16, element); + break; + case ElementX: + element->frame.origin.x = tuple->value->int16; + break; + case ElementY: + element->frame.origin.y = tuple->value->int16; + break; + case ElementWidth: + element->frame.size.w = tuple->value->uint16; + break; + case ElementHeight: + element->frame.size.h = tuple->value->uint16; + break; + case ElementBackgroundColor: + element->background_color = tuple->value->uint8; + break; + case ElementBorderColor: + element->border_color = tuple->value->uint8; + break; + case ElementRadius: + ((SimplyElementRect*) element)->radius = tuple->value->uint16; + break; + } + } + simply_stage_update(stage); +} + static void received_callback(DictionaryIterator *iter, void *context) { Tuple *tuple = dict_find(iter, 0); if (!tuple) { @@ -343,6 +433,12 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_image: handle_set_image(iter, context); break; + case SimplyACmd_setStage: + handle_set_stage(iter, context); + break; + case SimplyACmd_stageElement: + handle_set_stage_element(iter, context); + break; } } diff --git a/src/simply_msg.h b/src/simply_msg.h index 179a4651..5fab9256 100644 --- a/src/simply_msg.h +++ b/src/simply_msg.h @@ -7,28 +7,19 @@ #define TRANSACTION_ID_INVALID (-1) void simply_msg_init(Simply *simply); - void simply_msg_deinit(); bool simply_msg_single_click(ButtonId button); - bool simply_msg_long_click(ButtonId button); bool simply_msg_window_show(uint32_t id); - bool simply_msg_window_hide(uint32_t id); bool simply_msg_accel_tap(AccelAxisType axis, int32_t direction); - bool simply_msg_accel_data(AccelData *accel, uint32_t num_samples, int32_t transaction_id); bool simply_msg_menu_get_section(uint16_t index); - bool simply_msg_menu_get_item(uint16_t section, uint16_t index); - bool simply_msg_menu_select_click(uint16_t section, uint16_t index); - bool simply_msg_menu_select_long_click(uint16_t section, uint16_t index); - bool simply_msg_menu_hide(uint16_t section, uint16_t index); - diff --git a/src/simplyjs.c b/src/simplyjs.c index 684ede3b..3ad11a87 100644 --- a/src/simplyjs.c +++ b/src/simplyjs.c @@ -3,6 +3,7 @@ #include "simply_accel.h" #include "simply_res.h" #include "simply_splash.h" +#include "simply_stage.h" #include "simply_menu.h" #include "simply_ui.h" #include "simply_msg.h" @@ -14,6 +15,7 @@ static Simply *init(void) { simply->accel = simply_accel_create(); simply->res = simply_res_create(); simply->splash = simply_splash_create(simply); + simply->stage = simply_stage_create(simply); simply->menu = simply_menu_create(simply); simply->ui = simply_ui_create(simply); @@ -28,6 +30,7 @@ static void deinit(Simply *simply) { simply_msg_deinit(); simply_ui_destroy(simply->ui); simply_menu_destroy(simply->menu); + simply_stage_destroy(simply->stage); simply_res_destroy(simply->res); simply_accel_destroy(simply->accel); } diff --git a/src/simplyjs.h b/src/simplyjs.h index 1db7a792..a034232a 100644 --- a/src/simplyjs.h +++ b/src/simplyjs.h @@ -8,6 +8,7 @@ struct Simply { struct SimplyAccel *accel; struct SimplyRes *res; struct SimplySplash *splash; + struct SimplyStage *stage; struct SimplyMenu *menu; struct SimplyUi *ui; }; From 70720b99283759e07f09f07c284c84de5cfe6fdb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:17:40 -0700 Subject: [PATCH 146/791] Add ui stage class --- src/js/ui/menu.js | 2 +- src/js/ui/stage.js | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/js/ui/stage.js diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 8738dd0f..af5d75ae 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -18,7 +18,7 @@ util2.copy(Emitter.prototype, Menu.prototype); Menu.prototype._show = function() { this._resolveMenu(); - return Window.prototype._show.apply(this, arguments); + Window.prototype._show.apply(this, arguments); }; Menu.prototype._prop = function() { diff --git a/src/js/ui/stage.js b/src/js/ui/stage.js new file mode 100644 index 00000000..3c98a644 --- /dev/null +++ b/src/js/ui/stage.js @@ -0,0 +1,72 @@ +var util2 = require('lib/util2'); +var Emitter = require('base/emitter'); +var WindowStack = require('ui/windowstack'); +var Window = require('ui/window'); +var simply = require('simply'); + +var Stage = function(stageDef) { + Window.call(this, stageDef); + this._items = []; +}; + +util2.inherit(Stage, Window); + +util2.copy(Emitter.prototype, Stage.prototype); + +Stage.prototype._show = function() { + Window.prototype._show.apply(this, arguments); + this.each(function(element, index) { + this._insert(index, element); + }.bind(this)); +}; + +Stage.prototype._prop = function() { + if (this === WindowStack.top()) { + simply.impl.stage.apply(this, arguments); + } +}; + +Stage.prototype.each = function(callback) { + this._items.forEach(callback); + return this; +}; + +Stage.prototype.index = function(element) { + return this._items.indexOf(element); +}; + +Stage.prototype._insert = function(index, element) { + if (this === WindowStack.top()) { + simply.impl.stageElement(element.state, index); + } +}; + +Stage.prototype._remove = function(element, broadcast) { + if (broadcast === false) { return; } + if (this === WindowStack.top()) { + simply.impl.stageRemove(element._id()); + } +}; + +Stage.prototype.insert = function(index, element) { + element.remove(false); + this._items.splice(index, 0, element); + element.parent = this; + this._insert(this.index(element), element); + return this; +}; + +Stage.prototype.add = function(element) { + return this.insert(this._items.length, element); +}; + +Stage.prototype.remove = function(element, broadcast) { + var index = this.index(element); + if (index === -1) { return this; } + this._remove(element, broadcast); + this._items.splice(index, 1); + delete element.parent; + return this; +}; + +module.exports = Stage; From e644bde73e3d686512968c9804631eaeb9badd96 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:18:08 -0700 Subject: [PATCH 147/791] Add Vector2 utility from Three.js --- src/js/lib/vector2.js | 169 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 src/js/lib/vector2.js diff --git a/src/js/lib/vector2.js b/src/js/lib/vector2.js new file mode 100644 index 00000000..d300d613 --- /dev/null +++ b/src/js/lib/vector2.js @@ -0,0 +1,169 @@ +/** + * Vector2 from Three.js + * https://github.com/mrdoob/three.js + * + * @author mr.doob / http://mrdoob.com/ + * @author philogb / http://blog.thejit.org/ + * @author egraether / http://egraether.com/ + * @author zz85 / http://www.lab4games.net/zz85/blog + */ + +var Vector2 = function ( x, y ) { + + this.x = x || 0; + this.y = y || 0; + +}; + +Vector2.prototype = { + + constructor: Vector2, + + set: function ( x, y ) { + + this.x = x; + this.y = y; + + return this; + + }, + + copy: function ( v ) { + + this.x = v.x; + this.y = v.y; + + return this; + + }, + + clone: function () { + + return new Vector2( this.x, this.y ); + + }, + + add: function ( v1, v2 ) { + + this.x = v1.x + v2.x; + this.y = v1.y + v2.y; + + return this; + + }, + + addSelf: function ( v ) { + + this.x += v.x; + this.y += v.y; + + return this; + + }, + + sub: function ( v1, v2 ) { + + this.x = v1.x - v2.x; + this.y = v1.y - v2.y; + + return this; + + }, + + subSelf: function ( v ) { + + this.x -= v.x; + this.y -= v.y; + + return this; + + }, + + multiplyScalar: function ( s ) { + + this.x *= s; + this.y *= s; + + return this; + + }, + + divideScalar: function ( s ) { + + if ( s ) { + + this.x /= s; + this.y /= s; + + } else { + + this.set( 0, 0 ); + + } + + return this; + + }, + + + negate: function() { + + return this.multiplyScalar( -1 ); + + }, + + dot: function ( v ) { + + return this.x * v.x + this.y * v.y; + + }, + + lengthSq: function () { + + return this.x * this.x + this.y * this.y; + + }, + + length: function () { + + return Math.sqrt( this.lengthSq() ); + + }, + + normalize: function () { + + return this.divideScalar( this.length() ); + + }, + + distanceTo: function ( v ) { + + return Math.sqrt( this.distanceToSquared( v ) ); + + }, + + distanceToSquared: function ( v ) { + + var dx = this.x - v.x, dy = this.y - v.y; + return dx * dx + dy * dy; + + }, + + + setLength: function ( l ) { + + return this.normalize().multiplyScalar( l ); + + }, + + equals: function( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) ); + + } + +}; + +if (typeof module !== 'undefined') { + module.exports = Vector2; +} From e035c01c62b5878c65993a910d07f35945a1890c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:18:41 -0700 Subject: [PATCH 148/791] Add stage element class --- src/js/ui/element.js | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/js/ui/element.js diff --git a/src/js/ui/element.js b/src/js/ui/element.js new file mode 100644 index 00000000..f0f3b85d --- /dev/null +++ b/src/js/ui/element.js @@ -0,0 +1,54 @@ +var util2 = require('lib/util2'); +var Vector2 = require('lib/vector2'); +var WindowStack = require('ui/windowstack'); +var Propable = require('ui/propable'); +var simply = require('simply'); + +var elementProps = [ + 'position', + 'size', + 'borderColor', + 'backgroundColor', +]; + +var accessorProps = elementProps; + +var nextId = 1; + +var StageElement = function(elementDef) { + this.state = elementDef || {}; + this.state.id = nextId++; +}; + +util2.copy(Propable.prototype, StageElement.prototype); + +Propable.makeAccessors(accessorProps, StageElement.prototype); + +StageElement.prototype._id = function() { + return this.state.id; +}; + +StageElement.prototype._prop = function() { + if (!this.state.position) { + this.state.position = new Vector2(); + } + if (!this.state.size) { + this.state.size = new Vector2(); + } + if (this.parent === WindowStack.top()) { + simply.impl.stageElement.apply(this, arguments); + } +}; + +StageElement.prototype.index = function() { + if (!this.parent) { return -1; } + return this.parent.index(this); +}; + +StageElement.prototype.remove = function(broadcast) { + if (!this.parent) { return this; } + this.parent.remove(this, broadcast); + return this; +}; + +module.exports = StageElement; From d9765ff05218e4d5d8f7deffd768492cba489541 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:18:58 -0700 Subject: [PATCH 149/791] Add rect element class --- src/js/ui/rect.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/js/ui/rect.js diff --git a/src/js/ui/rect.js b/src/js/ui/rect.js new file mode 100644 index 00000000..fc8aa80b --- /dev/null +++ b/src/js/ui/rect.js @@ -0,0 +1,11 @@ +var util2 = require('lib/util2'); +var StageElement = require('ui/element'); + +var Rect = function(elementDef) { + StageElement.call(this, elementDef); + this.state.type = 1; +}; + +util2.inherit(Rect, StageElement); + +module.exports = Rect; From 8047abe55e5ab1f72ea9cd2cd0cedf22eeab6e41 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:19:45 -0700 Subject: [PATCH 150/791] Add message pebble set stage, element --- src/js/simply-pebble.js | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index ac538841..135b1b2c 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -17,6 +17,15 @@ if (typeof Image === 'undefined') { window.Image = function(){}; } +var Color = function(x) { + switch (x) { + case 'clear': return ~0; + case 'black': return 0; + case 'white': return 1; + } + return Number(x); +}; + var setWindowParams = [{ name: 'clear', type: Boolean, @@ -70,6 +79,8 @@ var setMenuParams = setWindowParams.concat([{ type: Number, }]); +var setStageParams = setWindowParams; + var commands = [{ name: 'setWindow', params: setWindowParams, @@ -208,6 +219,34 @@ var commands = [{ }, { name: 'pixels', }], +}, { + name: 'setStage', + params: setStageParams, +}, { + name: 'stageElement', + params: [{ + name: 'id', + }, { + name: 'type', + }, { + name: 'index', + }, { + name: 'x', + }, { + name: 'y', + }, { + name: 'width', + }, { + name: 'height', + }, { + name: 'backgroundColor', + type: Color, + }, { + name: 'borderColor', + type: Color, + }, { + name: 'radius', + }], }]; var commandMap = {}; @@ -289,6 +328,8 @@ var toParam = function(param, v) { v = v ? 1 : 0; } else if (param.type === Image && typeof v !== 'number') { v = ImageService.load(v); + } else if (param.type === Color) { + v = Color(v); } return v; }; @@ -449,6 +490,32 @@ SimplyPebble.image = function(id, gbitmap) { SimplyPebble.sendPacket(packet); }; +SimplyPebble.stage = function(stageDef) { + var command = commandMap.setStage; + var packet = makePacket(command, stageDef); + SimplyPebble.sendPacket(packet); +}; + +SimplyPebble.stageElement = function(elementDef, index) { + var command = commandMap.stageElement; + var packetDef = util2.copy(elementDef); + packetDef.index = index; + if (packetDef.position) { + var position = packetDef.position; + delete packetDef.position; + packetDef.x = position.x; + packetDef.y = position.y; + } + if (packetDef.size) { + var size = packetDef.size; + delete packetDef.size; + packetDef.width = size.x; + packetDef.height = size.y; + } + var packet = makePacket(command, packetDef); + SimplyPebble.sendPacket(packet); +}; + var readInt = function(packet, width, pos, signed) { var value = 0; pos = pos || 0; From d193b168a5b43b704ff939d128cd6746adcf2c04 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:20:27 -0700 Subject: [PATCH 151/791] Export Vector2, Stage, Rect --- src/js/exports.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/js/exports.js b/src/js/exports.js index 155a36aa..c949d24f 100644 --- a/src/js/exports.js +++ b/src/js/exports.js @@ -1,15 +1,15 @@ -var Settings = require('base/settings'); -var Accel = require('base/accel'); -var Card = require('ui/card'); -var Menu = require('ui/menu'); -Pebble.Settings = Settings; +Pebble.Settings = require('base/settings'); -Pebble.Accel = Accel; +Pebble.Accel = require('base/accel'); var UI = {}; -UI.Card = Card; -UI.Menu = Menu; + +UI.Vector2 = require('lib/vector2'); +UI.Card = require('ui/card'); +UI.Menu = require('ui/menu'); +UI.Stage = require('ui/stage'); +UI.Rect = require('ui/rect'); Pebble.UI = UI; From 36ed96338b46b4736e1e178ed1e7a5cc3ebbb216 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:37:10 -0700 Subject: [PATCH 152/791] Add stage remove element --- src/js/simply-pebble.js | 13 +++++++++++++ src/simply_msg.c | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index 135b1b2c..32e42e6f 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -247,6 +247,11 @@ var commands = [{ }, { name: 'radius', }], +}, { + name: 'stageRemove', + params: [{ + name: 'id', + }], }]; var commandMap = {}; @@ -516,6 +521,14 @@ SimplyPebble.stageElement = function(elementDef, index) { SimplyPebble.sendPacket(packet); }; +SimplyPebble.stageRemove = function(elementId) { + console.log('stageRemove'); + var command = commandMap.stageRemove; + var packet = makePacket(command); + packet[command.paramMap.id.id] = elementId; + SimplyPebble.sendPacket(packet); +}; + var readInt = function(packet, width, pos, signed) { var value = 0; pos = pos || 0; diff --git a/src/simply_msg.c b/src/simply_msg.c index 94ffb7be..e499d625 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -393,6 +393,21 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { simply_stage_update(stage); } +static void handle_remove_stage_element(DictionaryIterator *iter, Simply *simply) { + SimplyStage *stage = simply->stage; + Tuple *tuple; + uint32_t id = 0; + if ((tuple = dict_find(iter, ElementId))) { + id = tuple->value->uint32; + } + SimplyElementCommon *element = simply_stage_get_element(stage, id); + if (!element) { + return; + } + simply_stage_remove_element(stage, element); + simply_stage_update(stage); +} + static void received_callback(DictionaryIterator *iter, void *context) { Tuple *tuple = dict_find(iter, 0); if (!tuple) { @@ -439,6 +454,9 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_stageElement: handle_set_stage_element(iter, context); break; + case SimplyACmd_stageRemove: + handle_remove_stage_element(iter, context); + break; } } From 9649fc1494e0e09db28aa6a94f7602a96b9b81af Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 25 May 2014 23:43:58 -0700 Subject: [PATCH 153/791] Add circle element class --- src/js/exports.js | 1 + src/js/ui/circle.js | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 src/js/ui/circle.js diff --git a/src/js/exports.js b/src/js/exports.js index c949d24f..f265b9d5 100644 --- a/src/js/exports.js +++ b/src/js/exports.js @@ -10,6 +10,7 @@ UI.Card = require('ui/card'); UI.Menu = require('ui/menu'); UI.Stage = require('ui/stage'); UI.Rect = require('ui/rect'); +UI.Circle = require('ui/circle'); Pebble.UI = UI; diff --git a/src/js/ui/circle.js b/src/js/ui/circle.js new file mode 100644 index 00000000..7320f6a1 --- /dev/null +++ b/src/js/ui/circle.js @@ -0,0 +1,11 @@ +var util2 = require('lib/util2'); +var StageElement = require('ui/element'); + +var Circle = function(elementDef) { + StageElement.call(this, elementDef); + this.state.type = 2; +}; + +util2.inherit(Circle, StageElement); + +module.exports = Circle; From 0b8c0d14cb03f6ff90be6353bb8ccdd634a96e8a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 01:05:39 -0700 Subject: [PATCH 154/791] Add strset which changes an allocated string --- src/simply_ui.c | 13 +------------ src/util/string.h | 12 ++++++++++++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/simply_ui.c b/src/simply_ui.c index 443a6ae0..a7049342 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -65,20 +65,9 @@ void simply_ui_set_style(SimplyUi *self, int style_index) { layer_mark_dirty(self->ui_layer.layer); } -static void set_text(char **str_field, const char *str) { - free(*str_field); - - if (!is_string(str)) { - *str_field = NULL; - return; - } - - *str_field = strdup2(str); -} - void simply_ui_set_text(SimplyUi *self, SimplyUiTextfield textfield, const char *str) { char **str_field = &self->ui_layer.textfields[textfield]; - set_text(str_field, str); + strset(str_field, str); if (self->ui_layer.layer) { layer_mark_dirty(self->ui_layer.layer); } diff --git a/src/util/string.h b/src/util/string.h index 2b31ada2..00dcab66 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -17,3 +17,15 @@ static inline char *strdup2(const char *str) { strcpy(buffer, str); return buffer; } + +static inline void strset(char **str_field, const char *str) { + free(*str_field); + + if (!is_string(str)) { + *str_field = NULL; + return; + } + + *str_field = strdup2(str); +} + From a7236f59a00dd8849f5bbb076ffee2f0d7de291f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 01:06:13 -0700 Subject: [PATCH 155/791] Add simply stage text element backend --- src/simply_stage.c | 17 +++++++++++++++-- src/simply_stage.h | 15 +++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/simply_stage.c b/src/simply_stage.c index f6de67a8..d3db86e6 100644 --- a/src/simply_stage.c +++ b/src/simply_stage.c @@ -18,7 +18,7 @@ static void destroy_element(SimplyStage *self, SimplyElementCommon *element) { free(element); } -static void rect_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRect* element) { +static void rect_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { if (element->background_color != GColorClear) { graphics_context_set_fill_color(ctx, element->background_color); graphics_fill_rect(ctx, element->frame, element->radius, GCornersAll); @@ -29,7 +29,7 @@ static void rect_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRec } } -static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementCircle* element) { +static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementCircle *element) { if (element->background_color != GColorClear) { graphics_context_set_fill_color(ctx, element->background_color); graphics_fill_circle(ctx, element->frame.origin, element->radius); @@ -40,6 +40,15 @@ static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementC } } +static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementText *element) { + rect_element_draw(ctx, self, (SimplyElementRect*) element); + if (element->text_color != GColorClear && element->text) { + graphics_context_set_text_color(ctx, element->text_color); + graphics_draw_text(ctx, element->text, element->font, element->frame, + element->overflow_mode, element->alignment, NULL); + } +} + static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyStage *self = *(void**) layer_get_data(layer); @@ -54,6 +63,9 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { case SimplyElementTypeCircle: circle_element_draw(ctx, self, (SimplyElementCircle*) element); break; + case SimplyElementTypeText: + text_element_draw(ctx, self, (SimplyElementText*) element); + break; } element = (SimplyElementCommon*) element->node.next; } @@ -70,6 +82,7 @@ static SimplyElementCommon* alloc_element(SimplyElementType type) { case SimplyElementTypeNone: return NULL; case SimplyElementTypeRect: return malloc0(sizeof(SimplyElementRect)); case SimplyElementTypeCircle: return malloc0(sizeof(SimplyElementCircle)); + case SimplyElementTypeText: return malloc0(sizeof(SimplyElementText)); } return NULL; } diff --git a/src/simply_stage.h b/src/simply_stage.h index df3bfb70..66788633 100644 --- a/src/simply_stage.h +++ b/src/simply_stage.h @@ -22,6 +22,7 @@ enum SimplyElementType { SimplyElementTypeNone = 0, SimplyElementTypeRect = 1, SimplyElementTypeCircle = 2, + SimplyElementTypeText = 3, }; struct SimplyStageLayer { @@ -62,6 +63,20 @@ struct SimplyElementRect { typedef struct SimplyElementRect SimplyElementCircle; +typedef struct SimplyElementText SimplyElementText; + +struct SimplyElementText { + union { + struct SimplyElementRect common; + struct SimplyElementCommonDef; + }; + char *text; + GFont font; + GColor text_color:2; + GTextOverflowMode overflow_mode; + GTextAlignment alignment; +}; + SimplyStage *simply_stage_create(Simply *simply); void simply_stage_destroy(SimplyStage *self); From 19e8008fc733b3a22434448928f5db9ceb4726f1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 01:06:29 -0700 Subject: [PATCH 156/791] Add text element msg backend --- src/simply_msg.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/simply_msg.c b/src/simply_msg.c index e499d625..84b675d0 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -104,6 +104,11 @@ enum ElementParam { ElementBackgroundColor, ElementBorderColor, ElementRadius, + ElementText, + ElementTextFont, + ElementTextColor, + ElementTextOverflow, + ElementTextAlignment, }; static void check_splash(Simply *simply) { @@ -359,7 +364,7 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { type = tuple->value->int32; } SimplyElementCommon *element = simply_stage_auto_element(stage, id, type); - if (!element) { + if (!element || element->type != type) { return; } for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { @@ -388,6 +393,21 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { case ElementRadius: ((SimplyElementRect*) element)->radius = tuple->value->uint16; break; + case ElementText: + strset(&((SimplyElementText*) element)->text, tuple->value->cstring); + break; + case ElementTextFont: + ((SimplyElementText*) element)->font = fonts_get_system_font(tuple->value->cstring); + break; + case ElementTextColor: + ((SimplyElementText*) element)->text_color = tuple->value->uint8; + break; + case ElementTextOverflow: + ((SimplyElementText*) element)->overflow_mode = tuple->value->uint8; + break; + case ElementTextAlignment: + ((SimplyElementText*) element)->alignment = tuple->value->uint8; + break; } } simply_stage_update(stage); From 939ebb20878f9fb860b3f8288bd94b1754d5b97d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 01:06:50 -0700 Subject: [PATCH 157/791] Add text element class --- src/js/ui/text.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/js/ui/text.js diff --git a/src/js/ui/text.js b/src/js/ui/text.js new file mode 100644 index 00000000..feaf31ed --- /dev/null +++ b/src/js/ui/text.js @@ -0,0 +1,22 @@ +var util2 = require('lib/util2'); +var Propable = require('ui/propable'); +var StageElement = require('ui/element'); + +var textProps = [ + 'text', + 'font', + 'color', + 'textOverflow', + 'textAlign', +]; + +var Text = function(elementDef) { + StageElement.call(this, elementDef); + this.state.type = 3; +}; + +util2.inherit(Text, StageElement); + +Propable.makeAccessors(textProps, Text.prototype); + +module.exports = Text; From 2d68ef87ec6539f66fe7b426011057abcb9cba31 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 01:07:32 -0700 Subject: [PATCH 158/791] Add message pebble text element --- src/js/simply-pebble.js | 47 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index 32e42e6f..a91d7852 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -26,6 +26,34 @@ var Color = function(x) { return Number(x); }; +var Font = function(x) { + x = x.toUpperCase(); + x = x.replace(/[- ]/g, '_'); + if (!x.match(/^RESOURCE_ID/)) { + x = 'RESOURCE_ID_' + x; + } + x = x.replace(/_+/g, '_'); + return x; +}; + +var TextOverflowMode = function(x) { + switch (x) { + case 'wrap' : return 0; + case 'ellipsis': return 1; + case 'fill' : return 2; + } + return Number(x); +}; + +var TextAlignment = function(x) { + switch (x) { + case 'left' : return 0; + case 'center': return 1; + case 'right' : return 2; + } + return Number(x); +}; + var setWindowParams = [{ name: 'clear', type: Boolean, @@ -246,6 +274,21 @@ var commands = [{ type: Color, }, { name: 'radius', + }, { + name: 'text', + type: String, + }, { + name: 'font', + type: Font, + }, { + name: 'color', + type: Color, + }, { + name: 'textOverflow', + type: TextOverflowMode, + }, { + name: 'textAlign', + type: TextAlignment, }], }, { name: 'stageRemove', @@ -333,8 +376,8 @@ var toParam = function(param, v) { v = v ? 1 : 0; } else if (param.type === Image && typeof v !== 'number') { v = ImageService.load(v); - } else if (param.type === Color) { - v = Color(v); + } else if (typeof param.type === 'function') { + v = param.type(v); } return v; }; From 72d362f4c6cc84e8e7bdaa071a33b49ded988911 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 01:07:35 -0700 Subject: [PATCH 159/791] Export UI Text stage element --- src/js/exports.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/exports.js b/src/js/exports.js index f265b9d5..a24c0531 100644 --- a/src/js/exports.js +++ b/src/js/exports.js @@ -11,6 +11,7 @@ UI.Menu = require('ui/menu'); UI.Stage = require('ui/stage'); UI.Rect = require('ui/rect'); UI.Circle = require('ui/circle'); +UI.Text = require('ui/text'); Pebble.UI = UI; From 3d4fe135b05bc5598971b3cd5b8f87c39a1c5f57 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 01:42:13 -0700 Subject: [PATCH 160/791] Fix stage element to identify itself in property updates --- src/js/ui/element.js | 8 +++++++- src/js/ui/propable.js | 2 +- src/simply_msg.c | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/js/ui/element.js b/src/js/ui/element.js index f0f3b85d..2859c074 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -28,7 +28,11 @@ StageElement.prototype._id = function() { return this.state.id; }; -StageElement.prototype._prop = function() { +StageElement.prototype._type = function() { + return this.state.type; +}; + +StageElement.prototype._prop = function(elementDef) { if (!this.state.position) { this.state.position = new Vector2(); } @@ -36,6 +40,8 @@ StageElement.prototype._prop = function() { this.state.size = new Vector2(); } if (this.parent === WindowStack.top()) { + elementDef.id = this._id(); + elementDef.type = this._type(); simply.impl.stageElement.apply(this, arguments); } }; diff --git a/src/js/ui/propable.js b/src/js/ui/propable.js index f268356c..defd2e89 100644 --- a/src/js/ui/propable.js +++ b/src/js/ui/propable.js @@ -11,7 +11,7 @@ Propable.makeAccessor = function(k) { return this.state[k]; } else { this.state[k] = value; - this._prop(myutil.toObject(k, value), value); + this._prop(myutil.toObject(k, value)); return this; } }; diff --git a/src/simply_msg.c b/src/simply_msg.c index 84b675d0..1c188be3 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -343,7 +343,7 @@ static void handle_set_stage(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { - case SetMenu_id: + case SetStage_id: stage->window.id = tuple->value->uint32; break; } From 67ad00df553833e71d6561edc02a0a5b6fd5d619 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 01:42:41 -0700 Subject: [PATCH 161/791] Change simply msg button config to work for all simply windows --- src/simply_msg.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/simply_msg.c b/src/simply_msg.c index 1c188be3..9c296b06 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -117,10 +117,18 @@ static void check_splash(Simply *simply) { } } -static void handle_set_window(DictionaryIterator *iter, Simply *simply) { +static SimplyWindow *get_top_simply_window(Simply *simply) { Window *base_window = window_stack_get_top_window(); SimplyWindow *window = window_get_user_data(base_window); if (!window || (void*) window == simply->splash) { + return NULL; + } + return window; +} + +static void handle_set_window(DictionaryIterator *iter, Simply *simply) { + SimplyWindow *window = get_top_simply_window(simply); + if (!window) { return; } Tuple *tuple; @@ -209,11 +217,14 @@ static void handle_vibe(DictionaryIterator *iter, Simply *simply) { } static void handle_config_buttons(DictionaryIterator *iter, Simply *simply) { - SimplyUi *ui = simply->ui; + SimplyWindow *window = get_top_simply_window(simply); + if (!window) { + return; + } Tuple *tuple; for (int i = 0; i < NUM_BUTTONS; ++i) { if ((tuple = dict_find(iter, i + 1))) { - simply_window_set_button(&ui->window, i, tuple->value->int32); + simply_window_set_button(window, i, tuple->value->int32); } } } From 8043f57651021643d17d4cb5a26a9dec338a35a5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 01:43:31 -0700 Subject: [PATCH 162/791] Fix simply window click config provider to set the click context --- src/simply_window.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/simply_window.c b/src/simply_window.c index 1530849c..a10ca8d8 100644 --- a/src/simply_window.c +++ b/src/simply_window.c @@ -120,6 +120,7 @@ static void click_config_provider(void *context) { SimplyWindow *self = context; for (int i = 0; i < NUM_BUTTONS; ++i) { if (!self->is_scrollable || (i != BUTTON_ID_UP && i != BUTTON_ID_DOWN)) { + window_set_click_context(i, context); window_single_click_subscribe(i, (ClickHandler) single_click_handler); window_long_click_subscribe(i, 500, (ClickHandler) long_click_handler, NULL); } From 7c59e73ba75d1009a580baf25984e3d4b9d59b05 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 02:16:32 -0700 Subject: [PATCH 163/791] Add simply stage image element backend --- src/simply_stage.c | 30 ++++++++++++++++++++++++++++-- src/simply_stage.h | 12 ++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/simply_stage.c b/src/simply_stage.c index d3db86e6..dccdd91e 100644 --- a/src/simply_stage.c +++ b/src/simply_stage.c @@ -6,6 +6,9 @@ #include "simplyjs.h" +#include "util/graphics.h" +#include "util/string.h" + #include static bool id_filter(List1Node *node, void *data) { @@ -18,17 +21,25 @@ static void destroy_element(SimplyStage *self, SimplyElementCommon *element) { free(element); } -static void rect_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { +static void rect_element_draw_background(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { if (element->background_color != GColorClear) { graphics_context_set_fill_color(ctx, element->background_color); graphics_fill_rect(ctx, element->frame, element->radius, GCornersAll); } +} + +static void rect_element_draw_border(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { if (element->border_color != GColorClear) { graphics_context_set_stroke_color(ctx, element->border_color); graphics_draw_round_rect(ctx, element->frame, element->radius); } } +static void rect_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { + rect_element_draw_background(ctx, self, element); + rect_element_draw_border(ctx, self, element); +} + static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementCircle *element) { if (element->background_color != GColorClear) { graphics_context_set_fill_color(ctx, element->background_color); @@ -42,13 +53,24 @@ static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementC static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementText *element) { rect_element_draw(ctx, self, (SimplyElementRect*) element); - if (element->text_color != GColorClear && element->text) { + if (element->text_color != GColorClear && is_string(element->text)) { graphics_context_set_text_color(ctx, element->text_color); graphics_draw_text(ctx, element->text, element->font, element->frame, element->overflow_mode, element->alignment, NULL); } } +static void image_element_draw(GContext *ctx, SimplyStage *self, SimplyElementImage *element) { + graphics_context_set_compositing_mode(ctx, element->compositing); + rect_element_draw_background(ctx, self, (SimplyElementRect*) element); + GBitmap *bitmap = simply_res_get_image(self->window.simply->res, element->image); + if (bitmap) { + graphics_draw_bitmap_centered(ctx, bitmap, element->frame); + } + rect_element_draw_border(ctx, self, (SimplyElementRect*) element); + graphics_context_set_compositing_mode(ctx, GCompOpAssign); +} + static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyStage *self = *(void**) layer_get_data(layer); @@ -66,6 +88,9 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { case SimplyElementTypeText: text_element_draw(ctx, self, (SimplyElementText*) element); break; + case SimplyElementTypeImage: + image_element_draw(ctx, self, (SimplyElementImage*) element); + break; } element = (SimplyElementCommon*) element->node.next; } @@ -83,6 +108,7 @@ static SimplyElementCommon* alloc_element(SimplyElementType type) { case SimplyElementTypeRect: return malloc0(sizeof(SimplyElementRect)); case SimplyElementTypeCircle: return malloc0(sizeof(SimplyElementCircle)); case SimplyElementTypeText: return malloc0(sizeof(SimplyElementText)); + case SimplyElementTypeImage: return malloc0(sizeof(SimplyElementImage)); } return NULL; } diff --git a/src/simply_stage.h b/src/simply_stage.h index 66788633..41a13fa5 100644 --- a/src/simply_stage.h +++ b/src/simply_stage.h @@ -23,6 +23,7 @@ enum SimplyElementType { SimplyElementTypeRect = 1, SimplyElementTypeCircle = 2, SimplyElementTypeText = 3, + SimplyElementTypeImage = 4, }; struct SimplyStageLayer { @@ -77,6 +78,17 @@ struct SimplyElementText { GTextAlignment alignment; }; +typedef struct SimplyElementImage SimplyElementImage; + +struct SimplyElementImage { + union { + struct SimplyElementRect common; + struct SimplyElementCommonDef; + }; + uint32_t image; + GCompOp compositing; +}; + SimplyStage *simply_stage_create(Simply *simply); void simply_stage_destroy(SimplyStage *self); From 32b0b9025c83a0da31752a1eb930205e97ff3866 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 02:16:51 -0700 Subject: [PATCH 164/791] Add simply msg stage image element --- src/simply_msg.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/simply_msg.c b/src/simply_msg.c index 9c296b06..bd62301b 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -109,6 +109,8 @@ enum ElementParam { ElementTextColor, ElementTextOverflow, ElementTextAlignment, + ElementImage, + ElementCompositing, }; static void check_splash(Simply *simply) { @@ -419,6 +421,12 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { case ElementTextAlignment: ((SimplyElementText*) element)->alignment = tuple->value->uint8; break; + case ElementImage: + ((SimplyElementImage*) element)->image = tuple->value->uint32; + break; + case ElementCompositing: + ((SimplyElementImage*) element)->compositing = tuple->value->uint8; + break; } } simply_stage_update(stage); From c97f1ec4ac2b24a3e49d735f9d46862c17a23ba1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 02:17:08 -0700 Subject: [PATCH 165/791] Add image element class --- src/js/ui/image.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/js/ui/image.js diff --git a/src/js/ui/image.js b/src/js/ui/image.js new file mode 100644 index 00000000..cb80e8c1 --- /dev/null +++ b/src/js/ui/image.js @@ -0,0 +1,19 @@ +var util2 = require('lib/util2'); +var Propable = require('ui/propable'); +var StageElement = require('ui/element'); + +var imageProps = [ + 'image', + 'compositing', +]; + +var ImageElement = function(elementDef) { + StageElement.call(this, elementDef); + this.state.type = 4; +}; + +util2.inherit(ImageElement, StageElement); + +Propable.makeAccessors(imageProps, ImageElement.prototype); + +module.exports = ImageElement; From 842db2f03868b2f95c7351b47fb75e726648aa81 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 02:17:20 -0700 Subject: [PATCH 166/791] Add message pebble stage image element --- src/js/simply-pebble.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index a91d7852..d25f7726 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -54,6 +54,20 @@ var TextAlignment = function(x) { return Number(x); }; +var CompositingOp = function(x) { + switch (x) { + case 'assign': + case 'normal': return 0; + case 'assignInverted': + case 'invert': return 1; + case 'or' : return 2; + case 'and' : return 3; + case 'clear' : return 4; + case 'set' : return 5; + } + return Number(x); +}; + var setWindowParams = [{ name: 'clear', type: Boolean, @@ -289,6 +303,12 @@ var commands = [{ }, { name: 'textAlign', type: TextAlignment, + }, { + name: 'image', + type: Image, + }, { + name: 'compositing', + type: CompositingOp, }], }, { name: 'stageRemove', From 6b6989553814964ae851834690a9825406719583 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 02:17:41 -0700 Subject: [PATCH 167/791] Export UI Image stage element --- src/js/exports.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/exports.js b/src/js/exports.js index a24c0531..d92ecfbc 100644 --- a/src/js/exports.js +++ b/src/js/exports.js @@ -12,6 +12,7 @@ UI.Stage = require('ui/stage'); UI.Rect = require('ui/rect'); UI.Circle = require('ui/circle'); UI.Text = require('ui/text'); +UI.Image = require('ui/image'); Pebble.UI = UI; From 44cd0e0af789991325af14f52031f35bc4f05845 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 02:38:38 -0700 Subject: [PATCH 168/791] Free text element text on destroy --- src/simply_stage.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/simply_stage.c b/src/simply_stage.c index dccdd91e..07ed248b 100644 --- a/src/simply_stage.c +++ b/src/simply_stage.c @@ -18,6 +18,12 @@ static bool id_filter(List1Node *node, void *data) { static void destroy_element(SimplyStage *self, SimplyElementCommon *element) { if (!element) { return; } list1_remove(&self->stage_layer.elements, &element->node); + switch (element->type) { + default: break; + case SimplyElementTypeText: + free(((SimplyElementText*) element)->text); + break; + } free(element); } From 2f1bd2acb9b2449faacdff6e8724e96c4850726f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 04:09:54 -0700 Subject: [PATCH 169/791] Add stage element animation backend --- src/simply_stage.c | 88 +++++++++++++++++++++++++++++++++++++++++++--- src/simply_stage.h | 15 ++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/simply_stage.c b/src/simply_stage.c index 07ed248b..644c7c95 100644 --- a/src/simply_stage.c +++ b/src/simply_stage.c @@ -15,6 +15,10 @@ static bool id_filter(List1Node *node, void *data) { return (((SimplyElementCommon*) node)->id == (uint32_t)(uintptr_t) data); } +static bool animation_filter(List1Node *node, void *data) { + return (((SimplyAnimation*) node)->animation == (PropertyAnimation*) data); +} + static void destroy_element(SimplyStage *self, SimplyElementCommon *element) { if (!element) { return; } list1_remove(&self->stage_layer.elements, &element->node); @@ -27,6 +31,13 @@ static void destroy_element(SimplyStage *self, SimplyElementCommon *element) { free(element); } +static void destroy_animation(SimplyStage *self, SimplyAnimation *animation) { + if (!animation) { return; } + list1_remove(&self->stage_layer.animations, &animation->node); + property_animation_destroy(animation->animation); + free(animation); +} + static void rect_element_draw_background(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { if (element->background_color != GColorClear) { graphics_context_set_fill_color(ctx, element->background_color); @@ -108,7 +119,7 @@ static void *malloc0(size_t size) { return buf; } -static SimplyElementCommon* alloc_element(SimplyElementType type) { +static SimplyElementCommon *alloc_element(SimplyElementType type) { switch (type) { case SimplyElementTypeNone: return NULL; case SimplyElementTypeRect: return malloc0(sizeof(SimplyElementRect)); @@ -119,11 +130,11 @@ static SimplyElementCommon* alloc_element(SimplyElementType type) { return NULL; } -SimplyElementCommon* simply_stage_auto_element(SimplyStage *self, uint32_t id, SimplyElementType type) { +SimplyElementCommon *simply_stage_auto_element(SimplyStage *self, uint32_t id, SimplyElementType type) { if (!id) { return NULL; } - SimplyElementCommon* element = (SimplyElementCommon*) list1_find( + SimplyElementCommon *element = (SimplyElementCommon*) list1_find( self->stage_layer.elements, id_filter, (void*)(uintptr_t) id); if (element) { return element; @@ -137,15 +148,78 @@ SimplyElementCommon* simply_stage_auto_element(SimplyStage *self, uint32_t id, S return element; } -SimplyElementCommon* simply_stage_insert_element(SimplyStage *self, int index, SimplyElementCommon *element) { +SimplyElementCommon *simply_stage_insert_element(SimplyStage *self, int index, SimplyElementCommon *element) { simply_stage_remove_element(self, element); return (SimplyElementCommon*) list1_insert(&self->stage_layer.elements, index, &element->node); } -SimplyElementCommon* simply_stage_remove_element(SimplyStage *self, SimplyElementCommon *element) { +SimplyElementCommon *simply_stage_remove_element(SimplyStage *self, SimplyElementCommon *element) { return (SimplyElementCommon*) list1_remove(&self->stage_layer.elements, &element->node); } +static void element_frame_setter(void *subject, GRect frame) { + SimplyAnimation *animation = subject; + animation->element->frame = frame; + simply_stage_update(animation->stage); +} + +static GRect element_frame_getter(void *subject) { + SimplyAnimation *animation = subject; + return animation->element->frame; +} + +static void animation_stopped(Animation *base_animation, bool finished, void *context) { + SimplyStage *self = context; + SimplyAnimation *animation = (SimplyAnimation*) list1_find( + self->stage_layer.animations, animation_filter, base_animation); + if (animation) { + destroy_animation(self, animation); + } +} + +SimplyAnimation *simply_stage_animate_element(SimplyStage *self, + SimplyElementCommon *element, SimplyAnimation* animation, GRect to_frame) { + if (!animation) { + return NULL; + } + + animation->stage = self; + animation->element = element; + + static const PropertyAnimationImplementation implementation = { + .base = { + .update = (AnimationUpdateImplementation) property_animation_update_grect, + }, + .accessors = { + .setter = { .grect = (const GRectSetter) element_frame_setter }, + .getter = { .grect = (const GRectGetter) element_frame_getter }, + }, + }; + + PropertyAnimation *property_animation = property_animation_create(&implementation, animation, NULL, NULL); + if (!property_animation) { + free(animation); + return NULL; + } + + property_animation->values.from.grect = element->frame; + property_animation->values.to.grect = to_frame; + + animation->animation = property_animation; + list1_append(&self->stage_layer.animations, &animation->node); + + Animation *base_animation = (Animation*) property_animation; + animation_set_duration(base_animation, animation->duration); + animation_set_curve(base_animation, animation->curve); + animation_set_handlers(base_animation, (AnimationHandlers) { + .stopped = animation_stopped, + }, self); + + animation_schedule(base_animation); + + return animation; +} + static void window_load(Window *window) { SimplyStage *self = window_get_user_data(window); @@ -175,6 +249,10 @@ static void window_disappear(Window *window) { while (self->stage_layer.elements) { destroy_element(self, (SimplyElementCommon*) self->stage_layer.elements); } + + while (self->stage_layer.animations) { + destroy_animation(self, (SimplyAnimation*) self->stage_layer.animations); + } } static void window_unload(Window *window) { diff --git a/src/simply_stage.h b/src/simply_stage.h index 41a13fa5..77126d2f 100644 --- a/src/simply_stage.h +++ b/src/simply_stage.h @@ -29,6 +29,7 @@ enum SimplyElementType { struct SimplyStageLayer { Layer *layer; List1Node *elements; + List1Node *animations; }; struct SimplyStage { @@ -89,6 +90,17 @@ struct SimplyElementImage { GCompOp compositing; }; +typedef struct SimplyAnimation SimplyAnimation; + +struct SimplyAnimation { + List1Node node; + SimplyStage *stage; + SimplyElementCommon *element; + PropertyAnimation *animation; + uint32_t duration; + AnimationCurve curve; +}; + SimplyStage *simply_stage_create(Simply *simply); void simply_stage_destroy(SimplyStage *self); @@ -102,3 +114,6 @@ SimplyElementCommon* simply_stage_auto_element(SimplyStage *self, uint32_t id, S SimplyElementCommon* simply_stage_insert_element(SimplyStage *self, int index, SimplyElementCommon *element); SimplyElementCommon* simply_stage_remove_element(SimplyStage *self, SimplyElementCommon *element); + +SimplyAnimation *simply_stage_animate_element(SimplyStage *self, + SimplyElementCommon *element, SimplyAnimation* animation, GRect to_frame); From 6887efd0efb186586d79589a768f11caf1c5c4d3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 04:10:29 -0700 Subject: [PATCH 170/791] Add simply msg animate element --- src/simply_msg.c | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/simply_msg.c b/src/simply_msg.c index bd62301b..89603ce9 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -38,6 +38,7 @@ enum SimplyACmd { SimplyACmd_setStage, SimplyACmd_stageElement, SimplyACmd_stageRemove, + SimplyACmd_stageAnimate, }; typedef enum SimplySetWindowParam SimplySetWindowParam; @@ -436,7 +437,7 @@ static void handle_remove_stage_element(DictionaryIterator *iter, Simply *simply SimplyStage *stage = simply->stage; Tuple *tuple; uint32_t id = 0; - if ((tuple = dict_find(iter, ElementId))) { + if ((tuple = dict_find(iter, 1))) { id = tuple->value->uint32; } SimplyElementCommon *element = simply_stage_get_element(stage, id); @@ -447,6 +448,43 @@ static void handle_remove_stage_element(DictionaryIterator *iter, Simply *simply simply_stage_update(stage); } +static void handle_animate_stage_element(DictionaryIterator *iter, Simply *simply) { + SimplyStage *stage = simply->stage; + Tuple *tuple; + uint32_t id = 0; + if ((tuple = dict_find(iter, 1))) { + id = tuple->value->uint32; + } + SimplyElementCommon *element = simply_stage_get_element(stage, id); + if (!element) { + return; + } + GRect to_frame = element->frame; + SimplyAnimation *animation = malloc(sizeof(*animation)); + if (!animation) { + return; + } + if ((tuple = dict_find(iter, 2))) { + to_frame.origin.x = tuple->value->int16; + } + if ((tuple = dict_find(iter, 3))) { + to_frame.origin.y = tuple->value->int16; + } + if ((tuple = dict_find(iter, 4))) { + to_frame.size.w = tuple->value->int16; + } + if ((tuple = dict_find(iter, 5))) { + to_frame.size.h = tuple->value->int16; + } + if ((tuple = dict_find(iter, 6))) { + animation->duration = tuple->value->uint32; + } + if ((tuple = dict_find(iter, 7))) { + animation->curve = tuple->value->uint8; + } + simply_stage_animate_element(stage, element, animation, to_frame); +} + static void received_callback(DictionaryIterator *iter, void *context) { Tuple *tuple = dict_find(iter, 0); if (!tuple) { @@ -496,6 +534,9 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_stageRemove: handle_remove_stage_element(iter, context); break; + case SimplyACmd_stageAnimate: + handle_animate_stage_element(iter, context); + break; } } From 463d25962379b055b5a435067ada4192f02e9c64 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 04:11:15 -0700 Subject: [PATCH 171/791] Add stage element animate method --- src/js/simply-pebble.js | 59 +++++++++++++++++++++++++++++++++++++---- src/js/ui/element.js | 21 ++++++++++++--- src/js/ui/stage.js | 2 +- 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index d25f7726..aaefb22c 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -68,6 +68,16 @@ var CompositingOp = function(x) { return Number(x); }; +var AnimationCurve = function(x) { + switch (x) { + case 'linear' : return 0; + case 'easeIn' : return 1; + case 'easeOut' : return 2; + case 'easeInOut': return 3; + } + return Number(x); +}; + var setWindowParams = [{ name: 'clear', type: Boolean, @@ -315,6 +325,24 @@ var commands = [{ params: [{ name: 'id', }], +}, { + name: 'stageAnimate', + params: [{ + name: 'id', + }, { + name: 'x', + }, { + name: 'y', + }, { + name: 'width', + }, { + name: 'height', + }, { + name: 'duration', + }, { + name: 'easing', + type: AnimationCurve, + }], }]; var commandMap = {}; @@ -564,10 +592,7 @@ SimplyPebble.stage = function(stageDef) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.stageElement = function(elementDef, index) { - var command = commandMap.stageElement; - var packetDef = util2.copy(elementDef); - packetDef.index = index; +var toFramePacket = function(packetDef) { if (packetDef.position) { var position = packetDef.position; delete packetDef.position; @@ -580,18 +605,42 @@ SimplyPebble.stageElement = function(elementDef, index) { packetDef.width = size.x; packetDef.height = size.y; } + return packetDef; +}; + +SimplyPebble.stageElement = function(elementId, elementType, elementDef, index) { + var command = commandMap.stageElement; + var packetDef = util2.copy(elementDef); + packetDef.id = elementId; + packetDef.type = elementType; + packetDef.index = index; + packetDef = toFramePacket(packetDef); var packet = makePacket(command, packetDef); SimplyPebble.sendPacket(packet); }; SimplyPebble.stageRemove = function(elementId) { - console.log('stageRemove'); var command = commandMap.stageRemove; var packet = makePacket(command); packet[command.paramMap.id.id] = elementId; SimplyPebble.sendPacket(packet); }; +SimplyPebble.stageAnimate = function(elementId, animateDef, duration, easing) { + var command = commandMap.stageAnimate; + var packetDef = util2.copy(animateDef); + packetDef.id = elementId; + if (duration) { + packetDef.duration = duration; + } + if (easing) { + packetDef.easing = easing; + } + packetDef = toFramePacket(packetDef); + var packet = makePacket(command, packetDef); + SimplyPebble.sendPacket(packet); +}; + var readInt = function(packet, width, pos, signed) { var value = 0; pos = pos || 0; diff --git a/src/js/ui/element.js b/src/js/ui/element.js index 2859c074..33bcb130 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -1,5 +1,6 @@ var util2 = require('lib/util2'); var Vector2 = require('lib/vector2'); +var myutil = require('base/myutil'); var WindowStack = require('ui/windowstack'); var Propable = require('ui/propable'); var simply = require('simply'); @@ -40,9 +41,7 @@ StageElement.prototype._prop = function(elementDef) { this.state.size = new Vector2(); } if (this.parent === WindowStack.top()) { - elementDef.id = this._id(); - elementDef.type = this._type(); - simply.impl.stageElement.apply(this, arguments); + simply.impl.stageElement(this._id(), this._type(), elementDef); } }; @@ -57,4 +56,20 @@ StageElement.prototype.remove = function(broadcast) { return this; }; +StageElement.prototype._animate = function(animateDef, duration) { + if (this.parent === WindowStack.top()) { + simply.impl.stageAnimate(this._id(), animateDef, duration || 400, animateDef.easing || 'easeInOut'); + } +}; + +StageElement.prototype.animate = function(field, value, duration) { + if (typeof field === 'object') { + duration = value; + } + var animateDef = myutil.toObject(field, value); + util2.copy(animateDef, this.state); + this._animate(animateDef, duration); + return this; +}; + module.exports = StageElement; diff --git a/src/js/ui/stage.js b/src/js/ui/stage.js index 3c98a644..b6a5a644 100644 --- a/src/js/ui/stage.js +++ b/src/js/ui/stage.js @@ -37,7 +37,7 @@ Stage.prototype.index = function(element) { Stage.prototype._insert = function(index, element) { if (this === WindowStack.top()) { - simply.impl.stageElement(element.state, index); + simply.impl.stageElement(element._id(), element._type(), element.state, index); } }; From 5060720e05fef037b322f9397202f73319c4f8c1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 04:45:42 -0700 Subject: [PATCH 172/791] Add text element time format support --- src/js/simply-pebble.js | 23 +++++++++++++++++++++ src/js/ui/text.js | 2 ++ src/simply_msg.c | 14 +++++++++++++ src/simply_stage.c | 45 +++++++++++++++++++++++++++++++++++++++-- src/simply_stage.h | 8 ++++++-- 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/js/simply-pebble.js b/src/js/simply-pebble.js index aaefb22c..202ac212 100644 --- a/src/js/simply-pebble.js +++ b/src/js/simply-pebble.js @@ -1,4 +1,5 @@ var util2 = require('lib/util2'); +var myutil = require('base/myutil'); var Settings = require('base/settings'); var Accel = require('base/accel'); var ImageService = require('base/image'); @@ -54,6 +55,22 @@ var TextAlignment = function(x) { return Number(x); }; +var TimeUnits = function(x) { + var z = 0; + x = myutil.toObject(x, true); + for (var k in x) { + switch (k) { + case 'seconds': z |= (1 << 0); break; + case 'minutes': z |= (1 << 1); break; + case 'hours' : z |= (1 << 2); break; + case 'days' : z |= (1 << 3); break; + case 'months' : z |= (1 << 4); break; + case 'years' : z |= (1 << 5); break; + } + } + return z; +}; + var CompositingOp = function(x) { switch (x) { case 'assign': @@ -313,6 +330,12 @@ var commands = [{ }, { name: 'textAlign', type: TextAlignment, + }, { + name: 'time', + type: Boolean, + }, { + name: 'timeUnits', + type: TimeUnits, }, { name: 'image', type: Image, diff --git a/src/js/ui/text.js b/src/js/ui/text.js index feaf31ed..df3cbd18 100644 --- a/src/js/ui/text.js +++ b/src/js/ui/text.js @@ -8,6 +8,8 @@ var textProps = [ 'color', 'textOverflow', 'textAlign', + 'time', + 'timeUnits', ]; var Text = function(elementDef) { diff --git a/src/simply_msg.c b/src/simply_msg.c index 89603ce9..5fd74f26 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -110,6 +110,8 @@ enum ElementParam { ElementTextColor, ElementTextOverflow, ElementTextAlignment, + ElementTextIsTime, + ElementTextTimeUnits, ElementImage, ElementCompositing, }; @@ -381,6 +383,7 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { if (!element || element->type != type) { return; } + bool update_ticker = false; for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { case ElementIndex: @@ -422,6 +425,14 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { case ElementTextAlignment: ((SimplyElementText*) element)->alignment = tuple->value->uint8; break; + case ElementTextIsTime: + ((SimplyElementText*) element)->is_time = tuple->value->uint8; + update_ticker = true; + break; + case ElementTextTimeUnits: + ((SimplyElementText*) element)->time_units = tuple->value->uint8; + update_ticker = true; + break; case ElementImage: ((SimplyElementImage*) element)->image = tuple->value->uint32; break; @@ -430,6 +441,9 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { break; } } + if (update_ticker) { + simply_stage_update_ticker(stage); + } simply_stage_update(stage); } diff --git a/src/simply_stage.c b/src/simply_stage.c index 644c7c95..3cf6c1ed 100644 --- a/src/simply_stage.c +++ b/src/simply_stage.c @@ -68,11 +68,23 @@ static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementC } } +static char *format_time(char *format) { + time_t now = time(NULL); + struct tm* tm = localtime(&now); + static char time_text[256]; + strftime(time_text, sizeof(time_text), format, tm); + return time_text; +} + static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementText *element) { rect_element_draw(ctx, self, (SimplyElementRect*) element); - if (element->text_color != GColorClear && is_string(element->text)) { + char *text = element->text; + if (element->text_color != GColorClear && is_string(text)) { + if (element->is_time) { + text = format_time(text); + } graphics_context_set_text_color(ctx, element->text_color); - graphics_draw_text(ctx, element->text, element->font, element->frame, + graphics_draw_text(ctx, text, element->font, element->frame, element->overflow_mode, element->alignment, NULL); } } @@ -240,6 +252,8 @@ static void window_load(Window *window) { static void window_appear(Window *window) { SimplyStage *self = window_get_user_data(window); simply_msg_window_show(self->window.id); + + simply_stage_update_ticker(self); } static void window_disappear(Window *window) { @@ -253,6 +267,8 @@ static void window_disappear(Window *window) { while (self->stage_layer.animations) { destroy_animation(self, (SimplyAnimation*) self->stage_layer.animations); } + + simply_stage_update_ticker(self); } static void window_unload(Window *window) { @@ -278,6 +294,31 @@ void simply_stage_update(SimplyStage *self) { layer_mark_dirty(self->stage_layer.layer); } +void handle_tick(struct tm *tick_time, TimeUnits units_changed) { + layer_mark_dirty(window_get_root_layer(window_stack_get_top_window())); +} + +void simply_stage_update_ticker(SimplyStage *self) { + TimeUnits units = 0; + + SimplyElementCommon *element = (SimplyElementCommon*) self->stage_layer.elements; + while (element) { + if (element->type == SimplyElementTypeText) { + SimplyElementText *text = (SimplyElementText*) element; + if (text->is_time) { + units |= text->time_units; + } + } + element = (SimplyElementCommon*) element->node.next; + } + + if (units) { + tick_timer_service_subscribe(units, handle_tick); + } else { + tick_timer_service_unsubscribe(); + } +} + SimplyStage *simply_stage_create(Simply *simply) { SimplyStage *self = malloc(sizeof(*self)); *self = (SimplyStage) { .window.simply = simply }; diff --git a/src/simply_stage.h b/src/simply_stage.h index 77126d2f..9516d50e 100644 --- a/src/simply_stage.h +++ b/src/simply_stage.h @@ -74,9 +74,11 @@ struct SimplyElementText { }; char *text; GFont font; + TimeUnits time_units:8; GColor text_color:2; - GTextOverflowMode overflow_mode; - GTextAlignment alignment; + GTextOverflowMode overflow_mode:2; + GTextAlignment alignment:2; + bool is_time:1; }; typedef struct SimplyElementImage SimplyElementImage; @@ -109,6 +111,8 @@ void simply_stage_show(SimplyStage *self); void simply_stage_update(SimplyStage *self); +void simply_stage_update_ticker(SimplyStage *self); + SimplyElementCommon* simply_stage_auto_element(SimplyStage *self, uint32_t id, SimplyElementType type); SimplyElementCommon* simply_stage_insert_element(SimplyStage *self, int index, SimplyElementCommon *element); From 6cf1d3a5380bc1a7274b239c759fdfeb6e59f8b9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 06:19:33 -0700 Subject: [PATCH 173/791] Fix settings custom webview open and close --- src/js/base/settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/base/settings.js b/src/js/base/settings.js index 40ea955a..8b301657 100644 --- a/src/js/base/settings.js +++ b/src/js/base/settings.js @@ -88,13 +88,13 @@ Settings.config = function(opt, open, close) { open: open, close: close, }; - state.webview.listeners.push(listener); + state.listeners.push(listener); }; Settings.onOpenConfig = function(e) { var options; var url; - var listener = util2.last(state.webview.listeners); + var listener = util2.last(state.listeners); if (listener) { url = listener.params.url; options = state.options; @@ -113,7 +113,7 @@ Settings.onOpenConfig = function(e) { }; Settings.onCloseConfig = function(e) { - var listener = util2.last(state.webview.listeners); + var listener = util2.last(state.listeners); var options = {}; if (e.response) { options = JSON.parse(decodeURIComponent(e.response)); From c6cc3ffbde1ca9c4257b25556865973de4685d68 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 14:24:28 -0700 Subject: [PATCH 174/791] Add myutil shadow for copying onto undefined fields --- src/js/base/myutil.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/js/base/myutil.js b/src/js/base/myutil.js index 4b2bd41d..0fc3ee11 100644 --- a/src/js/base/myutil.js +++ b/src/js/base/myutil.js @@ -2,6 +2,15 @@ var util2 = require('lib/util2'); var myutil = {}; +myutil.shadow = function(a, b) { + for (var k in a) { + if (typeof b === 'undefined') { + b[k] = a[k]; + } + } + return b; +}; + myutil.defun = function(fn, fargs, fbody) { if (!fbody) { fbody = fargs; From 11d9edd2473103d4460315adbe0828b584a152f2 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 14:25:05 -0700 Subject: [PATCH 175/791] Add default properties for ui stage elements --- src/js/ui/circle.js | 7 +++++++ src/js/ui/image.js | 7 +++++++ src/js/ui/rect.js | 7 +++++++ src/js/ui/text.js | 8 ++++++++ 4 files changed, 29 insertions(+) diff --git a/src/js/ui/circle.js b/src/js/ui/circle.js index 7320f6a1..1111c2c1 100644 --- a/src/js/ui/circle.js +++ b/src/js/ui/circle.js @@ -1,8 +1,15 @@ var util2 = require('lib/util2'); +var myutil = require('base/myutil'); var StageElement = require('ui/element'); +var defaults = { + backgroundColor: 'white', + borderColor: 'clear', +}; + var Circle = function(elementDef) { StageElement.call(this, elementDef); + myutil.shadow(defaults, this.state); this.state.type = 2; }; diff --git a/src/js/ui/image.js b/src/js/ui/image.js index cb80e8c1..4a4c8912 100644 --- a/src/js/ui/image.js +++ b/src/js/ui/image.js @@ -1,4 +1,5 @@ var util2 = require('lib/util2'); +var myutil = require('base/myutil'); var Propable = require('ui/propable'); var StageElement = require('ui/element'); @@ -7,8 +8,14 @@ var imageProps = [ 'compositing', ]; +var defaults = { + backgroundColor: 'clear', + borderColor: 'clear', +}; + var ImageElement = function(elementDef) { StageElement.call(this, elementDef); + myutil.shadow(defaults, this.state); this.state.type = 4; }; diff --git a/src/js/ui/rect.js b/src/js/ui/rect.js index fc8aa80b..d1a1e5fb 100644 --- a/src/js/ui/rect.js +++ b/src/js/ui/rect.js @@ -1,8 +1,15 @@ var util2 = require('lib/util2'); +var myutil = require('base/myutil'); var StageElement = require('ui/element'); +var defaults = { + backgroundColor: 'white', + borderColor: 'clear', +}; + var Rect = function(elementDef) { StageElement.call(this, elementDef); + myutil.shadow(defaults, this.state); this.state.type = 1; }; diff --git a/src/js/ui/text.js b/src/js/ui/text.js index df3cbd18..a53e7b05 100644 --- a/src/js/ui/text.js +++ b/src/js/ui/text.js @@ -1,4 +1,5 @@ var util2 = require('lib/util2'); +var myutil = require('base/myutil'); var Propable = require('ui/propable'); var StageElement = require('ui/element'); @@ -12,8 +13,15 @@ var textProps = [ 'timeUnits', ]; +var defaults = { + backgroundColor: 'clear', + borderColor: 'clear', + color: 'white', +}; + var Text = function(elementDef) { StageElement.call(this, elementDef); + myutil.shadow(defaults, this.state); this.state.type = 3; }; From 19e61a8ba09acb18bcc948baf27a9e74fc587b2f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 20:41:26 -0500 Subject: [PATCH 176/791] Change welcome text to only show if there was no communication --- src/simply_msg.c | 8 ++++++++ src/simply_msg.h | 1 + src/simply_ui.c | 9 +-------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/simply_msg.c b/src/simply_msg.c index 5fd74f26..64fd0c4a 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -116,6 +116,12 @@ enum ElementParam { ElementCompositing, }; +static bool s_has_communicated = false; + +bool simply_msg_has_communicated() { + return s_has_communicated; +} + static void check_splash(Simply *simply) { if (simply->splash) { simply_ui_show(simply->ui); @@ -505,6 +511,8 @@ static void received_callback(DictionaryIterator *iter, void *context) { return; } + s_has_communicated = true; + switch (tuple->value->uint8) { case SimplyACmd_setWindow: handle_set_window(iter, context); diff --git a/src/simply_msg.h b/src/simply_msg.h index 5fab9256..e8a6fcf4 100644 --- a/src/simply_msg.h +++ b/src/simply_msg.h @@ -8,6 +8,7 @@ void simply_msg_init(Simply *simply); void simply_msg_deinit(); +bool simply_msg_has_communicated(); bool simply_msg_single_click(ButtonId button); bool simply_msg_long_click(ButtonId button); diff --git a/src/simply_ui.c b/src/simply_ui.c index a7049342..3754824d 100644 --- a/src/simply_ui.c +++ b/src/simply_ui.c @@ -217,14 +217,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } static void show_welcome_text(SimplyUi *self) { - const char *title_text = self->ui_layer.textfields[UiTitle]; - const char *subtitle_text = self->ui_layer.textfields[UiSubtitle]; - const char *body_text = self->ui_layer.textfields[UiBody]; - - if (title_text || subtitle_text || body_text) { - return; - } - if (self->window.simply->menu->menu_layer.menu_layer) { + if (simply_msg_has_communicated()) { return; } From bf1abe82bbeda725757702c3dc4bc801917d482d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 26 May 2014 21:02:15 -0500 Subject: [PATCH 177/791] Add simply stage clear --- src/simply_msg.c | 3 +++ src/simply_stage.c | 22 +++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/simply_msg.c b/src/simply_msg.c index 64fd0c4a..a7ec8898 100644 --- a/src/simply_msg.c +++ b/src/simply_msg.c @@ -365,6 +365,9 @@ static void handle_set_stage(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { + case SetStage_clear: + simply_stage_clear(stage); + break; case SetStage_id: stage->window.id = tuple->value->uint32; break; diff --git a/src/simply_stage.c b/src/simply_stage.c index 3cf6c1ed..1a4045a4 100644 --- a/src/simply_stage.c +++ b/src/simply_stage.c @@ -38,6 +38,18 @@ static void destroy_animation(SimplyStage *self, SimplyAnimation *animation) { free(animation); } +void simply_stage_clear(SimplyStage *self) { + while (self->stage_layer.elements) { + destroy_element(self, (SimplyElementCommon*) self->stage_layer.elements); + } + + while (self->stage_layer.animations) { + destroy_animation(self, (SimplyAnimation*) self->stage_layer.animations); + } + + simply_stage_update_ticker(self); +} + static void rect_element_draw_background(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { if (element->background_color != GColorClear) { graphics_context_set_fill_color(ctx, element->background_color); @@ -260,15 +272,7 @@ static void window_disappear(Window *window) { SimplyStage *self = window_get_user_data(window); simply_msg_window_hide(self->window.id); - while (self->stage_layer.elements) { - destroy_element(self, (SimplyElementCommon*) self->stage_layer.elements); - } - - while (self->stage_layer.animations) { - destroy_animation(self, (SimplyAnimation*) self->stage_layer.animations); - } - - simply_stage_update_ticker(self); + simply_stage_clear(self); } static void window_unload(Window *window) { From c63273fca7122ba33474733c7a4f4cb4dabf25d1 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Tue, 27 May 2014 03:00:31 -0400 Subject: [PATCH 178/791] Refactoring Simply C interface with main.c - Simply is now initialized from a separate main.c - All Simply files moved into simply/ - All Simply headers included by simply.h --- src/main.c | 15 +++++++++++++++ src/{simplyjs.c => simply/simply.c} | 12 +++--------- src/{simplyjs.h => simply/simply.h} | 2 ++ src/{ => simply}/simply_accel.c | 2 +- src/{ => simply}/simply_accel.h | 0 src/{ => simply}/simply_menu.c | 2 +- src/{ => simply}/simply_menu.h | 2 +- src/{ => simply}/simply_msg.c | 2 +- src/{ => simply}/simply_msg.h | 2 +- src/{ => simply}/simply_res.c | 0 src/{ => simply}/simply_res.h | 0 src/{ => simply}/simply_splash.c | 2 +- src/{ => simply}/simply_splash.h | 2 +- src/{ => simply}/simply_ui.c | 2 +- src/{ => simply}/simply_ui.h | 2 +- src/{ => simply}/simply_window.c | 2 +- src/{ => simply}/simply_window.h | 2 +- 17 files changed, 31 insertions(+), 20 deletions(-) create mode 100644 src/main.c rename src/{simplyjs.c => simply/simply.c} (81%) rename src/{simplyjs.h => simply/simply.h} (83%) rename src/{ => simply}/simply_accel.c (98%) rename src/{ => simply}/simply_accel.h (100%) rename src/{ => simply}/simply_menu.c (99%) rename src/{ => simply}/simply_menu.h (98%) rename src/{ => simply}/simply_msg.c (99%) rename src/{ => simply}/simply_msg.h (97%) rename src/{ => simply}/simply_res.c (100%) rename src/{ => simply}/simply_res.h (100%) rename src/{ => simply}/simply_splash.c (98%) rename src/{ => simply}/simply_splash.h (92%) rename src/{ => simply}/simply_ui.c (99%) rename src/{ => simply}/simply_ui.h (97%) rename src/{ => simply}/simply_window.c (99%) rename src/{ => simply}/simply_window.h (97%) diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..c7f8abf3 --- /dev/null +++ b/src/main.c @@ -0,0 +1,15 @@ +/** + * PebbleJS Project main file. + */ + +#include +#include "simply/simply.h" + +/* + * By default, we 'simply' load Simply and start running it. + */ +int main(void) { + Simply *simply = simply_init(); + app_event_loop(); + simply_deinit(simply); +} diff --git a/src/simplyjs.c b/src/simply/simply.c similarity index 81% rename from src/simplyjs.c rename to src/simply/simply.c index 684ede3b..65533970 100644 --- a/src/simplyjs.c +++ b/src/simply/simply.c @@ -1,4 +1,4 @@ -#include "simplyjs.h" +#include "simply.h" #include "simply_accel.h" #include "simply_res.h" @@ -9,7 +9,7 @@ #include -static Simply *init(void) { +Simply *simply_init(void) { Simply *simply = malloc(sizeof(*simply)); simply->accel = simply_accel_create(); simply->res = simply_res_create(); @@ -24,17 +24,11 @@ static Simply *init(void) { return simply; } -static void deinit(Simply *simply) { +void simply_deinit(Simply *simply) { simply_msg_deinit(); simply_ui_destroy(simply->ui); simply_menu_destroy(simply->menu); simply_res_destroy(simply->res); simply_accel_destroy(simply->accel); -} - -int main(void) { - Simply *simply = init(); - app_event_loop(); - deinit(simply); free(simply); } diff --git a/src/simplyjs.h b/src/simply/simply.h similarity index 83% rename from src/simplyjs.h rename to src/simply/simply.h index 1db7a792..1fac01c9 100644 --- a/src/simplyjs.h +++ b/src/simply/simply.h @@ -12,3 +12,5 @@ struct Simply { struct SimplyUi *ui; }; +Simply *simply_init(); +void simply_deinit(Simply *); diff --git a/src/simply_accel.c b/src/simply/simply_accel.c similarity index 98% rename from src/simply_accel.c rename to src/simply/simply_accel.c index 01bf43c3..ec146452 100644 --- a/src/simply_accel.c +++ b/src/simply/simply_accel.c @@ -2,7 +2,7 @@ #include "simply_msg.h" -#include "simplyjs.h" +#include "simply.h" #include diff --git a/src/simply_accel.h b/src/simply/simply_accel.h similarity index 100% rename from src/simply_accel.h rename to src/simply/simply_accel.h diff --git a/src/simply_menu.c b/src/simply/simply_menu.c similarity index 99% rename from src/simply_menu.c rename to src/simply/simply_menu.c index 074b5686..716bb824 100644 --- a/src/simply_menu.c +++ b/src/simply/simply_menu.c @@ -4,7 +4,7 @@ #include "simply_msg.h" -#include "simplyjs.h" +#include "simply.h" #include diff --git a/src/simply_menu.h b/src/simply/simply_menu.h similarity index 98% rename from src/simply_menu.h rename to src/simply/simply_menu.h index d59c66db..98709932 100644 --- a/src/simply_menu.h +++ b/src/simply/simply_menu.h @@ -2,7 +2,7 @@ #include "simply_window.h" -#include "simplyjs.h" +#include "simply.h" #include "util/list1.h" diff --git a/src/simply_msg.c b/src/simply/simply_msg.c similarity index 99% rename from src/simply_msg.c rename to src/simply/simply_msg.c index 833f0297..4602cb19 100644 --- a/src/simply_msg.c +++ b/src/simply/simply_msg.c @@ -5,7 +5,7 @@ #include "simply_menu.h" #include "simply_ui.h" -#include "simplyjs.h" +#include "simply.h" #include "util/string.h" diff --git a/src/simply_msg.h b/src/simply/simply_msg.h similarity index 97% rename from src/simply_msg.h rename to src/simply/simply_msg.h index 179a4651..e37f66ca 100644 --- a/src/simply_msg.h +++ b/src/simply/simply_msg.h @@ -1,6 +1,6 @@ #pragma once -#include "simplyjs.h" +#include "simply.h" #include diff --git a/src/simply_res.c b/src/simply/simply_res.c similarity index 100% rename from src/simply_res.c rename to src/simply/simply_res.c diff --git a/src/simply_res.h b/src/simply/simply_res.h similarity index 100% rename from src/simply_res.h rename to src/simply/simply_res.h diff --git a/src/simply_splash.c b/src/simply/simply_splash.c similarity index 98% rename from src/simply_splash.c rename to src/simply/simply_splash.c index 111d026d..da83030a 100644 --- a/src/simply_splash.c +++ b/src/simply/simply_splash.c @@ -1,6 +1,6 @@ #include "simply_splash.h" -#include "simplyjs.h" +#include "simply.h" #include diff --git a/src/simply_splash.h b/src/simply/simply_splash.h similarity index 92% rename from src/simply_splash.h rename to src/simply/simply_splash.h index eedff8ea..4f57c9b4 100644 --- a/src/simply_splash.h +++ b/src/simply/simply_splash.h @@ -1,6 +1,6 @@ #pragma once -#include "simplyjs.h" +#include "simply.h" #include diff --git a/src/simply_ui.c b/src/simply/simply_ui.c similarity index 99% rename from src/simply_ui.c rename to src/simply/simply_ui.c index 23f3df51..8891e914 100644 --- a/src/simply_ui.c +++ b/src/simply/simply_ui.c @@ -4,7 +4,7 @@ #include "simply_res.h" #include "simply_menu.h" -#include "simplyjs.h" +#include "simply.h" #include "util/graphics.h" #include "util/string.h" diff --git a/src/simply_ui.h b/src/simply/simply_ui.h similarity index 97% rename from src/simply_ui.h rename to src/simply/simply_ui.h index 150f2846..38998d5d 100644 --- a/src/simply_ui.h +++ b/src/simply/simply_ui.h @@ -2,7 +2,7 @@ #include "simply_window.h" -#include "simplyjs.h" +#include "simply.h" #include diff --git a/src/simply_window.c b/src/simply/simply_window.c similarity index 99% rename from src/simply_window.c rename to src/simply/simply_window.c index ee2de81c..f586e7dc 100644 --- a/src/simply_window.c +++ b/src/simply/simply_window.c @@ -4,7 +4,7 @@ #include "simply_res.h" #include "simply_menu.h" -#include "simplyjs.h" +#include "simply.h" #include "util/graphics.h" #include "util/string.h" diff --git a/src/simply_window.h b/src/simply/simply_window.h similarity index 97% rename from src/simply_window.h rename to src/simply/simply_window.h index 490dc687..c354b3e8 100644 --- a/src/simply_window.h +++ b/src/simply/simply_window.h @@ -1,6 +1,6 @@ #pragma once -#include "simplyjs.h" +#include "simply.h" #include From 032ab7107d62bcf8cb3dff7c14fe2460e7f8e003 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 27 May 2014 04:25:52 -0400 Subject: [PATCH 179/791] Fixup to define simply stage clear prototype --- src/simply_stage.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/simply_stage.h b/src/simply_stage.h index 9516d50e..d97915d4 100644 --- a/src/simply_stage.h +++ b/src/simply_stage.h @@ -109,6 +109,8 @@ void simply_stage_destroy(SimplyStage *self); void simply_stage_show(SimplyStage *self); +void simply_stage_clear(SimplyStage *self); + void simply_stage_update(SimplyStage *self); void simply_stage_update_ticker(SimplyStage *self); From b17afdf3c29d3fc7f97b3dc7557ac23998e696ad Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 27 May 2014 04:29:29 -0400 Subject: [PATCH 180/791] Fixup myutil shadow to correctly check the property --- src/js/base/myutil.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/base/myutil.js b/src/js/base/myutil.js index 0fc3ee11..b59f02cb 100644 --- a/src/js/base/myutil.js +++ b/src/js/base/myutil.js @@ -4,7 +4,7 @@ var myutil = {}; myutil.shadow = function(a, b) { for (var k in a) { - if (typeof b === 'undefined') { + if (typeof b[k] === 'undefined') { b[k] = a[k]; } } From 7e20801725758bda29b5ce577ee3fc9b082e1140 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Tue, 27 May 2014 14:32:37 -0400 Subject: [PATCH 181/791] Reorganizing simplyjs based on discussion with Huy - Made the "remote packages" system an optional feature in the smartpackage namespace - Made Settings an independent package - Made all the UI app depend on simply.impl provided by simply-pebble.js - Moved the old "Simply" to be just a compatibility layer in simply/simply --- src/js/exports.js | 19 ----- src/js/{base => lib}/emitter.js | 0 src/js/{base => lib}/myutil.js | 17 +++++ src/js/main.js | 69 ++++++++++++++++++- src/js/{base => settings}/settings.js | 10 +-- src/js/simply.js | 64 ----------------- src/js/simply/simply.js | 36 ++++++++++ .../{base => smartpackage}/package-pebble.js | 7 +- src/js/{base => smartpackage}/package.js | 30 ++++---- src/js/{base => ui}/accel.js | 4 +- src/js/ui/card.js | 6 +- src/js/ui/circle.js | 2 +- src/js/ui/element.js | 4 +- src/js/ui/image.js | 2 +- src/js/{base/image.js => ui/imageservice.js} | 12 +++- src/js/ui/menu.js | 6 +- src/js/ui/propable.js | 2 +- src/js/ui/rect.js | 2 +- src/js/{ => ui}/simply-pebble.js | 59 ++++++++-------- src/js/ui/simply.js | 13 ++++ src/js/ui/stage.js | 4 +- src/js/ui/text.js | 2 +- src/js/ui/vibe.js | 7 ++ src/js/ui/window.js | 8 +-- src/js/ui/windowstack.js | 4 +- src/{ => simply}/simply_stage.c | 2 +- src/{ => simply}/simply_stage.h | 4 +- 27 files changed, 232 insertions(+), 163 deletions(-) delete mode 100644 src/js/exports.js rename src/js/{base => lib}/emitter.js (100%) rename src/js/{base => lib}/myutil.js (75%) rename src/js/{base => settings}/settings.js (91%) delete mode 100644 src/js/simply.js create mode 100644 src/js/simply/simply.js rename src/js/{base => smartpackage}/package-pebble.js (94%) rename src/js/{base => smartpackage}/package.js (89%) rename src/js/{base => ui}/accel.js (98%) rename src/js/{base/image.js => ui/imageservice.js} (90%) rename src/js/{ => ui}/simply-pebble.js (93%) create mode 100644 src/js/ui/simply.js create mode 100644 src/js/ui/vibe.js rename src/{ => simply}/simply_stage.c (99%) rename src/{ => simply}/simply_stage.h (97%) diff --git a/src/js/exports.js b/src/js/exports.js deleted file mode 100644 index d92ecfbc..00000000 --- a/src/js/exports.js +++ /dev/null @@ -1,19 +0,0 @@ - -Pebble.Settings = require('base/settings'); - -Pebble.Accel = require('base/accel'); - -var UI = {}; - -UI.Vector2 = require('lib/vector2'); -UI.Card = require('ui/card'); -UI.Menu = require('ui/menu'); -UI.Stage = require('ui/stage'); -UI.Rect = require('ui/rect'); -UI.Circle = require('ui/circle'); -UI.Text = require('ui/text'); -UI.Image = require('ui/image'); - -Pebble.UI = UI; - -module.exports = Pebble; diff --git a/src/js/base/emitter.js b/src/js/lib/emitter.js similarity index 100% rename from src/js/base/emitter.js rename to src/js/lib/emitter.js diff --git a/src/js/base/myutil.js b/src/js/lib/myutil.js similarity index 75% rename from src/js/base/myutil.js rename to src/js/lib/myutil.js index 0fc3ee11..56cc0bbe 100644 --- a/src/js/base/myutil.js +++ b/src/js/lib/myutil.js @@ -57,4 +57,21 @@ myutil.toFlags = function(flags) { return flags; }; +/** + * Returns an absolute path based on a root path and a relative path. + */ +myutil.abspath = function(root, path) { + if (!path) { + path = root; + } + if (path.match(/^\/\//)) { + var m = root && root.match(/^(\w+:)\/\//); + path = (m ? m[1] : 'http:') + path; + } + if (root && !path.match(/^\w+:\/\//)) { + path = root + path; + } + return path; +}; + module.exports = myutil; diff --git a/src/js/main.js b/src/js/main.js index 06588530..e9c51554 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -1,5 +1,70 @@ -var SimplyPebble = require('simply-pebble'); +console.log("Running main.js ..."); + +Pebble.Settings = require('settings/settings'); +Pebble.Accel = require('ui/accel'); + +var UI = {}; +UI.Vector2 = require('lib/vector2'); +UI.Card = require('ui/card'); +UI.Menu = require('ui/menu'); +UI.Stage = require('ui/stage'); +UI.Rect = require('ui/rect'); +UI.Circle = require('ui/circle'); +UI.Text = require('ui/text'); +UI.Image = require('ui/image'); +Pebble.UI = UI; + +//Pebble.SmartPackage = require('pebble/smartpackage'); + +function dumpError(err) { + if (typeof err === 'object') { + if (err.message) { + console.log('\nMessage: ' + err.message); + } + if (err.stack) { + console.log('\nStacktrace:'); + console.log('===================='); + console.log(err.stack); + } + } else { + console.log('dumpError :: argument is not an object'); + } +} Pebble.addEventListener('ready', function(e) { - SimplyPebble.init(); + try { + console.log("Running the Pebble.ready event...."); + + // Load the SimplyJS Pebble implementation + require('ui/simply-pebble').init(); + + Pebble.Settings.init(); + Pebble.Accel.init(); + + var wind = new UI.Card({ title: "PebbleJS", body: "Saying Hello World" }); + wind.show(); + + wind.on('singleClick', function(e) { + var menu = new UI.Menu(); + menu.items(0, [ { title: 'Hello World!', subtitle: 'text' }, { title: 'item2' } ]); + menu.show(); + }); + + console.log("Done running init"); + + // Load local file + //require('app.js'); + + // Or use Smart Package to load a remote JS file + //Pebble.SmartPackage.init('http://www.sarfata.org/myapp.js'); + + // or Ask the user to enter a URL to run in the Settings + //Pebble.SmartPackage.initWithSettings(); + + // or Load a list of app and let the user choose in the Settings which one to run. + //Pebble.SmartPackage.loadBundle('http://www.sarfata.org/myapps.json'); + } + catch (err) { + dumpError(err); + } }); diff --git a/src/js/base/settings.js b/src/js/settings/settings.js similarity index 91% rename from src/js/base/settings.js rename to src/js/settings/settings.js index 8b301657..cd5d496c 100644 --- a/src/js/base/settings.js +++ b/src/js/settings/settings.js @@ -1,5 +1,4 @@ var util2 = require('lib/util2'); -var package = require('base/package'); var Settings = module.exports; @@ -12,6 +11,9 @@ Settings.init = function() { options: {}, listeners: [], }; + // Register listeners for the Settings + Pebble.addEventListener('showConfiguration', Settings.onOpenConfig); + Pebble.addEventListener('webviewclosed', Settings.onCloseConfig); }; Settings.mainScriptUrl = function(scriptUrl) { @@ -27,7 +29,7 @@ Settings.mainScriptUrl = function(scriptUrl) { }; var getOptionsKey = function(path) { - return 'options:' + (path || package.module.filename); + return 'options:' + path; }; Settings.saveOptions = function(path) { @@ -92,6 +94,7 @@ Settings.config = function(opt, open, close) { }; Settings.onOpenConfig = function(e) { + console.log("Settings.onOpenConfig"); var options; var url; var listener = util2.last(state.listeners); @@ -126,7 +129,4 @@ Settings.onCloseConfig = function(e) { }; return listener.close(e); } - if (options.scriptUrl) { - package.loadMainScript(options.scriptUrl); - } }; diff --git a/src/js/simply.js b/src/js/simply.js deleted file mode 100644 index ee58a526..00000000 --- a/src/js/simply.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Simply.js - * @namespace simply - */ - -var ajax = require('lib/ajax'); -var util2 = require('lib/util2'); - -var myutil = require('base/myutil'); -var package = require('base/package'); -var Emitter = require('base/emitter'); -var Settings = require('base/settings'); -var Accel = require('base/accel'); -var ImageService = require('base/image'); - -var WindowStack = require('ui/windowstack'); -var Card = require('ui/card'); - -require('exports'); - -var simply = module.exports; - -ajax.onHandler = function(type, handler) { - return simply.wrapHandler(handler, 2); -}; - -Emitter.prototype.wrapHandler = simply.wrapHandler; - -simply.init = function() { - package.loadMainScript(); -}; - -simply.wrapHandler = function(handler) { - return package.impl.wrapHandler.apply(this, arguments); -}; - -simply.reset = function() { - simply.run = true; - - Settings.init(); - Accel.init(); - ImageService.init(); - WindowStack.init(); -}; - -simply.text = function(textDef) { - var wind = WindowStack.top(); - if (!wind || !(wind instanceof Card)) { - wind = new Card(textDef); - wind.show(); - } else { - wind.prop(textDef, true); - } -}; - -/** - * Vibrates the Pebble. - * There are three support vibe types: short, long, and double. - * @memberOf simply - * @param {string} [type=short] - The vibe type. - */ -simply.vibe = function() { - return simply.impl.vibe.apply(this, arguments); -}; diff --git a/src/js/simply/simply.js b/src/js/simply/simply.js new file mode 100644 index 00000000..8cb5c0da --- /dev/null +++ b/src/js/simply/simply.js @@ -0,0 +1,36 @@ +/** + * Simply.js + * + * Provides the classic "SimplyJS" API on top of PebbleJS. + * + * Not to be confused with ui/Simply which abstracts the implementation used + * to interface with the underlying hardware. + * + * @namespace simply + */ + +var WindowStack = require('ui/windowstack'); +var Card = require('ui/card'); +var Vibe = require('ui/vibe'); + +simply.text = function(textDef) { + var wind = WindowStack.top(); + if (!wind || !(wind instanceof Card)) { + wind = new Card(textDef); + wind.show(); + } else { + wind.prop(textDef, true); + } +}; + +/** + * Vibrates the Pebble. + * There are three support vibe types: short, long, and double. + * @memberOf simply + * @param {string} [type=short] - The vibe type. + */ +simply.vibe = function(type) { + return Vibe.vibrate(type); +}; + +module.exports = simply; diff --git a/src/js/base/package-pebble.js b/src/js/smartpackage/package-pebble.js similarity index 94% rename from src/js/base/package-pebble.js rename to src/js/smartpackage/package-pebble.js index 46702729..fb311bfe 100644 --- a/src/js/base/package-pebble.js +++ b/src/js/smartpackage/package-pebble.js @@ -1,6 +1,6 @@ -var myutil = require('base/myutil'); -var package = require('base/package'); -var simply = require('simply'); +var myutil = require('lib/myutil'); +var package = require('smartpackage/package'); +var simply = require('simply/simply'); var packageImpl = module.exports; @@ -56,7 +56,6 @@ var papply = packageImpl.papply = function(f, args, path) { subtitle: scope, body: e.line + ' ' + e.message, }, true); - simply.state.run = false; } }; diff --git a/src/js/base/package.js b/src/js/smartpackage/package.js similarity index 89% rename from src/js/base/package.js rename to src/js/smartpackage/package.js index 036eb995..c89c55a4 100644 --- a/src/js/base/package.js +++ b/src/js/smartpackage/package.js @@ -1,7 +1,7 @@ var ajax = require('lib/ajax'); var util2 = require('lib/util2'); -var myutil = require('base/myutil'); -var Settings = require('base/settings'); +var myutil = require('lib/myutil'); +var Settings = require('settings/settings'); var simply = require('simply'); var package = module.exports; @@ -16,26 +16,28 @@ package.basename = function(path) { return path.match(/[^\/]*$/)[0]; }; +/** + * Converts a relative path to an absolute path + * using the path of the currently running script + * (package.module) or optionaly, the given root. + * + * The first argument is optional: + * abspath(path); + * abspath(root, path); + */ package.abspath = function(root, path) { + // Handle optional first argument if (!path) { path = root; root = null; } + // Use the package root if no root provided. if (!root && package.module) { root = package.basepath(package.module.filename); } - if (!path) { - path = root; - } - if (path.match(/^\/\//)) { - var m = root && root.match(/^(\w+:)\/\//); - path = (m ? m[1] : 'http:') + path; - } - if (root && !path.match(/^\w+:\/\//)) { - path = root + path; - } - return path; -}; + return myutil.abspath(root, path); +} + package.name = function(rootfile, path) { if (!path) { diff --git a/src/js/base/accel.js b/src/js/ui/accel.js similarity index 98% rename from src/js/base/accel.js rename to src/js/ui/accel.js index cc9bcbd2..95e59e86 100644 --- a/src/js/base/accel.js +++ b/src/js/ui/accel.js @@ -1,4 +1,4 @@ -var Emitter = require('base/emitter'); +var Emitter = require('lib/emitter'); var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); @@ -6,7 +6,7 @@ var Accel = new Emitter(); module.exports = Accel; -var simply = require('simply'); +var simply = require('ui/simply'); var state; diff --git a/src/js/ui/card.js b/src/js/ui/card.js index 0a853b8a..bcf621be 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -1,10 +1,10 @@ var util2 = require('lib/util2'); -var myutil = require('base/myutil'); -var Emitter = require('base/emitter'); +var myutil = require('lib/myutil'); +var Emitter = require('lib/emitter'); var WindowStack = require('ui/windowstack'); var Propable = require('ui/propable'); var Window = require('ui/window'); -var simply = require('simply'); +var simply = require('ui/simply'); /** * Sets the title field. The title field is the first and largest text field available. diff --git a/src/js/ui/circle.js b/src/js/ui/circle.js index 1111c2c1..174a878d 100644 --- a/src/js/ui/circle.js +++ b/src/js/ui/circle.js @@ -1,5 +1,5 @@ var util2 = require('lib/util2'); -var myutil = require('base/myutil'); +var myutil = require('lib/myutil'); var StageElement = require('ui/element'); var defaults = { diff --git a/src/js/ui/element.js b/src/js/ui/element.js index 33bcb130..b9ad044c 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -1,9 +1,9 @@ var util2 = require('lib/util2'); var Vector2 = require('lib/vector2'); -var myutil = require('base/myutil'); +var myutil = require('lib/myutil'); var WindowStack = require('ui/windowstack'); var Propable = require('ui/propable'); -var simply = require('simply'); +var simply = require('ui/simply'); var elementProps = [ 'position', diff --git a/src/js/ui/image.js b/src/js/ui/image.js index 4a4c8912..d2b75d14 100644 --- a/src/js/ui/image.js +++ b/src/js/ui/image.js @@ -1,5 +1,5 @@ var util2 = require('lib/util2'); -var myutil = require('base/myutil'); +var myutil = require('lib/myutil'); var Propable = require('ui/propable'); var StageElement = require('ui/element'); diff --git a/src/js/base/image.js b/src/js/ui/imageservice.js similarity index 90% rename from src/js/base/image.js rename to src/js/ui/imageservice.js index 119516a7..4c2c81d9 100644 --- a/src/js/base/image.js +++ b/src/js/ui/imageservice.js @@ -1,6 +1,6 @@ var imagelib = require('lib/image'); -var package = require('base/package'); -var simply = require('simply'); +var myutil = require('lib/myutil'); +var simply = require('ui/simply'); var ImageService = module.exports; @@ -10,6 +10,7 @@ ImageService.init = function() { state = Image.state = { cache: {}, nextId: 1, + rootURL: null }; }; @@ -59,7 +60,7 @@ ImageService.load = function(opt, reset, callback) { callback = reset; reset = null; } - var url = package.abspath(opt.url); + var url = myutil.abspath(state.rootURL, opt.url); var hash = makeImageHash(opt); var image = state.cache[hash]; if (image) { @@ -93,3 +94,8 @@ ImageService.load = function(opt, reset, callback) { }); return image.id; }; + +ImageService.setRootURL = function(url) { + state.rootURL = url; +}; + diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index af5d75ae..49bead01 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -1,9 +1,9 @@ var util2 = require('lib/util2'); -var myutil = require('base/myutil'); -var Emitter = require('base/emitter'); +var myutil = require('lib/myutil'); +var Emitter = require('lib/emitter'); var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); -var simply = require('simply'); +var simply = require('ui/simply'); var Menu = function(menuDef) { Window.call(this, menuDef); diff --git a/src/js/ui/propable.js b/src/js/ui/propable.js index defd2e89..fb9a5ce7 100644 --- a/src/js/ui/propable.js +++ b/src/js/ui/propable.js @@ -1,5 +1,5 @@ var util2 = require('lib/util2'); -var myutil = require('base/myutil'); +var myutil = require('lib/myutil'); var Propable = function(def) { this.state = def || {}; diff --git a/src/js/ui/rect.js b/src/js/ui/rect.js index d1a1e5fb..76321ce8 100644 --- a/src/js/ui/rect.js +++ b/src/js/ui/rect.js @@ -1,5 +1,5 @@ var util2 = require('lib/util2'); -var myutil = require('base/myutil'); +var myutil = require('lib/myutil'); var StageElement = require('ui/element'); var defaults = { diff --git a/src/js/simply-pebble.js b/src/js/ui/simply-pebble.js similarity index 93% rename from src/js/simply-pebble.js rename to src/js/ui/simply-pebble.js index 202ac212..505ecd07 100644 --- a/src/js/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1,23 +1,28 @@ var util2 = require('lib/util2'); -var myutil = require('base/myutil'); -var Settings = require('base/settings'); -var Accel = require('base/accel'); -var ImageService = require('base/image'); +var myutil = require('lib/myutil'); +var Accel = require('ui/accel'); +var ImageService = require('ui/imageservice'); var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); var Menu = require('ui/menu'); -var simply = require('simply'); -var SimplyPebble = {}; +var simply = require('ui/simply'); -var package = require('base/package'); -var packageImpl = require('base/package-pebble'); -package.impl = packageImpl; +/** + * This package provides the underlying implementation for the ui/* classes. + * + * This implementation uses PebbleKit JS AppMessage to send commands to a Pebble Watch. + */ +/* Make JSHint happy */ if (typeof Image === 'undefined') { window.Image = function(){}; } +/* + * First part of this file is defining the commands and types that we will use later. + */ + var Color = function(x) { switch (x) { case 'clear': return ~0; @@ -368,6 +373,8 @@ var commands = [{ }], }]; +// Build the commandMap and map each command to an integer. + var commandMap = {}; for (var i = 0, ii = commands.length; i < ii; ++i) { @@ -425,20 +432,23 @@ var actionBarTypeMap = { down: 'actionDown', }; + +/* + * SimplyPebble object provides the actual methods to communicate with Pebble. + * + * It's an implementation of an abstract interface used by all the other classes. + */ + var SimplyPebble = {}; SimplyPebble.init = function() { - simply.impl = SimplyPebble; - simply.init(); -}; + // Register listeners for app message communication + Pebble.addEventListener('appmessage', SimplyPebble.onAppMessage); -SimplyPebble.onShowConfiguration = function(e) { - Settings.onOpenConfig(e); + // Register this implementation as the one currently in use + simply.impl = SimplyPebble; }; -SimplyPebble.onWebViewClosed = function(e) { - Settings.onCloseConfig(e); -}; var toParam = function(param, v) { if (param.type === String) { @@ -476,9 +486,6 @@ var makePacket = function(command, def) { }; SimplyPebble.sendPacket = function(packet) { - if (!simply.run) { - return; - } var send; send = function() { Pebble.sendAppMessage(packet, util2.noop, send); @@ -564,8 +571,10 @@ SimplyPebble.accelConfig = function(configDef) { SimplyPebble.sendPacket(packet); }; +var accelListeners = []; + SimplyPebble.accelPeek = function(callback) { - simply.state.accel.listeners.push(callback); + accelListeners.push(callback); var command = commandMap.getAccelData; var packet = makePacket(command); SimplyPebble.sendPacket(packet); @@ -716,8 +725,8 @@ SimplyPebble.onAppMessage = function(e) { if (typeof transactionId === 'undefined') { Accel.emitAccelData(accels); } else { - var handlers = simply.state.accel.listeners; - simply.state.accel.listeners = []; + var handlers = accelListeners; + accelListeners = []; for (var j = 0, jj = handlers.length; j < jj; ++j) { Accel.emitAccelData(accels, handlers[j]); } @@ -736,8 +745,4 @@ SimplyPebble.onAppMessage = function(e) { } }; -Pebble.addEventListener('showConfiguration', SimplyPebble.onShowConfiguration); -Pebble.addEventListener('webviewclosed', SimplyPebble.onWebViewClosed); -Pebble.addEventListener('appmessage', SimplyPebble.onAppMessage); - module.exports = SimplyPebble; diff --git a/src/js/ui/simply.js b/src/js/ui/simply.js new file mode 100644 index 00000000..a0d5f2d9 --- /dev/null +++ b/src/js/ui/simply.js @@ -0,0 +1,13 @@ +/** + * This file provides an easy way to switch the actual implementation used by all the + * ui objects. + * + * simply.impl provides the actual communication layer to the hardware. + */ + +var simply = {}; + +// Override this with the actual implementation you want to use. +simply.impl = undefined; + +module.exports = simply; diff --git a/src/js/ui/stage.js b/src/js/ui/stage.js index b6a5a644..62bd3b36 100644 --- a/src/js/ui/stage.js +++ b/src/js/ui/stage.js @@ -1,8 +1,8 @@ var util2 = require('lib/util2'); -var Emitter = require('base/emitter'); +var Emitter = require('lib/emitter'); var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); -var simply = require('simply'); +var simply = require('ui/simply'); var Stage = function(stageDef) { Window.call(this, stageDef); diff --git a/src/js/ui/text.js b/src/js/ui/text.js index a53e7b05..708e0dd7 100644 --- a/src/js/ui/text.js +++ b/src/js/ui/text.js @@ -1,5 +1,5 @@ var util2 = require('lib/util2'); -var myutil = require('base/myutil'); +var myutil = require('lib/myutil'); var Propable = require('ui/propable'); var StageElement = require('ui/element'); diff --git a/src/js/ui/vibe.js b/src/js/ui/vibe.js new file mode 100644 index 00000000..99784f7f --- /dev/null +++ b/src/js/ui/vibe.js @@ -0,0 +1,7 @@ +var Vibe = module.exports; + +Vibe.vibrate = function(type) { + // TODO: Provide implementation here + console.log("Vibrate watch"); +}; + diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 9bbc7a17..6410d28f 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -1,10 +1,10 @@ var util2 = require('lib/util2'); -var myutil = require('base/myutil'); -var Emitter = require('base/emitter'); -var Accel = require('base/accel'); +var myutil = require('lib/myutil'); +var Emitter = require('lib/emitter'); +var Accel = require('ui/accel'); var WindowStack = require('ui/windowstack'); var Propable = require('ui/propable'); -var simply = require('simply'); +var simply = require('ui/simply'); var buttons = [ 'back', diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index 957007d2..44838afa 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -1,6 +1,6 @@ var util2 = require('lib/util2'); -var myutil = require('base/myutil'); -var Emitter = require('base/emitter'); +var myutil = require('lib/myutil'); +var Emitter = require('lib/emitter'); var WindowStack = function() { this.init(); diff --git a/src/simply_stage.c b/src/simply/simply_stage.c similarity index 99% rename from src/simply_stage.c rename to src/simply/simply_stage.c index 1a4045a4..a235901b 100644 --- a/src/simply_stage.c +++ b/src/simply/simply_stage.c @@ -4,7 +4,7 @@ #include "simply_msg.h" -#include "simplyjs.h" +#include "simply.h" #include "util/graphics.h" #include "util/string.h" diff --git a/src/simply_stage.h b/src/simply/simply_stage.h similarity index 97% rename from src/simply_stage.h rename to src/simply/simply_stage.h index 9516d50e..9a3255ff 100644 --- a/src/simply_stage.h +++ b/src/simply/simply_stage.h @@ -2,7 +2,7 @@ #include "simply_window.h" -#include "simplyjs.h" +#include "simply/simply.h" #include "util/list1.h" @@ -105,6 +105,8 @@ struct SimplyAnimation { SimplyStage *simply_stage_create(Simply *simply); +void simply_stage_clear(SimplyStage *self); + void simply_stage_destroy(SimplyStage *self); void simply_stage_show(SimplyStage *self); From d9f64ee2faa41aa8f791f155318366c1e66dbe42 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Tue, 27 May 2014 16:57:25 -0400 Subject: [PATCH 182/791] Moved user code to app.js and did some cleanup - Removed public/demo.js (not valid anymore) - Removed README.md in src/js - Added a README.md in the root folder --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++ public/demo.js | 23 --------------------- src/js/README.md | 15 -------------- src/js/app.js | 33 +++++++++++++++++++++++++++++ src/js/main.js | 54 +++++++++++++++++++++--------------------------- 5 files changed, 105 insertions(+), 68 deletions(-) create mode 100644 README.md delete mode 100644 public/demo.js delete mode 100644 src/js/README.md create mode 100644 src/js/app.js diff --git a/README.md b/README.md new file mode 100644 index 00000000..6d39431e --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +Pebble.JS +========= + +Pebble.JS is a JavaScript framework for Pebble development. Pebble.JS allows you to write great looking Pebble apps with only JavaScript code. + +## How does it work? + +Pebble.JS is built on top of the Pebble SDK. Your JavaScript code runs on the phone and uses the Pebble.JS library to exchange messages with the Pebble watch currently connected. + +The Pebble runs the Pebble.JS application which translates the commands into a UI on the screen and relays user interactions (accelerometer, buttons, etc) to the code. + +## How to use it? + +Just open the `src/js/app.js` and start writing code! + + var card = new Pebble.UI.Card({ title: "Hello World", body: "Your first Pebble app!" }); + card.show(); + +Reacting to button interactions is also very easy: + + card.on('singleClick', function(e) { + card.subtitle("Button " + e.button + " pressed."); + } + +And making HTTP connection too, with the included `ajax` library. + + var ajax = require('ajax'); + ajax({ url: 'http://api.theysaidso.com/qod.json', type: 'json' }, + function(data) { + card.body(data.contents.quote); + card.title(data.contents.author); + }); + +You can do much more with Pebble.JS: + + - Get accelerometer values + - Display complex UI mixing geometric elements, text and images + - Animate elements on the screen + - Display arbitrary long menus + - Use the GPS and LocalStorage on the phone + - etc! + +The full API reference is available on the [Pebble developer website](http://developer.getpebble.com/). + +## Who wrote this? Is this supported by Pebble? + +Pebble.JS started as [Simply.JS](http://www.simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! + diff --git a/public/demo.js b/public/demo.js deleted file mode 100644 index 22676f5a..00000000 --- a/public/demo.js +++ /dev/null @@ -1,23 +0,0 @@ -/* global simply */ -console.log('Simply.js demo!'); - -simply.text({ - title: 'Simply Demo!', - body: 'This is a demo. Press buttons or tap the watch!', -}, true); - -simply.on('singleClick', function(e) { - console.log('single clicked ' + e.button + '!'); - simply.subtitle('Pressed ' + e.button + '!'); -}); - -simply.on('longClick', function(e) { - console.log('long clicked ' + e.button + '!'); - simply.subtitle('Long pressed ' + e.button + '!'); - simply.vibe(); -}); - -simply.on('accelTap', function(e) { - console.log('accel tapped axis ' + e.axis + ' ' + e.direction + '!'); - simply.subtitle('Tapped ' + (e.direction > 0 ? '+' : '-') + e.axis + '!'); -}); diff --git a/src/js/README.md b/src/js/README.md deleted file mode 100644 index 65e8e81b..00000000 --- a/src/js/README.md +++ /dev/null @@ -1,15 +0,0 @@ - -# Simply.js - -Simply.js allows you to write interactive text for your Pebble with just JavaScript. - -This is the API reference of Simply.js generated with JSDoc. - -Simply.js provides the following modules: - - * [simply](simply.html) - The Simply.js framework. - * [ajax](global.html#ajax) - An ajax micro library. - * [require](global.html#require) - A synchronous dependency loader provided by simply. - -Visit [simplyjs.meiguro.com](http://simplyjs.meiguro.com) for more details. - diff --git a/src/js/app.js b/src/js/app.js new file mode 100644 index 00000000..d491b1c8 --- /dev/null +++ b/src/js/app.js @@ -0,0 +1,33 @@ +// Welcome to PebbleJS! +// +// This is where you write your app. + +var wind = new Pebble.UI.Card({ title: "PebbleJS", body: "Saying Hello World" }); +wind.show(); + +wind.on('singleClick', function(e) { + try { + console.log("Button pressed: " + JSON.stringify(e)); + if (e.button == 'up') { + var menu = new Pebble.UI.Menu(); + menu.section(0, [ { title: 'Hello World!', subtitle: 'text' }, { title: 'item2' } ]); + menu.show(); + } + else if (e.button === 'select') { + var stage = new Pebble.UI.Stage(); + stage.add(new Pebble.UI.Text({ text: 'Yo!', position: new Pebble.UI.Vector2(10, 10), size: new Pebble.UI.Vector2(100, 30) })); + stage.show(); + } + else /* 'down' */ { + var card = new Pebble.UI.Card(); + card.title("A PebbleJS Card"); + card.subtitle("With subtitle"); + card.body("This is the simplest element you can push to the screen"); + card.show(); + } + } + catch (err) { + console.log(err); + } +}); + diff --git a/src/js/main.js b/src/js/main.js index e9c51554..f242e3fe 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -1,4 +1,10 @@ -console.log("Running main.js ..."); +/* + * This is the main PebbleJS file. You do not need to modify this file unless + * you want to change the way PebbleJS starts, the script it runs or the libraries + * it loads. + * + * By default, this will initialize all the libraries and run app.js + */ Pebble.Settings = require('settings/settings'); Pebble.Accel = require('ui/accel'); @@ -16,44 +22,17 @@ Pebble.UI = UI; //Pebble.SmartPackage = require('pebble/smartpackage'); -function dumpError(err) { - if (typeof err === 'object') { - if (err.message) { - console.log('\nMessage: ' + err.message); - } - if (err.stack) { - console.log('\nStacktrace:'); - console.log('===================='); - console.log(err.stack); - } - } else { - console.log('dumpError :: argument is not an object'); - } -} - Pebble.addEventListener('ready', function(e) { try { console.log("Running the Pebble.ready event...."); - // Load the SimplyJS Pebble implementation require('ui/simply-pebble').init(); - Pebble.Settings.init(); Pebble.Accel.init(); - - var wind = new UI.Card({ title: "PebbleJS", body: "Saying Hello World" }); - wind.show(); - - wind.on('singleClick', function(e) { - var menu = new UI.Menu(); - menu.items(0, [ { title: 'Hello World!', subtitle: 'text' }, { title: 'item2' } ]); - menu.show(); - }); - - console.log("Done running init"); + console.log("Done loading PebbleJS - Starting app."); // Load local file - //require('app.js'); + require('app.js'); // Or use Smart Package to load a remote JS file //Pebble.SmartPackage.init('http://www.sarfata.org/myapp.js'); @@ -68,3 +47,18 @@ Pebble.addEventListener('ready', function(e) { dumpError(err); } }); + +function dumpError(err) { + if (typeof err === 'object') { + if (err.message) { + console.log('\nMessage: ' + err.message); + } + if (err.stack) { + console.log('\nStacktrace:'); + console.log('===================='); + console.log(err.stack); + } + } else { + console.log('dumpError :: argument is not an object'); + } +} From a39a27691e9e620ebbd62f4166e68b748c629909 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Wed, 28 May 2014 01:05:01 -0400 Subject: [PATCH 183/791] Handle the Android app cancelling Settings The Android app sends 'CANCEL' when the configuration window is dismissed. We now handle this cleanly. --- src/js/settings/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index cd5d496c..ee80f5e6 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -118,7 +118,7 @@ Settings.onOpenConfig = function(e) { Settings.onCloseConfig = function(e) { var listener = util2.last(state.listeners); var options = {}; - if (e.response) { + if (e.response && e.response !== 'CANCELLED') { options = JSON.parse(decodeURIComponent(e.response)); } if (listener) { From 9c0c4bf0dfcb2b6c3664367000899f0656f76a01 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Wed, 28 May 2014 01:31:38 -0400 Subject: [PATCH 184/791] Added a safe library to protect app execution The safe library wraps all the callbacks accessible to the app by a try/catch statement. This way all runtime errors should be properly detected and trigger an exception with stacktrace and line number. --- src/js/app.js | 37 +++++++++++------------- src/js/lib/safe.js | 71 ++++++++++++++++++++++++++++++++++++++++++++++ src/js/main.js | 49 +++++++++++--------------------- src/js/ui/tests.js | 28 ++++++++++++++++++ 4 files changed, 131 insertions(+), 54 deletions(-) create mode 100644 src/js/lib/safe.js create mode 100644 src/js/ui/tests.js diff --git a/src/js/app.js b/src/js/app.js index d491b1c8..6b7f535b 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -6,28 +6,23 @@ var wind = new Pebble.UI.Card({ title: "PebbleJS", body: "Saying Hello World" }) wind.show(); wind.on('singleClick', function(e) { - try { - console.log("Button pressed: " + JSON.stringify(e)); - if (e.button == 'up') { - var menu = new Pebble.UI.Menu(); - menu.section(0, [ { title: 'Hello World!', subtitle: 'text' }, { title: 'item2' } ]); - menu.show(); - } - else if (e.button === 'select') { - var stage = new Pebble.UI.Stage(); - stage.add(new Pebble.UI.Text({ text: 'Yo!', position: new Pebble.UI.Vector2(10, 10), size: new Pebble.UI.Vector2(100, 30) })); - stage.show(); - } - else /* 'down' */ { - var card = new Pebble.UI.Card(); - card.title("A PebbleJS Card"); - card.subtitle("With subtitle"); - card.body("This is the simplest element you can push to the screen"); - card.show(); - } + console.log("Button pressed: " + JSON.stringify(e)); + if (e.button == 'up') { + var menu = new Pebble.UI.Menu(); + menu.section(0, [ { title: 'Hello World!', subtitle: 'text' }, { title: 'item2' } ]); + menu.show(); } - catch (err) { - console.log(err); + else if (e.button === 'select') { + var stage = new Pebble.UI.Stage(); + stage.add(new Pebble.UI.Text({ text: 'Yo!', position: new Pebble.UI.Vector2(10, 10), size: new Pebble.UI.Vector2(100, 30) })); + stage.show(); + } + else /* 'down' */ { + var card = new Pebble.UI.Card(); + card.title("A PebbleJS Card"); + card.subtitle("With subtitle"); + card.body("This is the simplest element you can push to the screen"); + card.show(); } }); diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js new file mode 100644 index 00000000..e6402e7e --- /dev/null +++ b/src/js/lib/safe.js @@ -0,0 +1,71 @@ +/* safe.js - Building a safer world for Pebble.JS Developers + * + * This library provides wrapper around all the asynchronous handlers that developers + * have access to so that error messages are caught and displayed nicely in the pebble tool + * console. + */ + +var ajax = require('lib/ajax'); + +/* We use this function to dump error messages to the console. */ +function dumpError(err) { + if (typeof err === 'object') { + if (err.message) { + console.log('Message: ' + err.message); + } + if (err.stack) { + console.log('Stacktrace:'); + console.log(err.stack); + } + } else { + console.log('dumpError :: argument is not an object'); + } +} + +/* Takes a function and return a new function with a call to it wrapped in a try/catch statement */ +function protect(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (err) { + dumpError(err); + } + }; +} + +/* Wrap event handlers added by Pebble.addEventListener */ +var pblAddEventListener = Pebble.addEventListener; +Pebble.addEventListener = function(eventName, eventCallback) { + pblAddEventListener.call(this, eventName, protect(eventCallback)); +}; + +var pblSendMessage = Pebble.sendAppMessage; +Pebble.sendAppMessage = function(message, success, failure) { + return pblSendMessage.call(this, message, protect(success), protect(failure)); +}; + +/* Wrap setTimeout and setInterval */ +var originalSetTimeout = setTimeout; +setTimeout = function(callback, delay) { + return originalSetTimeout(protect(callback), delay); +}; +var originalSetInterval = setInterval; +setInterval = function(callback, delay) { + return originalSetInterval(protect(callback), delay); +}; + +/* Wrap the success and failure callback of the ajax library */ +ajax.onHandler = function(eventName, callback) { + return protect(callback); +}; + +/* Wrap the geolocation API Callbacks */ +var watchPosition = navigator.geolocation.watchPosition; +navigator.geolocation.watchPosition = function(success, error, options) { + return watchPosition.call(this, protect(success), protect(error), options); +}; +var getCurrentPosition = navigator.geolocation.getCurrentPosition; +navigator.geolocation.getCurrentPosition = function(success, error, options) { + return getCurrentPosition.call(this, protect(success), protect(error), options); +}; diff --git a/src/js/main.js b/src/js/main.js index f242e3fe..e6160558 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -6,6 +6,8 @@ * By default, this will initialize all the libraries and run app.js */ +require('lib/safe'); + Pebble.Settings = require('settings/settings'); Pebble.Accel = require('ui/accel'); @@ -23,42 +25,23 @@ Pebble.UI = UI; //Pebble.SmartPackage = require('pebble/smartpackage'); Pebble.addEventListener('ready', function(e) { - try { - console.log("Running the Pebble.ready event...."); - // Load the SimplyJS Pebble implementation - require('ui/simply-pebble').init(); - Pebble.Settings.init(); - Pebble.Accel.init(); - console.log("Done loading PebbleJS - Starting app."); + // Load the SimplyJS Pebble implementation + require('ui/simply-pebble').init(); + Pebble.Settings.init(); + Pebble.Accel.init(); + console.log("Done loading PebbleJS - Starting app."); - // Load local file - require('app.js'); + // Load local file + //require('app.js'); + require('ui/tests'); - // Or use Smart Package to load a remote JS file - //Pebble.SmartPackage.init('http://www.sarfata.org/myapp.js'); + // Or use Smart Package to load a remote JS file + //Pebble.SmartPackage.init('http://www.sarfata.org/myapp.js'); - // or Ask the user to enter a URL to run in the Settings - //Pebble.SmartPackage.initWithSettings(); + // or Ask the user to enter a URL to run in the Settings + //Pebble.SmartPackage.initWithSettings(); - // or Load a list of app and let the user choose in the Settings which one to run. - //Pebble.SmartPackage.loadBundle('http://www.sarfata.org/myapps.json'); - } - catch (err) { - dumpError(err); - } + // or Load a list of app and let the user choose in the Settings which one to run. + //Pebble.SmartPackage.loadBundle('http://www.sarfata.org/myapps.json'); }); -function dumpError(err) { - if (typeof err === 'object') { - if (err.message) { - console.log('\nMessage: ' + err.message); - } - if (err.stack) { - console.log('\nStacktrace:'); - console.log('===================='); - console.log(err.stack); - } - } else { - console.log('dumpError :: argument is not an object'); - } -} diff --git a/src/js/ui/tests.js b/src/js/ui/tests.js new file mode 100644 index 00000000..233e9783 --- /dev/null +++ b/src/js/ui/tests.js @@ -0,0 +1,28 @@ + +var tests = {}; + +tests.setTimeoutErrors = function () { + var i = 0; + setInterval(function() { + wind.titlex("i = " + i++); + }, 1000); +}; + +tests.ajaxErrors = function() { + var ajax = require('lib/ajax'); + ajaxCallback = function(reqStatus, reqBody, request) { + console.logx('broken call'); + }; + ajax({url: 'http://www.google.fr/' }, ajaxCallback, ajaxCallback); +}; + +tests.geolocationErrors = function () { + navigator.geolocation.getCurrentPosition(function(coords) { + console.logx("Got coords: " + coords); + }); +}; + +for (var test in tests) { + console.log('Running test: ' + test); + tests[test](); +} From a23a319dc7cf5d33f3222200b6e4f94606f930bf Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 27 May 2014 13:04:18 -0400 Subject: [PATCH 185/791] Change simply stage update to check for the layer --- src/simply/simply_stage.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index a235901b..e253d22c 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -295,7 +295,9 @@ void simply_stage_show(SimplyStage *self) { } void simply_stage_update(SimplyStage *self) { - layer_mark_dirty(self->stage_layer.layer); + if (self->stage_layer.layer) { + layer_mark_dirty(self->stage_layer.layer); + } } void handle_tick(struct tm *tick_time, TimeUnits units_changed) { From bc13d361cfcd57e126597210b1b3c20878fca27f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 02:10:11 -0400 Subject: [PATCH 186/791] Change menu to have one section by default --- src/js/app.js | 2 +- src/js/ui/menu.js | 6 ++++++ src/js/ui/simply-pebble.js | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/js/app.js b/src/js/app.js index 6b7f535b..41d92f1a 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -9,7 +9,7 @@ wind.on('singleClick', function(e) { console.log("Button pressed: " + JSON.stringify(e)); if (e.button == 'up') { var menu = new Pebble.UI.Menu(); - menu.section(0, [ { title: 'Hello World!', subtitle: 'text' }, { title: 'item2' } ]); + menu.items(0, [ { title: 'Hello World!', subtitle: 'text' }, { title: 'item2' } ]); menu.show(); } else if (e.button === 'select') { diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 49bead01..e698ce40 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -72,6 +72,9 @@ var getSections = function(target) { return (this.state.sections = sections); } } + if (!sections) { + return (this.state.sections = []); + } }; var getSection = function(e) { @@ -92,6 +95,9 @@ var getSection = function(e) { return (sections[e.section] = section); } } + if (sections && !sections[e.section]) { + return (sections[e.section] = {}); + } }; var getItems = function(e, target) { diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 505ecd07..ea9f2374 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -586,6 +586,9 @@ SimplyPebble.menu = function(menuDef) { if (packetDef.sections instanceof Array) { packetDef.sections = packetDef.sections.length; } + if (!packetDef.sections) { + packetDef.sections = 1; + } var packet = makePacket(command, packetDef); SimplyPebble.sendPacket(packet); }; From c10d39fde3feb4cff0592c48eee6aa4a2f14b7f7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 02:11:09 -0400 Subject: [PATCH 187/791] Fix WindowStack to correctly handle removing windows of the same type For now, the window stack in the js layer is virtual and not represented on the backend. There is only a single window on the backend to represent each type, so a window with a window of the same type beneath being popped in js should trigger a content change rather than a pop in the backend. --- src/js/ui/simply-pebble.js | 2 +- src/js/ui/windowstack.js | 20 +++++++++++++++++--- src/simply/simply_window.c | 3 +-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index ea9f2374..e6cc5017 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -698,7 +698,7 @@ SimplyPebble.onAppMessage = function(e) { switch (command.name) { case 'windowHide': - WindowStack.remove(payload[1], false); + WindowStack.emitHide(payload[1]); break; case 'singleClick': case 'longClick': diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index 44838afa..42d6fe08 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -1,6 +1,7 @@ var util2 = require('lib/util2'); var myutil = require('lib/myutil'); var Emitter = require('lib/emitter'); +var simply = require('ui/simply'); var WindowStack = function() { this.init(); @@ -50,6 +51,14 @@ WindowStack.prototype._hide = function(item, broadcast) { item._hide(broadcast); }; +WindowStack.prototype.at = function(index) { + return this._items[index]; +}; + +WindowStack.prototype.index = function(item) { + return this._items.indexOf(item); +}; + WindowStack.prototype.push = function(item) { if (item === this.top()) { return; } this.remove(item); @@ -68,13 +77,14 @@ WindowStack.prototype.remove = function(item, broadcast) { item = this.get(item); } if (!item) { return; } - var index = this._items.indexOf(item); + var index = this.index(item); if (index === -1) { return item; } var wasTop = (item === this.top()); this._items.splice(index, 1); if (wasTop) { - this._show(this.top()); - this._hide(item, broadcast); + var top = this.top(); + this._show(top); + this._hide(item, top && top.constructor === item.constructor ? false : broadcast); } return item; }; @@ -89,4 +99,8 @@ WindowStack.prototype.get = function(windowId) { } }; +WindowStack.prototype.emitHide = function(windowId) { + this.remove(this.get(windowId)); +}; + module.exports = new WindowStack(); diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index da71147a..63baf93e 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -99,8 +99,7 @@ static void single_click_handler(ClickRecognizerRef recognizer, void *context) { ButtonId button = click_recognizer_get_button_id(recognizer); bool is_enabled = (self->button_mask & (1 << button)); if (button == BUTTON_ID_BACK && !is_enabled) { - bool animated = true; - window_stack_pop(animated); + simply_msg_window_hide(self->id); } if (is_enabled) { simply_msg_single_click(button); From 64451fccb08c7182daa22a4595603d56f847b3fd Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 02:31:36 -0400 Subject: [PATCH 188/791] Change Window show implementation to also clear the window --- src/js/ui/simply-pebble.js | 3 +++ src/js/ui/window.js | 2 +- src/simply/simply_window.c | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index e6cc5017..2e612eaa 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -500,6 +500,9 @@ SimplyPebble.buttonConfig = function(buttonConf) { }; var toClearFlags = function(clear) { + if (clear === true) { + clear = 'all'; + } if (clear === 'all') { clear = ~0; } else if (typeof clear === 'string') { diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 6410d28f..a0d6c217 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -79,7 +79,7 @@ Window.prototype.hide = function() { }; Window.prototype._show = function() { - this._prop(this.state); + this._prop(this.state, true); }; Window.prototype.show = function() { diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 63baf93e..5de5d94b 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -66,7 +66,7 @@ void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { action_bar_layer_add_to_window(self->action_bar_layer, self->window); action_bar_layer_set_click_config_provider(self->action_bar_layer, click_config_provider); } else { - window_set_click_config_provider(self->window, click_config_provider); + scroll_layer_set_click_config_onto_window(self->scroll_layer, self->window); } } From d0621c0df5c634771173e3c40fa653c80257cec4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 27 May 2014 16:52:02 -0400 Subject: [PATCH 189/791] Use the fallback font if no font is specified --- src/simply/simply_stage.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index e253d22c..b9a8a555 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -95,9 +95,9 @@ static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementTex if (element->is_time) { text = format_time(text); } + GFont font = element->font ? element->font : fonts_get_system_font(FONT_KEY_FONT_FALLBACK); graphics_context_set_text_color(ctx, element->text_color); - graphics_draw_text(ctx, text, element->font, element->frame, - element->overflow_mode, element->alignment, NULL); + graphics_draw_text(ctx, text, font, element->frame, element->overflow_mode, element->alignment, NULL); } } From 4680ee1fd66522fbcd111ab8153139cf66aaecbc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 04:39:37 -0400 Subject: [PATCH 190/791] Run the setIntervalErrors test once --- src/js/ui/tests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/ui/tests.js b/src/js/ui/tests.js index 233e9783..dd527337 100644 --- a/src/js/ui/tests.js +++ b/src/js/ui/tests.js @@ -3,7 +3,8 @@ var tests = {}; tests.setTimeoutErrors = function () { var i = 0; - setInterval(function() { + var interval = setInterval(function() { + clearInterval(interval); wind.titlex("i = " + i++); }, 1000); }; From a2f78ea5fdc8bdc384e7b61b15aeb0b6975ceb22 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 06:19:55 -0400 Subject: [PATCH 191/791] Update loader.js and wscript to define linenos --- src/js/loader.js | 31 ++++++++++++++++++++++++++++--- wscript | 24 +++++++++++++++++++----- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/js/loader.js b/src/js/loader.js index b5484785..c522628f 100644 --- a/src/js/loader.js +++ b/src/js/loader.js @@ -3,29 +3,54 @@ var __loader = (function() { var __loader = {}; __loader.packages = {}; -__loader.loaders = {}; +__loader.packagesLinenoOrder = []; + __loader.require = function(path) { if (!path.match(/\.js$/)) { path += '.js'; } + var module = __loader.packages[path]; if (!module) { throw new Error("Cannot find module'" + path + "'"); } + if (module.exports) { return module.exports; } + module.exports = {}; module.loader(module, __loader.require); module.loaded = true; + return module.exports; }; -__loader.define = function(path, loader) { - __loader.packages[path] = { +__loader.define = function(path, lineno, loader) { + var module = { filename: path, + lineno: lineno, loader: loader, }; + + __loader.packages[path] = module; + __loader.packagesLinenoOrder.push(module); + __loader.packagesLinenoOrder.sort(function(a, b) { + return a.lineno - b.lineno; + }); +}; + +__loader.getPackageByLineno = function(lineno) { + var packages = __loader.packagesLinenoOrder; + var module = packages[0]; + for (var i = 1, ii = packages.length; i < ii; ++i) { + var next = packages[i]; + if (next.lineno > lineno) { + break; + } + module = next; + } + return module; }; return __loader; diff --git a/wscript b/wscript index 5972df10..70d91df9 100644 --- a/wscript +++ b/wscript @@ -35,23 +35,37 @@ def concat_javascript(self, *k, **kw): return [] def concat_javascript_task(task): - LOADER_TEMPLATE = "__loader.define({relpath}, function(module, require) {{\n{body}\n}});" + LOADER_TEMPLATE = "__loader.define({relpath}, {lineno}, function(module, require) {{\n{body}\n}});" + + def loader_translate(source, lineno): + return LOADER_TEMPLATE.format( + relpath=json.dumps(source['relpath']), + lineno=lineno, + body=source['body']) sources = [] for node in task.inputs: relpath = os.path.relpath(node.abspath(), js_path) with open(node.abspath(), 'r') as f: + body = f.read() if relpath == 'loader.js': - sources.insert(0, f.read()) + sources.insert(0, body) elif relpath.startswith('vendor/'): - sources.append(f.read()) + sources.append(body) else: - sources.append(LOADER_TEMPLATE.format(relpath=json.dumps(relpath), body=f.read())) + sources.append({ 'relpath': relpath, 'body': body }) sources.append('__loader.require("main");') with open(task.outputs[0].abspath(), 'w') as f: - f.write('\n'.join(sources)) + lineno = 1 + for source in sources: + if type(source) is dict: + out = loader_translate(source, lineno) + else: + out = source + f.write(out + '\n') + lineno += out.count('\n') + 1 js_target = self.path.make_node('build/src/js/pebble-js-app.js') From e329d010c48da321c3a95983753c5021f9d05d2f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 06:21:07 -0400 Subject: [PATCH 192/791] Add safe.js filename stracetrace line translation using loader.js --- src/js/lib/safe.js | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index e6402e7e..0bbc0cb7 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -7,6 +7,40 @@ var ajax = require('lib/ajax'); +/* The name of the concatenated file to translate */ +var translateName = 'pebble-js-app.js'; + +/* Translates a line of the stack trace to the originating file */ +function translateLine(line, scope, name, lineno, colno) { + if (name === translateName) { + var pkg = __loader.getPackageByLineno(lineno); + if (pkg) { + name = pkg.filename; + lineno -= pkg.lineno; + } + } + return (scope || '') + name + ':' + lineno + ':' + colno; +} + +/* Matches ( '@' )? ':' ':' */ +var stackLineRegExp = /([^\s@]+@)?([^\s@:]+):(\d+):(\d+)/; + +/* Translates a stack trace to the originating files */ +function translateStack(stack) { + var lines = stack.split('\n'); + for (var i = lines.length - 1; i >= 0; --i) { + var line = lines[i]; + var m = line.match(stackLineRegExp); + if (m) { + line = lines[i] = translateLine.apply(this, m); + } + if (line.match(module.filename)) { + lines.splice(--i, 2); + } + } + return lines.join('\n'); +} + /* We use this function to dump error messages to the console. */ function dumpError(err) { if (typeof err === 'object') { @@ -15,7 +49,7 @@ function dumpError(err) { } if (err.stack) { console.log('Stacktrace:'); - console.log(err.stack); + console.log(translateStack(err.stack)); } } else { console.log('dumpError :: argument is not an object'); From e79f9318c93ac848bba2cd5786d66dd8b87e5d40 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 06:38:36 -0400 Subject: [PATCH 193/791] Add loader.js pseudopackage to package order for translating loader.js lines --- src/js/loader.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/js/loader.js b/src/js/loader.js index c522628f..bcf4cd3e 100644 --- a/src/js/loader.js +++ b/src/js/loader.js @@ -3,7 +3,8 @@ var __loader = (function() { var __loader = {}; __loader.packages = {}; -__loader.packagesLinenoOrder = []; + +__loader.packagesLinenoOrder = [{ filename: 'loader.js', lineno: 0 }]; __loader.require = function(path) { if (!path.match(/\.js$/)) { @@ -42,8 +43,8 @@ __loader.define = function(path, lineno, loader) { __loader.getPackageByLineno = function(lineno) { var packages = __loader.packagesLinenoOrder; - var module = packages[0]; - for (var i = 1, ii = packages.length; i < ii; ++i) { + var module; + for (var i = 0, ii = packages.length; i < ii; ++i) { var next = packages[i]; if (next.lineno > lineno) { break; From 14243a38cdd2e3174c771e429b94bb9a6a986333 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 07:29:11 -0400 Subject: [PATCH 194/791] Add simply msg window hide reliability timer --- src/js/ui/windowstack.js | 4 +++- src/simply/simply_msg.c | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index 42d6fe08..355b1f4a 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -100,7 +100,9 @@ WindowStack.prototype.get = function(windowId) { }; WindowStack.prototype.emitHide = function(windowId) { - this.remove(this.get(windowId)); + var wind = this.get(windowId); + if (wind !== this.top()) { return; } + this.remove(wind); }; module.exports = new WindowStack(); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index e9cedc66..62dce3a9 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -149,9 +149,6 @@ static void handle_set_window(DictionaryIterator *iter, Simply *simply) { } for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { - case SetWindow_id: - window->id = tuple->value->uint32; - break; case SetWindow_action: simply_window_set_action_bar(window, tuple->value->int32); break; @@ -626,7 +623,7 @@ bool simply_msg_window_show(uint32_t id) { return (app_message_outbox_send() == APP_MSG_OK); } -bool simply_msg_window_hide(uint32_t id) { +static bool msg_window_hide(uint32_t id) { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { return false; @@ -636,6 +633,18 @@ bool simply_msg_window_hide(uint32_t id) { return (app_message_outbox_send() == APP_MSG_OK); } +static void window_hide_timer_callback(void *data) { + uint32_t id = (uintptr_t)(void*) data; + if (!msg_window_hide(id)) { + app_timer_register(10, window_hide_timer_callback, data); + } +} + +bool simply_msg_window_hide(uint32_t id) { + window_hide_timer_callback((void*)(uintptr_t) id); + return true; +} + bool simply_msg_accel_tap(AccelAxisType axis, int32_t direction) { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { From fea7f12517f30f7cc8eeb7f9d949fb1694a2fa73 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Wed, 28 May 2014 10:01:26 -0400 Subject: [PATCH 195/791] Run app.js (and not the tests) by default. --- src/js/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index e6160558..373626f0 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -32,8 +32,8 @@ Pebble.addEventListener('ready', function(e) { console.log("Done loading PebbleJS - Starting app."); // Load local file - //require('app.js'); - require('ui/tests'); + require('app.js'); + //require('ui/tests'); // Or use Smart Package to load a remote JS file //Pebble.SmartPackage.init('http://www.sarfata.org/myapp.js'); From 69bc9a18ec92d62c7ae882cb14cd6972fa829a0c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 11:04:40 -0400 Subject: [PATCH 196/791] Add loader.js support for implicitly requiring .json and index.js files --- src/js/loader.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/js/loader.js b/src/js/loader.js index bcf4cd3e..4c8462b2 100644 --- a/src/js/loader.js +++ b/src/js/loader.js @@ -6,12 +6,16 @@ __loader.packages = {}; __loader.packagesLinenoOrder = [{ filename: 'loader.js', lineno: 0 }]; +__loader.extpaths = ['?', '?.js', '?.json', '?/index.js']; + __loader.require = function(path) { - if (!path.match(/\.js$/)) { - path += '.js'; + var module; + var extpaths = __loader.extpaths; + for (var i = 0, ii = extpaths.length; !module && i < ii; ++i) { + var filepath = extpaths[i].replace('?', path); + module = __loader.packages[filepath]; } - var module = __loader.packages[path]; if (!module) { throw new Error("Cannot find module'" + path + "'"); } From e56bc7544ca0fad8b1bf42908133c79e236642c6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 11:06:02 -0400 Subject: [PATCH 197/791] Add appinfo.json as a loadable package --- wscript | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/wscript b/wscript index 70d91df9..29558d7a 100644 --- a/wscript +++ b/wscript @@ -35,7 +35,10 @@ def concat_javascript(self, *k, **kw): return [] def concat_javascript_task(task): + LOADER_PATH = "loader.js" LOADER_TEMPLATE = "__loader.define({relpath}, {lineno}, function(module, require) {{\n{body}\n}});" + JSON_TEMPLATE = "module.exports = {body};" + APPINFO_PATH = "appinfo.json" def loader_translate(source, lineno): return LOADER_TEMPLATE.format( @@ -48,13 +51,17 @@ def concat_javascript(self, *k, **kw): relpath = os.path.relpath(node.abspath(), js_path) with open(node.abspath(), 'r') as f: body = f.read() - if relpath == 'loader.js': + if relpath == LOADER_PATH: sources.insert(0, body) elif relpath.startswith('vendor/'): sources.append(body) else: sources.append({ 'relpath': relpath, 'body': body }) + with open(APPINFO_PATH, 'r') as f: + body = JSON_TEMPLATE.format(body=f.read()) + sources.append({ 'relpath': APPINFO_PATH, 'body': body }) + sources.append('__loader.require("main");') with open(task.outputs[0].abspath(), 'w') as f: From 1ba2c1a161e6d7987ffb26e2b7f8eace7072fddb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 11:30:22 -0400 Subject: [PATCH 198/791] Resolve builtin image names and paths to their resource id --- src/js/lib/myutil.js | 9 +++++++++ src/js/ui/imageservice.js | 27 ++++++++++++++++++++++++++- src/js/ui/simply-pebble.js | 5 ++--- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/js/lib/myutil.js b/src/js/lib/myutil.js index 8bfcb368..863b8b90 100644 --- a/src/js/lib/myutil.js +++ b/src/js/lib/myutil.js @@ -74,4 +74,13 @@ myutil.abspath = function(root, path) { return path; }; +/** + * Converts a name to a C constant name format of UPPER_CASE_UNDERSCORE. + */ +myutil.toCConstantName = function(x) { + x = x.toUpperCase(); + x = x.replace(/[- ]/g, '_'); + return x; +}; + module.exports = myutil; diff --git a/src/js/ui/imageservice.js b/src/js/ui/imageservice.js index 4c2c81d9..f79a30fb 100644 --- a/src/js/ui/imageservice.js +++ b/src/js/ui/imageservice.js @@ -1,15 +1,21 @@ var imagelib = require('lib/image'); var myutil = require('lib/myutil'); var simply = require('ui/simply'); +var appinfo = require('appinfo'); var ImageService = module.exports; +var resources = (function() { + var resources = appinfo.resources; + return resources && resources.media || []; +})(); + var state; ImageService.init = function() { state = Image.state = { cache: {}, - nextId: 1, + nextId: resources.length, rootURL: null }; }; @@ -99,3 +105,22 @@ ImageService.setRootURL = function(url) { state.rootURL = url; }; +/** + * Resolve an image path to an id. If the image is defined in appinfo, the index of the resource is used, + * otherwise a new id is generated for dynamic loading. + */ +ImageService.resolve = function(opt) { + var path = opt; + if (typeof opt === 'object') { + path = opt.url; + } + path = path.replace(/#.*/, ''); + var cname = myutil.toCConstantName(path); + for (var i = 0, ii = resources.length; i < ii; ++i) { + var res = resources[i]; + if (res.name === cname || res.file === path) { + return i + 1; + } + } + return ImageService.load(opt); +}; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 2e612eaa..6cae9c41 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -33,8 +33,7 @@ var Color = function(x) { }; var Font = function(x) { - x = x.toUpperCase(); - x = x.replace(/[- ]/g, '_'); + x = myutil.toCConstantName(x); if (!x.match(/^RESOURCE_ID/)) { x = 'RESOURCE_ID_' + x; } @@ -456,7 +455,7 @@ var toParam = function(param, v) { } else if (param.type === Boolean) { v = v ? 1 : 0; } else if (param.type === Image && typeof v !== 'number') { - v = ImageService.load(v); + v = ImageService.resolve(v); } else if (typeof param.type === 'function') { v = param.type(v); } From 09a88d591acde81a879d8aae1018414c1c008a56 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 11:51:18 -0400 Subject: [PATCH 199/791] Resolve jshint warnings in tests.js --- src/js/ui/tests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/ui/tests.js b/src/js/ui/tests.js index dd527337..7ececa86 100644 --- a/src/js/ui/tests.js +++ b/src/js/ui/tests.js @@ -2,6 +2,7 @@ var tests = {}; tests.setTimeoutErrors = function () { + /* global wind */ var i = 0; var interval = setInterval(function() { clearInterval(interval); @@ -11,7 +12,7 @@ tests.setTimeoutErrors = function () { tests.ajaxErrors = function() { var ajax = require('lib/ajax'); - ajaxCallback = function(reqStatus, reqBody, request) { + var ajaxCallback = function(reqStatus, reqBody, request) { console.logx('broken call'); }; ajax({url: 'http://www.google.fr/' }, ajaxCallback, ajaxCallback); From e33f4a0dc7e8dfc68faf8707bb6b167d9e459235 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 11:51:39 -0400 Subject: [PATCH 200/791] Add appinfo and image service resolve tests --- src/js/ui/tests.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/js/ui/tests.js b/src/js/ui/tests.js index 7ececa86..7060f8cf 100644 --- a/src/js/ui/tests.js +++ b/src/js/ui/tests.js @@ -24,6 +24,15 @@ tests.geolocationErrors = function () { }); }; +tests.loadAppinfo = function() { + console.log('longName: ' + require('appinfo').longName); +}; + +tests.resolveBultinImagePath = function() { + var ImageService = require('ui/imageservice'); + console.log('image-logo-splash = resource #' + ImageService.resolve('images/logo_splash.png')); +}; + for (var test in tests) { console.log('Running test: ' + test); tests[test](); From e24aeb17fbc6d9df4c5f1c9473a84a8c91365a17 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 12:12:09 -0400 Subject: [PATCH 201/791] Add util window with window stack schedule top window render --- src/simply/simply_res.c | 3 +-- src/simply/simply_stage.c | 3 ++- src/util/window.h | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 src/util/window.h diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index ae8a22f4..5528a6a4 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -33,8 +33,7 @@ GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16 image->bitmap.addr = malloc(pixels_size); memcpy(image->bitmap.addr, pixels, pixels_size); - Window *window = window_stack_get_top_window(); - layer_mark_dirty(window_get_root_layer(window)); + window_stack_schedule_top_window_render(); return &image->bitmap; } diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index b9a8a555..63d2ed79 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -8,6 +8,7 @@ #include "util/graphics.h" #include "util/string.h" +#include "util/window.h" #include @@ -301,7 +302,7 @@ void simply_stage_update(SimplyStage *self) { } void handle_tick(struct tm *tick_time, TimeUnits units_changed) { - layer_mark_dirty(window_get_root_layer(window_stack_get_top_window())); + window_stack_schedule_top_window_render(); } void simply_stage_update_ticker(SimplyStage *self) { diff --git a/src/util/window.h b/src/util/window.h new file mode 100644 index 00000000..62e8e604 --- /dev/null +++ b/src/util/window.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +static inline void window_stack_schedule_top_window_render() { + layer_mark_dirty(window_get_root_layer(window_stack_get_top_window())); +} From c46ff1272b2081c1139d17771e461214dc88191c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 12:14:45 -0400 Subject: [PATCH 202/791] Add simply res add bundled image --- src/js/app.js | 6 +++++- src/simply/simply_res.c | 28 ++++++++++++++++++++++++++++ src/simply/simply_res.h | 7 ++----- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 41d92f1a..5aee24dc 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -2,7 +2,11 @@ // // This is where you write your app. -var wind = new Pebble.UI.Card({ title: "PebbleJS", body: "Saying Hello World" }); +var wind = new Pebble.UI.Card({ + title: "PebbleJS", + icon: "images/menu_icon.png", + body: "Saying Hello World" +}); wind.show(); wind.on('singleClick', function(e) { diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 5528a6a4..2dd12133 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -1,5 +1,7 @@ #include "simply_res.h" +#include "util/window.h" + #include static bool id_filter(List1Node *node, void *data) { @@ -12,6 +14,26 @@ static void destroy_image(SimplyRes *self, SimplyImage *image) { free(image); } +GBitmap *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { + SimplyImage *image = malloc(sizeof(*image)); + if (!image) { + return NULL; + } + + GBitmap *bitmap = gbitmap_create_with_resource(id); + if (!bitmap) { + free(image); + return NULL; + } + + image->bitmap = *bitmap; + free(bitmap); + + window_stack_schedule_top_window_render(); + + return &image->bitmap; +} + GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint32_t *pixels) { SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); if (image) { @@ -19,6 +41,9 @@ GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16 image->bitmap.addr = NULL; } else { image = malloc(sizeof(*image)); + if (!image) { + return NULL; + } *image = (SimplyImage) { .id = id }; list1_prepend(&self->images, &image->node); } @@ -53,6 +78,9 @@ GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder if (image) { return &image->bitmap; } + if (id <= ARRAY_LENGTH(resource_crc_table)) { + return simply_res_add_bundled_image(self, id); + } if (!image && is_placeholder) { return simply_res_add_image(self, id, 0, 0, NULL); } diff --git a/src/simply/simply_res.h b/src/simply/simply_res.h index c602e6e9..218878bf 100644 --- a/src/simply/simply_res.h +++ b/src/simply/simply_res.h @@ -21,14 +21,11 @@ struct SimplyImage { }; SimplyRes *simply_res_create(); - void simply_res_destroy(SimplyRes *self); - void simply_res_clear(SimplyRes *self); +GBitmap *simply_res_add_bundled_image(SimplyRes *self, uint32_t id); GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint32_t *pixels); - -void simply_res_remove_image(SimplyRes *self, uint32_t id); - GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder); +void simply_res_remove_image(SimplyRes *self, uint32_t id); From 1586a067b1c1bad68efc9df7c1df19b21ef2a59d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 12:34:00 -0400 Subject: [PATCH 203/791] Move UI namespace export to ui/index.js --- src/js/main.js | 12 +----------- src/js/ui/index.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 src/js/ui/index.js diff --git a/src/js/main.js b/src/js/main.js index 373626f0..163278ab 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -10,17 +10,7 @@ require('lib/safe'); Pebble.Settings = require('settings/settings'); Pebble.Accel = require('ui/accel'); - -var UI = {}; -UI.Vector2 = require('lib/vector2'); -UI.Card = require('ui/card'); -UI.Menu = require('ui/menu'); -UI.Stage = require('ui/stage'); -UI.Rect = require('ui/rect'); -UI.Circle = require('ui/circle'); -UI.Text = require('ui/text'); -UI.Image = require('ui/image'); -Pebble.UI = UI; +Pebble.UI = require('ui'); //Pebble.SmartPackage = require('pebble/smartpackage'); diff --git a/src/js/ui/index.js b/src/js/ui/index.js new file mode 100644 index 00000000..908623a2 --- /dev/null +++ b/src/js/ui/index.js @@ -0,0 +1,12 @@ +var UI = {}; + +UI.Vector2 = require('lib/vector2'); +UI.Card = require('ui/card'); +UI.Menu = require('ui/menu'); +UI.Stage = require('ui/stage'); +UI.Rect = require('ui/rect'); +UI.Circle = require('ui/circle'); +UI.Text = require('ui/text'); +UI.Image = require('ui/image'); + +module.exports= UI; From 2136c682258761e9cbd96e7a7e7e48c7a8f5b418 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 12:37:53 -0400 Subject: [PATCH 204/791] Fix simply window to pop on back if no communication occurred --- src/simply/simply_window.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 5de5d94b..e0dba213 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -99,7 +99,12 @@ static void single_click_handler(ClickRecognizerRef recognizer, void *context) { ButtonId button = click_recognizer_get_button_id(recognizer); bool is_enabled = (self->button_mask & (1 << button)); if (button == BUTTON_ID_BACK && !is_enabled) { - simply_msg_window_hide(self->id); + if (simply_msg_has_communicated()) { + simply_msg_window_hide(self->id); + } else { + bool animated = true; + window_stack_pop(animated); + } } if (is_enabled) { simply_msg_single_click(button); From 6403fb0d4f48602341dfcd9622073ffb0c3ba1ce Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 13:59:13 -0400 Subject: [PATCH 205/791] Fix menu to request items visually from top to bottom --- src/simply/simply_menu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 716bb824..97f5ee27 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -109,7 +109,7 @@ static void add_section(SimplyMenu *self, SimplyMenuSection *section) { destroy_section(self, (SimplyMenuSection*) list1_last(self->menu_layer.sections)); } destroy_section_by_index(self, section->index); - list1_prepend(&self->menu_layer.sections, §ion->node); + list1_append(&self->menu_layer.sections, §ion->node); } static void add_item(SimplyMenu *self, SimplyMenuItem *item) { @@ -117,7 +117,7 @@ static void add_item(SimplyMenu *self, SimplyMenuItem *item) { destroy_item(self, (SimplyMenuItem*) list1_last(self->menu_layer.items)); } destroy_item_by_index(self, item->section, item->index); - list1_prepend(&self->menu_layer.items, &item->node); + list1_append(&self->menu_layer.items, &item->node); } static void request_menu_section(SimplyMenu *self, uint16_t section_index) { From 0cd30f993d91992edc438b993f5354f20f5a7810 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 14:58:04 -0400 Subject: [PATCH 206/791] Add safe.js namespace for exporting --- src/js/lib/safe.js | 50 ++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 0bbc0cb7..800f1d14 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -5,14 +5,18 @@ * console. */ +/* global __loader */ + var ajax = require('lib/ajax'); +var safe = {}; + /* The name of the concatenated file to translate */ -var translateName = 'pebble-js-app.js'; +safe.translateName = 'pebble-js-app.js'; /* Translates a line of the stack trace to the originating file */ -function translateLine(line, scope, name, lineno, colno) { - if (name === translateName) { +safe.translateLine = function(line, scope, name, lineno, colno) { + if (name === safe.translateName) { var pkg = __loader.getPackageByLineno(lineno); if (pkg) { name = pkg.filename; @@ -20,86 +24,88 @@ function translateLine(line, scope, name, lineno, colno) { } } return (scope || '') + name + ':' + lineno + ':' + colno; -} +}; /* Matches ( '@' )? ':' ':' */ var stackLineRegExp = /([^\s@]+@)?([^\s@:]+):(\d+):(\d+)/; /* Translates a stack trace to the originating files */ -function translateStack(stack) { +safe.translateStack = function(stack) { var lines = stack.split('\n'); for (var i = lines.length - 1; i >= 0; --i) { var line = lines[i]; var m = line.match(stackLineRegExp); if (m) { - line = lines[i] = translateLine.apply(this, m); + line = lines[i] = safe.translateLine.apply(this, m); } if (line.match(module.filename)) { lines.splice(--i, 2); } } return lines.join('\n'); -} +}; /* We use this function to dump error messages to the console. */ -function dumpError(err) { +safe.dumpError = function(err) { if (typeof err === 'object') { if (err.message) { console.log('Message: ' + err.message); } if (err.stack) { console.log('Stacktrace:'); - console.log(translateStack(err.stack)); + console.log(safe.translateStack(err.stack)); } } else { console.log('dumpError :: argument is not an object'); } -} +}; /* Takes a function and return a new function with a call to it wrapped in a try/catch statement */ -function protect(fn) { +safe.protect = function(fn) { return function() { try { return fn.apply(this, arguments); } catch (err) { - dumpError(err); + safe.dumpError(err); } }; -} +}; /* Wrap event handlers added by Pebble.addEventListener */ var pblAddEventListener = Pebble.addEventListener; Pebble.addEventListener = function(eventName, eventCallback) { - pblAddEventListener.call(this, eventName, protect(eventCallback)); + pblAddEventListener.call(this, eventName, safe.protect(eventCallback)); }; var pblSendMessage = Pebble.sendAppMessage; Pebble.sendAppMessage = function(message, success, failure) { - return pblSendMessage.call(this, message, protect(success), protect(failure)); + return pblSendMessage.call(this, message, safe.protect(success), safe.protect(failure)); }; /* Wrap setTimeout and setInterval */ var originalSetTimeout = setTimeout; -setTimeout = function(callback, delay) { - return originalSetTimeout(protect(callback), delay); +window.setTimeout = function(callback, delay) { + return originalSetTimeout(safe.protect(callback), delay); }; var originalSetInterval = setInterval; -setInterval = function(callback, delay) { - return originalSetInterval(protect(callback), delay); +window.setInterval = function(callback, delay) { + return originalSetInterval(safe.protect(callback), delay); }; /* Wrap the success and failure callback of the ajax library */ ajax.onHandler = function(eventName, callback) { - return protect(callback); + return safe.protect(callback); }; /* Wrap the geolocation API Callbacks */ var watchPosition = navigator.geolocation.watchPosition; navigator.geolocation.watchPosition = function(success, error, options) { - return watchPosition.call(this, protect(success), protect(error), options); + return watchPosition.call(this, safe.protect(success), safe.protect(error), options); }; var getCurrentPosition = navigator.geolocation.getCurrentPosition; navigator.geolocation.getCurrentPosition = function(success, error, options) { - return getCurrentPosition.call(this, protect(success), protect(error), options); + return getCurrentPosition.call(this, safe.protect(success), safe.protect(error), options); }; + +module.exports = safe; From 85cc6048a3bc9b085243c8622af9ba31962bdc17 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 14:59:10 -0400 Subject: [PATCH 207/791] Change simply menu to support empty section headers --- src/simply/simply_menu.c | 25 ++++++++++++++++++++----- src/simply/simply_msg.c | 2 +- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 97f5ee27..39efd6ec 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -14,6 +14,8 @@ #define REQUEST_DELAY_MS 10 +static char EMPTY_TITLE[] = ""; + static bool section_filter(List1Node *node, void *data) { SimplyMenuSection *section = (SimplyMenuSection*) node; uint16_t section_index = (uint16_t)(uintptr_t) data; @@ -28,7 +30,7 @@ static bool item_filter(List1Node *node, void *data) { return (item->section == section_index && item->index == row); } -static bool empty_filter(List1Node *node, void *data) { +static bool request_filter(List1Node *node, void *data) { SimplyMenuCommon *item = (SimplyMenuCommon*) node; return (item->title == NULL); } @@ -40,7 +42,7 @@ static SimplyMenuSection *get_menu_section(SimplyMenu *self, int index) { static void destroy_section(SimplyMenu *self, SimplyMenuSection *section) { if (!section) { return; } list1_remove(&self->menu_layer.sections, §ion->node); - if (section->title) { + if (section->title && section->title != EMPTY_TITLE) { free(section->title); section->title = NULL; } @@ -82,13 +84,13 @@ static void schedule_get_timer(SimplyMenu *self); static void request_menu_node(void *data) { SimplyMenu *self = data; self->menu_layer.get_timer = NULL; - SimplyMenuSection *section = (SimplyMenuSection*) list1_find(self->menu_layer.sections, empty_filter, NULL); + SimplyMenuSection *section = (SimplyMenuSection*) list1_find(self->menu_layer.sections, request_filter, NULL); bool found = false; if (section) { simply_msg_menu_get_section(section->index); found = true; } - SimplyMenuItem *item = (SimplyMenuItem*) list1_find(self->menu_layer.items, empty_filter, NULL); + SimplyMenuItem *item = (SimplyMenuItem*) list1_find(self->menu_layer.items, request_filter, NULL); if (item) { simply_msg_menu_get_item(item->section, item->index); found = true; @@ -154,11 +156,17 @@ void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections) { } void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section) { + if (section->title == NULL) { + section->title = EMPTY_TITLE; + } add_section(self, section); mark_dirty(self); } void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item) { + if (item->title == NULL) { + item->title = EMPTY_TITLE; + } add_item(self, item); mark_dirty(self); } @@ -175,7 +183,9 @@ static uint16_t menu_get_num_rows_callback(MenuLayer *menu_layer, uint16_t secti } static int16_t menu_get_header_height_callback(MenuLayer *menu_layer, uint16_t section_index, void *data) { - return MENU_CELL_BASIC_HEADER_HEIGHT; + SimplyMenu *self = data; + SimplyMenuSection *section = get_menu_section(self, section_index); + return section && section->title && section->title != EMPTY_TITLE ? MENU_CELL_BASIC_HEADER_HEIGHT : 0; } static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer, uint16_t section_index, void *data) { @@ -194,6 +204,11 @@ static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer, ui static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index, void *data) { SimplyMenu *self = data; + SimplyMenuSection *section = get_menu_section(self, cell_index->section); + if (!section) { + request_menu_section(self, cell_index->section); + return; + } SimplyMenuItem *item = get_menu_item(self, cell_index->section, cell_index->row); if (!item) { request_menu_item(self, cell_index->section, cell_index->row); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 62dce3a9..99bc5ad9 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -284,7 +284,7 @@ static void handle_set_menu_section(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; uint16_t section_index = 0; uint16_t num_items = 1; - char *title = "Section"; + char *title = NULL; if ((tuple = dict_find(iter, 1))) { section_index = tuple->value->uint16; } From 04f9c91ab111b7e99a1b305b2c3b270183b92732 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 22:18:03 -0400 Subject: [PATCH 208/791] Add util memory with malloc0 which mallocs and memsets 0 --- src/simply/simply_msg.c | 1 + src/simply/simply_stage.c | 7 +------ src/util/memory.h | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 src/util/memory.h diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 99bc5ad9..822fae01 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -8,6 +8,7 @@ #include "simply.h" +#include "util/memory.h" #include "util/string.h" #include diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 63d2ed79..03372e35 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -7,6 +7,7 @@ #include "simply.h" #include "util/graphics.h" +#include "util/memory.h" #include "util/string.h" #include "util/window.h" @@ -138,12 +139,6 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } } -static void *malloc0(size_t size) { - void *buf = malloc(size); - memset(buf, 0, size); - return buf; -} - static SimplyElementCommon *alloc_element(SimplyElementType type) { switch (type) { case SimplyElementTypeNone: return NULL; diff --git a/src/util/memory.h b/src/util/memory.h new file mode 100644 index 00000000..45bc2cdf --- /dev/null +++ b/src/util/memory.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +static inline void *malloc0(size_t size) { + void *buf = malloc(size); + if (!buf) { + return buf; + } + + memset(buf, 0, size); + return buf; +} + From 244a6530ec80a6098d67e1aff4a54b26b34d37f0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 22:20:52 -0400 Subject: [PATCH 209/791] Fix animation allocation to memset 0 --- src/simply/simply_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 822fae01..070abfc8 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -481,7 +481,7 @@ static void handle_animate_stage_element(DictionaryIterator *iter, Simply *simpl return; } GRect to_frame = element->frame; - SimplyAnimation *animation = malloc(sizeof(*animation)); + SimplyAnimation *animation = malloc0(sizeof(*animation)); if (!animation) { return; } From b7fbaa54fd7520ed7bdd47b06b5356727e38feb2 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Wed, 28 May 2014 22:25:15 -0400 Subject: [PATCH 210/791] Starting some work on doc for pebblejs --- jsdoc.json | 7 +++++-- src/js/lib/vector2.js | 5 +++++ src/js/simply/simply.js | 6 ++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/jsdoc.json b/jsdoc.json index 94bca640..66f40cf2 100644 --- a/jsdoc.json +++ b/jsdoc.json @@ -1,8 +1,11 @@ { "source": { "include": [ - "src/js/ajax.js", - "src/js/simply.js" + "src/js/loader.js", + "src/js/lib/*.js", + "src/js/ui/*.js", + "src/js/settings/*.js", + "src/js/simply/*.js" ] } } diff --git a/src/js/lib/vector2.js b/src/js/lib/vector2.js index d300d613..53fbe78f 100644 --- a/src/js/lib/vector2.js +++ b/src/js/lib/vector2.js @@ -8,6 +8,11 @@ * @author zz85 / http://www.lab4games.net/zz85/blog */ +/** + * Create a new vector with given dimensions. + * @param x + * @param y + */ var Vector2 = function ( x, y ) { this.x = x || 0; diff --git a/src/js/simply/simply.js b/src/js/simply/simply.js index 8cb5c0da..acd1b4c1 100644 --- a/src/js/simply/simply.js +++ b/src/js/simply/simply.js @@ -13,6 +13,12 @@ var WindowStack = require('ui/windowstack'); var Card = require('ui/card'); var Vibe = require('ui/vibe'); +/** + * Sets the information to be displayed on the screen. + * + * @memberOf simply + * @param {object} textDef: A structure defining what to display. See {@link textDef}. + */ simply.text = function(textDef) { var wind = WindowStack.top(); if (!wind || !(wind instanceof Card)) { From 46ac926d2546fa663ae0395422d68206ff0a4f14 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 22:25:26 -0400 Subject: [PATCH 211/791] Add simply stage inverter element backend --- src/simply/simply_msg.c | 16 ++++++++++++---- src/simply/simply_stage.c | 37 ++++++++++++++++++++++++++++++++++++- src/simply/simply_stage.h | 15 ++++++++++----- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 070abfc8..bebfa3fa 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -397,16 +397,20 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { simply_stage_insert_element(stage, tuple->value->uint16, element); break; case ElementX: - element->frame.origin.x = tuple->value->int16; + frame.origin.x = tuple->value->int16; + update_frame = true; break; case ElementY: - element->frame.origin.y = tuple->value->int16; + frame.origin.y = tuple->value->int16; + update_frame = true; break; case ElementWidth: - element->frame.size.w = tuple->value->uint16; + frame.size.w = tuple->value->uint16; + update_frame = true; break; case ElementHeight: - element->frame.size.h = tuple->value->uint16; + frame.size.h = tuple->value->uint16; + update_frame = true; break; case ElementBackgroundColor: element->background_color = tuple->value->uint8; @@ -448,6 +452,9 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { break; } } + if (update_frame) { + simply_stage_set_element_frame(stage, element, frame); + } if (update_ticker) { simply_stage_update_ticker(stage); } @@ -642,6 +649,7 @@ static void window_hide_timer_callback(void *data) { } bool simply_msg_window_hide(uint32_t id) { + if (!id) { return true; } window_hide_timer_callback((void*)(uintptr_t) id); return true; } diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 03372e35..7aacd002 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -29,6 +29,9 @@ static void destroy_element(SimplyStage *self, SimplyElementCommon *element) { case SimplyElementTypeText: free(((SimplyElementText*) element)->text); break; + case SimplyElementTypeInverter: + inverter_layer_destroy(((SimplyElementInverter*) element)->inverter_layer); + break; } free(element); } @@ -134,6 +137,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { case SimplyElementTypeImage: image_element_draw(ctx, self, (SimplyElementImage*) element); break; + case SimplyElementTypeInverter: + break; } element = (SimplyElementCommon*) element->node.next; } @@ -146,6 +151,11 @@ static SimplyElementCommon *alloc_element(SimplyElementType type) { case SimplyElementTypeCircle: return malloc0(sizeof(SimplyElementCircle)); case SimplyElementTypeText: return malloc0(sizeof(SimplyElementText)); case SimplyElementTypeImage: return malloc0(sizeof(SimplyElementImage)); + case SimplyElementTypeInverter: { + SimplyElementInverter *element = malloc0(sizeof(SimplyElementInverter)); + element->inverter_layer = inverter_layer_create(GRect(0, 0, 0, 0)); + return &element->common; + } } return NULL; } @@ -170,16 +180,41 @@ SimplyElementCommon *simply_stage_auto_element(SimplyStage *self, uint32_t id, S SimplyElementCommon *simply_stage_insert_element(SimplyStage *self, int index, SimplyElementCommon *element) { simply_stage_remove_element(self, element); + switch (element->type) { + default: break; + case SimplyElementTypeInverter: + layer_add_child(self->stage_layer.layer, + inverter_layer_get_layer(((SimplyElementInverter*) element)->inverter_layer)); + break; + } return (SimplyElementCommon*) list1_insert(&self->stage_layer.elements, index, &element->node); } SimplyElementCommon *simply_stage_remove_element(SimplyStage *self, SimplyElementCommon *element) { + switch (element->type) { + default: break; + case SimplyElementTypeInverter: + layer_remove_from_parent(inverter_layer_get_layer(((SimplyElementInverter*) element)->inverter_layer)); + break; + } return (SimplyElementCommon*) list1_remove(&self->stage_layer.elements, &element->node); } +void simply_stage_set_element_frame(SimplyStage *self, SimplyElementCommon *element, GRect frame) { + element->frame = frame; + switch (element->type) { + default: break; + case SimplyElementTypeInverter: { + Layer *layer = inverter_layer_get_layer(((SimplyElementInverter*) element)->inverter_layer); + layer_set_frame(layer, element->frame); + break; + } + } +} + static void element_frame_setter(void *subject, GRect frame) { SimplyAnimation *animation = subject; - animation->element->frame = frame; + simply_stage_set_element_frame(animation->stage, animation->element, frame); simply_stage_update(animation->stage); } diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index ce2ef35a..644c77ba 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -24,6 +24,7 @@ enum SimplyElementType { SimplyElementTypeCircle = 2, SimplyElementTypeText = 3, SimplyElementTypeImage = 4, + SimplyElementTypeInverter = 5, }; struct SimplyStageLayer { @@ -92,6 +93,13 @@ struct SimplyElementImage { GCompOp compositing; }; +typedef struct SimplyElementInverter SimplyElementInverter; + +struct SimplyElementInverter { + SimplyElementCommonMember; + InverterLayer *inverter_layer; +}; + typedef struct SimplyAnimation SimplyAnimation; struct SimplyAnimation { @@ -104,9 +112,7 @@ struct SimplyAnimation { }; SimplyStage *simply_stage_create(Simply *simply); - void simply_stage_clear(SimplyStage *self); - void simply_stage_destroy(SimplyStage *self); void simply_stage_show(SimplyStage *self); @@ -114,14 +120,13 @@ void simply_stage_show(SimplyStage *self); void simply_stage_clear(SimplyStage *self); void simply_stage_update(SimplyStage *self); - void simply_stage_update_ticker(SimplyStage *self); SimplyElementCommon* simply_stage_auto_element(SimplyStage *self, uint32_t id, SimplyElementType type); - SimplyElementCommon* simply_stage_insert_element(SimplyStage *self, int index, SimplyElementCommon *element); - SimplyElementCommon* simply_stage_remove_element(SimplyStage *self, SimplyElementCommon *element); +void simply_stage_set_element_frame(SimplyStage *self, SimplyElementCommon *element, GRect frame); + SimplyAnimation *simply_stage_animate_element(SimplyStage *self, SimplyElementCommon *element, SimplyAnimation* animation, GRect to_frame); From 87404a3cf5bda4f0fff00645c1197e42baedb123 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 22:26:02 -0400 Subject: [PATCH 212/791] Add inverter element class --- src/js/ui/inverter.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/js/ui/inverter.js diff --git a/src/js/ui/inverter.js b/src/js/ui/inverter.js new file mode 100644 index 00000000..ce504bb2 --- /dev/null +++ b/src/js/ui/inverter.js @@ -0,0 +1,12 @@ +var util2 = require('lib/util2'); +var myutil = require('lib/myutil'); +var StageElement = require('ui/element'); + +var Inverter = function(elementDef) { + StageElement.call(this, elementDef); + this.state.type = 5; +}; + +util2.inherit(Inverter, StageElement); + +module.exports = Inverter; From 0ca91d023ddaec267c9f54453e55434c4ae9387c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 22:26:48 -0400 Subject: [PATCH 213/791] fixup inverter backend --- src/simply/simply_msg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index bebfa3fa..397d172d 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -390,6 +390,8 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { if (!element || element->type != type) { return; } + GRect frame = element->frame; + bool update_frame = false; bool update_ticker = false; for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { From b068ecd20927f00f018b1b13e6972993d5607ee9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 22:27:34 -0400 Subject: [PATCH 214/791] Add simply res bundled images to image cache on load --- src/simply/simply_res.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 2dd12133..125638ab 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -26,8 +26,12 @@ GBitmap *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { return NULL; } - image->bitmap = *bitmap; - free(bitmap); + *image = (SimplyImage) { + .id = id, + .bitmap = *bitmap, + }; + + list1_prepend(&self->images, &image->node); window_stack_schedule_top_window_render(); From 8a4b11988c7b2fd3b2415db58afcb3e5246774f7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 22:28:20 -0400 Subject: [PATCH 215/791] Fix accel auto subscribe --- src/js/ui/accel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/ui/accel.js b/src/js/ui/accel.js index 95e59e86..cd2a1a91 100644 --- a/src/js/ui/accel.js +++ b/src/js/ui/accel.js @@ -1,11 +1,11 @@ var Emitter = require('lib/emitter'); -var WindowStack = require('ui/windowstack'); -var Window = require('ui/window'); var Accel = new Emitter(); module.exports = Accel; +var WindowStack = require('ui/windowstack'); +var Window = require('ui/window'); var simply = require('ui/simply'); var state; @@ -47,7 +47,7 @@ var accelDataListenerCount = function() { Accel.autoSubscribe = function() { if (state.subscribeMode !== 'auto') { return; } - var subscribe = (accelDataListenerCount > 0); + var subscribe = (accelDataListenerCount() > 0); if (subscribe !== state.subscribe) { return Accel.config(subscribe, true); } From 81dc576325e7419a49ced6e0f28ec363e95ced9d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 22:31:14 -0400 Subject: [PATCH 216/791] Fix menu sections method to resolve on given a function while active --- src/js/ui/menu.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index e698ce40..48d77320 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -203,7 +203,9 @@ Menu.prototype._emitSelect = function(e) { Menu.prototype.sections = function(sections) { if (typeof sections === 'function') { + delete this.state.sections; this.sectionsProvider = sections; + this._resolveMenu(); return this; } this.state.sections = sections; From 35aba00dceb19a3c8f6c0345e172c9fb007f3275 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 28 May 2014 22:33:28 -0400 Subject: [PATCH 217/791] Stage animations are cancelled upon receiving a new animation on the same elements --- src/simply/simply_stage.c | 9 +++++++++ src/simply/simply_stage.h | 1 + src/simply/simply_window.c | 3 +++ src/util/string.h | 9 +++++++-- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 7aacd002..ffed64f1 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -21,6 +21,10 @@ static bool animation_filter(List1Node *node, void *data) { return (((SimplyAnimation*) node)->animation == (PropertyAnimation*) data); } +static bool animation_element_filter(List1Node *node, void *data) { + return (((SimplyAnimation*) node)->element == (SimplyElementCommon*) data); +} + static void destroy_element(SimplyStage *self, SimplyElementCommon *element) { if (!element) { return; } list1_remove(&self->stage_layer.elements, &element->node); @@ -237,6 +241,11 @@ SimplyAnimation *simply_stage_animate_element(SimplyStage *self, if (!animation) { return NULL; } + SimplyAnimation *prev_animation = (SimplyAnimation*) list1_find( + self->stage_layer.animations, animation_element_filter, element); + if (prev_animation) { + animation_unschedule((Animation*) prev_animation->animation); + } animation->stage = self; animation->element = element; diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index 644c77ba..d02691b2 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -116,6 +116,7 @@ void simply_stage_clear(SimplyStage *self); void simply_stage_destroy(SimplyStage *self); void simply_stage_show(SimplyStage *self); +void simply_stage_clear(SimplyStage *self); void simply_stage_clear(SimplyStage *self); diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index e0dba213..f20dfcfd 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -53,10 +53,13 @@ void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { } // HACK: Refresh app chrome state + uint32_t id = self->id; + self->id = 0; Window *window = window_create(); window_stack_push(window, false); window_stack_remove(window, false); window_destroy(window); + self->id = id; } void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { diff --git a/src/util/string.h b/src/util/string.h index 00dcab66..36b120fe 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -13,8 +13,13 @@ static inline char *strdup2(const char *str) { return NULL; } - char *buffer = malloc(strlen(str) + 1); - strcpy(buffer, str); + size_t n = strlen(str); + char *buffer = malloc(n + 1); + if (!buffer) { + return NULL; + } + + strncpy(buffer, str, n + 1); return buffer; } From 07c2677345ab70bc3d22110621af0c6735ef4323 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Wed, 28 May 2014 23:43:19 -0400 Subject: [PATCH 218/791] Disable settings to avoid config window popping up on android --- src/js/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 163278ab..30206efd 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -8,7 +8,7 @@ require('lib/safe'); -Pebble.Settings = require('settings/settings'); +//Pebble.Settings = require('settings/settings'); Pebble.Accel = require('ui/accel'); Pebble.UI = require('ui'); @@ -17,7 +17,7 @@ Pebble.UI = require('ui'); Pebble.addEventListener('ready', function(e) { // Load the SimplyJS Pebble implementation require('ui/simply-pebble').init(); - Pebble.Settings.init(); + //Pebble.Settings.init(); Pebble.Accel.init(); console.log("Done loading PebbleJS - Starting app."); From f636e100f33459238458cf6d5ed6e0f78e111636 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Wed, 28 May 2014 23:42:47 -0400 Subject: [PATCH 219/791] Fix simply.vibe() --- src/js/simply/simply.js | 2 ++ src/js/ui/vibe.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/js/simply/simply.js b/src/js/simply/simply.js index 8cb5c0da..01ac6d21 100644 --- a/src/js/simply/simply.js +++ b/src/js/simply/simply.js @@ -13,6 +13,8 @@ var WindowStack = require('ui/windowstack'); var Card = require('ui/card'); var Vibe = require('ui/vibe'); +var simply = {}; + simply.text = function(textDef) { var wind = WindowStack.top(); if (!wind || !(wind instanceof Card)) { diff --git a/src/js/ui/vibe.js b/src/js/ui/vibe.js index 99784f7f..9887f8e3 100644 --- a/src/js/ui/vibe.js +++ b/src/js/ui/vibe.js @@ -1,7 +1,7 @@ var Vibe = module.exports; +var simply = require('ui/simply'); Vibe.vibrate = function(type) { - // TODO: Provide implementation here - console.log("Vibrate watch"); + simply.impl.vibe(type); }; From 6646d0c83868d383674794fe4accc2ab1e407966 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Wed, 28 May 2014 23:54:47 -0400 Subject: [PATCH 220/791] add .ds_store in gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 34d82ee0..0f5add12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ - +.DS_Store /.lock-* /build/ /out/ From e49889952d8fc67585b2fec6e0e023ee6853540a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 29 May 2014 00:36:47 -0400 Subject: [PATCH 221/791] Add safe.js Android stack trace translation support --- src/js/lib/safe.js | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 800f1d14..d55b4531 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -29,8 +29,7 @@ safe.translateLine = function(line, scope, name, lineno, colno) { /* Matches ( '@' )? ':' ':' */ var stackLineRegExp = /([^\s@]+@)?([^\s@:]+):(\d+):(\d+)/; -/* Translates a stack trace to the originating files */ -safe.translateStack = function(stack) { +safe.translateStackIOS = function(stack) { var lines = stack.split('\n'); for (var i = lines.length - 1; i >= 0; --i) { var line = lines[i]; @@ -45,6 +44,41 @@ safe.translateStack = function(stack) { return lines.join('\n'); }; +safe.translateStackAndroid = function(stack) { + var lines = stack.split('\n'); + for (var i = lines.length - 1; i >= 0; --i) { + var line = lines[i]; + var m = line.match(/\?params=(.*):(\d+):(\d+)/); + var name, lineno, colno; + if (m) { + name = JSON.parse(decodeURIComponent(m[1])).loadUrl.replace(/^.*\//, ''); + lineno = m[2]; + colno = m[3]; + } else { + m = line.match(/^.*\/(.*?):(\d+):(\d+)/); + if (m) { + name = m[1]; + lineno = m[2]; + colno = m[3]; + } + } + if (name) { + var pos = safe.translateLine(line, null, name, lineno, colno); + line = lines[i] = line.replace(/\(.*\)/, '(' + pos + ')'); + } + } + return lines.join('\n'); +}; + +/* Translates a stack trace to the originating files */ +safe.translateStack = function(stack) { + if (stack.match('com.getpebble.android')) { + return safe.translateStackAndroid(stack); + } else { + return safe.translateStackIOS(stack); + } +}; + /* We use this function to dump error messages to the console. */ safe.dumpError = function(err) { if (typeof err === 'object') { From e7dccc6f730adf4611321e6edc0d0ff716b60011 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 29 May 2014 01:06:12 -0400 Subject: [PATCH 222/791] Add safe.js Android support for purely filename stack trace lines --- src/js/lib/safe.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index d55b4531..846d6ec8 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -64,10 +64,15 @@ safe.translateStackAndroid = function(stack) { } if (name) { var pos = safe.translateLine(line, null, name, lineno, colno); - line = lines[i] = line.replace(/\(.*\)/, '(' + pos + ')'); + if (line.match(/\(.*\)/)) { + line = line.replace(/\(.*\)/, '(' + pos + ')'); + } else { + line = line.replace(/[^\s\/]*\/.*$/, pos); + } + lines[i] = line; } } - return lines.join('\n'); + return 'JavaScript Error:\n' + lines.join('\n'); }; /* Translates a stack trace to the originating files */ @@ -82,13 +87,12 @@ safe.translateStack = function(stack) { /* We use this function to dump error messages to the console. */ safe.dumpError = function(err) { if (typeof err === 'object') { - if (err.message) { - console.log('Message: ' + err.message); - } if (err.stack) { - console.log('Stacktrace:'); console.log(safe.translateStack(err.stack)); } + else if (err.message) { + console.log('JavaScript Error: ' + err.message); + } } else { console.log('dumpError :: argument is not an object'); } From c37d5fb4f62ef6352984f25bae9513caff14cf54 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 29 May 2014 01:15:21 -0400 Subject: [PATCH 223/791] Rename app to Pebble.js --- appinfo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo.json b/appinfo.json index 9a4bbda0..71c177c4 100644 --- a/appinfo.json +++ b/appinfo.json @@ -1,7 +1,7 @@ { "uuid": "133215f0-cf20-4c05-997b-3c9be5a64e5b", - "shortName": "Simply.js", - "longName": "Simply.js", + "shortName": "Pebble.js", + "longName": "Pebble.js", "companyName": "Meiguro", "versionCode": 1, "versionLabel": "0.3.4", From 8a3ad935e174c35e2f5d530a6204bdac08fdb0fe Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 29 May 2014 01:15:51 -0400 Subject: [PATCH 224/791] Bump version to 0.4.0 --- appinfo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo.json b/appinfo.json index 71c177c4..9cbfa691 100644 --- a/appinfo.json +++ b/appinfo.json @@ -4,7 +4,7 @@ "longName": "Pebble.js", "companyName": "Meiguro", "versionCode": 1, - "versionLabel": "0.3.4", + "versionLabel": "0.4.0", "capabilities": [ "configurable" ], "watchapp": { "watchface": false From c25e924731f470498643da2de71ffdd62275e4ac Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 29 May 2014 01:23:51 -0400 Subject: [PATCH 225/791] Remove releases directory --- releases/simply-js-v0.1.1-for-fw-v2.0-beta3.pbw | Bin 14583 -> 0 bytes releases/simply-js-v0.2.1-for-fw-v2.0-beta3.pbw | Bin 18123 -> 0 bytes releases/simply-js-v0.2.2-for-fw-v2.0-beta4.pbw | Bin 49517 -> 0 bytes releases/simply-js-v0.2.3-for-fw-v2.0-beta4.pbw | Bin 49680 -> 0 bytes releases/simply-js-v0.2.4-for-fw-v2.0-beta4.pbw | Bin 53765 -> 0 bytes releases/simply-js-v0.2.5-for-fw-v2.0-beta5.pbw | Bin 59663 -> 0 bytes releases/simply-js-v0.3.0-for-fw-v2.0.0.pbw | Bin 73291 -> 0 bytes releases/simply-js-v0.3.1-for-fw-v2.0.0.pbw | Bin 77816 -> 0 bytes releases/simply-js-v0.3.2-for-fw-v2.0.1.pbw | Bin 78001 -> 0 bytes releases/simply-js-v0.3.3-for-fw-v2.0.2.pbw | Bin 78010 -> 0 bytes releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw | Bin 78010 -> 0 bytes 11 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 releases/simply-js-v0.1.1-for-fw-v2.0-beta3.pbw delete mode 100644 releases/simply-js-v0.2.1-for-fw-v2.0-beta3.pbw delete mode 100644 releases/simply-js-v0.2.2-for-fw-v2.0-beta4.pbw delete mode 100644 releases/simply-js-v0.2.3-for-fw-v2.0-beta4.pbw delete mode 100644 releases/simply-js-v0.2.4-for-fw-v2.0-beta4.pbw delete mode 100644 releases/simply-js-v0.2.5-for-fw-v2.0-beta5.pbw delete mode 100644 releases/simply-js-v0.3.0-for-fw-v2.0.0.pbw delete mode 100644 releases/simply-js-v0.3.1-for-fw-v2.0.0.pbw delete mode 100644 releases/simply-js-v0.3.2-for-fw-v2.0.1.pbw delete mode 100644 releases/simply-js-v0.3.3-for-fw-v2.0.2.pbw delete mode 100644 releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw diff --git a/releases/simply-js-v0.1.1-for-fw-v2.0-beta3.pbw b/releases/simply-js-v0.1.1-for-fw-v2.0-beta3.pbw deleted file mode 100644 index 4f191a43f777d272343e998f7411dc9822ffb3bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14583 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 diff --git a/releases/simply-js-v0.2.1-for-fw-v2.0-beta3.pbw b/releases/simply-js-v0.2.1-for-fw-v2.0-beta3.pbw deleted file mode 100644 index 1d5736ae9bddc9a678467fac1cb32e1dedc8e9a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18123 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 diff --git a/releases/simply-js-v0.2.2-for-fw-v2.0-beta4.pbw b/releases/simply-js-v0.2.2-for-fw-v2.0-beta4.pbw deleted file mode 100644 index b26f92e263b15421bb7f3be615295259f2392439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49517 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

=kCdE?NS7g03dML{ts_(L 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 diff --git a/releases/simply-js-v0.2.3-for-fw-v2.0-beta4.pbw b/releases/simply-js-v0.2.3-for-fw-v2.0-beta4.pbw deleted file mode 100644 index f26c0d25731013d7485408ea57ade50b1cb87cef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49680 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 A$N&HU diff --git a/releases/simply-js-v0.2.4-for-fw-v2.0-beta4.pbw b/releases/simply-js-v0.2.4-for-fw-v2.0-beta4.pbw deleted file mode 100644 index 662f0ef4189c2f8b71db737ab144e78853a058c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53765 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 diff --git a/releases/simply-js-v0.2.5-for-fw-v2.0-beta5.pbw b/releases/simply-js-v0.2.5-for-fw-v2.0-beta5.pbw deleted file mode 100644 index e5d0b08832a51e89a7241b867a7537a4a81f0cce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59663 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 Ie?hVT3tlyTFaQ7m diff --git a/releases/simply-js-v0.3.0-for-fw-v2.0.0.pbw b/releases/simply-js-v0.3.0-for-fw-v2.0.0.pbw deleted file mode 100644 index 26e4b5387edbc00dc35d20c670fdcef6ab831803..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73291 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Ó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 Z)eV@Onv=%6d1Fu8Y&V2Be+SYD{tNckB0~TG diff --git a/releases/simply-js-v0.3.3-for-fw-v2.0.2.pbw b/releases/simply-js-v0.3.3-for-fw-v2.0.2.pbw deleted file mode 100644 index 85887497ab66ecd2e222648112ae00c5db3365a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78010 zcmeFa3v`^vbtc$+SfXh?=vc}6mGO@zMRc(<{A)E;YpUz$vAd3q4jn(b zf9UWkwT;)u>Z6Ux@qD9l_4>;7S~CpGHg53Py~joej@^4=w0CDX-L{P78r}C7rfbdV z!FsLUHRX>@kLRZ{&1TuPP+B^I-wPWTHXe7peFJ#=;6MM+g;(Ami}B|s@b;u%K=*f{ zFEfQ|?f8MC`;Q-w#Wuy>6N|0;#`~WF`#@|ioNs& z{EIF9tHonWSGLEN{%UdmjC-VTd_oJ>EAr|+q3UW#s2=AkNxGVA6U8}_Q&$y z-LccL$=D|m{^{6fV_%J}@9+k`ScxtD#aq{Z^n)+0>14}eOaJz**y3H!U-#OEH5;D) ztsjoPvUZ>caJO9c2G%0=_e|;yk<=eV>N=z@n$%5^)ax#vt;ABXrJs823&8#&u#bOD z>-wj__LDEaeDV68HK}zkul>`tshMxCdqwlid|m%ed|mT?26-#YyXgy+STFEJFA>)C zS2ca^t#t#j#L1VxQ2A5j(X?TcHi5Etw<-JBS38bu^k)0F4QT%E7Zuk1ZzY~*O^M3y zfBDMmFTbsAzZ*4p&nE=_mDm3gv21hSTd$hD0ajOD|H{`v*<07_@&=NiUe{ZPna8XC zli2p2f#3f!u#d-74k#L1nt1!Y!ciyR z-XMPm-@ab{4!?aJ{*p_8<#mQ)_vr`0}t+xz2Xh*dofz-y;5qel=`+S z^%p3$_U*MFIey@{Db;x4I#(Asap~{gS~Kv};yNk!yC}C^YWuCrYdg+-nf3lJfbdsu zt@+5|1BV5|{EJ=x;s4tI*g&jP@LjlZ;D-R^4*+Fpaf3klXP3WzVEwn&JVjWsrT+nt z{zf1@b@}GaXAiFHI56;xe18&A`=5T~V=vqs$LQ#&pzLdx zy-Ic9%IiPyc7^;Nzw-M3dL^;APWT{>^sdVvd-305+%`zRrTMqk53Kd@_wp;JIdb>C zb=^Sk0O9=dsK#t+PZ!K?Ev@u>g8Ke_WHLv22SJeJ#U?^a18$NTWg;?U19nSZ^h8! z#K0I}{@ms3@7Xgj*T443>+YHBU;kumbL`2Ve-#pY>3xtm*MDj0MIU(YhdiZhdkiK1 z^vzfBd&_0OTKd-I&6QZ(8+iD%^39E3+w=nAuYugS6X|OJL*?EpFYS@GzkL}{s%AVi+TGxMA+W(gq_dIsfG4}h#{!jJq_|!+9Uz5J^<#p-zz5Lf-P7Le^ zg+2^fckH{D9?1>>)1@2VTC;O~`ftAZ;;TzPG721bQSRTf>4mxekD~U^zPTp8_EPNc zH=p_Nfpsq{9*bY!gt2k@eX-b$F5iEz`Ci+6=0gW=cqz7d&E~{nYH^cb9$R|yYae-D z%RVM$_n<6rJ@za2{K}VD&-s>aKl|dvPd*k~s=s;t(;J>R4*br%dHpj#@We-|*FCv;@%pEqKurG4>z=sr z8Sjgq0~dVk(og*TkKykg^yx3Zw(&jh``NYs`TSgLsqyBsmFK?fJ-_Z;YzM~t=GZIi z2G%XDS$ETu->!V*`Hz@b#@zhm=4v!%(~~zn^R4G&o1eS*>Eq8`{IM^^o?P?fXJ5S` zw&8QJrP<5RU3@V1jeofL&gU*Z9Q&bXZu$8SJ^7Iv*1fj&h8v#z*rmrWuX*Lz(%Ktt zetPZy9NYX#WjTaDx_HYsVoMLSK>2gTei(2zK3VMlql+iL@qwTJ*rgx6yy=-(>i(DG zzs7d{D1P1pjDDggw%Mx^E5f#zUH7@?F8Q}ti(LGLOV>T| zs{?yv9{ZPnj>)bE3ub0ApTPr*EbGy%-mV+00;i& zr9WEq@b|ly)_#$)Cbsm@rO%?3&!R;6(sLKrAAj!Rb;nOv*L;q%%|E|%y1EY3{GTp8 zTU~FY(%@Ha1H>3$eCHA&{qdzWBHQ*}`bEIusQL6aLp^5|7Oj)b%jlk^cN5Q z$iOWF8!DVB{@{%pp5F`|E4DdxdJ|@#*i!XU?6ox;t{=D);UB!zF>qi2GG?iCDYp3^ z?$4b5-!FbJhPnD+Z0Y}Aq?9A>KYJ+wI;hqbTl$5sYFK!6KH-nU5Aw)em3EUGUT3n zJ|i+DmcDc8@yCwbdrRjh`>%g|B@rhi`82Fvq>IC9X8O z@W#G-{_^7GCm?}N-?VviA+~fg+V!3R4?KR~r8oL9uYiL8-KFckm>4*Mwtnyu`m$pa z`s|rEw%?P0#E(7sUW|Q8`X9RVXBT6St^JpzPUg~kgi7Ci<3|UMEyXtO8F&b>C*OGP z;)Z9RySVY$U%+UMEq&&VN}d#a@{KhE5905xH#GjXH$Hmg`hhRxsUZ))(KB#6((3q2 zedU=qHvZbdiy!<&$cbm41SgQUX5ZK^JT~{n&G&qvI@f>8jNpUTU!6ddd%fJE_tUt{2jb}`t+686IY7h zhG$Pd2;O+&4aR@`P0Y_1ue?rqeC74uy5iyYj!Q8Q^GodJ-&k|x%Ip7naXscE4gcOE z*CZPLtwpZ(HT<PBh z>B2WH?s4G_i~TOVcJVtN!vD{OaTi{?&~)Lyx$tQh{?3IbT=*|8?7?7A9RA&f zk_-RIg^COR;e~M*{+$cwT=*Loe!_*na^Z0o{*4PyxbPP*{G1Da;lfic{6Aj!ybFK+ z!k@YDXD__s!cSaCgZUJF8rYj$1ePt3BUdNrI-HJg}?jKcMuH_)AwNVXm~T#X2rXEs3Z13wNY()4YACfX=JK(uUwzam+22$osy;@6jZtX%1hHz<6gn9*2aW2>$UM&RP)Gmb;>WKz*R$9X@g5xTgtTv+hkd z{H!M#(q89ez0vIC-)Z?%^2hzA&j{0yP8eIadH4qc!@TU(F^s0XMl)Y4<)=y(YGZnG z5>{&5^k&yBH z%vi?jtTdaG+3xO{nVF1#E)V;-pMmkaj~$kKG(SG=cdkSn(lOWB^v?yf@y|_)h(JA! zNpze~R+`8+Gu{vq$cde_<#!UXO8!ywD3Jma3OAE5(p+0O-|(0nq(UF&8w~`!df^PJ z@Q}o;WG>|+5PGVWD4G}j$ZWR%$Q2|(pojEFu*@U&8JIdg$PEqgkI&W-_q>k@BSywhQXD z{J3Y+4Bwa{IzpRC$O2@325#UUQ*dANTV$&>-KKtrc^$_>&SRD`0K#p3++> zGE*Jr7y@mPvjRrLII~T){UB$ed%3Lq@nHB+e;E1YWlT1i=j<3VN}HW>|`nhhAo8#VN8 z_#7Jow>UjDg@NWZt11I0K%HvC$23?HszCOJMOA)$ygt+LX6w@gAfwKTDh@I74*xbR zeU!38zVFT5IbN-u4T~dXpRw_JAwRC1L^18nakLnTwLsa3el(w6L&ukue;k`$U1glD z(c)!#MnT$nVZm!m7qNpx-aG*U*{=%5-nj5dzUfsPAoBP)2FXmt$FNlP-QLp^jCW)i zt3K-2R;Z+mS$!H(Tr06(wi1EL?hzSl2P$(lNI$@?)tesbBlPa@)MuNK09C+_+LSc* z&7?Y=@2X`lLArn}q+KqT^Ey@1cWMTRWyX6L(Np7D`M`vvQx<1KBlL4j$(WD%d9U-X zLnk_!k9jD6H3LG7?Cu7~G%yG=*rrODQkW(2Uy8Xg>zShU+F?WNoG?SiMGy|LcM%p} zMw-SV%C?&bSXhZpla5))=G-fju!0OIWQi3mZPHQN=G4*)7HjkpO4R%r@4>;Nhr!|Z zseDbyY>(6o=INSgc&1suA478LV7}oel4-U-Ez}f|<8`1*M8E_@u@X~hYQ8Kz%%`Qs zGPR{8or=dJ84PiQ;$e}QqPV23pbcp%+VQTTGS#4^)8o>w(jVXhMq31IoQyumrnBu&=|FejA-NUiZ|QKp}61;7ULpTXhop=0-ElyxzulIIl-&9>nA*e!N@ zt)`4o1(#(8WND(sU8N|I*PbwN$lEGShh`@Vbuu|RFH^0R{By_3iOxQZAr|WD_4+(~ zd6=2D%Bb*UOr*fmB8*LOjDS&56v;XL`6k#Q11;+WgspOpASLWmn?gU$LP=`+*dniu z`Ka5Ccc`3mLLB1H?2>tinSqbD(r&Njd+B=YG(a?a4pMkk6cs+!1W*kg%irP@drlgPcW zVDov!s}*Od%s7}G!=gS_f-*Y3D!>w!d;xer0bHl?D?O(5=aWHVnTcf3Laxp%#atmX z>*uHD=X=s6geO3t3O{^&%=rFUhRX<-^0N$%Ay|RC3BwiUuhyoUKEJB?QU!@$XC%$( zOo-Ymtr`t0Qn}QbRIk5dTQ#-Y-?^=l+OgZ;wQVe|8Bt*)mnx@j+vb}X!3}YxRPXj} zMVGCa&%qN$3l*m6SCI%ccua{6E|)cJks5v>iALlb$7X8Bbsag2zT3JrksmIO#Yrk`l84WhSOp~qz{b*yX(nJ<(vp=3HAbWw#}Wa{tPm+u?Q zH!GQa^?#Sg83|4o|I%f*k?K1_-~&_g>IOsD};G~fjj*%hRYfh)*0CH(dGmG+iWsbr!E0uPr)ppq5vXMVl|5szSe zltz+>dnCcwB)V=al``z(jit%~e!wF+R}Bf9Jm_?^K4X5V7%=5rq}4ChhbeTCry{uCDMoD_hj*is3WE<$9@EeQ59uJcLsXPa?d$ki~w(*>|v z32<{9=SerlLwr7&fLNF5J6+lf^n1|Z1*hi3DG%exWHEOeDvpEI<9n%Wv7tRARghD7 z5?K}fy~SMbb{5g-g(6#*v-3d$!%1r$OII*a-E0M3#JfG~edH;eeqF;Gh9U7M@Qt)Z$QJ|M>9@#I39N^VE4Y>@XV ziE>hCOin588{3OcPoBw4PB$uvN@6S^bG1>O}4pAPR~B2SD_NreCK*a)e~=KXH($sor!?Qglj0Fd;l8P$Zgh z=}|QF?by9LyJPpRpa#a;9Qj*!f(v>uAa3pLA@)5OnYZrjA#d~qML6LqTkeH~%WvJ< zwH+A{oGfSRP+ZWg8ML&(sQhrXFmeasnDD;d-o9!fE1zeO)N7Iu-iOvQR|TwKvnTt` zn9OHGa1h?NeMjF}lQbSmLU`Y<+xx~%(nKf;VJ1zOq*^Em;eEH?etTc7knQSaxkzb3oQor3~o`Q3Yzq!MtsIe%iZIh*b1O;6Wo ze>+h>3w10Dd$fKZslk}aPYWp9wLOh>z*OU?Uv3@)U9&yHubL5Xsx{21)zTpKm@GNj z#teupQmTT*?ucdsXOtLKZPf86#J@8k4j>fi%4*{Q!bXdMz4^#>R2%o^?`2FWSi^v+ zm0lL_JHS948YNA3UtxS*Bx{rwxfqj88{uMUdknRNMCZb=*o>lxXoou4R`H0CaqHF~ zA>0*q5|IuyK!Tn;7ot+WZzUNYAC0G}QDj3(&TM217lEu{qF>N0sl zcT8qA0VYSN!XZ{V-vTCIC`{$@>AKD$3Hh6&geWC7QY0bRQ&NJ4>}NyNU=JHJrl26{ z0Fy*x=6sWeu+C8pw%p8ANaqeIwRP)cA=#Xoh3t^cheSNnO*!5jPeC!jqP|$+3}a_C zt%<_DBX6Z`NF+-FwGpYIn1d>7XB4lvH=h$UF>~)N-(hF{y{ZBFIrPkMc?5zV#)M{q z)K3?3utJIQhp9-?Q@tZegiC!;&s;tkGL1^F;4 ztZun4hnaQ)(^@Ir(UZhiS8uX_Rp8mZg|4oJaw=upA8KnrZ^-XJ_GTu8X5FWThfg(5 z4UKds!DUW&_{6E<`BNiXy2sL(w;(-T!l_dl7eMVNRM(3b0SdTT(5bC}J}u^K12D@K z_7*V9iMH?nlpRdOBYifIJt%5~n@k-m_*z9d7mv$?38)TKIh7-nkVZFP*28^_*ZJ7T z6rJU#oh{2Dk#s1+>-mfsjfrF*W|e_N0i$2njLGzns!s{|o9hS9nK*I~2xZomMyRyT zj{{nfZ5G$7u1BCZ;@kvFs3<}b=BfozLvy>N)ziq6Z#0k4I0OQeFtcynipVB5N05DQ zG7s|GuA1hmB^@o2E_LO4Ed@d{<1WR^=x0#3jIi6=fMp*e3s@FmT8#JOUp#9x$GZx9 zCGsKsi)STx7yg;x82-7erM|<3?72{WSAvDZrNhO;<&PAyv!?t|Jj+pGhxH*F-4Z|G z;*nsn4`FmyAvd24rO+Z;1VLKiD73*(&57< zP^p-}#Dobn8YVC^V*)2ny2?s@1BL9Pwnmd~>jPC3xcTcqn{Nl$d;_fH$JrM7dni=R zA*lu%$$9KqS&9^8s=3b}J9KU`aV$Zl!18(g)G17Y5SrxEZ%WTr44(3k~} z2o@p_w3q~g0;mSdI8|hqmm`Dv)Vw7rm0$z+?cUSZdwWk$cDuhLQb|ZegvEt~BXGja z2n|c1Qf(Zca0J4~AnFU}wu2%V9fmVGO8j9g00hx+5j$!)eEeao1D55mT+eak4`Vs7 zEXR!Vg*4U5e%Y~41%W}`e7kylLE;Z8t9y(8q)=6)6jar z(*nhbC!IDV&P^quS%pDN5fK>M)&77M%y3}M%+JGU$pExCu8sm#D@Op9S4 zeI|W2J#MT4TuZN8j^o(-BimYHD&*5e><-FoJZP=T#m&!$qat|($-V@dSEnRJk0 zHWZ$LbhnMpRm7~$??Av*Xhm0HBRSm{MW{3?M9vZgk0W9P(7r~(r`ZI++b+p5RA|{h z0TsL_QqbT>VO`{66~jsv$}qbM;*_2?LA63I8+!Kc*as<35*hrUpr$a!r3{LULt_=~ zR`kEUw;Am@k{l`BN83oDuQ%JH4FtS#imw!e2wR0g&Q)LcZnR^ZQmZB5Rwf`HCPDmG z`OtSFJCw|D_jESF%F^TEL3NKCR5Z_&=I5a^!;%P@t4+^gg5^F4%%(Ck;V*iJmVB6^ z87cc52t?WYAJ97{!DHmJt&KxJ31hK+L!yR>2a6mij8}krlgYJOfTpfrAx7W)yn> zR{Ov*N|X}vO5qN-jN%f?7sdcLCMrf%xeIIwYOz2PT1XnEWk{t;AZ{zpj#|WsDx2!} z(F7+9A2JQ86|zkGCjiJIHC#GXwDybinG`tNTEwxE^GEs;MJy&cf($u&$&C1n*gwR~ ziJgKYz`97hnaLViXck|&3^@(pB6gNswd+jUQgoFjRfH|65J(Mqj*dHex9gynuM}*` zE)#}RNAK3Hy~gG!8A5q;7%pVdG_8~YXFx%93%fv7?LKuXacVd@&p#vhGn^Ry#Ho=} z!z0_0r$!i?Nn&+vSBI)J(lFvc!4_DYpT`*U7ebXCYhT%L*C|x^i7neweVIgZe)!bL z+`JeK`>o|fR94;z$hK%F4brmT$HAgmY%^cvT=&5($+ptBf7U| zo2EpV%a~&{1wPhOey*Gr=>-iPB-~df)Dj}G1b~jj%}kIsY)t)lh%CgZij~3L1$cA% zMeKQyL`F?O8W4k_lY}Vbc8ZAy2AL$YK!A&kDCdr1sev^{3URx!(ujN5mJz*sfh6Jp z-)Y1<$_>)afHj7#PGO{S8s9TwN*2w?$9hJY5(J4q43iW^(}MV`0J~7n6hr-fA4n_x z4iaed9toOPq%{+IN~mzQ6#Y)5WEGT+bI{^G0=Ius?E5%CXU~F6u%J*r7OkC&qXqQN zjx#L+J&`IVvvJ)O41>CdprSd$jTj1s3HMI5K@~SSw4~a(qWAXOumQYvD@2>$bV8rI zJ;AUz;rc&-#wmb1A>r9DKQ%T@Uy4SsUt_^@QllymuAvara6hpVTi`Mh=E*$9{;gKZ zJUWTW7A8-#xC6R&>#DE@Dffag-VKB{;@lxqJIg&M*j$bz`3h z`_H+ZyY|82IUL7jBHSPC1s`HRZa6*>#}^XyV*>l`Cbi7e3RAal-=`myc)Dy-v28sZ zFG=dIeL4Lq#nWR7Od|%FyaTW$84K{VBGi*KK(Jdr9G_(_{F#lXE6$8q#ri*2NLPn< zjKB>5z8b?jq3ER_Fbo3GRt-_>!3ecz2Rs&OCj2bCbc_M0%vL7KUAfypzOLKY zDoQ|nmALl^Lq42=5@;6oH`8T-I(e+_B4b7=|S5GhghfPG4sQxgru+4ziI&#InVwN_1zpIi;RQ8qnZp&g87|Y$Z zEkQeVDpBn6lW;rfP0tBNWBYpg^4UB#z|eNp-Ls4v%VHam1;#?SJ{$~25yeHBB@Dgd z)3At^t2MtA2o4d-TeosYOqL*`7Q-xVX0F5>kv4lOMD8L54CHAQaTOWr%j5uZ=sg3uI>U zQ#F(B3@xX;W z4KU?TU<*^uMd0|w^dy|(2oXZphp9aiCzrLKX0YrJ8UwRpG8;6lNDT(+1c!66kS@XZ zppc7;Qy+vyK^O+oP5oAYbgh)GK=nvh3%TC(nL=)R`fMS$BRvi^COuKe?Ml}Qx!vh{ zfsSC4fE0{0H|0#!OVi_!SNazU3lG^ewr*{g*X-VFh6 z*xUGY`yOoh?w9Wqkurx{^0by2Vx{vz9Ve4;wP_0S!%*s>(o@4voH_+KgZ25r z!9M;s-(R1Hos_@&_}@G%WPK(4srSv#4AB_=KQ@+tg^X3- zbyotWFoyj3L-YCh{qvPd-~8djee)9ⅇb*-~7;AZ+hp#{Dbr7&-cwg{BYmAtS+Y8 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 diff --git a/releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw b/releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw deleted file mode 100644 index 28a29f8593c552420b5309af8d46e7d0f8350d8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78010 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 Date: Thu, 29 May 2014 01:24:10 -0400 Subject: [PATCH 226/791] Remove public directory --- public/settings.html | 67 -------------------------------------------- 1 file changed, 67 deletions(-) delete mode 100644 public/settings.html diff --git a/public/settings.html b/public/settings.html deleted file mode 100644 index 937c55be..00000000 --- a/public/settings.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - -Simply.js Settings - - - - - - - - - - -

-
- -
- - - - From 810a3bc720f0becae26ce3c7302b0a604f1488f8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 29 May 2014 10:29:34 -0400 Subject: [PATCH 227/791] Change window action to optionally take a visibility flag --- src/js/ui/window.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/ui/window.js b/src/js/ui/window.js index a0d6c217..de327eca 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -117,9 +117,9 @@ Window.prototype.prop = function(field, value, clear) { return this; }; -Window.prototype._action = function(actionDef) { +Window.prototype._action = function(visible) { if (this === WindowStack.top()) { - simply.impl.window({ action: typeof actionDef === 'boolean' ? actionDef : this.state.action }, 'action'); + simply.impl.window({ action: typeof visible === 'boolean' ? visible : this.state.action }, 'action'); } }; @@ -131,7 +131,7 @@ Window.prototype.action = function(field, value, clear) { if (arguments.length === 0) { return action; } - if (arguments.length === 1 && typeof field !== 'object') { + if (arguments.length === 1 && typeof field === 'string') { return action[field]; } if (typeof field !== 'string') { @@ -143,7 +143,7 @@ Window.prototype.action = function(field, value, clear) { if (typeof field !== 'boolean') { util2.copy(myutil.toObject(field, value), this.state.action); } - this._action(); + this._action(field); return this; }; From bb62c8c17098c81ba661dadd2f9736c345078836 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 29 May 2014 10:35:41 -0400 Subject: [PATCH 228/791] Add simply window set action bar background color --- src/simply/simply_msg.c | 4 ++++ src/simply/simply_window.c | 17 +++++++++++++++++ src/simply/simply_window.h | 1 + 3 files changed, 22 insertions(+) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 397d172d..95e02803 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -51,6 +51,7 @@ enum SimplySetWindowParam { SetWindow_actionUp, SetWindow_actionSelect, SetWindow_actionDown, + SetWindow_actionBackgroundColor, SetWindow_fullscreen, SetWindow_scrollable, SetWindowLast, @@ -158,6 +159,9 @@ static void handle_set_window(DictionaryIterator *iter, Simply *simply) { case SetWindow_actionDown: simply_window_set_action_bar_icon(window, tuple->key - SetWindow_action, tuple->value->int32); break; + case SetWindow_actionBackgroundColor: + simply_window_set_action_bar_background_color(window, tuple->value->uint8); + break; case SetWindow_fullscreen: simply_window_set_fullscreen(window, tuple->value->int32); break; diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index f20dfcfd..a351fa94 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -64,6 +64,11 @@ void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { self->is_action_bar = is_action_bar; + + if (!self->action_bar_layer) { + return; + } + action_bar_layer_remove_from_window(self->action_bar_layer); if (is_action_bar) { action_bar_layer_add_to_window(self->action_bar_layer, self->window); @@ -74,6 +79,10 @@ void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { } void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint32_t id) { + if (!self->action_bar_layer) { + return; + } + if (id) { GBitmap *icon = simply_res_auto_image(self->simply->res, id, true); action_bar_layer_set_icon(self->action_bar_layer, button, icon); @@ -83,6 +92,14 @@ void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint } } +void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor background_color) { + if (!self->action_bar_layer) { + return; + } + + action_bar_layer_set_background_color(self->action_bar_layer, background_color); +} + void simply_window_action_bar_clear(SimplyWindow *self) { for (ButtonId button = BUTTON_ID_UP; button <= BUTTON_ID_DOWN; ++button) { action_bar_layer_clear_icon(self->action_bar_layer, button); diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index 3835adb8..36646f39 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -33,4 +33,5 @@ void simply_window_set_button(SimplyWindow *self, ButtonId button, bool enable); void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar); void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint32_t id); +void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor background_color); void simply_window_action_bar_clear(SimplyWindow *self); From 5f7d6249a9c8cabe5fb7cc802254f4435285228b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 29 May 2014 11:14:51 -0400 Subject: [PATCH 229/791] Add window action backgroundColor property --- src/js/ui/simply-pebble.js | 35 +++++++++++++++++++---------------- src/js/ui/window.js | 1 + 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 6cae9c41..1993fc45 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -19,7 +19,7 @@ if (typeof Image === 'undefined') { window.Image = function(){}; } -/* +/** * First part of this file is defining the commands and types that we will use later. */ @@ -116,6 +116,9 @@ var setWindowParams = [{ }, { name: 'actionDown', type: Image, +}, { + name: 'actionBackgroundColor', + type: Color, }, { name: 'fullscreen', type: Boolean, @@ -429,10 +432,11 @@ var actionBarTypeMap = { up: 'actionUp', select: 'actionSelect', down: 'actionDown', + backgroundColor: 'actionBackgroundColor', }; -/* +/** * SimplyPebble object provides the actual methods to communicate with Pebble. * * It's an implementation of an abstract interface used by all the other classes. @@ -518,6 +522,16 @@ var toClearFlags = function(clear) { return clear; }; +var setActionPacket = function(packet, command, actionDef) { + if (actionDef) { + if (typeof actionDef === 'boolean') { + actionDef = { action: actionDef }; + } + setPacket(packet, command, actionDef, actionBarTypeMap); + } + return packet; +}; + SimplyPebble.window = function(windowDef, clear) { clear = toClearFlags(clear); var command = commandMap.setWindow; @@ -525,13 +539,7 @@ SimplyPebble.window = function(windowDef, clear) { if (clear) { packet[command.paramMap.clear.id] = clear; } - var actionDef = windowDef.action; - if (actionDef) { - if (typeof actionDef === 'boolean') { - actionDef = { action: actionDef }; - } - setPacket(packet, command, actionDef, actionBarTypeMap); - } + setActionPacket(packet, command, windowDef.action); SimplyPebble.sendPacket(packet); }; @@ -549,13 +557,7 @@ SimplyPebble.card = function(cardDef, clear) { if (clear) { packet[command.paramMap.clear.id] = clear; } - var actionDef = cardDef.action; - if (actionDef) { - if (typeof actionDef === 'boolean') { - actionDef = { action: actionDef }; - } - setPacket(packet, command, actionDef, actionBarTypeMap); - } + setActionPacket(packet, command, cardDef.action); SimplyPebble.sendPacket(packet); }; @@ -626,6 +628,7 @@ SimplyPebble.image = function(id, gbitmap) { SimplyPebble.stage = function(stageDef) { var command = commandMap.setStage; var packet = makePacket(command, stageDef); + setActionPacket(packet, command, stageDef.action); SimplyPebble.sendPacket(packet); }; diff --git a/src/js/ui/window.js b/src/js/ui/window.js index de327eca..4691fcf7 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -38,6 +38,7 @@ var actionProps = [ 'up', 'select', 'back', + 'backgroundColor', ]; var accessorProps = configProps; From 28a5673ac9dab30cd9c6da976092a050004b2ae8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 30 May 2014 10:48:15 -0400 Subject: [PATCH 230/791] Add safe translateError which adds the error to the stack if needed --- src/js/lib/safe.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 846d6ec8..9df0de85 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -72,7 +72,7 @@ safe.translateStackAndroid = function(stack) { lines[i] = line; } } - return 'JavaScript Error:\n' + lines.join('\n'); + return lines.join('\n'); }; /* Translates a stack trace to the originating files */ @@ -84,15 +84,23 @@ safe.translateStack = function(stack) { } }; +safe.translateError = function(err) { + var message = err.message; + var stack = err.stack; + var result = ['JavaScript Error:']; + if (message && (!stack || !stack.match(message))) { + result.push(message); + } + if (stack) { + result.push(safe.translateStack(stack)); + } + return result.join('\n'); +}; + /* We use this function to dump error messages to the console. */ safe.dumpError = function(err) { if (typeof err === 'object') { - if (err.stack) { - console.log(safe.translateStack(err.stack)); - } - else if (err.message) { - console.log('JavaScript Error: ' + err.message); - } + console.log(safe.translateError(err)); } else { console.log('dumpError :: argument is not an object'); } From 813e0a59ffffa2fb0559afbbe6b7323a681048c2 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 30 May 2014 11:05:42 -0400 Subject: [PATCH 231/791] Add safe.js translation of iOS stack trace format to node style --- src/js/lib/safe.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 9df0de85..67cf566b 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -14,8 +14,10 @@ var safe = {}; /* The name of the concatenated file to translate */ safe.translateName = 'pebble-js-app.js'; -/* Translates a line of the stack trace to the originating file */ -safe.translateLine = function(line, scope, name, lineno, colno) { +safe.indent = ' '; + +/* Translates a source line position to the originating file */ +safe.translatePos = function(name, lineno, colno) { if (name === safe.translateName) { var pkg = __loader.getPackageByLineno(lineno); if (pkg) { @@ -23,11 +25,18 @@ safe.translateLine = function(line, scope, name, lineno, colno) { lineno -= pkg.lineno; } } - return (scope || '') + name + ':' + lineno + ':' + colno; + return name + ':' + lineno + ':' + colno; +}; + + +/* Translates an iOS stack tace line to node style */ +safe.translateLineIOS = function(line, scope, name, lineno, colno) { + var pos = safe.translatePos(name, lineno, colno); + return safe.indent + 'at ' + (scope ? scope + ' (' + pos + ')' : pos); }; /* Matches ( '@' )? ':' ':' */ -var stackLineRegExp = /([^\s@]+@)?([^\s@:]+):(\d+):(\d+)/; +var stackLineRegExp = /(?:([^\s@]+)@)?([^\s@:]+):(\d+):(\d+)/; safe.translateStackIOS = function(stack) { var lines = stack.split('\n'); @@ -35,7 +44,7 @@ safe.translateStackIOS = function(stack) { var line = lines[i]; var m = line.match(stackLineRegExp); if (m) { - line = lines[i] = safe.translateLine.apply(this, m); + line = lines[i] = safe.translateLineIOS.apply(this, m); } if (line.match(module.filename)) { lines.splice(--i, 2); @@ -63,7 +72,7 @@ safe.translateStackAndroid = function(stack) { } } if (name) { - var pos = safe.translateLine(line, null, name, lineno, colno); + var pos = safe.translatePos(name, lineno, colno); if (line.match(/\(.*\)/)) { line = line.replace(/\(.*\)/, '(' + pos + ')'); } else { From 891028dfbace478fa17f34022bb12f993e78853f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 30 May 2014 12:10:19 -0400 Subject: [PATCH 232/791] Add error name to error message if it's missing --- src/js/lib/safe.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 67cf566b..1d658dca 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -94,10 +94,14 @@ safe.translateStack = function(stack) { }; safe.translateError = function(err) { + var name = err.name; var message = err.message; var stack = err.stack; var result = ['JavaScript Error:']; if (message && (!stack || !stack.match(message))) { + if (name && !message.match(message)) { + message = name + ': ' + message; + } result.push(message); } if (stack) { From ed4957d2d2b14ed7ebc1f80f2dbec24fd3f90cd5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 30 May 2014 16:01:15 -0400 Subject: [PATCH 233/791] Change simply splash to display a tiled loading image --- appinfo.json | 4 +++ resources/images/tile_splash.png | Bin 0 -> 18123 bytes src/simply/simply.c | 2 +- src/simply/simply_menu.c | 2 +- src/simply/simply_splash.c | 41 ++++++++++++++++++++----------- src/simply/simply_splash.h | 3 +-- src/simply/simply_stage.c | 2 +- src/simply/simply_ui.c | 2 +- 8 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 resources/images/tile_splash.png diff --git a/appinfo.json b/appinfo.json index 9cbfa691..0694a652 100644 --- a/appinfo.json +++ b/appinfo.json @@ -21,6 +21,10 @@ "type": "png", "name": "IMAGE_LOGO_SPLASH", "file": "images/logo_splash.png" + }, { + "type": "png", + "name": "IMAGE_TILE_SPLASH", + "file": "images/tile_splash.png" }, { "type": "font", "name": "MONO_FONT_14", diff --git a/resources/images/tile_splash.png b/resources/images/tile_splash.png new file mode 100644 index 0000000000000000000000000000000000000000..c75d93ff31da1829f30a5196381c8bdf19fe8651 GIT binary patch literal 18123 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 UWsG2{L!b#b*tl31SZ&_<50e7AZU6uP literal 0 HcmV?d00001 diff --git a/src/simply/simply.c b/src/simply/simply.c index e6caaec4..da8c0d61 100644 --- a/src/simply/simply.c +++ b/src/simply/simply.c @@ -19,7 +19,7 @@ Simply *simply_init(void) { simply->menu = simply_menu_create(simply); simply->ui = simply_ui_create(simply); - bool animated = true; + bool animated = false; window_stack_push(simply->splash->window, animated); simply_msg_init(simply); diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 39efd6ec..04dfb38a 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -289,7 +289,7 @@ void simply_menu_show(SimplyMenu *self) { return; } if (!window_stack_contains_window(self->window.window)) { - bool animated = true; + bool animated = !self->window.simply->splash; window_stack_push(self->window.window, animated); } } diff --git a/src/simply/simply_splash.c b/src/simply/simply_splash.c index da83030a..2dc82ad1 100644 --- a/src/simply/simply_splash.c +++ b/src/simply/simply_splash.c @@ -2,25 +2,36 @@ #include "simply.h" +#include "util/graphics.h" + #include -static void window_load(Window *window) { - SimplySplash *self = window_get_user_data(window); +void layer_update_callback(Layer *layer, GContext *ctx) { + SimplySplash *self = (SimplySplash*) window_get_user_data((Window*) layer); + + GRect frame = layer_get_frame(layer); + +#if SPLASH_LOGO + graphics_draw_bitmap_centered(ctx, self->image, frame); +#else + graphics_draw_bitmap_in_rect(ctx, self->image, frame); +#endif +} - self->logo = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_LOGO_SPLASH); - Layer *window_layer = window_get_root_layer(window); - GRect bounds = layer_get_bounds(window_layer); +static void window_load(Window *window) { + SimplySplash *self = window_get_user_data(window); - self->logo_layer = bitmap_layer_create(bounds); - bitmap_layer_set_bitmap(self->logo_layer, self->logo); - bitmap_layer_set_alignment(self->logo_layer, GAlignCenter); - layer_add_child(window_layer, bitmap_layer_get_layer(self->logo_layer)); +#if SPLASH_LOGO + self->image = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_LOGO_SPLASH); +#else + self->image = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_TILE_SPLASH); +#endif } static void window_disappear(Window *window) { SimplySplash *self = window_get_user_data(window); - bool animated = true; + bool animated = false; window_stack_remove(self->window, animated); simply_splash_destroy(self); } @@ -31,19 +42,21 @@ SimplySplash *simply_splash_create(Simply *simply) { self->window = window_create(); window_set_user_data(self->window, self); - window_set_fullscreen(self->window, true); - window_set_background_color(self->window, GColorBlack); + window_set_fullscreen(self->window, false); + window_set_background_color(self->window, GColorWhite); window_set_window_handlers(self->window, (WindowHandlers) { .load = window_load, .disappear = window_disappear, }); + layer_set_update_proc(window_get_root_layer(self->window), layer_update_callback); + return self; } void simply_splash_destroy(SimplySplash *self) { - bitmap_layer_destroy(self->logo_layer); - gbitmap_destroy(self->logo); + gbitmap_destroy(self->image); + window_destroy(self->window); self->simply->splash = NULL; diff --git a/src/simply/simply_splash.h b/src/simply/simply_splash.h index 4f57c9b4..bf4bfdca 100644 --- a/src/simply/simply_splash.h +++ b/src/simply/simply_splash.h @@ -9,8 +9,7 @@ typedef struct SimplySplash SimplySplash; struct SimplySplash { Simply *simply; Window *window; - BitmapLayer *logo_layer; - GBitmap *logo; + GBitmap *image; }; SimplySplash *simply_splash_create(Simply *simply); diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index ffed64f1..9c52dec1 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -329,7 +329,7 @@ void simply_stage_show(SimplyStage *self) { return; } if (!window_stack_contains_window(self->window.window)) { - bool animated = true; + bool animated = !self->window.simply->splash; window_stack_push(self->window.window, animated); } } diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 4a1fca5c..c1f09026 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -271,7 +271,7 @@ void simply_ui_show(SimplyUi *self) { return; } if (!window_stack_contains_window(self->window.window)) { - bool animated = true; + bool animated = !self->window.simply->splash; window_stack_push(self->window.window, animated); } } From 6e125aaaecd9e7ff54f0d87ef5a4ad97a97cad12 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 30 May 2014 18:02:13 -0400 Subject: [PATCH 234/791] Change loader.js internal name from __loader to loader --- src/js/loader.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/js/loader.js b/src/js/loader.js index 4c8462b2..8708e88e 100644 --- a/src/js/loader.js +++ b/src/js/loader.js @@ -1,19 +1,19 @@ var __loader = (function() { -var __loader = {}; +var loader = {}; -__loader.packages = {}; +loader.packages = {}; -__loader.packagesLinenoOrder = [{ filename: 'loader.js', lineno: 0 }]; +loader.packagesLinenoOrder = [{ filename: 'loader.js', lineno: 0 }]; -__loader.extpaths = ['?', '?.js', '?.json', '?/index.js']; +loader.extpaths = ['?', '?.js', '?.json', '?/index.js']; -__loader.require = function(path) { +loader.require = function(path) { var module; - var extpaths = __loader.extpaths; + var extpaths = loader.extpaths; for (var i = 0, ii = extpaths.length; !module && i < ii; ++i) { var filepath = extpaths[i].replace('?', path); - module = __loader.packages[filepath]; + module = loader.packages[filepath]; } if (!module) { @@ -25,28 +25,28 @@ __loader.require = function(path) { } module.exports = {}; - module.loader(module, __loader.require); + module.loader(module, loader.require); module.loaded = true; return module.exports; }; -__loader.define = function(path, lineno, loader) { +loader.define = function(path, lineno, loader) { var module = { filename: path, lineno: lineno, loader: loader, }; - __loader.packages[path] = module; - __loader.packagesLinenoOrder.push(module); - __loader.packagesLinenoOrder.sort(function(a, b) { + loader.packages[path] = module; + loader.packagesLinenoOrder.push(module); + loader.packagesLinenoOrder.sort(function(a, b) { return a.lineno - b.lineno; }); }; -__loader.getPackageByLineno = function(lineno) { - var packages = __loader.packagesLinenoOrder; +loader.getPackageByLineno = function(lineno) { + var packages = loader.packagesLinenoOrder; var module; for (var i = 0, ii = packages.length; i < ii; ++i) { var next = packages[i]; @@ -58,6 +58,6 @@ __loader.getPackageByLineno = function(lineno) { return module; }; -return __loader; +return loader; })(); From cfefe64b498be2e5cb7cc52c3ffa659e9812dcfb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 30 May 2014 18:31:34 -0400 Subject: [PATCH 235/791] Add loader.js paths for attempting to load relative to multiple paths --- src/js/loader.js | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/js/loader.js b/src/js/loader.js index 8708e88e..828929ab 100644 --- a/src/js/loader.js +++ b/src/js/loader.js @@ -8,14 +8,10 @@ loader.packagesLinenoOrder = [{ filename: 'loader.js', lineno: 0 }]; loader.extpaths = ['?', '?.js', '?.json', '?/index.js']; -loader.require = function(path) { - var module; - var extpaths = loader.extpaths; - for (var i = 0, ii = extpaths.length; !module && i < ii; ++i) { - var filepath = extpaths[i].replace('?', path); - module = loader.packages[filepath]; - } +loader.paths = ['lib', 'vendor']; +loader.require = function(path) { + var module = loader.getPackage(path); if (!module) { throw new Error("Cannot find module'" + path + "'"); } @@ -31,18 +27,38 @@ loader.require = function(path) { return module.exports; }; -loader.define = function(path, lineno, loader) { +var compareLineno = function(a, b) { return a.lineno - b.lineno; }; + +loader.define = function(path, lineno, loadfun) { var module = { filename: path, lineno: lineno, - loader: loader, + loader: loadfun, }; loader.packages[path] = module; loader.packagesLinenoOrder.push(module); - loader.packagesLinenoOrder.sort(function(a, b) { - return a.lineno - b.lineno; - }); + loader.packagesLinenoOrder.sort(compareLineno); +}; + +loader.getPackage = function(path) { + var module = loader.getPackageAtPath(path); + var paths = loader.paths; + for (var i = 0, ii = paths.length; !module && i < ii; ++i) { + var dirpath = paths[i]; + module = loader.getPackageAtPath(dirpath + '/' + path); + } + return module; +}; + +loader.getPackageAtPath = function(path) { + var module; + var extpaths = loader.extpaths; + for (var i = 0, ii = extpaths.length; !module && i < ii; ++i) { + var filepath = extpaths[i].replace('?', path); + module = loader.packages[filepath]; + } + return module; }; loader.getPackageByLineno = function(lineno) { From fb8573ab93bca16e1924a110926a8ba574d22148 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 30 May 2014 18:34:52 -0400 Subject: [PATCH 236/791] Remove lib from requires to modules in lib --- src/js/lib/myutil.js | 2 +- src/js/lib/safe.js | 2 +- src/js/main.js | 2 +- src/js/settings/settings.js | 2 +- src/js/smartpackage/package-pebble.js | 2 +- src/js/smartpackage/package.js | 8 ++++---- src/js/ui/accel.js | 2 +- src/js/ui/card.js | 6 +++--- src/js/ui/circle.js | 4 ++-- src/js/ui/element.js | 6 +++--- src/js/ui/image.js | 4 ++-- src/js/ui/imageservice.js | 4 ++-- src/js/ui/index.js | 2 +- src/js/ui/inverter.js | 4 ++-- src/js/ui/menu.js | 6 +++--- src/js/ui/propable.js | 4 ++-- src/js/ui/rect.js | 4 ++-- src/js/ui/simply-pebble.js | 4 ++-- src/js/ui/stage.js | 4 ++-- src/js/ui/tests.js | 8 ++++---- src/js/ui/text.js | 4 ++-- src/js/ui/window.js | 6 +++--- src/js/ui/windowstack.js | 6 +++--- 23 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/js/lib/myutil.js b/src/js/lib/myutil.js index 863b8b90..e46cb6ae 100644 --- a/src/js/lib/myutil.js +++ b/src/js/lib/myutil.js @@ -1,4 +1,4 @@ -var util2 = require('lib/util2'); +var util2 = require('util2'); var myutil = {}; diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 1d658dca..d60d50fd 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -7,7 +7,7 @@ /* global __loader */ -var ajax = require('lib/ajax'); +var ajax = require('ajax'); var safe = {}; diff --git a/src/js/main.js b/src/js/main.js index 30206efd..6f852f46 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -6,7 +6,7 @@ * By default, this will initialize all the libraries and run app.js */ -require('lib/safe'); +require('safe'); //Pebble.Settings = require('settings/settings'); Pebble.Accel = require('ui/accel'); diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index ee80f5e6..18a2790a 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -1,4 +1,4 @@ -var util2 = require('lib/util2'); +var util2 = require('util2'); var Settings = module.exports; diff --git a/src/js/smartpackage/package-pebble.js b/src/js/smartpackage/package-pebble.js index fb311bfe..72dc9b7a 100644 --- a/src/js/smartpackage/package-pebble.js +++ b/src/js/smartpackage/package-pebble.js @@ -1,4 +1,4 @@ -var myutil = require('lib/myutil'); +var myutil = require('myutil'); var package = require('smartpackage/package'); var simply = require('simply/simply'); diff --git a/src/js/smartpackage/package.js b/src/js/smartpackage/package.js index c89c55a4..eea8dc63 100644 --- a/src/js/smartpackage/package.js +++ b/src/js/smartpackage/package.js @@ -1,6 +1,6 @@ -var ajax = require('lib/ajax'); -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); +var ajax = require('ajax'); +var util2 = require('util2'); +var myutil = require('myutil'); var Settings = require('settings/settings'); var simply = require('simply'); @@ -36,7 +36,7 @@ package.abspath = function(root, path) { root = package.basepath(package.module.filename); } return myutil.abspath(root, path); -} +}; package.name = function(rootfile, path) { diff --git a/src/js/ui/accel.js b/src/js/ui/accel.js index cd2a1a91..5612e2a8 100644 --- a/src/js/ui/accel.js +++ b/src/js/ui/accel.js @@ -1,4 +1,4 @@ -var Emitter = require('lib/emitter'); +var Emitter = require('emitter'); var Accel = new Emitter(); diff --git a/src/js/ui/card.js b/src/js/ui/card.js index bcf621be..6399d514 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -1,6 +1,6 @@ -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); -var Emitter = require('lib/emitter'); +var util2 = require('util2'); +var myutil = require('myutil'); +var Emitter = require('emitter'); var WindowStack = require('ui/windowstack'); var Propable = require('ui/propable'); var Window = require('ui/window'); diff --git a/src/js/ui/circle.js b/src/js/ui/circle.js index 174a878d..d08a8a47 100644 --- a/src/js/ui/circle.js +++ b/src/js/ui/circle.js @@ -1,5 +1,5 @@ -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); +var util2 = require('util2'); +var myutil = require('myutil'); var StageElement = require('ui/element'); var defaults = { diff --git a/src/js/ui/element.js b/src/js/ui/element.js index b9ad044c..42e23404 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -1,6 +1,6 @@ -var util2 = require('lib/util2'); -var Vector2 = require('lib/vector2'); -var myutil = require('lib/myutil'); +var util2 = require('util2'); +var Vector2 = require('vector2'); +var myutil = require('myutil'); var WindowStack = require('ui/windowstack'); var Propable = require('ui/propable'); var simply = require('ui/simply'); diff --git a/src/js/ui/image.js b/src/js/ui/image.js index d2b75d14..55466988 100644 --- a/src/js/ui/image.js +++ b/src/js/ui/image.js @@ -1,5 +1,5 @@ -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); +var util2 = require('util2'); +var myutil = require('myutil'); var Propable = require('ui/propable'); var StageElement = require('ui/element'); diff --git a/src/js/ui/imageservice.js b/src/js/ui/imageservice.js index f79a30fb..e6a4e7ae 100644 --- a/src/js/ui/imageservice.js +++ b/src/js/ui/imageservice.js @@ -1,5 +1,5 @@ -var imagelib = require('lib/image'); -var myutil = require('lib/myutil'); +var myutil = require('myutil'); +var imagelib = require('image'); var simply = require('ui/simply'); var appinfo = require('appinfo'); diff --git a/src/js/ui/index.js b/src/js/ui/index.js index 908623a2..d0728eda 100644 --- a/src/js/ui/index.js +++ b/src/js/ui/index.js @@ -1,6 +1,6 @@ var UI = {}; -UI.Vector2 = require('lib/vector2'); +UI.Vector2 = require('vector2'); UI.Card = require('ui/card'); UI.Menu = require('ui/menu'); UI.Stage = require('ui/stage'); diff --git a/src/js/ui/inverter.js b/src/js/ui/inverter.js index ce504bb2..e18ce7c1 100644 --- a/src/js/ui/inverter.js +++ b/src/js/ui/inverter.js @@ -1,5 +1,5 @@ -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); +var util2 = require('util2'); +var myutil = require('myutil'); var StageElement = require('ui/element'); var Inverter = function(elementDef) { diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 48d77320..ccfa8a5d 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -1,6 +1,6 @@ -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); -var Emitter = require('lib/emitter'); +var util2 = require('util2'); +var myutil = require('myutil'); +var Emitter = require('emitter'); var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); var simply = require('ui/simply'); diff --git a/src/js/ui/propable.js b/src/js/ui/propable.js index fb9a5ce7..e6528209 100644 --- a/src/js/ui/propable.js +++ b/src/js/ui/propable.js @@ -1,5 +1,5 @@ -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); +var util2 = require('util2'); +var myutil = require('myutil'); var Propable = function(def) { this.state = def || {}; diff --git a/src/js/ui/rect.js b/src/js/ui/rect.js index 76321ce8..638b6211 100644 --- a/src/js/ui/rect.js +++ b/src/js/ui/rect.js @@ -1,5 +1,5 @@ -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); +var util2 = require('util2'); +var myutil = require('myutil'); var StageElement = require('ui/element'); var defaults = { diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 1993fc45..b989902a 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1,5 +1,5 @@ -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); +var util2 = require('util2'); +var myutil = require('myutil'); var Accel = require('ui/accel'); var ImageService = require('ui/imageservice'); var WindowStack = require('ui/windowstack'); diff --git a/src/js/ui/stage.js b/src/js/ui/stage.js index 62bd3b36..e45920bb 100644 --- a/src/js/ui/stage.js +++ b/src/js/ui/stage.js @@ -1,5 +1,5 @@ -var util2 = require('lib/util2'); -var Emitter = require('lib/emitter'); +var util2 = require('util2'); +var Emitter = require('emitter'); var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); var simply = require('ui/simply'); diff --git a/src/js/ui/tests.js b/src/js/ui/tests.js index 7060f8cf..9275ac5b 100644 --- a/src/js/ui/tests.js +++ b/src/js/ui/tests.js @@ -6,21 +6,21 @@ tests.setTimeoutErrors = function () { var i = 0; var interval = setInterval(function() { clearInterval(interval); - wind.titlex("i = " + i++); + wind.titlex('i = ' + i++); }, 1000); }; tests.ajaxErrors = function() { - var ajax = require('lib/ajax'); + var ajax = require('ajax'); var ajaxCallback = function(reqStatus, reqBody, request) { console.logx('broken call'); }; - ajax({url: 'http://www.google.fr/' }, ajaxCallback, ajaxCallback); + ajax({ url: 'http://www.google.fr/' }, ajaxCallback, ajaxCallback); }; tests.geolocationErrors = function () { navigator.geolocation.getCurrentPosition(function(coords) { - console.logx("Got coords: " + coords); + console.logx('Got coords: ' + coords); }); }; diff --git a/src/js/ui/text.js b/src/js/ui/text.js index 708e0dd7..95df689c 100644 --- a/src/js/ui/text.js +++ b/src/js/ui/text.js @@ -1,5 +1,5 @@ -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); +var util2 = require('util2'); +var myutil = require('myutil'); var Propable = require('ui/propable'); var StageElement = require('ui/element'); diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 4691fcf7..464f5428 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -1,6 +1,6 @@ -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); -var Emitter = require('lib/emitter'); +var util2 = require('util2'); +var myutil = require('myutil'); +var Emitter = require('emitter'); var Accel = require('ui/accel'); var WindowStack = require('ui/windowstack'); var Propable = require('ui/propable'); diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index 355b1f4a..a0f30402 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -1,6 +1,6 @@ -var util2 = require('lib/util2'); -var myutil = require('lib/myutil'); -var Emitter = require('lib/emitter'); +var util2 = require('util2'); +var myutil = require('myutil'); +var Emitter = require('emitter'); var simply = require('ui/simply'); var WindowStack = function() { From cd2f73faeaefdce7479a9f496afd6c6581e365c6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 16:39:35 -0400 Subject: [PATCH 237/791] Rename card banner to image --- src/js/ui/card.js | 2 +- src/js/ui/simply-pebble.js | 2 +- src/simply/simply_msg.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/ui/card.js b/src/js/ui/card.js index bcf621be..e4e2a1fb 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -38,7 +38,7 @@ var textProps = [ var imageProps = [ 'icon', 'subicon', - 'banner', + 'image', ]; var actionProps = [ diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 1993fc45..eb8fd0e0 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -143,7 +143,7 @@ var setCardParams = setWindowParams.concat([{ name: 'subicon', type: Image, }, { - name: 'banner', + name: 'image', type: Image, }, { name: 'style', diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 95e02803..f2e20e91 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -67,7 +67,7 @@ enum SimplySetUiParam { SetUi_body, SetUi_icon, SetUi_subicon, - SetUi_banner, + SetUi_image, SetUi_style, }; @@ -206,7 +206,7 @@ static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { break; case SetUi_icon: case SetUi_subicon: - case SetUi_banner: + case SetUi_image: ui->ui_layer.imagefields[tuple->key - SetUi_icon] = tuple->value->uint32; break; case SetUi_style: From fd9bc632232e44d36201014b00067d337a9557f5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 16:44:20 -0400 Subject: [PATCH 238/791] Change text element to use update time unit to determine time formatting --- src/js/ui/simply-pebble.js | 5 +---- src/js/ui/text.js | 3 +-- src/simply/simply_msg.c | 11 +++-------- src/simply/simply_stage.c | 6 +++--- src/simply/simply_stage.h | 3 +-- 5 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index eb8fd0e0..d14397c1 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -338,10 +338,7 @@ var commands = [{ name: 'textAlign', type: TextAlignment, }, { - name: 'time', - type: Boolean, - }, { - name: 'timeUnits', + name: 'updateTimeUnit', type: TimeUnits, }, { name: 'image', diff --git a/src/js/ui/text.js b/src/js/ui/text.js index 708e0dd7..0926d761 100644 --- a/src/js/ui/text.js +++ b/src/js/ui/text.js @@ -9,8 +9,7 @@ var textProps = [ 'color', 'textOverflow', 'textAlign', - 'time', - 'timeUnits', + 'updateTimeUnit', ]; var defaults = { diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index f2e20e91..4de49d89 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -112,8 +112,7 @@ enum ElementParam { ElementTextColor, ElementTextOverflow, ElementTextAlignment, - ElementTextIsTime, - ElementTextTimeUnits, + ElementTextUpdateTimeUnit, ElementImage, ElementCompositing, }; @@ -442,12 +441,8 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { case ElementTextAlignment: ((SimplyElementText*) element)->alignment = tuple->value->uint8; break; - case ElementTextIsTime: - ((SimplyElementText*) element)->is_time = tuple->value->uint8; - update_ticker = true; - break; - case ElementTextTimeUnits: - ((SimplyElementText*) element)->time_units = tuple->value->uint8; + case ElementTextUpdateTimeUnit: + ((SimplyElementText*) element)->time_unit = tuple->value->uint8; update_ticker = true; break; case ElementImage: diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 9c52dec1..39e6c5cc 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -101,7 +101,7 @@ static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementTex rect_element_draw(ctx, self, (SimplyElementRect*) element); char *text = element->text; if (element->text_color != GColorClear && is_string(text)) { - if (element->is_time) { + if (element->time_unit) { text = format_time(text); } GFont font = element->font ? element->font : fonts_get_system_font(FONT_KEY_FONT_FALLBACK); @@ -351,8 +351,8 @@ void simply_stage_update_ticker(SimplyStage *self) { while (element) { if (element->type == SimplyElementTypeText) { SimplyElementText *text = (SimplyElementText*) element; - if (text->is_time) { - units |= text->time_units; + if (text->time_unit) { + units |= text->time_unit; } } element = (SimplyElementCommon*) element->node.next; diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index d02691b2..7ad3c965 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -75,11 +75,10 @@ struct SimplyElementText { }; char *text; GFont font; - TimeUnits time_units:8; + TimeUnits time_unit:8; GColor text_color:2; GTextOverflowMode overflow_mode:2; GTextAlignment alignment:2; - bool is_time:1; }; typedef struct SimplyElementImage SimplyElementImage; From b223170b2d880f39987a05e8d77c54d16dce3395 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 17:01:14 -0400 Subject: [PATCH 239/791] Export ui inverter stage element class --- src/js/ui/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/ui/index.js b/src/js/ui/index.js index 908623a2..5e171765 100644 --- a/src/js/ui/index.js +++ b/src/js/ui/index.js @@ -8,5 +8,6 @@ UI.Rect = require('ui/rect'); UI.Circle = require('ui/circle'); UI.Text = require('ui/text'); UI.Image = require('ui/image'); +UI.Inverter = require('ui/inverter'); module.exports= UI; From 2532d589af52ee5186f7f9603642cf01914b792d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 17:14:33 -0400 Subject: [PATCH 240/791] Rename the singleClick event to click --- src/js/app.js | 2 +- src/js/ui/simply-pebble.js | 4 ++-- src/js/ui/window.js | 6 +++--- src/simply/simply_msg.c | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 5aee24dc..64e89d9e 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -9,7 +9,7 @@ var wind = new Pebble.UI.Card({ }); wind.show(); -wind.on('singleClick', function(e) { +wind.on('click', function(e) { console.log("Button pressed: " + JSON.stringify(e)); if (e.button == 'up') { var menu = new Pebble.UI.Menu(); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index d14397c1..46431974 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -174,7 +174,7 @@ var commands = [{ name: 'setCard', params: setCardParams, }, { - name: 'singleClick', + name: 'click', params: [{ name: 'button', }], @@ -702,7 +702,7 @@ SimplyPebble.onAppMessage = function(e) { case 'windowHide': WindowStack.emitHide(payload[1]); break; - case 'singleClick': + case 'click': case 'longClick': var button = buttons[payload[1]]; Window.emitClick(command.name, button); diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 4691fcf7..18015908 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -149,7 +149,7 @@ Window.prototype.action = function(field, value, clear) { }; var isBackEvent = function(type, subtype) { - return ((type === 'singleClick' || type === 'longClick') && subtype === 'back'); + return ((type === 'click' || type === 'longClick') && subtype === 'back'); }; Window.prototype.onAddHandler = function(type, subtype) { @@ -228,7 +228,7 @@ Window.prototype._buttonAutoConfig = function() { if (!buttonState || buttonState.configMode !== 'auto') { return; } - var singleBackCount = this.listenerCount('singleClick', 'back'); + var singleBackCount = this.listenerCount('click', 'back'); var longBackCount = this.listenerCount('longClick', 'back'); var useBack = singleBackCount + longBackCount > 0; if (useBack !== buttonState.config.back) { @@ -249,7 +249,7 @@ Window.emit = function(type, subtype, e, klass) { /** * Simply.js button click event. This can either be a single click or long click. - * Use the event type 'singleClick' or 'longClick' to subscribe to these events. + * Use the event type 'click' or 'longClick' to subscribe to these events. * @typedef simply.clickEvent * @property {string} button - The button that was pressed: 'back', 'up', 'select', or 'down'. This is also the event subtype. */ diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 4de49d89..9b76e149 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -20,7 +20,7 @@ enum SimplyACmd { SimplyACmd_windowShow, SimplyACmd_windowHide, SimplyACmd_setUi, - SimplyACmd_singleClick, + SimplyACmd_click, SimplyACmd_longClick, SimplyACmd_accelTap, SimplyACmd_vibe, @@ -615,7 +615,7 @@ static bool send_click(SimplyACmd type, ButtonId button) { } bool simply_msg_single_click(ButtonId button) { - return send_click(SimplyACmd_singleClick, button); + return send_click(SimplyACmd_click, button); } bool simply_msg_long_click(ButtonId button) { From e85a85478b591c784d795918d13eb42c19e6d8a0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 18:16:55 -0400 Subject: [PATCH 241/791] Add simply res custom font --- src/simply/simply_msg.c | 6 +++++- src/simply/simply_res.c | 45 ++++++++++++++++++++++++++++++++++++++++- src/simply/simply_res.h | 35 ++++++++++++++++++++++++++++---- src/simply/simply_ui.c | 3 ++- 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 9b76e149..ffbf71c7 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -430,7 +430,11 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { strset(&((SimplyElementText*) element)->text, tuple->value->cstring); break; case ElementTextFont: - ((SimplyElementText*) element)->font = fonts_get_system_font(tuple->value->cstring); + if (tuple->type == TUPLE_CSTRING) { + ((SimplyElementText*) element)->font = fonts_get_system_font(tuple->value->cstring); + } else { + ((SimplyElementText*) element)->font = simply_res_get_font(simply->res, tuple->value->uint32); + } break; case ElementTextColor: ((SimplyElementText*) element)->text_color = tuple->value->uint8; diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 125638ab..79acca98 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -5,7 +5,7 @@ #include static bool id_filter(List1Node *node, void *data) { - return (((SimplyImage*) node)->id == (uint32_t)(uintptr_t) data); + return (((SimplyResItemCommon*) node)->id == (uint32_t)(uintptr_t) data); } static void destroy_image(SimplyRes *self, SimplyImage *image) { @@ -14,6 +14,12 @@ static void destroy_image(SimplyRes *self, SimplyImage *image) { free(image); } +static void destroy_font(SimplyRes *self, SimplyFont *font) { + list1_remove(&self->fonts, &font->node); + fonts_unload_custom_font(font->font); + free(font); +} + GBitmap *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { SimplyImage *image = malloc(sizeof(*image)); if (!image) { @@ -91,10 +97,47 @@ GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder return NULL; } +GFont *simply_res_auto_font(SimplyRes *self, uint32_t id) { + if (!id) { + return NULL; + } + SimplyFont *font = (SimplyFont*) list1_find(self->fonts, id_filter, (void*)(uintptr_t) id); + if (font) { + return &font->font; + } + if (id <= ARRAY_LENGTH(resource_crc_table)) { + return simply_res_add_custom_font(self, id); + } + return NULL; +} + void simply_res_clear(SimplyRes *self) { while (self->images) { destroy_image(self, (SimplyImage*) self->images); } + + while (self->fonts) { + destroy_font(self, (SimplyFont*) self->fonts); + } +} + +GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id) { + SimplyFont *font = malloc(sizeof(*font)); + if (!font) { + return NULL; + } + + GFont custom_font = fonts_load_custom_font(resource_get_handle(id)); + if (!custom_font) { + free(font); + return NULL; + } + + list1_prepend(&self->fonts, &font->node); + + window_stack_schedule_top_window_render(); + + return &font->font; } SimplyRes *simply_res_create() { diff --git a/src/simply/simply_res.h b/src/simply/simply_res.h index 218878bf..15e1905e 100644 --- a/src/simply/simply_res.h +++ b/src/simply/simply_res.h @@ -6,20 +6,44 @@ #define simply_res_get_image(self, id) simply_res_auto_image(self, id, false) -typedef struct SimplyRes SimplyRes; +#define simply_res_get_font(self, id) simply_res_auto_font(self, id) -typedef struct SimplyImage SimplyImage; +typedef struct SimplyRes SimplyRes; struct SimplyRes { List1Node *images; + List1Node *fonts; }; +typedef struct SimplyResItemCommon SimplyResItemCommon; + +#define SimplyResItemCommonDef { \ + List1Node node; \ + uint32_t id; \ +} + +struct SimplyResItemCommon SimplyResItemCommonDef; + +#define SimplyResItemCommonMember \ + union { \ + struct SimplyResItemCommon common; \ + struct SimplyResItemCommonDef; \ + } + +typedef struct SimplyImage SimplyImage; + struct SimplyImage { - List1Node node; - uint32_t id; + SimplyResItemCommonMember; GBitmap bitmap; }; +typedef struct SimplyFont SimplyFont; + +struct SimplyFont { + SimplyResItemCommonMember; + GFont font; +}; + SimplyRes *simply_res_create(); void simply_res_destroy(SimplyRes *self); void simply_res_clear(SimplyRes *self); @@ -28,4 +52,7 @@ GBitmap *simply_res_add_bundled_image(SimplyRes *self, uint32_t id); GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint32_t *pixels); GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder); +GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id); +GFont *simply_res_auto_font(SimplyRes *self, uint32_t id); + void simply_res_remove_image(SimplyRes *self, uint32_t id); diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index c1f09026..4d87c75a 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -60,7 +60,8 @@ void simply_ui_set_style(SimplyUi *self, int style_index) { } self->ui_layer.style = &STYLES[style_index]; if (self->ui_layer.style->custom_body_font_id) { - self->ui_layer.custom_body_font = fonts_load_custom_font(self->ui_layer.custom_body_font); + self->ui_layer.custom_body_font = fonts_load_custom_font( + resource_get_handle(self->ui_layer.style->custom_body_font_id)); } layer_mark_dirty(self->ui_layer.layer); } From 82c8c419f77357b553154f760c0f7d2cc27d6b82 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Sat, 31 May 2014 18:34:59 -0400 Subject: [PATCH 242/791] Simplify main.js and remove dead code, comments, etc --- src/js/main.js | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 163278ab..1124fbd0 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -3,35 +3,12 @@ * you want to change the way PebbleJS starts, the script it runs or the libraries * it loads. * - * By default, this will initialize all the libraries and run app.js + * By default, this will run app.js */ require('lib/safe'); -Pebble.Settings = require('settings/settings'); -Pebble.Accel = require('ui/accel'); -Pebble.UI = require('ui'); - -//Pebble.SmartPackage = require('pebble/smartpackage'); - Pebble.addEventListener('ready', function(e) { - // Load the SimplyJS Pebble implementation - require('ui/simply-pebble').init(); - Pebble.Settings.init(); - Pebble.Accel.init(); - console.log("Done loading PebbleJS - Starting app."); - // Load local file require('app.js'); - //require('ui/tests'); - - // Or use Smart Package to load a remote JS file - //Pebble.SmartPackage.init('http://www.sarfata.org/myapp.js'); - - // or Ask the user to enter a URL to run in the Settings - //Pebble.SmartPackage.initWithSettings(); - - // or Load a list of app and let the user choose in the Settings which one to run. - //Pebble.SmartPackage.loadBundle('http://www.sarfata.org/myapps.json'); }); - From 32a0c12be0f2e9c2b5073e964338dc01b4ad3917 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 19:03:39 -0400 Subject: [PATCH 243/791] Fix safe.js translate stack android to translate only relevant lines --- src/js/lib/safe.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index d60d50fd..d5357218 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -55,16 +55,13 @@ safe.translateStackIOS = function(stack) { safe.translateStackAndroid = function(stack) { var lines = stack.split('\n'); - for (var i = lines.length - 1; i >= 0; --i) { + for (var i = lines.length - 1; i > 0; --i) { var line = lines[i]; - var m = line.match(/\?params=(.*):(\d+):(\d+)/); var name, lineno, colno; - if (m) { - name = JSON.parse(decodeURIComponent(m[1])).loadUrl.replace(/^.*\//, ''); - lineno = m[2]; - colno = m[3]; + if (line.match(/jskit_startup\.html/)) { + lines.splice(i, 1); } else { - m = line.match(/^.*\/(.*?):(\d+):(\d+)/); + var m = line.match(/^.*\/(.*?):(\d+):(\d+)/); if (m) { name = m[1]; lineno = m[2]; @@ -73,6 +70,7 @@ safe.translateStackAndroid = function(stack) { } if (name) { var pos = safe.translatePos(name, lineno, colno); + console.log(pos, name, lineno, colno); if (line.match(/\(.*\)/)) { line = line.replace(/\(.*\)/, '(' + pos + ')'); } else { From 235717d710eca71f872547a6d0ae32641767f434 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 19:04:43 -0400 Subject: [PATCH 244/791] Add loader.js multiple require paths support --- src/js/loader.js | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/js/loader.js b/src/js/loader.js index 828929ab..5ee4f548 100644 --- a/src/js/loader.js +++ b/src/js/loader.js @@ -8,12 +8,30 @@ loader.packagesLinenoOrder = [{ filename: 'loader.js', lineno: 0 }]; loader.extpaths = ['?', '?.js', '?.json', '?/index.js']; -loader.paths = ['lib', 'vendor']; +loader.paths = ['/', 'lib', 'vendor']; -loader.require = function(path) { - var module = loader.getPackage(path); +loader.basepath = function(path) { + return path.replace(/[^\/]*$/, ''); +}; + +var replace = function(a, regexp, b) { + var z; + do { + z = a; + } while (z !== (a = a.replace(regexp, b))); + return z; +}; + +loader.normalize = function(path) { + path = replace(path, /(?:\/\.?\/)+/g, '/'); + path = replace(path, /[^\/]*\/\.\.\//, ''); + return path; +}; + +loader.require = function(path, requirer) { + var module = loader.getPackage(path, requirer); if (!module) { - throw new Error("Cannot find module'" + path + "'"); + throw new Error("Cannot find module '" + path + "'"); } if (module.exports) { @@ -21,7 +39,7 @@ loader.require = function(path) { } module.exports = {}; - module.loader(module, loader.require); + module.loader(module, function(path) { return loader.require(path, module); }); module.loaded = true; return module.exports; @@ -41,8 +59,16 @@ loader.define = function(path, lineno, loadfun) { loader.packagesLinenoOrder.sort(compareLineno); }; -loader.getPackage = function(path) { - var module = loader.getPackageAtPath(path); +loader.getPackage = function(path, requirer) { + var module; + if (requirer) { + module = loader.getPackageAtPath(loader.basepath(requirer.filename) + '/' + path); + } + + if (!module) { + module = loader.getPackageAtPath(path); + } + var paths = loader.paths; for (var i = 0, ii = paths.length; !module && i < ii; ++i) { var dirpath = paths[i]; @@ -52,6 +78,8 @@ loader.getPackage = function(path) { }; loader.getPackageAtPath = function(path) { + path = loader.normalize(path); + var module; var extpaths = loader.extpaths; for (var i = 0, ii = extpaths.length; !module && i < ii; ++i) { From 646456aac7409e46cdbc82b01355435a667b0ed4 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Sat, 31 May 2014 19:08:11 -0400 Subject: [PATCH 245/791] Switch doc to markdown format and flatdoc --- README.md | 662 +++++++++++++++++++++++++++++++++++++++++++++++++++-- jsdoc.json | 11 - 2 files changed, 638 insertions(+), 35 deletions(-) delete mode 100644 jsdoc.json diff --git a/README.md b/README.md index 6d39431e..d9592757 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,74 @@ -Pebble.JS +Pebble.js ========= -Pebble.JS is a JavaScript framework for Pebble development. Pebble.JS allows you to write great looking Pebble apps with only JavaScript code. +Pebble.js lets you write beautiful Pebble applications completely in JavaScript. -## How does it work? +Pebble.js applications run on your phone. They have access to all the resources of your phone (Internet connectivity, GPS, almost unlimited memory, etc). Because they are written in JavaScript they are also perfect to make HTTP request and connect your Pebble to the Internet. -Pebble.JS is built on top of the Pebble SDK. Your JavaScript code runs on the phone and uses the Pebble.JS library to exchange messages with the Pebble watch currently connected. +> ![JSConf 2014](http://2014.jsconf.us/img/logo.png) +> +> Pebble.js was announced during JSConf 2014! -The Pebble runs the Pebble.JS application which translates the commands into a UI on the screen and relays user interactions (accelerometer, buttons, etc) to the code. +## Getting Started -## How to use it? + * In CloudPebble.net -Just open the `src/js/app.js` and start writing code! + The easiest way to use Pebble.js is in [CloudPebble.net](http://www.cloudpebble.net). Select the 'Pebble.js' project type when creating a new project. - var card = new Pebble.UI.Card({ title: "Hello World", body: "Your first Pebble app!" }); - card.show(); + [Build a Pebble.js application now in CloudPebble.net >](http://www.cloudpebble.net) -Reacting to button interactions is also very easy: + * With the Pebble SDK - card.on('singleClick', function(e) { - card.subtitle("Button " + e.button + " pressed."); - } + This option allows you to customize Pebble.js. Follow the [Pebble SDK installation instructions](http://developer.getpebble.com/2/getting-started/) to install the SDK on your computer and [fork this project](http://github.com/pebble/pebblejs) on Github. + + The main entry point for your application is in `src/js/app.js` file. -And making HTTP connection too, with the included `ajax` library. + [Install the Pebble SDK on your computer >](http://developer.getpebble.com/2/getting-started/) - var ajax = require('ajax'); - ajax({ url: 'http://api.theysaidso.com/qod.json', type: 'json' }, - function(data) { - card.body(data.contents.quote); - card.title(data.contents.author); - }); -You can do much more with Pebble.JS: +Pebble.js applications follow modern JavaScript best practices. To get started, you just need to call `require('ui')` to load the UI module and start building user interfaces. + +````js +var UI = require('ui'); +```` + +The basic block to build user interface is the [Card]. A Card is a type of [Window] that occupies the entire screen and allows you to display some text in a pre-structured way: a title at the top, a subtitle below it and a body area for larger paragraphs. Cards can be made scrollable to display large quantities of information, you can also add images next to the title, subtitle or in the body area. + +````js +var window = new UI.Card({ + title: 'Hello World', + body: 'This is your first Pebble app!', + scrollable: true +}); +```` + +After creating a window, you push it on the screen with the `show()` method. + +````js +window.show(); +```` + +To interact with the users, you can use the buttons or the accelerometer. You add callbacks to a window with the `.on()` method: + +````js +window.on('click', function(e) { + window.subtitle("Button " + e.button + " pressed."); +} +```` + +Making HTTP connection is very easy with the included `ajax` library. + +````js +var ajax = require('ajax'); +ajax({ url: 'http://api.theysaidso.com/qod.json', type: 'json' }, + function(data) { + window.body(data.contents.quote); + window.title(data.contents.author); + } +); +```` + +You can do much more with Pebble.js: - Get accelerometer values - Display complex UI mixing geometric elements, text and images @@ -40,9 +77,586 @@ You can do much more with Pebble.JS: - Use the GPS and LocalStorage on the phone - etc! -The full API reference is available on the [Pebble developer website](http://developer.getpebble.com/). +Keep reading for the full [API Reference]. + +## Using Images + +You can use images in your Pebble.js application. Currently all images must be embedded in your applications. They will be resized and converted to black and white when you build your project. + +We recommend that you follow these guidelines when preparing your images for Pebble: + + * Resize all images for the screen of Pebble. A fullscreen image will be 144 pixels wide by 168 pixels high. + * Use an image editor or [HyperDither](http://www.tinrocket.com/hyperdither/) to dither your image in black and white. + * Remember that the maximum size for a Pebble application is 100kB. You will quickly reach that limit if you add too many images. + +To add an image in your application, edit the `appinfo.json` file and add your image: + +````js +{ + "type": "png", + "name": "IMAGE_CHOOSE_A_UNIQUE_IDENTIFIER", + "file": "images/your_image.png" +} +```` + +> If you are using CloudPebble, you can add images in your project configuration (coming soon!). + +To reference your image in Pebble.js, you can use the `name` field or the `file` field. + +````js +// These two examples are both valid ways to show the image declared above in a Card +window.icon('images/your_image.png'); +window.icon('IMAGE_CHOOSE_A_UNIQUE_IDENTIFIER'); +```` + +## Using Fonts + +You can use any of the Pebble system fonts in your Pebble.js applications. Please refer to [this Pebble Developer's blog post](https://developer.getpebble.com/blog/2013/07/24/Using-Pebble-System-Fonts/) for a list of all the Pebble system fonts. + +````js +var Vector2 = require('lib/vector2'); +var stage = new UI.Stage(); + +var text = new UI.Text({ size: Vector2(144, 168), position: Vector2(0, 0), font: 'GOTHIC_18_BOLD', text: "Gothic 18 Bold" }); +stage.add(text); +stage.show(); +```` + +## Examples + +Coming Soon! + +## Acknowledgements + +Pebble.js started as [Simply.JS](http://www.simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! + +This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). + +# API Reference + +## Global namespace + +### require(dependency) + +Loads another JavaScript file allowing you to write a multi-file project. Package loading loosely follows the CommonJS format. + +Exporting is possible by modifying or setting `module.exports` within the required file. The module path is also available as `module.path`. This currently only supports a relative path to another JavaScript file. + +### Pebble + +The `Pebble` object from [PebbleKit JavaScript](https://developer.getpebble.com/2/guides/javascript-guide.html) is available as a global variable. It's usage is discouraged in Pebble.js, instead you should use the objects documented below who provide a cleaner object interface to the same functionalities. + +### window (global) + +A `window` object is provided with a subset of the standard APIs you would find in a normal browser. It's direct usage is discouraged because available functionalities may differ between the iOS and Android runtime environment. + +More specifically: + + - XHR and WebSocket are supported on iOS and Android + - The `` element is not available on iOS + +If in doubt, please contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com). + +## UI + +The UI framework contains all the classes needed to build the user interface of your Pebble applications and interact with the user. + +### Accel + +The `Accel` module allows you to get events from the accelerometer on Pebble. + +You can use the accelerometer in two different ways: + + - To detect tap events. Those events are triggered when the user flicks his wrist or tap on the Pebble. They are the same events that are used to turn the Pebble back-light on. Tap events come with a property to tell you in which direction the Pebble was shook. Tap events are very battery efficient because they are generated directly by the accelerometer inside Pebble. + - To continuously receive streaming data from the accelerometer. In this mode the Pebble will collect accelerometer samples at a specified frequency (from 10Hz to 100Hz), batch those events in an array and pass those to an event handler. In this mode, the Pebble accelerometer needs to continuously transmit data to the processor and to the Bluetooth radio. This will drain the battery much faster. + +````js +var Accel = require('ui/accel'); +```` + +#### Accel.init() + +Before you can use the accelerometer, you must call `Accel.init()`. + +````js +Accel.init(); +```` + +#### Accel.config(accelConfig) + +This function configures the accelerometer. It takes an `accelConfig` object: + +| Name | Type | Argument | Default | Description | +| ---- | :----: | :--------: | --------- | ------------- | +| `rate` | number | (optional) | 100 | The rate accelerometer data points are generated in hertz. Valid values are 10, 25, 50, and 100. | +| `samples` | number | (optional) | 25 | The number of accelerometer data points to accumulate in a batch before calling the event handler. Valid values are 1 to 25 inclusive. | +| `subscribe` | boolean | (optional) | automatic | Whether to subscribe to accelerometer data events. Accel.accelPeek cannot be used when subscribed. Pebble.js will automatically (un)subscribe for you depending on the amount of accelData handlers registered. | + +The number of callbacks will depend on the configuration of the accelerometer. With the default rate of 100Hz and 25 samples, your callback will be called every 250ms with 25 samples each time. + +**Important:** If you configure the accelerometer to send data many events, you will overload the bluetooth connection. We recommend that you send at most 5 events per second. + +#### Accel.peek(callback) + +Peeks at the current accelerometer value. You pass a callback function that will be called with the data point as an event. + +````js +Accel.peek(function(data)) { + console.log('Current acceleration on axis are: X=' + data.x + ' Y=' + data.y + ' Z=' + data.z); +}); +```` + +#### Accel.on('accelTap', callback) + +Subscribe to the `accelTap` event. The callback function will be called with two parameters: + + * `axis`: The axis the tap event occurred on: 'x', 'y', or 'z'. + * `direction`: The direction of the tap along the axis: 1 or -1. + +````js +Accel.on('accelTap', function (axis, direction) { + console.log("Tap event on axis: " + axis + " and direction: " + direction); +} +```` + +#### Accel.on('accelData', callback) + +Subscribe to the 'accelData' event. The callback function will be called with three parameters: + + * `numSamples`: The number of accelerometer samples in this event. + * `sample`: The first data point in the batch. This is provided for convenience. + * `samples`: The accelerometer samples in an array. + +One accelerometer data point is an object with the following properties: + +| Property | Type | Description | +| -------- | : ---: | ------------ | +| `x` | Number | The acceleration across the x-axis (from left to right when facing your Pebble) | +| `y` | Number | The acceleration across the y-axis (from the bottom of the screen to the top of the screen) | +| `z` | Number | The acceleration across the z-axis (going through your Pebble from the back side of your Pebble to the front side - and then through your head if Pebble is facing you ;) | +| `vibe` | boolean | A boolean indicating whether Pebble was vibrating when this sample was measured. | +| `time` | Number | The amount of ticks in millisecond resolution when this point was measured. | + +````js +Accel.on('accelData', function(numSamples, sample, samples) { + console.log("Just received " + numSamples + " from the accelerometer."); +} +```` + +### Window + +`Window` is the basic building block in your Pebble.js application. All window share some common properties and methods. + +Pebble.js provides three types of Windows: + + * [Card]: Displays a title, a subtitle, an image and some text on a screen. The position of the elements are fixed and cannot be changed. + * [Menu]: Displays a menu on the Pebble screen. This is similar to the standard system menu in Pebble. + * [Stage]: This is the most flexible Window. It allows you to add different [Element] ([Circle], [Image], [Rect], [Text]) and to specify a position and size for each of them. You can also animate them. + +| Name | Type | Default | Description | +| ---- | :-------: | --------- | ------------- | +| `clear` | boolean | | | +| `action` | boolean | false | When true, an action bar will be shown on the right side of the screen. | +| `actionUp` | Image | None | An image to display in the action bar, next to the up button. | +| `actionSelect` | Image | None | An image to display in the action bar, next to the select button. +| `actionDown` | Image | None | An image to display in the action bar, next to the down button. | +| `fullscreen` | boolean | false | When true, the Pebble status bar will not be visible and the window will use the entire screen. | +| `scrollable` | boolean | false | When true, the up and down button will scroll the content of this Card. | + +#### Window.show() + +This will push the window to the screen and display it. If user press the 'back' button, they will navigate to the previous screen. + +#### Window.hide() + +This hides the window. + +If the window is currently displayed, this will take the user to the previously displayed window. + +If the window is not currently displayed, this will remove it from the window stack. The user will not be able to get back to it with the back button. + +````js +var splashScreen = new UI.Card({ 'image': 'images/splash.png' }); +splashScreen.show(); + +var mainScreen = new UI.Menu(); + +setTimeout(function() { + // Display the mainScreen + mainScreen.show(); + // Hide the splashScreen to avoid showing it when the user press Back. + splashScreen.hide(); +}, 400); +```` + +#### Window.on('click', button, handler) + +Registers a handler to call when `button` is pressed. + +````js +window.on('click', 'up', function() { + console.log('Up clicked!'); +}); +```` + +You can register a handler for the 'up', 'select', 'down', and 'back' buttons. + +**Note:** You can also register button handlers for `longClick`. + +#### Window.on('longClick', button, handler) + +Just like `Window.on('click', button, handler)` but for 'longClick' events. + +#### Window.action() + +Accessor to the `action` property. See [Window]. + +#### Window.actionUp() + +Accessor to the `actionUp` property. See [Window]. + +#### Window.actionSelect() + +Accessor to the `actionSelect` property. See [Window]. + +#### Window.actionDown() + +Accessor to the `actionDown` property. See [Window]. + +#### Window.fullscreen(fullscreen) + +Accessor to the `fullscreen` property. See [Window]. + +### Card + +A Card is a type of [Window] that allows you to display a title, a subtitle, an image and a body on the screen of Pebble. + +Just like any window, you can initialize a Card by passing an object to the constructor or by calling accessors to change the properties. + +````js +var card = new UI.Card({ + title: "Hello People!" +}); +card.body("This is the content of my card!"); +```` + +The properties available on a [Card] are: + +| Name | Type | Default | Description | +| ---- | :-------: | --------- | ------------- | +| `title` | string | "" | Text to display in the title field at the top of the screen | +| `subtitle` | string | "" | Text to display below the title | +| `body` | string | "" | Text to display in the body field. | +| `icon` | Image | null | An image to display before the title text. Refer to [Using Images] for instructions on how to include images in your app. | +| `subicon` | Image | null | An image to display before the subtitle text. Refer to [Using Images] for instructions on how to include images in your app. | +| `banner` | Image | null | An image to display in the center of the screen. Refer to [Using Images] for instructions on how to include images in your app. | +| `scrollable` | boolean | false | Whether the user can scroll this card with the up and down button. When this is enabled, click events on the up and down button will not be transmitted to your app. | +| `style` | string | "small" | Selects the font used to display the body. This can be 'small', 'large' or 'mono' | + +The small and large styles correspond to the system notification styles. Mono sets a monospace font for the body textfield, enabling more complex text UIs or ASCII art. + +Note that all fields will automatically span multiple lines if needed and that you can '\n' to insert line breaks. + +### Menu + +A menu is a type of [Window] that displays a standard Pebble menu on the screen of Pebble. + +Just like any window, you can initialize a Menu by passing an object to the constructor or by calling accessors to change the properties. + +The properties available on a [Menu] are: + +| Name | Type | Default | Description | +| ---- |:-------:|---------|-------------| +| `scrollable` | boolean | false | Whether the user can scroll this card with the up and down button. When this is enabled, click events on the up and down button will not be transmitted to your app. | +| `sections` | Number | | | + +#### Menu.section(sectionIndex, section) + +#### Menu.items(sectionIndex, items) + +#### Menu.item(sectionIndex, itemIndex, item) + +#### Menu.on('select', callback) + +Registers a callback called when an item in the menu is selected. + +**Note:** You can also register a callback for 'longSelect' event, triggered when the user long clicks on an item. + +#### Menu.on('longSelect', callback) + +See [Menu.on('select, callback)] + +### Stage + +A stage is a type of [Window] that displays a completely customizable user interface on the screen. When you initialize it, a [Stage] is empty and you will need to create instances of [Element] and add them to the stage. + +Just like any window, you can initialize a Stage by passing an object to the constructor or by calling accessors to change the properties. + +The properties available on a [Stage] are: + +| Name | Type | Default | Description | +| ---- |:-------:|---------|-------------| +| `scrollable` | boolean | false | Whether the user can scroll this card with the up and down button. When this is enabled, click events on the up and down button will not be transmitted to your app. | + +#### Stage.add(element) + +Adds an element to to this stage. This element will be immediately visible. + +#### Stage.insert(index, element) + +Inserts an element at a specific index in the list of Element. + +#### Stage.remove(element, broadcast) + +Removes an element from the [Stage]. + +#### Stage.index(element) + +Returns the index of an element in the Stage or -1 if the element is not in the Stage. + +#### Stage.each(callback) + +Iterates over all the element on the stage. + +````js +stage.each(function(element) { + console.log("Element: ", element); +} +```` + +### Element + +There are four types of [Element] that can be instantiated at the moment: [Circle], [Image], [Rectangle] and [Text]. + +They all share some common properties: + +| Name | Type | Default | Description | +| ------------ | :-------: | --------- | ------------- | +| `position` | Vector2 | | Position of this element in the stage. | +| `size` | Vector2 | | Size of this element in this stage. | +| `borderColor` | string | '' | Color of the border of this element ('clear', 'black',or 'white'). | +| `backgroundColor` | string | '' | Background color of this element ('clear', 'black' or 'white'). | + +All properties can be initialized by passing an object when creating the Element, and changed with accessors functions who have the name of the properties. Calling an accessor without a parameter will return the current value. + +````js +var Vector2 = require('lib/vector2'); +var element = new Text({ position: new Vector2(0, 0), size: new Vector2(144, 168) }); +element.borderColor('white'); +console.log("This element background color is: ", element.backgroundColor()); +```` + +#### Element.index() + +Returns the index of this element in its stage or -1 if this element is not part of a stage. + +#### Element.remove() + +Removes this element from its stage. + +#### Element.animate(field, value, direction) + +#### Element.position(position) + +Accessor to the `position` property. See [Element]. + +#### Element.size(size) + +Accessor to the `size` property. See [Element]. + +#### Element.borderColor(color) + +Accessor to the `borderColor` property. See [Element]. + +#### Element.backgroundColor(color) + +Accessor to the `backgroundColor` property. See [Element]. + +### Circle + +An [Element] that displays a circle on the screen. + +Default properties value: + + * `backgroundColor`: 'white' + * `borderColor:`: 'clear' + +### Rect + +An [Element] that displays a rectangle on the screen. + +The [Rect] element has the following properties. Just like any other [Element] you can initialize those properties when creating the object or use the accessors. + +| Name | Type | Default | Description | +| ------------ | :-------: | --------- | ------------- | +| `backgroundColor` | string | "white" | Background color of this element ('clear', 'black' or 'white'). | +| `borderColor` | string | "clear" | Color of the border of this element ('clear', 'black',or 'white'). | + +### Text + +An [Element] that displays text on the screen. + +The [Text] element has the following properties. Just like any other [Element] you can initialize those properties when creating the object or use the accessors. + +| Name | Type | Default | Description | +| ------------ | :-------: | --------- | ------------- | +| `text` | string | "" | The text to display in this element. | +| `font` | string | | The font to use for that text element. See [Using Fonts] for more information on the different fonts available and how to add your own fonts. | +| `color` | | 'white' | Color of the text ('white', 'black' or 'clear'). | +| `textOverflow` | 'string' | | How to handle text overflow in this text element ('wrap', 'ellipsis' or 'fill'). | +| `textAlign` | 'string' | | How to align text in this element ('left', 'center' or 'right'). | +| `updateTimeUnits` | string | '' | Specifies the smallest time unit change that will cause this text to be redrawn ('seconds', 'minutes', 'hours', 'days', 'months', 'years'). Use this in conjunction with a time format string in the `text` property. This Text element will be redrawn every `updateTimeUnits` and the text parsed. The text formatting string format is described below. | +| `borderColor` | string | 'clear' | Color of the border of this element ('clear', 'black',or 'white'). | +| `backgroundColor` | string | 'clear' | Background color of this element ('clear', 'black' or 'white'). | + +#### Displaying time in a Text element + +If you want to display the current time or date in a Text element, you can use a time formatting string in the `text` property. The text element will be redrawn every `updateTimeUnits` and the format time will be re-interpreted. + +The available formatting options follows the C `strftime()` function: + +| Specifier | Replaced by | Example | +| ----------- | ------------- | --------- | +| %a | An abbreviation for the day of the week. | "Thu" | +| %A | The full name for the day of the week. | "Thursday" | +| %b | An abbreviation for the month name. | "Aug" | +| %B | The full name of the month. | "August" | +| %c | A string representing the complete date and time | "Mon Apr 01 13:13:13 1992" | +| %d | The day of the month, formatted with two digits. | "23" | +| %H | The hour (on a 24-hour clock), formatted with two digits. | "14" | +| %I | The hour (on a 12-hour clock), formatted with two digits. | "02" | +| %j | The count of days in the year, formatted with three digits (from `001` to `366`). | "235" | +| %m | The month number, formatted with two digits. | "08" | +| %M | The minute, formatted with two digits. | "55" | +| %p | Either `AM` or `PM` as appropriate. | "AM" | +| %S | The second, formatted with two digits. | "02" | +| %U | The week number, formatted with two digits (from `00` to `53`; week number 1 is taken as beginning with the first Sunday in a year). See also `%W`. | "33" | +| %w | A single digit representing the day of the week: Sunday is day 0. | "4" | +| %W | Another version of the week number: like `%U`, but counting week 1 as beginning with the first Monday in a year. | "34" | +| %x | A string representing the complete date. | "Mon Apr 01 1992" | +| %X | A string representing the full time of day (hours, minutes, and seconds). | "13:13:13" | +| %y | The last two digits of the year. | "01" | +| %Y | The full year, formatted with four digits to include the century. | "2001" | +| %Z | Defined by ANSI C as eliciting the time zone if available; it is not available in this implementation (which accepts `%Z` but generates no output for it). | | +| %% | A single character, `%`. | "%" | + +#### Text.text(text) + +Sets the text property. See [Text]. + +#### Text.font(font) + +Sets the font property. See [Text]. + +#### Text.color(color) + +Sets the textOverflow property. See [Text]. + +#### Text.textOverflow(textOverflow) + +Sets the textOverflow property. See [Text]. + +#### Text.textAlign(textAlign) + +Sets the textAlign property. See [Text]. + +#### Text.updateTimeUnit(updateTimeUnits) + +Sets the updateTimeUnits property. See [Text]. + +#### Text.borderColor(borderColor) + +Sets the borderColor property. See [Text]. + +#### Text.backgroundColor(backgroundColor) + +Sets the backgroundColor property. See [Text]. + +### Vibe + +`Vibe` allows you to trigger vibration on the user wrist. + +#### Vibe.vibrate(type) + +````js +var Vibe = require('ui/vibe'); + +// Send a long vibration to the user wrist +Vibe.vibrate('long'); +```` + +| Name | Type | Argument | Default | Description | +| ---- |:----:|:--------:|---------|-------------| +| `type` | string | optional | `short` | The duration of the vibration. `short`, `long` or `double`. | + +## lib + +The lib module contains some useful classes. + +### ajax + +This module gives you a very simple and easy way to make HTTP requests. + +```` +var ajax = require('lib/ajax'); + +ajax({ + url: 'api.theysaidso.com/qod.json', + type: 'json' + }, + function (data) { + console.log("Quote of the day is: " + data.contents.quote); + }, + function (error) { + console.log("Something bad happened :(" + error); + } +); +```` + +#### ajax(options, success, failure) + +The supported options are: + +| Name | Type | Argument | Default | Description | +| ---- | :----: | :--------: | --------- | ------------- | +| `url` | string | | | The URL to make the ajax request to. e.g. 'http://www.example.com?name=value' | +| `method` | string | (optional) | get | The HTTP method to use: 'get', 'post', 'put', 'delete', 'options', or any other standard method supported by the running environment. | +| `type` | string | (optional) | | The expected response format. Specify `json` to have ajax parse the response as json and pass an object as the data parameter. +| `data` | object | (optional) | | The request body, mainly to be used in combination with 'post' or 'put'. e.g. `{ username: 'guest' }` +| `headers` | object | (optional) | | Custom HTTP headers. Specify additional headers. e.g. `{ 'x-extra': 'Extra Header' }` +| `async` | boolean | (optional) | true | Whether the request will be asynchronous. Specify `false` for a blocking, synchronous request. +| `cache` | boolean | (optional) | true | Whether the result may be cached. Specify `false` to use the internal cache buster which appends the URL with the query parameter `_set` to the current time in milliseconds. | + +The `success` callback will be called if the HTTP request is successful (When the status code is 200). The only parameter is the data received from the server. If the option `type: 'json'` was set, the response will automatically be converted to an object; otherwise `data` is a string. + +The `failure` callback is called when an error occurred. The only parameter is a description of the error. + +### Vector2 + +A 2 dimensional vector. The constructor takes two parameter for the x and y properties. + +````js +var Vector2 = require('lib/vector2'); + +var vec = new Vector2(144, 168); +```` + +For more information, please refer to the Vector2 class documentation in the three.js library. -## Who wrote this? Is this supported by Pebble? -Pebble.JS started as [Simply.JS](http://www.simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! +[API Reference]: #api-reference +[Using Images]: #using-images +[Using Fonts]: #using-fonts +[Window]: #window +[Card]: #card +[Menu]: #menu +[Stage]: #stage +[Element]: #element +[Circle]: #circle +[Image]: #image +[Rect]: #rect +[Text]: #text +[Window.show()]: #window-show +[Window.hide()]: #window-hide +[Menu.on('select, callback)]: #menu-on-select-callback diff --git a/jsdoc.json b/jsdoc.json deleted file mode 100644 index 66f40cf2..00000000 --- a/jsdoc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "source": { - "include": [ - "src/js/loader.js", - "src/js/lib/*.js", - "src/js/ui/*.js", - "src/js/settings/*.js", - "src/js/simply/*.js" - ] - } -} From a9cb3162913cda43ac5e63b2376f3b1ff85678a9 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Sat, 31 May 2014 19:08:31 -0400 Subject: [PATCH 246/791] Remove incorrect type --- src/js/ui/simply-pebble.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 6cae9c41..98b895b2 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -143,8 +143,7 @@ var setCardParams = setWindowParams.concat([{ name: 'banner', type: Image, }, { - name: 'style', - type: Boolean, + name: 'style' }]); var setMenuParams = setWindowParams.concat([{ From bc8fca16d37d98f3f8497ec353d674e1045a285c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 16:44:20 -0400 Subject: [PATCH 247/791] Change text element to use update time unit to determine time formatting --- src/js/ui/simply-pebble.js | 5 +---- src/js/ui/text.js | 3 +-- src/simply/simply_msg.c | 11 +++-------- src/simply/simply_stage.c | 6 +++--- src/simply/simply_stage.h | 3 +-- 5 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 1993fc45..26825554 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -338,10 +338,7 @@ var commands = [{ name: 'textAlign', type: TextAlignment, }, { - name: 'time', - type: Boolean, - }, { - name: 'timeUnits', + name: 'updateTimeUnit', type: TimeUnits, }, { name: 'image', diff --git a/src/js/ui/text.js b/src/js/ui/text.js index 708e0dd7..0926d761 100644 --- a/src/js/ui/text.js +++ b/src/js/ui/text.js @@ -9,8 +9,7 @@ var textProps = [ 'color', 'textOverflow', 'textAlign', - 'time', - 'timeUnits', + 'updateTimeUnit', ]; var defaults = { diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 95e02803..1fcb6962 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -112,8 +112,7 @@ enum ElementParam { ElementTextColor, ElementTextOverflow, ElementTextAlignment, - ElementTextIsTime, - ElementTextTimeUnits, + ElementTextUpdateTimeUnit, ElementImage, ElementCompositing, }; @@ -442,12 +441,8 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { case ElementTextAlignment: ((SimplyElementText*) element)->alignment = tuple->value->uint8; break; - case ElementTextIsTime: - ((SimplyElementText*) element)->is_time = tuple->value->uint8; - update_ticker = true; - break; - case ElementTextTimeUnits: - ((SimplyElementText*) element)->time_units = tuple->value->uint8; + case ElementTextUpdateTimeUnit: + ((SimplyElementText*) element)->time_unit = tuple->value->uint8; update_ticker = true; break; case ElementImage: diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 9c52dec1..39e6c5cc 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -101,7 +101,7 @@ static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementTex rect_element_draw(ctx, self, (SimplyElementRect*) element); char *text = element->text; if (element->text_color != GColorClear && is_string(text)) { - if (element->is_time) { + if (element->time_unit) { text = format_time(text); } GFont font = element->font ? element->font : fonts_get_system_font(FONT_KEY_FONT_FALLBACK); @@ -351,8 +351,8 @@ void simply_stage_update_ticker(SimplyStage *self) { while (element) { if (element->type == SimplyElementTypeText) { SimplyElementText *text = (SimplyElementText*) element; - if (text->is_time) { - units |= text->time_units; + if (text->time_unit) { + units |= text->time_unit; } } element = (SimplyElementCommon*) element->node.next; diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index d02691b2..7ad3c965 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -75,11 +75,10 @@ struct SimplyElementText { }; char *text; GFont font; - TimeUnits time_units:8; + TimeUnits time_unit:8; GColor text_color:2; GTextOverflowMode overflow_mode:2; GTextAlignment alignment:2; - bool is_time:1; }; typedef struct SimplyElementImage SimplyElementImage; From b95ec45c22f93dc3e3ed3d8c5a7937367ec0c054 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 17:01:14 -0400 Subject: [PATCH 248/791] Export ui inverter stage element class --- src/js/ui/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/ui/index.js b/src/js/ui/index.js index 908623a2..5e171765 100644 --- a/src/js/ui/index.js +++ b/src/js/ui/index.js @@ -8,5 +8,6 @@ UI.Rect = require('ui/rect'); UI.Circle = require('ui/circle'); UI.Text = require('ui/text'); UI.Image = require('ui/image'); +UI.Inverter = require('ui/inverter'); module.exports= UI; From 5ceaab4fc97dfb012e9c11e12b3d42a19374a493 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 17:14:33 -0400 Subject: [PATCH 249/791] Rename the singleClick event to click --- src/js/app.js | 2 +- src/js/ui/simply-pebble.js | 4 ++-- src/js/ui/window.js | 6 +++--- src/simply/simply_msg.c | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 5aee24dc..64e89d9e 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -9,7 +9,7 @@ var wind = new Pebble.UI.Card({ }); wind.show(); -wind.on('singleClick', function(e) { +wind.on('click', function(e) { console.log("Button pressed: " + JSON.stringify(e)); if (e.button == 'up') { var menu = new Pebble.UI.Menu(); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 26825554..21df4542 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -174,7 +174,7 @@ var commands = [{ name: 'setCard', params: setCardParams, }, { - name: 'singleClick', + name: 'click', params: [{ name: 'button', }], @@ -702,7 +702,7 @@ SimplyPebble.onAppMessage = function(e) { case 'windowHide': WindowStack.emitHide(payload[1]); break; - case 'singleClick': + case 'click': case 'longClick': var button = buttons[payload[1]]; Window.emitClick(command.name, button); diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 4691fcf7..18015908 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -149,7 +149,7 @@ Window.prototype.action = function(field, value, clear) { }; var isBackEvent = function(type, subtype) { - return ((type === 'singleClick' || type === 'longClick') && subtype === 'back'); + return ((type === 'click' || type === 'longClick') && subtype === 'back'); }; Window.prototype.onAddHandler = function(type, subtype) { @@ -228,7 +228,7 @@ Window.prototype._buttonAutoConfig = function() { if (!buttonState || buttonState.configMode !== 'auto') { return; } - var singleBackCount = this.listenerCount('singleClick', 'back'); + var singleBackCount = this.listenerCount('click', 'back'); var longBackCount = this.listenerCount('longClick', 'back'); var useBack = singleBackCount + longBackCount > 0; if (useBack !== buttonState.config.back) { @@ -249,7 +249,7 @@ Window.emit = function(type, subtype, e, klass) { /** * Simply.js button click event. This can either be a single click or long click. - * Use the event type 'singleClick' or 'longClick' to subscribe to these events. + * Use the event type 'click' or 'longClick' to subscribe to these events. * @typedef simply.clickEvent * @property {string} button - The button that was pressed: 'back', 'up', 'select', or 'down'. This is also the event subtype. */ diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 1fcb6962..14f68895 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -20,7 +20,7 @@ enum SimplyACmd { SimplyACmd_windowShow, SimplyACmd_windowHide, SimplyACmd_setUi, - SimplyACmd_singleClick, + SimplyACmd_click, SimplyACmd_longClick, SimplyACmd_accelTap, SimplyACmd_vibe, @@ -615,7 +615,7 @@ static bool send_click(SimplyACmd type, ButtonId button) { } bool simply_msg_single_click(ButtonId button) { - return send_click(SimplyACmd_singleClick, button); + return send_click(SimplyACmd_click, button); } bool simply_msg_long_click(ButtonId button) { From da1e6476da7a0407a10044ab4a7024a071637ea4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 19:12:53 -0400 Subject: [PATCH 250/791] Add ui resource module for obtaining bundled resource ids --- src/js/ui/imageservice.js | 24 ++++-------------------- src/js/ui/resource.js | 28 ++++++++++++++++++++++++++++ src/js/ui/simply-pebble.js | 5 +++++ 3 files changed, 37 insertions(+), 20 deletions(-) create mode 100644 src/js/ui/resource.js diff --git a/src/js/ui/imageservice.js b/src/js/ui/imageservice.js index f79a30fb..261fc116 100644 --- a/src/js/ui/imageservice.js +++ b/src/js/ui/imageservice.js @@ -1,21 +1,16 @@ var imagelib = require('lib/image'); var myutil = require('lib/myutil'); +var Resource = require('ui/resource'); var simply = require('ui/simply'); -var appinfo = require('appinfo'); var ImageService = module.exports; -var resources = (function() { - var resources = appinfo.resources; - return resources && resources.media || []; -})(); - var state; ImageService.init = function() { state = Image.state = { cache: {}, - nextId: resources.length, + nextId: Resource.items.length + 1, rootURL: null }; }; @@ -110,17 +105,6 @@ ImageService.setRootURL = function(url) { * otherwise a new id is generated for dynamic loading. */ ImageService.resolve = function(opt) { - var path = opt; - if (typeof opt === 'object') { - path = opt.url; - } - path = path.replace(/#.*/, ''); - var cname = myutil.toCConstantName(path); - for (var i = 0, ii = resources.length; i < ii; ++i) { - var res = resources[i]; - if (res.name === cname || res.file === path) { - return i + 1; - } - } - return ImageService.load(opt); + var id = Resource.getId(opt); + return typeof id !== 'undefined' ? id : ImageService.load(opt); }; diff --git a/src/js/ui/resource.js b/src/js/ui/resource.js new file mode 100644 index 00000000..03b8eb22 --- /dev/null +++ b/src/js/ui/resource.js @@ -0,0 +1,28 @@ +var myutil = require('lib/myutil'); +var appinfo = require('appinfo'); + +var resources = (function() { + var resources = appinfo.resources; + return resources && resources.media || []; +})(); + +var Resource = {}; + +Resource.items = resources; + +Resource.getId = function(opt) { + var path = opt; + if (typeof opt === 'object') { + path = opt.url; + } + path = path.replace(/#.*/, ''); + var cname = myutil.toCConstantName(path); + for (var i = 0, ii = resources.length; i < ii; ++i) { + var res = resources[i]; + if (res.name === cname || res.file === path) { + return i + 1; + } + } +}; + +module.exports = Resource; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 46431974..00e99339 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1,5 +1,6 @@ var util2 = require('lib/util2'); var myutil = require('lib/myutil'); +var Resource = require('ui/resource'); var Accel = require('ui/accel'); var ImageService = require('ui/imageservice'); var WindowStack = require('ui/windowstack'); @@ -33,6 +34,10 @@ var Color = function(x) { }; var Font = function(x) { + var id = Resource.getId(x); + if (id) { + return id; + } x = myutil.toCConstantName(x); if (!x.match(/^RESOURCE_ID/)) { x = 'RESOURCE_ID_' + x; From 4aacbd53cb999bd8f435513ce755200550a0764a Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Sat, 31 May 2014 19:22:29 -0400 Subject: [PATCH 251/791] Update doc to not include lib/ when requiring --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d9592757..bbd43c93 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ window.icon('IMAGE_CHOOSE_A_UNIQUE_IDENTIFIER'); You can use any of the Pebble system fonts in your Pebble.js applications. Please refer to [this Pebble Developer's blog post](https://developer.getpebble.com/blog/2013/07/24/Using-Pebble-System-Fonts/) for a list of all the Pebble system fonts. ````js -var Vector2 = require('lib/vector2'); +var Vector2 = require('vector2'); var stage = new UI.Stage(); var text = new UI.Text({ size: Vector2(144, 168), position: Vector2(0, 0), font: 'GOTHIC_18_BOLD', text: "Gothic 18 Bold" }); @@ -440,7 +440,7 @@ They all share some common properties: All properties can be initialized by passing an object when creating the Element, and changed with accessors functions who have the name of the properties. Calling an accessor without a parameter will return the current value. ````js -var Vector2 = require('lib/vector2'); +var Vector2 = require('vector2'); var element = new Text({ position: new Vector2(0, 0), size: new Vector2(144, 168) }); element.borderColor('white'); console.log("This element background color is: ", element.backgroundColor()); @@ -589,16 +589,16 @@ Vibe.vibrate('long'); | ---- |:----:|:--------:|---------|-------------| | `type` | string | optional | `short` | The duration of the vibration. `short`, `long` or `double`. | -## lib +## Libraries -The lib module contains some useful classes. +Pebble.js includes several libraries to help you write applications. ### ajax This module gives you a very simple and easy way to make HTTP requests. ```` -var ajax = require('lib/ajax'); +var ajax = require('ajax'); ajax({ url: 'api.theysaidso.com/qod.json', @@ -636,7 +636,7 @@ The `failure` callback is called when an error occurred. The only parameter is a A 2 dimensional vector. The constructor takes two parameter for the x and y properties. ````js -var Vector2 = require('lib/vector2'); +var Vector2 = require('vector2'); var vec = new Vector2(144, 168); ```` From 1a51f983f87bd230c172109e6396b5cf08dfb9dc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 22:35:41 -0400 Subject: [PATCH 252/791] Add card and stage background color property --- src/js/ui/simply-pebble.js | 3 +++ src/js/ui/window.js | 1 + src/simply/simply_msg.c | 4 ++++ src/simply/simply_stage.c | 6 +++++- src/simply/simply_ui.c | 9 +++++++-- src/simply/simply_window.c | 6 +++++- src/simply/simply_window.h | 4 +++- 7 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index bdd67e9e..4d6bfb33 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -124,6 +124,9 @@ var setWindowParams = [{ }, { name: 'actionBackgroundColor', type: Color, +}, { + name: 'backgroundColor', + type: Color, }, { name: 'fullscreen', type: Boolean, diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 10693129..1e57b696 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -32,6 +32,7 @@ var configProps = [ 'fullscreen', 'style', 'scrollable', + 'backgroundColor', ]; var actionProps = [ diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index ffbf71c7..0633074e 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -52,6 +52,7 @@ enum SimplySetWindowParam { SetWindow_actionSelect, SetWindow_actionDown, SetWindow_actionBackgroundColor, + SetWindow_backgroundColor, SetWindow_fullscreen, SetWindow_scrollable, SetWindowLast, @@ -161,6 +162,9 @@ static void handle_set_window(DictionaryIterator *iter, Simply *simply) { case SetWindow_actionBackgroundColor: simply_window_set_action_bar_background_color(window, tuple->value->uint8); break; + case SetWindow_backgroundColor: + simply_window_set_background_color(window, tuple->value->uint32); + break; case SetWindow_fullscreen: simply_window_set_fullscreen(window, tuple->value->int32); break; diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 39e6c5cc..57319d01 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -1,7 +1,7 @@ #include "simply_stage.h" +#include "simply_window.h" #include "simply_res.h" - #include "simply_msg.h" #include "simply.h" @@ -124,6 +124,9 @@ static void image_element_draw(GContext *ctx, SimplyStage *self, SimplyElementIm static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyStage *self = *(void**) layer_get_data(layer); + graphics_context_set_fill_color(ctx, self->window.background_color); + graphics_fill_rect(ctx, layer_get_frame(layer), 0, GCornerNone); + SimplyElementCommon *element = (SimplyElementCommon*) self->stage_layer.elements; while (element) { switch (element->type) { @@ -370,6 +373,7 @@ SimplyStage *simply_stage_create(Simply *simply) { *self = (SimplyStage) { .window.simply = simply }; simply_window_init(&self->window, simply); + simply_window_set_background_color(&self->window, GColorBlack); window_set_user_data(self->window.window, self); window_set_window_handlers(self->window.window, (WindowHandlers) { diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 4d87c75a..6551fe0e 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -175,8 +175,13 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } } - graphics_context_set_fill_color(ctx, GColorWhite); - graphics_fill_rect(ctx, frame, 4, GCornersAll); + graphics_context_set_fill_color(ctx, GColorBlack); + graphics_fill_rect(ctx, frame, 0, GCornerNone); + + if (self->window.background_color == GColorWhite) { + graphics_context_set_fill_color(ctx, GColorWhite); + graphics_fill_rect(ctx, frame, 4, GCornersAll); + } if (title_icon) { GRect icon_frame = (GRect) { diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index a351fa94..a3dc44f4 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -62,6 +62,10 @@ void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { self->id = id; } +void simply_window_set_background_color(SimplyWindow *self, GColor background_color) { + self->background_color = background_color; +} + void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { self->is_action_bar = is_action_bar; @@ -204,7 +208,7 @@ SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { } Window *window = self->window = window_create(); - window_set_background_color(window, GColorBlack); + window_set_background_color(window, GColorClear); window_set_click_config_provider(window, click_config_provider); ActionBarLayer *action_bar_layer = self->action_bar_layer = action_bar_layer_create(); diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index 36646f39..943cb88b 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -13,7 +13,8 @@ struct SimplyWindow { Layer *layer; ActionBarLayer *action_bar_layer; uint32_t id; - uint32_t button_mask; + ButtonId button_mask:4; + GColor background_color:2; bool is_scrollable:1; bool is_action_bar:1; }; @@ -28,6 +29,7 @@ void simply_window_unload(SimplyWindow *self); void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable); void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen); +void simply_window_set_background_color(SimplyWindow *self, GColor background_color); void simply_window_set_button(SimplyWindow *self, ButtonId button, bool enable); From 1d6e02943181a934491a6f6c33d08ecabb0902f8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 31 May 2014 22:37:01 -0400 Subject: [PATCH 253/791] Fix simply msg to set window properties immediately --- src/simply/simply_msg.c | 22 +++++++++++++--------- src/simply/simply_window.c | 1 + 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 0633074e..6a0a5709 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -139,11 +139,7 @@ static SimplyWindow *get_top_simply_window(Simply *simply) { return window; } -static void handle_set_window(DictionaryIterator *iter, Simply *simply) { - SimplyWindow *window = get_top_simply_window(simply); - if (!window) { - return; - } +static void set_window(SimplyWindow *window, DictionaryIterator *iter) { Tuple *tuple; if ((tuple = dict_find(iter, SetWindow_clear))) { simply_window_set_action_bar(window, false); @@ -175,6 +171,14 @@ static void handle_set_window(DictionaryIterator *iter, Simply *simply) { } } +static void handle_set_window(DictionaryIterator *iter, Simply *simply) { + SimplyWindow *window = get_top_simply_window(simply); + if (!window) { + return; + } + set_window(window, iter); +} + static void handle_hide_window(DictionaryIterator *iter, Simply *simply) { Window *base_window = window_stack_get_top_window(); SimplyWindow *window = window_get_user_data(base_window); @@ -217,8 +221,8 @@ static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { break; } } - simply_ui_show(simply->ui); - handle_set_window(iter, simply); + set_window(&ui->window, iter); + simply_ui_show(ui); } static void handle_vibe(DictionaryIterator *iter, Simply *simply) { @@ -284,8 +288,8 @@ static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { break; } } + set_window(&menu->window, iter); simply_menu_show(menu); - handle_set_window(iter, simply); } static void handle_set_menu_section(DictionaryIterator *iter, Simply *simply) { @@ -378,8 +382,8 @@ static void handle_set_stage(DictionaryIterator *iter, Simply *simply) { break; } } + set_window(&stage->window, iter); simply_stage_show(stage); - handle_set_window(iter, simply); } static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index a3dc44f4..61187a3d 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -102,6 +102,7 @@ void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor ba } action_bar_layer_set_background_color(self->action_bar_layer, background_color); + simply_window_set_action_bar(self, true); } void simply_window_action_bar_clear(SimplyWindow *self) { From ad9f4ffa6f86bf8975e9b87074a1074574d32070 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 00:07:37 -0400 Subject: [PATCH 254/791] Fix some README.md typos --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bbd43c93..bc3c3330 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Pebble.js Pebble.js lets you write beautiful Pebble applications completely in JavaScript. -Pebble.js applications run on your phone. They have access to all the resources of your phone (Internet connectivity, GPS, almost unlimited memory, etc). Because they are written in JavaScript they are also perfect to make HTTP request and connect your Pebble to the Internet. +Pebble.js applications run on your phone. They have access to all the resources of your phone (Internet connectivity, GPS, almost unlimited memory, etc). Because they are written in JavaScript they are also perfect to make HTTP requests and connect your Pebble to the Internet. > ![JSConf 2014](http://2014.jsconf.us/img/logo.png) > @@ -21,7 +21,7 @@ Pebble.js applications run on your phone. They have access to all the resources This option allows you to customize Pebble.js. Follow the [Pebble SDK installation instructions](http://developer.getpebble.com/2/getting-started/) to install the SDK on your computer and [fork this project](http://github.com/pebble/pebblejs) on Github. - The main entry point for your application is in `src/js/app.js` file. + The main entry point for your application is in the `src/js/app.js` file. [Install the Pebble SDK on your computer >](http://developer.getpebble.com/2/getting-started/) @@ -32,7 +32,7 @@ Pebble.js applications follow modern JavaScript best practices. To get started, var UI = require('ui'); ```` -The basic block to build user interface is the [Card]. A Card is a type of [Window] that occupies the entire screen and allows you to display some text in a pre-structured way: a title at the top, a subtitle below it and a body area for larger paragraphs. Cards can be made scrollable to display large quantities of information, you can also add images next to the title, subtitle or in the body area. +The basic block to build user interface is the [Card]. A Card is a type of [Window] that occupies the entire screen and allows you to display some text in a pre-structured way: a title at the top, a subtitle below it and a body area for larger paragraphs. Cards can be made scrollable to display large quantities of information. You can also add images next to the title, subtitle or in the body area. ````js var window = new UI.Card({ @@ -42,13 +42,13 @@ var window = new UI.Card({ }); ```` -After creating a window, you push it on the screen with the `show()` method. +After creating a window, push it onto the screen with the `show()` method. ````js window.show(); ```` -To interact with the users, you can use the buttons or the accelerometer. You add callbacks to a window with the `.on()` method: +To interact with the users, use the buttons or the accelerometer. Add callbacks to a window with the `.on()` method: ````js window.on('click', function(e) { @@ -56,7 +56,7 @@ window.on('click', function(e) { } ```` -Making HTTP connection is very easy with the included `ajax` library. +Making HTTP connections is very easy with the included `ajax` library. ````js var ajax = require('ajax'); @@ -168,7 +168,7 @@ The `Accel` module allows you to get events from the accelerometer on Pebble. You can use the accelerometer in two different ways: - To detect tap events. Those events are triggered when the user flicks his wrist or tap on the Pebble. They are the same events that are used to turn the Pebble back-light on. Tap events come with a property to tell you in which direction the Pebble was shook. Tap events are very battery efficient because they are generated directly by the accelerometer inside Pebble. - - To continuously receive streaming data from the accelerometer. In this mode the Pebble will collect accelerometer samples at a specified frequency (from 10Hz to 100Hz), batch those events in an array and pass those to an event handler. In this mode, the Pebble accelerometer needs to continuously transmit data to the processor and to the Bluetooth radio. This will drain the battery much faster. + - To continuously receive streaming data from the accelerometer. In this mode the Pebble will collect accelerometer samples at a specified frequency (from 10Hz to 100Hz), batch those events in an array and pass those to an event handler. Because the Pebble accelerometer needs to continuously transmit data to the processor and to the Bluetooth radio, this will drain the battery much faster. ````js var Accel = require('ui/accel'); @@ -198,7 +198,7 @@ The number of callbacks will depend on the configuration of the accelerometer. W #### Accel.peek(callback) -Peeks at the current accelerometer value. You pass a callback function that will be called with the data point as an event. +Peeks at the current accelerometer value. The callback function will be called with the data point as an event. ````js Accel.peek(function(data)) { @@ -245,20 +245,20 @@ Accel.on('accelData', function(numSamples, sample, samples) { ### Window -`Window` is the basic building block in your Pebble.js application. All window share some common properties and methods. +`Window` is the basic building block in your Pebble.js application. All windows share some common properties and methods. Pebble.js provides three types of Windows: - * [Card]: Displays a title, a subtitle, an image and some text on a screen. The position of the elements are fixed and cannot be changed. + * [Card]: Displays a title, a subtitle, a banner image and text on a screen. The position of the elements are fixed and cannot be changed. * [Menu]: Displays a menu on the Pebble screen. This is similar to the standard system menu in Pebble. - * [Stage]: This is the most flexible Window. It allows you to add different [Element] ([Circle], [Image], [Rect], [Text]) and to specify a position and size for each of them. You can also animate them. + * [Stage]: This is the most flexible Window. It allows you to add different [Element]s ([Circle], [Image], [Rect], [Text]) and to specify a position and size for each of them. You can also animate them. | Name | Type | Default | Description | | ---- | :-------: | --------- | ------------- | | `clear` | boolean | | | | `action` | boolean | false | When true, an action bar will be shown on the right side of the screen. | | `actionUp` | Image | None | An image to display in the action bar, next to the up button. | -| `actionSelect` | Image | None | An image to display in the action bar, next to the select button. +| `actionSelect` | Image | None | An image to display in the action bar, next to the select button. | | `actionDown` | Image | None | An image to display in the action bar, next to the down button. | | `fullscreen` | boolean | false | When true, the Pebble status bar will not be visible and the window will use the entire screen. | | `scrollable` | boolean | false | When true, the up and down button will scroll the content of this Card. | From 024d10163ddbd71d5e3d5a6abaa072933f77823b Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Sat, 31 May 2014 21:25:10 -0400 Subject: [PATCH 255/791] Added first draft of menu doc --- README.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bc3c3330..f6a098d5 100644 --- a/README.md +++ b/README.md @@ -367,15 +367,59 @@ The properties available on a [Menu] are: | Name | Type | Default | Description | | ---- |:-------:|---------|-------------| -| `scrollable` | boolean | false | Whether the user can scroll this card with the up and down button. When this is enabled, click events on the up and down button will not be transmitted to your app. | -| `sections` | Number | | | +| `sections` | Array | `[]` | A list of all the sections to display. | + +A menu contains one or more sections. Each section has a title and contains zero or more items. An item must have a title. It can also have a subtitle and an icon. + +````js +var menu = new UI.Menu({ + sections: [ + { + title: "First section" + items: [ + { + title: "first item", + subtitle: "some subtitle", + icon: "images/item_icon.png" + } + ] + } + ] +}); +```` #### Menu.section(sectionIndex, section) +Define the section to be displayed at sectionIndex. + +````js +var section = { + title: "Another section", + items: [ + { + title: "With one item" + } + ] +} +menu.section(1, section); +```` + #### Menu.items(sectionIndex, items) +Define the items to display in a specific section. + +````js +menu.items(0, [ { title: "new item1" }, { title: "new item2" } ]); +```` + #### Menu.item(sectionIndex, itemIndex, item) +Define the item to display at index itemIndex in section sectionIndex. + +````js +menu.item(0, 0, { title: 'A new item', subtitle: 'replacing the previous one' }); +```` + #### Menu.on('select', callback) Registers a callback called when an item in the menu is selected. @@ -384,7 +428,7 @@ Registers a callback called when an item in the menu is selected. #### Menu.on('longSelect', callback) -See [Menu.on('select, callback)] +See `Menu.on('select, callback)` ### Stage From 03095f46d6bd0252f597f94c8747cb5b9aa40282 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Sat, 31 May 2014 21:25:30 -0400 Subject: [PATCH 256/791] Added doc.html to provide local access to the documentation --- doc.html | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 doc.html diff --git a/doc.html b/doc.html new file mode 100644 index 00000000..705655f4 --- /dev/null +++ b/doc.html @@ -0,0 +1,55 @@ + + + + + + + + Pebble.js + + + + + + + + + + + + + + + + + + + +
+
+

Pebble.js

+ +
+
+ + +
+
+ +
+ +
+
+ + + From 7a1b17a0dcb73a916d29242224c77b105dc38075 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Sat, 31 May 2014 22:08:05 -0400 Subject: [PATCH 257/791] Fix the main.js (it needs to initialize simply-pebble) --- src/js/main.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/main.js b/src/js/main.js index c58f6a8c..d852abe3 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -9,6 +9,8 @@ require('safe'); Pebble.addEventListener('ready', function(e) { + // Initialize the Pebble protocol + require('ui/simply-pebble.js').init(); // Load local file require('app.js'); }); From 02d65e28076c8d796b04fe80a9240fe40a7dbc7c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 02:06:13 -0700 Subject: [PATCH 258/791] Set the default simply card background color to white --- src/simply/simply_ui.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 6551fe0e..0b0b7f96 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -287,6 +287,7 @@ SimplyUi *simply_ui_create(Simply *simply) { *self = (SimplyUi) { .window.layer = NULL }; simply_window_init(&self->window, simply); + simply_window_set_background_color(&self->window, GColorWhite); window_set_user_data(self->window.window, self); window_set_window_handlers(self->window.window, (WindowHandlers) { From 5f8fd1bfd9d8f27558e91c8e82b93c8e430b8df8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 02:06:31 -0700 Subject: [PATCH 259/791] Touch up the default app.js --- src/js/app.js | 65 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 64e89d9e..b46dd351 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -1,32 +1,45 @@ -// Welcome to PebbleJS! -// -// This is where you write your app. +/** + * Welcome to Pebble.js! + * + * This is where you write your app. + */ -var wind = new Pebble.UI.Card({ - title: "PebbleJS", - icon: "images/menu_icon.png", - body: "Saying Hello World" +var UI = require('ui'); +var Vector2 = require('vector2'); + +var wind = new UI.Card({ + title: 'Pebble.js', + icon: 'images/menu_icon.png', + body: 'Saying Hello World' }); + wind.show(); -wind.on('click', function(e) { - console.log("Button pressed: " + JSON.stringify(e)); - if (e.button == 'up') { - var menu = new Pebble.UI.Menu(); - menu.items(0, [ { title: 'Hello World!', subtitle: 'text' }, { title: 'item2' } ]); - menu.show(); - } - else if (e.button === 'select') { - var stage = new Pebble.UI.Stage(); - stage.add(new Pebble.UI.Text({ text: 'Yo!', position: new Pebble.UI.Vector2(10, 10), size: new Pebble.UI.Vector2(100, 30) })); - stage.show(); - } - else /* 'down' */ { - var card = new Pebble.UI.Card(); - card.title("A PebbleJS Card"); - card.subtitle("With subtitle"); - card.body("This is the simplest element you can push to the screen"); - card.show(); - } +wind.on('click', 'up', function(e) { + var menu = new UI.Menu(); + menu.items(0, [{ + title: 'Hello World!', subtitle: 'Subtitle text' + }, { + title: 'Item #2' + }]); + menu.show(); }); +wind.on('click', 'select', function(e) { + var stage = new UI.Stage(); + var textfield = new UI.Text({ + text: 'Yo!', + position: new Vector2(10, 10), + size: new Vector2(100, 30), + }); + stage.add(textfield); + stage.show(); +}); + +wind.on('click', 'down', function(e) { + var card = new UI.Card(); + card.title('A Pebble.js Card'); + card.subtitle('With subtitle'); + card.body('This is the simplest window you can push to the screen.'); + card.show(); +}); From 56c13c527c6e88dc555a0718b105bcaf070135bd Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 02:19:20 -0700 Subject: [PATCH 260/791] Fix stage text element custom font support --- src/simply/simply_res.c | 49 +++++++++++++++++++++++------------------ src/simply/simply_res.h | 2 +- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 79acca98..4b6a9c43 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -97,13 +97,39 @@ GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder return NULL; } -GFont *simply_res_auto_font(SimplyRes *self, uint32_t id) { +GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id) { + SimplyFont *font = malloc(sizeof(*font)); + if (!font) { + return NULL; + } + + ResHandle handle = resource_get_handle(id); + if (!handle) { + return NULL; + } + + GFont custom_font = fonts_load_custom_font(handle); + if (!custom_font) { + free(font); + return NULL; + } + + font->font = custom_font; + + list1_prepend(&self->fonts, &font->node); + + window_stack_schedule_top_window_render(); + + return font->font; +} + +GFont simply_res_auto_font(SimplyRes *self, uint32_t id) { if (!id) { return NULL; } SimplyFont *font = (SimplyFont*) list1_find(self->fonts, id_filter, (void*)(uintptr_t) id); if (font) { - return &font->font; + return font->font; } if (id <= ARRAY_LENGTH(resource_crc_table)) { return simply_res_add_custom_font(self, id); @@ -121,25 +147,6 @@ void simply_res_clear(SimplyRes *self) { } } -GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id) { - SimplyFont *font = malloc(sizeof(*font)); - if (!font) { - return NULL; - } - - GFont custom_font = fonts_load_custom_font(resource_get_handle(id)); - if (!custom_font) { - free(font); - return NULL; - } - - list1_prepend(&self->fonts, &font->node); - - window_stack_schedule_top_window_render(); - - return &font->font; -} - SimplyRes *simply_res_create() { SimplyRes *self = malloc(sizeof(*self)); *self = (SimplyRes) { .images = NULL }; diff --git a/src/simply/simply_res.h b/src/simply/simply_res.h index 15e1905e..d21f317c 100644 --- a/src/simply/simply_res.h +++ b/src/simply/simply_res.h @@ -53,6 +53,6 @@ GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16 GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder); GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id); -GFont *simply_res_auto_font(SimplyRes *self, uint32_t id); +GFont simply_res_auto_font(SimplyRes *self, uint32_t id); void simply_res_remove_image(SimplyRes *self, uint32_t id); From 90076c0a2884da1c1e52644a1e1401028a042484 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 02:34:25 -0700 Subject: [PATCH 261/791] Fix README.md accel callbacks, string, menu, and ajax formatting --- README.md | 102 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index f6a098d5..31d2c917 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ To interact with the users, use the buttons or the accelerometer. Add callbacks ````js window.on('click', function(e) { - window.subtitle("Button " + e.button + " pressed."); + window.subtitle('Button ' + e.button + ' pressed.'); } ```` @@ -105,8 +105,8 @@ To reference your image in Pebble.js, you can use the `name` field or the `file` ````js // These two examples are both valid ways to show the image declared above in a Card -window.icon('images/your_image.png'); -window.icon('IMAGE_CHOOSE_A_UNIQUE_IDENTIFIER'); +card.icon('images/your_image.png'); +card.icon('IMAGE_CHOOSE_A_UNIQUE_IDENTIFIER'); ```` ## Using Fonts @@ -117,8 +117,13 @@ You can use any of the Pebble system fonts in your Pebble.js applications. Pleas var Vector2 = require('vector2'); var stage = new UI.Stage(); -var text = new UI.Text({ size: Vector2(144, 168), position: Vector2(0, 0), font: 'GOTHIC_18_BOLD', text: "Gothic 18 Bold" }); -stage.add(text); +var textfield = new UI.Text({ + position: Vector2(0, 0), + size: Vector2(144, 168), + font: 'GOTHIC_18_BOLD', + text: 'Gothic 18 Bold' +}); +stage.add(textfield); stage.show(); ```` @@ -206,26 +211,26 @@ Accel.peek(function(data)) { }); ```` -#### Accel.on('accelTap', callback) +#### Accel.on('tap', callback) -Subscribe to the `accelTap` event. The callback function will be called with two parameters: +Subscribe to the `tap` event. The callback function will be passed an event with the following fields: * `axis`: The axis the tap event occurred on: 'x', 'y', or 'z'. * `direction`: The direction of the tap along the axis: 1 or -1. ````js -Accel.on('accelTap', function (axis, direction) { - console.log("Tap event on axis: " + axis + " and direction: " + direction); -} +Accel.on('tap', function (e) { + console.log('Tap event on axis: ' + e.axis + ' and direction: ' + e.direction); +}); ```` #### Accel.on('accelData', callback) -Subscribe to the 'accelData' event. The callback function will be called with three parameters: +Subscribe to the 'accelData' event. The callback function will be passed an event with the following fields: - * `numSamples`: The number of accelerometer samples in this event. - * `sample`: The first data point in the batch. This is provided for convenience. - * `samples`: The accelerometer samples in an array. + * `samples`: The number of accelerometer samples in this event. + * `accel`: The first data point in the batch. This is provided for convenience. + * `accels`: The accelerometer samples in an array. One accelerometer data point is an object with the following properties: @@ -238,9 +243,9 @@ One accelerometer data point is an object with the following properties: | `time` | Number | The amount of ticks in millisecond resolution when this point was measured. | ````js -Accel.on('accelData', function(numSamples, sample, samples) { - console.log("Just received " + numSamples + " from the accelerometer."); -} +Accel.on('accelData', function(e) { + console.log('Just received ' + e.samples + ' from the accelerometer.'); +}); ```` ### Window @@ -276,7 +281,7 @@ If the window is currently displayed, this will take the user to the previously If the window is not currently displayed, this will remove it from the window stack. The user will not be able to get back to it with the back button. ````js -var splashScreen = new UI.Card({ 'image': 'images/splash.png' }); +var splashScreen = new UI.Card({ banner: 'images/splash.png' }); splashScreen.show(); var mainScreen = new UI.Menu(); @@ -294,7 +299,7 @@ setTimeout(function() { Registers a handler to call when `button` is pressed. ````js -window.on('click', 'up', function() { +wind.on('click', 'up', function() { console.log('Up clicked!'); }); ```` @@ -335,9 +340,9 @@ Just like any window, you can initialize a Card by passing an object to the cons ````js var card = new UI.Card({ - title: "Hello People!" + title: 'Hello People!' }); -card.body("This is the content of my card!"); +card.body('This is the content of my card!'); ```` The properties available on a [Card] are: @@ -373,18 +378,16 @@ A menu contains one or more sections. Each section has a title and contains zero ````js var menu = new UI.Menu({ - sections: [ - { - title: "First section" - items: [ - { - title: "first item", - subtitle: "some subtitle", - icon: "images/item_icon.png" - } - ] - } - ] + sections: [{ + title: 'First section' + items: [{ + title: 'First Item', + subtitle: 'Some subtitle', + icon: 'images/item_icon.png' + }, { + title: 'Second item' + }] + }] }); ```` @@ -394,13 +397,11 @@ Define the section to be displayed at sectionIndex. ````js var section = { - title: "Another section", - items: [ - { - title: "With one item" - } - ] -} + title: 'Another section', + items: [{ + title: 'With one item' + }] +}; menu.section(1, section); ```` @@ -409,7 +410,7 @@ menu.section(1, section); Define the items to display in a specific section. ````js -menu.items(0, [ { title: "new item1" }, { title: "new item2" } ]); +menu.items(0, [ { title: 'new item1' }, { title: 'new item2' } ]); ```` #### Menu.item(sectionIndex, itemIndex, item) @@ -450,7 +451,7 @@ Adds an element to to this stage. This element will be immediately visible. Inserts an element at a specific index in the list of Element. -#### Stage.remove(element, broadcast) +#### Stage.remove(element) Removes an element from the [Stage]. @@ -460,12 +461,12 @@ Returns the index of an element in the Stage or -1 if the element is not in the #### Stage.each(callback) -Iterates over all the element on the stage. +Iterates over all the elements on the stage. ````js stage.each(function(element) { - console.log("Element: ", element); -} + console.log('Element: ' + JSON.stringify(element)); +}); ```` ### Element @@ -476,8 +477,8 @@ They all share some common properties: | Name | Type | Default | Description | | ------------ | :-------: | --------- | ------------- | -| `position` | Vector2 | | Position of this element in the stage. | -| `size` | Vector2 | | Size of this element in this stage. | +| `position` | Vector2 | | Position of this element in the stage. | +| `size` | Vector2 | | Size of this element in this stage. | | `borderColor` | string | '' | Color of the border of this element ('clear', 'black',or 'white'). | | `backgroundColor` | string | '' | Background color of this element ('clear', 'black' or 'white'). | @@ -487,7 +488,7 @@ All properties can be initialized by passing an object when creating the Element var Vector2 = require('vector2'); var element = new Text({ position: new Vector2(0, 0), size: new Vector2(144, 168) }); element.borderColor('white'); -console.log("This element background color is: ", element.backgroundColor()); +console.log('This element background color is: ' + element.backgroundColor()); ```` #### Element.index() @@ -498,7 +499,7 @@ Returns the index of this element in its stage or -1 if this element is not part Removes this element from its stage. -#### Element.animate(field, value, direction) +#### Element.animate(field, value, duration) #### Element.position(position) @@ -644,7 +645,8 @@ This module gives you a very simple and easy way to make HTTP requests. ```` var ajax = require('ajax'); -ajax({ +ajax( + { url: 'api.theysaidso.com/qod.json', type: 'json' }, From 98657ea7b4b087df912139c6a4b34274566bab24 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 02:51:29 -0700 Subject: [PATCH 262/791] Add window accel tap and accel data docs --- README.md | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 31d2c917..c7dab675 100644 --- a/README.md +++ b/README.md @@ -35,25 +35,25 @@ var UI = require('ui'); The basic block to build user interface is the [Card]. A Card is a type of [Window] that occupies the entire screen and allows you to display some text in a pre-structured way: a title at the top, a subtitle below it and a body area for larger paragraphs. Cards can be made scrollable to display large quantities of information. You can also add images next to the title, subtitle or in the body area. ````js -var window = new UI.Card({ +var card = new UI.Card({ title: 'Hello World', body: 'This is your first Pebble app!', scrollable: true }); ```` -After creating a window, push it onto the screen with the `show()` method. +After creating a card window, push it onto the screen with the `show()` method. ````js -window.show(); +card.show(); ```` To interact with the users, use the buttons or the accelerometer. Add callbacks to a window with the `.on()` method: ````js -window.on('click', function(e) { - window.subtitle('Button ' + e.button + ' pressed.'); -} +card.on('click', function(e) { + card.subtitle('Button ' + e.button + ' pressed.'); +}); ```` Making HTTP connections is very easy with the included `ajax` library. @@ -115,8 +115,8 @@ You can use any of the Pebble system fonts in your Pebble.js applications. Pleas ````js var Vector2 = require('vector2'); -var stage = new UI.Stage(); +var stage = new UI.Stage(); var textfield = new UI.Text({ position: Vector2(0, 0), size: Vector2(144, 168), @@ -206,27 +206,35 @@ The number of callbacks will depend on the configuration of the accelerometer. W Peeks at the current accelerometer value. The callback function will be called with the data point as an event. ````js -Accel.peek(function(data)) { - console.log('Current acceleration on axis are: X=' + data.x + ' Y=' + data.y + ' Z=' + data.z); +Accel.peek(function(e)) { + console.log('Current acceleration on axis are: X=' + e.accel.x + ' Y=' + e.accel.y + ' Z=' + e.accel.z); }); ```` #### Accel.on('tap', callback) -Subscribe to the `tap` event. The callback function will be passed an event with the following fields: +Subscribe to the `Accel` `tap` event. The callback function will be passed an event with the following fields: * `axis`: The axis the tap event occurred on: 'x', 'y', or 'z'. * `direction`: The direction of the tap along the axis: 1 or -1. ````js -Accel.on('tap', function (e) { +Accel.on('tap', function(e) { console.log('Tap event on axis: ' + e.axis + ' and direction: ' + e.direction); }); ```` -#### Accel.on('accelData', callback) +A [Window] may subscribe to the `Accel` `tap` event using the `accelTap` event type. The callback function will only be called when the window is visible. -Subscribe to the 'accelData' event. The callback function will be passed an event with the following fields: +````js +wind.on('accelTap', function(e) { + console.log('Tapped the window'); +}); +```` + +#### Accel.on('data', callback) + +Subscribe to the accel 'data' event. The callback function will be passed an event with the following fields: * `samples`: The number of accelerometer samples in this event. * `accel`: The first data point in the batch. This is provided for convenience. @@ -243,11 +251,19 @@ One accelerometer data point is an object with the following properties: | `time` | Number | The amount of ticks in millisecond resolution when this point was measured. | ````js -Accel.on('accelData', function(e) { +Accel.on('data', function(e) { console.log('Just received ' + e.samples + ' from the accelerometer.'); }); ```` +A [Window] may also subscribe to the `Accel` `data` event using the `accelData` event type. The callback function will only be called when the window is visible. + +````js +wind.on('accelData', function(e) { + console.log('Accel data: ' + JSON.stringify(e.accels)); +}); +```` + ### Window `Window` is the basic building block in your Pebble.js application. All windows share some common properties and methods. From 60ea3299cd819219321fa4278da3e1f23d3aa695 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 02:59:03 -0700 Subject: [PATCH 263/791] Add additional require doc about multiple path search --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c7dab675..98aadfad 100644 --- a/README.md +++ b/README.md @@ -145,13 +145,13 @@ This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). Loads another JavaScript file allowing you to write a multi-file project. Package loading loosely follows the CommonJS format. -Exporting is possible by modifying or setting `module.exports` within the required file. The module path is also available as `module.path`. This currently only supports a relative path to another JavaScript file. +Exporting is possible by modifying or setting `module.exports` within the required file. The module path is also available as `module.filename`. `require` will look for the module relative to the loading module, the root path, and the Pebble.js library folder `lib` located at `src/js/lib`. ### Pebble The `Pebble` object from [PebbleKit JavaScript](https://developer.getpebble.com/2/guides/javascript-guide.html) is available as a global variable. It's usage is discouraged in Pebble.js, instead you should use the objects documented below who provide a cleaner object interface to the same functionalities. -### window (global) +### window -- browser A `window` object is provided with a subset of the standard APIs you would find in a normal browser. It's direct usage is discouraged because available functionalities may differ between the iOS and Android runtime environment. From 5f315188e3772322ed079dea7ba065b0d729a75c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 03:14:40 -0700 Subject: [PATCH 264/791] Add window action actionDef docs --- README.md | 50 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 98aadfad..ae9a6052 100644 --- a/README.md +++ b/README.md @@ -277,13 +277,32 @@ Pebble.js provides three types of Windows: | Name | Type | Default | Description | | ---- | :-------: | --------- | ------------- | | `clear` | boolean | | | -| `action` | boolean | false | When true, an action bar will be shown on the right side of the screen. | -| `actionUp` | Image | None | An image to display in the action bar, next to the up button. | -| `actionSelect` | Image | None | An image to display in the action bar, next to the select button. | -| `actionDown` | Image | None | An image to display in the action bar, next to the down button. | +| `action` | actionDef | None | An action bar will be shown when configured with an `actionDef`. | | `fullscreen` | boolean | false | When true, the Pebble status bar will not be visible and the window will use the entire screen. | | `scrollable` | boolean | false | When true, the up and down button will scroll the content of this Card. | +#### actionDef + +A `Window` action bar can be displayed by setting its `action` property to an `actionDef`. + +| Name | Type | Default | Description | +| ---- | :-------: | --------- | ------------- | +| `up` | Image | None | An image to display in the action bar, next to the up button. | +| `select` | Image | None | An image to display in the action bar, next to the select button. | +| `down` | Image | None | An image to display in the action bar, next to the down button. | +| `backgroundColor` | Image | 'black' | The background color of the action bar. You can set this to 'white' for windows with black backgrounds | + +````js +var card = new UI.Card({ + action: { + up: 'images/action_icon_plus.png', + down: 'images/action_icon_minus.png' + } +}); +```` + +You will need to add images to your project following the [Using Images] guide to display action bar icons. + #### Window.show() This will push the window to the screen and display it. If user press the 'back' button, they will navigate to the previous screen. @@ -328,21 +347,24 @@ You can register a handler for the 'up', 'select', 'down', and 'back' buttons. Just like `Window.on('click', button, handler)` but for 'longClick' events. -#### Window.action() - -Accessor to the `action` property. See [Window]. +#### Window.action(actionDef) -#### Window.actionUp() +This is a special nested accessor to the `action` property which takes an `actionDef`. It can be used to set a new actionDef. See [Window] for details on `actionDef. -Accessor to the `actionUp` property. See [Window]. - -#### Window.actionSelect() +````js +card.action({ + up: 'images/action_icon_up.png', + down: 'images/action_icon_down.png' +}); +```` -Accessor to the `actionSelect` property. See [Window]. +#### Window.action(field, value) -#### Window.actionDown() +You may also call `Window.action` with two arguments to set specific fields of the window's `action` propery. -Accessor to the `actionDown` property. See [Window]. +````js +card.action('up', 'images/action_icon_plus.png'); +```` #### Window.fullscreen(fullscreen) From 5d3fd3fa4a3e979278a5c06911ae157c02a8863d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 22:43:27 -0700 Subject: [PATCH 265/791] Fix README.md typos and add require example --- README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ae9a6052..1fc06027 100644 --- a/README.md +++ b/README.md @@ -141,9 +141,14 @@ This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). ## Global namespace -### require(dependency) +### require(path) -Loads another JavaScript file allowing you to write a multi-file project. Package loading loosely follows the CommonJS format. +Loads another JavaScript file allowing you to write a multi-file project. Package loading loosely follows the CommonJS format. `path` is the path to the dependency. + +````js +// src/js/dependency.js +var dep = require('dependency'); +```` Exporting is possible by modifying or setting `module.exports` within the required file. The module path is also available as `module.filename`. `require` will look for the module relative to the loading module, the root path, and the Pebble.js library folder `lib` located at `src/js/lib`. @@ -277,13 +282,13 @@ Pebble.js provides three types of Windows: | Name | Type | Default | Description | | ---- | :-------: | --------- | ------------- | | `clear` | boolean | | | -| `action` | actionDef | None | An action bar will be shown when configured with an `actionDef`. | +| `action` | actionDef | None | An action bar will be shown when configured with an `actionDef`. | | `fullscreen` | boolean | false | When true, the Pebble status bar will not be visible and the window will use the entire screen. | | `scrollable` | boolean | false | When true, the up and down button will scroll the content of this Card. | #### actionDef -A `Window` action bar can be displayed by setting its `action` property to an `actionDef`. +A `Window` action bar can be displayed by setting its Window `action` property to an `actionDef`. | Name | Type | Default | Description | | ---- | :-------: | --------- | ------------- | @@ -349,7 +354,7 @@ Just like `Window.on('click', button, handler)` but for 'longClick' events. #### Window.action(actionDef) -This is a special nested accessor to the `action` property which takes an `actionDef`. It can be used to set a new actionDef. See [Window] for details on `actionDef. +This is a special nested accessor to the `action` property which takes an `actionDef`. It can be used to set a new `actionDef`. See [Window] for details on `actionDef`. ````js card.action({ @@ -509,7 +514,7 @@ stage.each(function(element) { ### Element -There are four types of [Element] that can be instantiated at the moment: [Circle], [Image], [Rectangle] and [Text]. +There are four types of [Element] that can be instantiated at the moment: [Circle], [Image], [Rect] and [Text]. They all share some common properties: From 2e2f7e878ac0a7d923dff771ee79832b50cbb08f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 23:03:26 -0700 Subject: [PATCH 266/791] Add ui time text element which automatically formats time and handles time units --- src/js/ui/index.js | 3 +- src/js/ui/propable.js | 7 ++--- src/js/ui/simply-pebble.js | 2 +- src/js/ui/text.js | 2 +- src/js/ui/timetext.js | 59 ++++++++++++++++++++++++++++++++++++++ src/simply/simply_msg.c | 2 +- src/simply/simply_stage.c | 7 ++--- src/simply/simply_stage.h | 2 +- 8 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 src/js/ui/timetext.js diff --git a/src/js/ui/index.js b/src/js/ui/index.js index 75dd9eae..f6fe5a80 100644 --- a/src/js/ui/index.js +++ b/src/js/ui/index.js @@ -7,7 +7,8 @@ UI.Stage = require('ui/stage'); UI.Rect = require('ui/rect'); UI.Circle = require('ui/circle'); UI.Text = require('ui/text'); +UI.TimeText = require('ui/timetext'); UI.Image = require('ui/image'); UI.Inverter = require('ui/inverter'); -module.exports= UI; +module.exports = UI; diff --git a/src/js/ui/propable.js b/src/js/ui/propable.js index e6528209..efecc503 100644 --- a/src/js/ui/propable.js +++ b/src/js/ui/propable.js @@ -9,11 +9,10 @@ Propable.makeAccessor = function(k) { return function(value) { if (arguments.length === 0) { return this.state[k]; - } else { - this.state[k] = value; - this._prop(myutil.toObject(k, value)); - return this; } + this.state[k] = value; + this._prop(myutil.toObject(k, value)); + return this; }; }; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 4d6bfb33..a8ca649c 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -345,7 +345,7 @@ var commands = [{ name: 'textAlign', type: TextAlignment, }, { - name: 'updateTimeUnit', + name: 'updateTimeUnits', type: TimeUnits, }, { name: 'image', diff --git a/src/js/ui/text.js b/src/js/ui/text.js index 52164ca4..03aa153f 100644 --- a/src/js/ui/text.js +++ b/src/js/ui/text.js @@ -9,7 +9,7 @@ var textProps = [ 'color', 'textOverflow', 'textAlign', - 'updateTimeUnit', + 'updateTimeUnits', ]; var defaults = { diff --git a/src/js/ui/timetext.js b/src/js/ui/timetext.js new file mode 100644 index 00000000..7054758a --- /dev/null +++ b/src/js/ui/timetext.js @@ -0,0 +1,59 @@ +var util2 = require('util2'); +var Text = require('ui/text'); + +var TimeText = function(elementDef) { + Text.call(this, elementDef); + if (this.state.text) { + this.text(this.state.text); + } +}; + +util2.inherit(TimeText, Text); + +var formatUnits = { + a: 'days', + A: 'days', + b: 'months', + B: 'months', + c: 'seconds', + d: 'days', + H: 'hours', + I: 'hours', + j: 'days', + m: 'months', + M: 'minutes', + p: 'hours', + S: 'seconds', + U: 'days', + w: 'days', + W: 'days', + x: 'days', + X: 'seconds', + y: 'years', + Y: 'years', +}; + +var getUnitsFromText = function(text) { + var units = {}; + text.replace(/%(.)/g, function(_, code) { + var unit = formatUnits[code]; + if (unit) { + units[unit] = true; + } + return _; + }); + return units; +}; + +TimeText.prototype.text = function(text) { + if (arguments.length === 0) { + return this.state.text; + } + this.prop({ + text: text, + updateTimeUnits: getUnitsFromText(text), + }); + return this; +}; + +module.exports = TimeText; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 6a0a5709..845d18de 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -454,7 +454,7 @@ static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { ((SimplyElementText*) element)->alignment = tuple->value->uint8; break; case ElementTextUpdateTimeUnit: - ((SimplyElementText*) element)->time_unit = tuple->value->uint8; + ((SimplyElementText*) element)->time_units = tuple->value->uint8; update_ticker = true; break; case ElementImage: diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 57319d01..b9cfaa61 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -101,7 +101,7 @@ static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementTex rect_element_draw(ctx, self, (SimplyElementRect*) element); char *text = element->text; if (element->text_color != GColorClear && is_string(text)) { - if (element->time_unit) { + if (element->time_units) { text = format_time(text); } GFont font = element->font ? element->font : fonts_get_system_font(FONT_KEY_FONT_FALLBACK); @@ -353,10 +353,7 @@ void simply_stage_update_ticker(SimplyStage *self) { SimplyElementCommon *element = (SimplyElementCommon*) self->stage_layer.elements; while (element) { if (element->type == SimplyElementTypeText) { - SimplyElementText *text = (SimplyElementText*) element; - if (text->time_unit) { - units |= text->time_unit; - } + units |= ((SimplyElementText*) element)->time_units; } element = (SimplyElementCommon*) element->node.next; } diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index 7ad3c965..ab9538f4 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -75,7 +75,7 @@ struct SimplyElementText { }; char *text; GFont font; - TimeUnits time_unit:8; + TimeUnits time_units:8; GColor text_color:2; GTextOverflowMode overflow_mode:2; GTextAlignment alignment:2; From 803941739279c65e9e12f1351428edbd91764108 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 23:13:31 -0700 Subject: [PATCH 267/791] Add TimeText docs --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1fc06027..5e0d540d 100644 --- a/README.md +++ b/README.md @@ -277,7 +277,7 @@ Pebble.js provides three types of Windows: * [Card]: Displays a title, a subtitle, a banner image and text on a screen. The position of the elements are fixed and cannot be changed. * [Menu]: Displays a menu on the Pebble screen. This is similar to the standard system menu in Pebble. - * [Stage]: This is the most flexible Window. It allows you to add different [Element]s ([Circle], [Image], [Rect], [Text]) and to specify a position and size for each of them. You can also animate them. + * [Stage]: This is the most flexible Window. It allows you to add different [Element]s ([Circle], [Image], [Rect], [Text], [TimeText]) and to specify a position and size for each of them. You can also animate them. | Name | Type | Default | Description | | ---- | :-------: | --------- | ------------- | @@ -593,13 +593,16 @@ The [Text] element has the following properties. Just like any other [Element] y | `color` | | 'white' | Color of the text ('white', 'black' or 'clear'). | | `textOverflow` | 'string' | | How to handle text overflow in this text element ('wrap', 'ellipsis' or 'fill'). | | `textAlign` | 'string' | | How to align text in this element ('left', 'center' or 'right'). | -| `updateTimeUnits` | string | '' | Specifies the smallest time unit change that will cause this text to be redrawn ('seconds', 'minutes', 'hours', 'days', 'months', 'years'). Use this in conjunction with a time format string in the `text` property. This Text element will be redrawn every `updateTimeUnits` and the text parsed. The text formatting string format is described below. | | `borderColor` | string | 'clear' | Color of the border of this element ('clear', 'black',or 'white'). | | `backgroundColor` | string | 'clear' | Background color of this element ('clear', 'black' or 'white'). | -#### Displaying time in a Text element +### TimeText -If you want to display the current time or date in a Text element, you can use a time formatting string in the `text` property. The text element will be redrawn every `updateTimeUnits` and the format time will be re-interpreted. +A [Text] element that displays time formatted text on the screen. + +#### Displaying time in a TimeText element + +If you want to display the current time or date, use the `TimeText` element with a time formatting string in the `text` property. The time to redraw the time text element will be automatically calculated based on the format string. For example, a `TimeText` element with the format `'%M:%S'` will be redrawn every second because of the seconds format `%S`. The available formatting options follows the C `strftime()` function: From c5b48475218b77e58b3375f967056d19b1c67049 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 23:16:48 -0700 Subject: [PATCH 268/791] Add link to the three.js Vector2 documentation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e0d540d..e1bb0792 100644 --- a/README.md +++ b/README.md @@ -725,7 +725,7 @@ The `failure` callback is called when an error occurred. The only parameter is a ### Vector2 -A 2 dimensional vector. The constructor takes two parameter for the x and y properties. +A 2 dimensional vector. The constructor takes two parameters for the x and y values. ````js var Vector2 = require('vector2'); @@ -733,7 +733,7 @@ var Vector2 = require('vector2'); var vec = new Vector2(144, 168); ```` -For more information, please refer to the Vector2 class documentation in the three.js library. +For more information, please refer to the [Vector2 class documentation in the three.js library](http://threejs.org/docs/#Reference/Math/Vector2). [API Reference]: #api-reference From 65e3bf4539ce57c9c14a61b4c7c6c3378f0ef99a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 23:55:01 -0700 Subject: [PATCH 269/791] Change Stage into a Window mixin --- src/js/ui/stage.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/js/ui/stage.js b/src/js/ui/stage.js index e45920bb..fe5563f0 100644 --- a/src/js/ui/stage.js +++ b/src/js/ui/stage.js @@ -1,20 +1,16 @@ var util2 = require('util2'); var Emitter = require('emitter'); var WindowStack = require('ui/windowstack'); -var Window = require('ui/window'); var simply = require('ui/simply'); var Stage = function(stageDef) { - Window.call(this, stageDef); + this.state = stageDef || {}; this._items = []; }; -util2.inherit(Stage, Window); - util2.copy(Emitter.prototype, Stage.prototype); Stage.prototype._show = function() { - Window.prototype._show.apply(this, arguments); this.each(function(element, index) { this._insert(index, element); }.bind(this)); From 75307f6e461b84054d5aa477fabf314ba5bb56eb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 23:55:38 -0700 Subject: [PATCH 270/791] Change Window to mixin Stage --- src/js/ui/window.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 1e57b696..04e24e2d 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -4,6 +4,7 @@ var Emitter = require('emitter'); var Accel = require('ui/accel'); var WindowStack = require('ui/windowstack'); var Propable = require('ui/propable'); +var Stage = require('ui/stage'); var simply = require('ui/simply'); var buttons = [ @@ -50,6 +51,8 @@ var Window = function(windowDef) { this.state = windowDef || {}; this.state.id = nextId++; this._buttonInit(); + this._items = []; + this._dynamic = true; }; Window._codeName = 'window'; @@ -58,18 +61,14 @@ util2.copy(Emitter.prototype, Window.prototype); util2.copy(Propable.prototype, Window.prototype); +util2.copy(Stage.prototype, Window.prototype); + Propable.makeAccessors(accessorProps, Window.prototype); Window.prototype._id = function() { return this.state.id; }; -Window.prototype._prop = function() { - if (this === WindowStack.top()) { - simply.impl.window.apply(this, arguments); - } -}; - Window.prototype._hide = function(broadcast) { if (broadcast === false) { return; } simply.impl.windowHide(this._id()); @@ -82,6 +81,9 @@ Window.prototype.hide = function() { Window.prototype._show = function() { this._prop(this.state, true); + if (this._dynamic) { + Stage.prototype._show.call(this); + } }; Window.prototype.show = function() { @@ -89,6 +91,18 @@ Window.prototype.show = function() { return this; }; +Window.prototype._insert = function() { + if (this._dynamic) { + Stage.prototype._insert.apply(this, arguments); + } +}; + +Window.prototype._remove = function() { + if (this._dynamic) { + Stage.prototype._remove.apply(this, arguments); + } +}; + Window.prototype._clearAction = function() { actionProps.forEach(myutil.unset.bind(this, this.state.action)); }; From 672a155caa7082b97c5c7dbeea99f80601233cdb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 23:56:58 -0700 Subject: [PATCH 271/791] Mark Card and Menu as not dynamic Windows --- src/js/ui/card.js | 1 + src/js/ui/menu.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/js/ui/card.js b/src/js/ui/card.js index 4f070099..83518e01 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -64,6 +64,7 @@ var clearableProps = textProps.concat(imageProps); var Card = function(cardDef) { Window.call(this, cardDef); + this._dynamic = false; }; Card._codeName = 'card'; diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index ccfa8a5d..f2199988 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -7,6 +7,7 @@ var simply = require('ui/simply'); var Menu = function(menuDef) { Window.call(this, menuDef); + this._dynamic = false; this._sections = {}; }; From 1a3cf1098e4730750d60ce52ccf11ab2af3ca6f2 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 23:57:17 -0700 Subject: [PATCH 272/791] Export UI Window instead of Stage --- src/js/ui/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ui/index.js b/src/js/ui/index.js index f6fe5a80..e39b7a7f 100644 --- a/src/js/ui/index.js +++ b/src/js/ui/index.js @@ -1,9 +1,9 @@ var UI = {}; UI.Vector2 = require('vector2'); +UI.Window = require('ui/window'); UI.Card = require('ui/card'); UI.Menu = require('ui/menu'); -UI.Stage = require('ui/stage'); UI.Rect = require('ui/rect'); UI.Circle = require('ui/circle'); UI.Text = require('ui/text'); From 96ab8b820d7022a1d770549cefcb037e23d9bff6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Jun 2014 23:57:33 -0700 Subject: [PATCH 273/791] Update demo app.js to use Window instead of Stage --- src/js/app.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index b46dd351..a2080e75 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -7,36 +7,40 @@ var UI = require('ui'); var Vector2 = require('vector2'); -var wind = new UI.Card({ +var main = new UI.Card({ title: 'Pebble.js', icon: 'images/menu_icon.png', body: 'Saying Hello World' }); -wind.show(); +main.show(); -wind.on('click', 'up', function(e) { +main.on('click', 'up', function(e) { var menu = new UI.Menu(); menu.items(0, [{ - title: 'Hello World!', subtitle: 'Subtitle text' + title: 'Hello World!', + subtitle: 'Subtitle text' }, { title: 'Item #2' }]); + menu.on('select', function(e) { + console.log('Selected item: ' + e.section + ' ' + e.item); + }); menu.show(); }); -wind.on('click', 'select', function(e) { - var stage = new UI.Stage(); +main.on('click', 'select', function(e) { + var wind = new UI.Window(); var textfield = new UI.Text({ text: 'Yo!', position: new Vector2(10, 10), size: new Vector2(100, 30), }); - stage.add(textfield); - stage.show(); + wind.add(textfield); + wind.show(); }); -wind.on('click', 'down', function(e) { +main.on('click', 'down', function(e) { var card = new UI.Card(); card.title('A Pebble.js Card'); card.subtitle('With subtitle'); From 8cdcf9ba0cdcfd68a2dc6c6695c031a10d5f2852 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 01:38:26 -0700 Subject: [PATCH 274/791] Update Stage docs to refer to Window --- README.md | 105 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index e1bb0792..ee49bbee 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,8 @@ Making HTTP connections is very easy with the included `ajax` library. var ajax = require('ajax'); ajax({ url: 'http://api.theysaidso.com/qod.json', type: 'json' }, function(data) { - window.body(data.contents.quote); - window.title(data.contents.author); + card.body(data.contents.quote); + card.title(data.contents.author); } ); ```` @@ -116,15 +116,15 @@ You can use any of the Pebble system fonts in your Pebble.js applications. Pleas ````js var Vector2 = require('vector2'); -var stage = new UI.Stage(); +var wind = new UI.Window(); var textfield = new UI.Text({ position: Vector2(0, 0), size: Vector2(144, 168), font: 'GOTHIC_18_BOLD', text: 'Gothic 18 Bold' }); -stage.add(textfield); -stage.show(); +wind.add(textfield); +wind.show(); ```` ## Examples @@ -277,7 +277,7 @@ Pebble.js provides three types of Windows: * [Card]: Displays a title, a subtitle, a banner image and text on a screen. The position of the elements are fixed and cannot be changed. * [Menu]: Displays a menu on the Pebble screen. This is similar to the standard system menu in Pebble. - * [Stage]: This is the most flexible Window. It allows you to add different [Element]s ([Circle], [Image], [Rect], [Text], [TimeText]) and to specify a position and size for each of them. You can also animate them. + * [Window]: The Window by itself is the most flexible. It allows you to add different [Element]s ([Circle], [Image], [Rect], [Text], [TimeText]) and to specify a position and size for each of them. You can also animate them. | Name | Type | Default | Description | | ---- | :-------: | --------- | ------------- | @@ -290,8 +290,8 @@ Pebble.js provides three types of Windows: A `Window` action bar can be displayed by setting its Window `action` property to an `actionDef`. -| Name | Type | Default | Description | -| ---- | :-------: | --------- | ------------- | +| Name | Type | Default | Description | +| ---- | :-------: | --------- | ------------- | | `up` | Image | None | An image to display in the action bar, next to the up button. | | `select` | Image | None | An image to display in the action bar, next to the select button. | | `down` | Image | None | An image to display in the action bar, next to the down button. | @@ -375,6 +375,47 @@ card.action('up', 'images/action_icon_plus.png'); Accessor to the `fullscreen` property. See [Window]. +### Window (dynamic) + +A [Window] instantiated directly is dynamic window that can display a completely customizable user interface on the screen. When you initialize it, a window is empty and you will need to create instances of [Element] and add them to the window. [Card] and [Menu] will not display elements added to them in this way. + +````js +// Create a dynamic window +var wind = new UI.Window(); + +// Add a rect element +var rect = new UI.Rect({ size: Vector2(20, 20) }); +wind.add(rect); + +wind.show(); +```` + +#### Window.add(element) + +Adds an element to to this [Window]. This element will be immediately visible. + +#### Window.insert(index, element) + +Inserts an element at a specific index in the list of Element. + +#### Window.remove(element) + +Removes an element from the [Window]. + +#### Window.index(element) + +Returns the index of an element in the [Window] or -1 if the element is not in the window. + +#### Window.each(callback) + +Iterates over all the elements on the [Window]. + +````js +wind.each(function(element) { + console.log('Element: ' + JSON.stringify(element)); +}); +```` + ### Card A Card is a type of [Window] that allows you to display a title, a subtitle, an image and a body on the screen of Pebble. @@ -474,44 +515,6 @@ Registers a callback called when an item in the menu is selected. See `Menu.on('select, callback)` -### Stage - -A stage is a type of [Window] that displays a completely customizable user interface on the screen. When you initialize it, a [Stage] is empty and you will need to create instances of [Element] and add them to the stage. - -Just like any window, you can initialize a Stage by passing an object to the constructor or by calling accessors to change the properties. - -The properties available on a [Stage] are: - -| Name | Type | Default | Description | -| ---- |:-------:|---------|-------------| -| `scrollable` | boolean | false | Whether the user can scroll this card with the up and down button. When this is enabled, click events on the up and down button will not be transmitted to your app. | - -#### Stage.add(element) - -Adds an element to to this stage. This element will be immediately visible. - -#### Stage.insert(index, element) - -Inserts an element at a specific index in the list of Element. - -#### Stage.remove(element) - -Removes an element from the [Stage]. - -#### Stage.index(element) - -Returns the index of an element in the Stage or -1 if the element is not in the Stage. - -#### Stage.each(callback) - -Iterates over all the elements on the stage. - -````js -stage.each(function(element) { - console.log('Element: ' + JSON.stringify(element)); -}); -```` - ### Element There are four types of [Element] that can be instantiated at the moment: [Circle], [Image], [Rect] and [Text]. @@ -520,8 +523,8 @@ They all share some common properties: | Name | Type | Default | Description | | ------------ | :-------: | --------- | ------------- | -| `position` | Vector2 | | Position of this element in the stage. | -| `size` | Vector2 | | Size of this element in this stage. | +| `position` | Vector2 | | Position of this element in the window. | +| `size` | Vector2 | | Size of this element in this window. | | `borderColor` | string | '' | Color of the border of this element ('clear', 'black',or 'white'). | | `backgroundColor` | string | '' | Background color of this element ('clear', 'black' or 'white'). | @@ -536,11 +539,11 @@ console.log('This element background color is: ' + element.backgroundColor()); #### Element.index() -Returns the index of this element in its stage or -1 if this element is not part of a stage. +Returns the index of this element in its [Window] or -1 if this element is not part of a window. #### Element.remove() -Removes this element from its stage. +Removes this element from its [Window]. #### Element.animate(field, value, duration) @@ -567,7 +570,7 @@ An [Element] that displays a circle on the screen. Default properties value: * `backgroundColor`: 'white' - * `borderColor:`: 'clear' + * `borderColor`: 'clear' ### Rect From 531c9d66edd9b88f3a1a10795d52e642126a701c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 01:39:02 -0700 Subject: [PATCH 275/791] Add Element animate docs --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ee49bbee..a0b838d3 100644 --- a/README.md +++ b/README.md @@ -545,7 +545,34 @@ Returns the index of this element in its [Window] or -1 if this element is not p Removes this element from its [Window]. -#### Element.animate(field, value, duration) +#### Element.animate(animateDef, [duration=400]) + +The `position` and `size` properties can be animated. An `animateDef` is object with any supported properties specified. See [Element] for a description of those properties. The default animation duration is 400 milliseconds. + +````js +// Use the element's position and size to avoid allocating more vectors. +var pos = element.position(); +var size = element.size(); + +// Use the *Self methods to also avoid allocating more vectors. +pos.addSelf(size); +size.addSelf(size); + +// Schedule the animation with an animateDef +element.animate({ position: pos, size: size }); +```` + +When calling animate, the new values save immediately to the [Element]. + +#### Element.animate(field, value, [duration=400]) + +You can also animate a single property by specifying a field by its name. + +````js +var pos = element.position(); +pos.y += 20; +element.animate('position', pos, 1000); +```` #### Element.position(position) From a401b46175fe37a1305e22ea2795b0ecdd0d74d4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 02:06:47 -0700 Subject: [PATCH 276/791] Fix Window and Element self-referential wording --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a0b838d3..d70daa4c 100644 --- a/README.md +++ b/README.md @@ -392,7 +392,7 @@ wind.show(); #### Window.add(element) -Adds an element to to this [Window]. This element will be immediately visible. +Adds an element to to the [Window]. The element will be immediately visible. #### Window.insert(index, element) @@ -539,11 +539,11 @@ console.log('This element background color is: ' + element.backgroundColor()); #### Element.index() -Returns the index of this element in its [Window] or -1 if this element is not part of a window. +Returns the index of the element in its [Window] or -1 if the element is not part of a window. #### Element.remove() -Removes this element from its [Window]. +Removes the element from its [Window]. #### Element.animate(animateDef, [duration=400]) From f5bdaa15f4007b0b09473c73d1c421bf1d4ec553 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 02:42:55 -0700 Subject: [PATCH 277/791] Add simply menu clear to enable nested menues --- src/js/ui/simply-pebble.js | 10 +++++++--- src/simply/simply_menu.c | 21 ++++++++++++++------- src/simply/simply_menu.h | 4 +--- src/simply/simply_msg.c | 15 +++++++++------ src/simply/simply_stage.h | 3 --- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index a8ca649c..f8597012 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -537,10 +537,10 @@ var setActionPacket = function(packet, command, actionDef) { }; SimplyPebble.window = function(windowDef, clear) { - clear = toClearFlags(clear); var command = commandMap.setWindow; var packet = makePacket(command, windowDef); if (clear) { + clear = toClearFlags(clear); packet[command.paramMap.clear.id] = clear; } setActionPacket(packet, command, windowDef.action); @@ -555,10 +555,10 @@ SimplyPebble.windowHide = function(windowId) { }; SimplyPebble.card = function(cardDef, clear) { - clear = toClearFlags(clear); var command = commandMap.setCard; var packet = makePacket(command, cardDef); if (clear) { + clear = toClearFlags(clear); packet[command.paramMap.clear.id] = clear; } setActionPacket(packet, command, cardDef.action); @@ -588,7 +588,7 @@ SimplyPebble.accelPeek = function(callback) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.menu = function(menuDef) { +SimplyPebble.menu = function(menuDef, clear) { var command = commandMap.setMenu; var packetDef = util2.copy(menuDef); if (packetDef.sections instanceof Array) { @@ -598,6 +598,10 @@ SimplyPebble.menu = function(menuDef) { packetDef.sections = 1; } var packet = makePacket(command, packetDef); + if (clear) { + clear = toClearFlags(clear); + packet[command.paramMap.clear.id] = clear; + } SimplyPebble.sendPacket(packet); }; diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 04dfb38a..1919161d 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -153,6 +153,7 @@ void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections) { num_sections = 1; } self->menu_layer.num_sections = num_sections; + mark_dirty(self); } void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section) { @@ -266,13 +267,7 @@ static void window_disappear(Window *window) { SimplyMenu *self = window_get_user_data(window); simply_msg_window_hide(self->window.id); - while (self->menu_layer.sections) { - destroy_section(self, (SimplyMenuSection*) self->menu_layer.sections); - } - - while (self->menu_layer.items) { - destroy_item(self, (SimplyMenuItem*) self->menu_layer.items); - } + simply_menu_clear(self); } static void window_unload(Window *window) { @@ -284,6 +279,18 @@ static void window_unload(Window *window) { simply_window_unload(&self->window); } +void simply_menu_clear(SimplyMenu *self) { + while (self->menu_layer.sections) { + destroy_section(self, (SimplyMenuSection*) self->menu_layer.sections); + } + + while (self->menu_layer.items) { + destroy_item(self, (SimplyMenuItem*) self->menu_layer.items); + } + + mark_dirty(self); +} + void simply_menu_show(SimplyMenu *self) { if (!self->window.window) { return; diff --git a/src/simply/simply_menu.h b/src/simply/simply_menu.h index 98709932..f5699143 100644 --- a/src/simply/simply_menu.h +++ b/src/simply/simply_menu.h @@ -68,13 +68,11 @@ struct SimplyMenuItem { }; SimplyMenu *simply_menu_create(Simply *simply); - void simply_menu_destroy(SimplyMenu *self); void simply_menu_show(SimplyMenu *self); +void simply_menu_clear(SimplyMenu *self); void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections); - void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section); - void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 845d18de..2ee696e9 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -139,9 +139,9 @@ static SimplyWindow *get_top_simply_window(Simply *simply) { return window; } -static void set_window(SimplyWindow *window, DictionaryIterator *iter) { +static void set_window(SimplyWindow *window, DictionaryIterator *iter, Simply *simply) { Tuple *tuple; - if ((tuple = dict_find(iter, SetWindow_clear))) { + if (&simply->menu->window != window && (tuple = dict_find(iter, SetWindow_clear))) { simply_window_set_action_bar(window, false); simply_window_action_bar_clear(window); } @@ -176,7 +176,7 @@ static void handle_set_window(DictionaryIterator *iter, Simply *simply) { if (!window) { return; } - set_window(window, iter); + set_window(window, iter, simply); } static void handle_hide_window(DictionaryIterator *iter, Simply *simply) { @@ -221,7 +221,7 @@ static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { break; } } - set_window(&ui->window, iter); + set_window(&ui->window, iter, simply); simply_ui_show(ui); } @@ -280,6 +280,9 @@ static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { + case SetMenu_clear: + simply_menu_clear(menu); + break; case SetMenu_id: menu->window.id = tuple->value->uint32; break; @@ -288,7 +291,7 @@ static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { break; } } - set_window(&menu->window, iter); + set_window(&menu->window, iter, simply); simply_menu_show(menu); } @@ -382,7 +385,7 @@ static void handle_set_stage(DictionaryIterator *iter, Simply *simply) { break; } } - set_window(&stage->window, iter); + set_window(&stage->window, iter, simply); simply_stage_show(stage); } diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index ab9538f4..e3991cdd 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -111,14 +111,11 @@ struct SimplyAnimation { }; SimplyStage *simply_stage_create(Simply *simply); -void simply_stage_clear(SimplyStage *self); void simply_stage_destroy(SimplyStage *self); void simply_stage_show(SimplyStage *self); void simply_stage_clear(SimplyStage *self); -void simply_stage_clear(SimplyStage *self); - void simply_stage_update(SimplyStage *self); void simply_stage_update_ticker(SimplyStage *self); From e0db0940a5e5122bce1f8c081ac4fcf0e3872255 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 03:11:42 -0700 Subject: [PATCH 278/791] Add Accel.config doc clarification --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d70daa4c..d6be2f10 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,9 @@ Accel.init(); #### Accel.config(accelConfig) -This function configures the accelerometer. It takes an `accelConfig` object: +This function configures the accelerometer `data` events to your liking. The `tap` event requires no configuration for use. Configuring the accelerometer is a very error prone process, so it is recommended to not configure the accelerometer and use `data` events with the default configuration without calling `Accel.config`. + +`Accel.config` takes an `accelConfig` object with the following properties: | Name | Type | Argument | Default | Description | | ---- | :----: | :--------: | --------- | ------------- | @@ -204,7 +206,7 @@ This function configures the accelerometer. It takes an `accelConfig` object: The number of callbacks will depend on the configuration of the accelerometer. With the default rate of 100Hz and 25 samples, your callback will be called every 250ms with 25 samples each time. -**Important:** If you configure the accelerometer to send data many events, you will overload the bluetooth connection. We recommend that you send at most 5 events per second. +**Important:** If you configure the accelerometer to send many `data` events, you will overload the bluetooth connection. We recommend that you send at most 5 events per second. #### Accel.peek(callback) From 5af7a99f20ec34b9d1ab6d8a19b23c8e4a5a5866 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 03:29:44 -0700 Subject: [PATCH 279/791] Implement sequential pebble message send packet --- src/js/ui/simply-pebble.js | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index a8ca649c..3bd97b60 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -492,13 +492,35 @@ var makePacket = function(command, def) { return packet; }; -SimplyPebble.sendPacket = function(packet) { - var send; - send = function() { - Pebble.sendAppMessage(packet, util2.noop, send); - }; - send(); -}; +SimplyPebble.sendPacket = (function() { + var queue = []; + var sending = false; + + function stop() { + sending = false; + } + + function consume() { + queue.splice(0, 1); + if (queue.length === 0) { return stop(); } + cycle(); + } + + function cycle() { + var head = queue[0]; + if (!head) { return stop(); } + Pebble.sendAppMessage(head, consume, cycle); + } + + function send(packet) { + queue.push(packet); + if (sending) { return; } + sending = true; + cycle(); + } + + return send; +})(); SimplyPebble.buttonConfig = function(buttonConf) { var command = commandMap.configButtons; From 7848fad4771d8ecae7e808eb51fd76bc53cb3fa3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 06:37:19 -0700 Subject: [PATCH 280/791] Add util dict --- src/util/dict.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/util/dict.h diff --git a/src/util/dict.h b/src/util/dict.h new file mode 100644 index 00000000..79eefa68 --- /dev/null +++ b/src/util/dict.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +static inline void *dict_copy_to_buffer(DictionaryIterator *iter, size_t *length_out) { + size_t length = dict_size(iter); + void *buffer = malloc(length); + if (!buffer) { + return NULL; + } + + memcpy(buffer, iter->dictionary, length); + if (length_out) { + *length_out = length; + } + return buffer; +} + +static inline void dict_copy_from_buffer(DictionaryIterator *iter, void *buffer, size_t length) { + DictionaryIterator iter_copy = *iter; + dict_read_first(&iter_copy); + memcpy(iter->dictionary, buffer, length); + iter->cursor = (void*) iter->dictionary + length; +} From c576669629eada01020f033f20e20fe4fc83384a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 06:37:31 -0700 Subject: [PATCH 281/791] Add simply msg packet send queue with retry timer --- src/js/ui/simply-pebble.js | 5 ++ src/simply/simply.c | 8 +- src/simply/simply.h | 1 + src/simply/simply_accel.c | 7 +- src/simply/simply_accel.h | 11 ++- src/simply/simply_menu.c | 14 +-- src/simply/simply_msg.c | 178 +++++++++++++++++++++++++------------ src/simply/simply_msg.h | 43 ++++++--- src/simply/simply_stage.c | 4 +- src/simply/simply_stage.h | 2 +- src/simply/simply_ui.c | 4 +- src/simply/simply_window.c | 6 +- 12 files changed, 188 insertions(+), 95 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 3bd97b60..dc2d920a 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -727,6 +727,11 @@ SimplyPebble.onAppMessage = function(e) { var code = payload[0]; var command = commands[code]; + if (!command) { + console.log('Received unknown payload: ' + JSON.stringify(payload)); + return; + } + switch (command.name) { case 'windowHide': WindowStack.emitHide(payload[1]); diff --git a/src/simply/simply.c b/src/simply/simply.c index da8c0d61..bb0a2e1d 100644 --- a/src/simply/simply.c +++ b/src/simply/simply.c @@ -12,23 +12,23 @@ Simply *simply_init(void) { Simply *simply = malloc(sizeof(*simply)); - simply->accel = simply_accel_create(); - simply->res = simply_res_create(); + simply->accel = simply_accel_create(simply); + simply->res = simply_res_create(simply); simply->splash = simply_splash_create(simply); simply->stage = simply_stage_create(simply); simply->menu = simply_menu_create(simply); + simply->msg = simply_msg_create(simply); simply->ui = simply_ui_create(simply); bool animated = false; window_stack_push(simply->splash->window, animated); - simply_msg_init(simply); return simply; } void simply_deinit(Simply *simply) { - simply_msg_deinit(); simply_ui_destroy(simply->ui); + simply_msg_destroy(simply->msg); simply_menu_destroy(simply->menu); simply_stage_destroy(simply->stage); simply_res_destroy(simply->res); diff --git a/src/simply/simply.h b/src/simply/simply.h index 1fabc319..3b913867 100644 --- a/src/simply/simply.h +++ b/src/simply/simply.h @@ -10,6 +10,7 @@ struct Simply { struct SimplySplash *splash; struct SimplyStage *stage; struct SimplyMenu *menu; + struct SimplyMsg *msg; struct SimplyUi *ui; }; diff --git a/src/simply/simply_accel.c b/src/simply/simply_accel.c index ec146452..31d47dd1 100644 --- a/src/simply/simply_accel.c +++ b/src/simply/simply_accel.c @@ -9,7 +9,7 @@ SimplyAccel *s_accel = NULL; static void handle_accel_data(AccelData *data, uint32_t num_samples) { - simply_msg_accel_data(data, num_samples, TRANSACTION_ID_INVALID); + simply_msg_accel_data(s_accel->simply->msg, data, num_samples, TRANSACTION_ID_INVALID); } void simply_accel_set_data_rate(SimplyAccel *self, AccelSamplingRate rate) { @@ -48,16 +48,17 @@ void simply_accel_peek(SimplyAccel *self, AccelData *data) { } static void handle_accel_tap(AccelAxisType axis, int32_t direction) { - simply_msg_accel_tap(axis, direction); + simply_msg_accel_tap(s_accel->simply->msg, axis, direction); } -SimplyAccel *simply_accel_create(void) { +SimplyAccel *simply_accel_create(Simply *simply) { if (s_accel) { return s_accel; } SimplyAccel *self = malloc(sizeof(*self)); *self = (SimplyAccel) { + .simply = simply, .rate = ACCEL_SAMPLING_100HZ, .num_samples = 25, }; diff --git a/src/simply/simply_accel.h b/src/simply/simply_accel.h index 68636dd6..525c4028 100644 --- a/src/simply/simply_accel.h +++ b/src/simply/simply_accel.h @@ -1,24 +1,23 @@ #pragma once +#include "simply.h" + #include typedef struct SimplyAccel SimplyAccel; struct SimplyAccel { + Simply *simply; bool data_subscribed; AccelSamplingRate rate; uint32_t num_samples; }; -SimplyAccel *simply_accel_create(void); +SimplyAccel *simply_accel_create(Simply *simply); +void simply_accel_destroy(SimplyAccel *self); void simply_accel_set_data_rate(SimplyAccel *self, AccelSamplingRate rate); - void simply_accel_set_data_samples(SimplyAccel *self, uint32_t num_samples); - void simply_accel_set_data_subscribe(SimplyAccel *self, bool subscribe); void simply_accel_peek(SimplyAccel *self, AccelData *data); - -void simply_accel_destroy(SimplyAccel *self); - diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 04dfb38a..95324b94 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -87,12 +87,12 @@ static void request_menu_node(void *data) { SimplyMenuSection *section = (SimplyMenuSection*) list1_find(self->menu_layer.sections, request_filter, NULL); bool found = false; if (section) { - simply_msg_menu_get_section(section->index); + simply_msg_menu_get_section(self->window.simply->msg, section->index); found = true; } SimplyMenuItem *item = (SimplyMenuItem*) list1_find(self->menu_layer.items, request_filter, NULL); if (item) { - simply_msg_menu_get_item(item->section, item->index); + simply_msg_menu_get_item(self->window.simply->msg, item->section, item->index); found = true; } if (found) { @@ -223,11 +223,13 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI } static void menu_select_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { - simply_msg_menu_select_click(cell_index->section, cell_index->row); + SimplyMenu *self = data; + simply_msg_menu_select_click(self->window.simply->msg, cell_index->section, cell_index->row); } static void menu_select_long_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { - simply_msg_menu_select_long_click(cell_index->section, cell_index->row); + SimplyMenu *self = data; + simply_msg_menu_select_long_click(self->window.simply->msg, cell_index->section, cell_index->row); } static void window_load(Window *window) { @@ -259,12 +261,12 @@ static void window_load(Window *window) { static void window_appear(Window *window) { SimplyMenu *self = window_get_user_data(window); - simply_msg_window_show(self->window.id); + simply_msg_window_show(self->window.simply->msg, self->window.id); } static void window_disappear(Window *window) { SimplyMenu *self = window_get_user_data(window); - simply_msg_window_hide(self->window.id); + simply_msg_window_hide(self->window.simply->msg, self->window.id); while (self->menu_layer.sections) { destroy_section(self, (SimplyMenuSection*) self->menu_layer.sections); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 845d18de..4de53f49 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -8,6 +8,8 @@ #include "simply.h" +#include "util/dict.h" +#include "util/list1.h" #include "util/memory.h" #include "util/string.h" @@ -253,7 +255,7 @@ static void get_accel_data_timer_callback(void *context) { Simply *simply = context; AccelData data = { .x = 0 }; simply_accel_peek(simply->accel, &data); - if (!simply_msg_accel_data(&data, 1, 0)) { + if (!simply_msg_accel_data(simply->msg, &data, 1, 0)) { app_timer_register(10, get_accel_data_timer_callback, simply); } } @@ -599,7 +601,12 @@ static void failed_callback(DictionaryIterator *iter, AppMessageResult reason, S } } -void simply_msg_init(Simply *simply) { +SimplyMsg *simply_msg_create(Simply *simply) { + SimplyMsg *self = malloc(sizeof(*self)); + *self = (SimplyMsg) { .simply = simply }; + + simply->msg = self; + const uint32_t size_inbound = 2048; const uint32_t size_outbound = 512; app_message_open(size_inbound, size_outbound); @@ -610,75 +617,126 @@ void simply_msg_init(Simply *simply) { app_message_register_inbox_dropped(dropped_callback); app_message_register_outbox_sent(sent_callback); app_message_register_outbox_failed((AppMessageOutboxFailed) failed_callback); + + return self; } -void simply_msg_deinit() { +void simply_msg_destroy(SimplyMsg *self) { + if (!self) { + return; + } + app_message_deregister_callbacks(); + + self->simply->msg = NULL; + + free(self); +} + +static void destroy_packet(SimplyPacket *packet) { + if (!packet) { + return; + } + free(packet->buffer); + packet->buffer = NULL; + free(packet); } -static bool send_click(SimplyACmd type, ButtonId button) { +static bool send_msg(SimplyPacket *packet) { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { return false; } - dict_write_uint8(iter, 0, type); - dict_write_uint8(iter, 1, button); + dict_copy_from_buffer(iter, packet->buffer, packet->length); return (app_message_outbox_send() == APP_MSG_OK); } -bool simply_msg_single_click(ButtonId button) { - return send_click(SimplyACmd_click, button); +static void send_msg_retry(void *data) { + SimplyMsg *self = data; + SimplyPacket *packet = (SimplyPacket*) self->queue; + if (!packet) { + return; + } + if (!send_msg(packet)){ + app_timer_register(10, send_msg_retry, self); + return; + } + list1_remove(&self->queue, &packet->node); + destroy_packet(packet); } -bool simply_msg_long_click(ButtonId button) { - return send_click(SimplyACmd_longClick, button); +static SimplyPacket *add_packet(SimplyMsg *self, void *buffer, size_t length) { + SimplyPacket *packet = malloc0(sizeof(*packet)); + if (!packet) { + free(buffer); + return NULL; + } + *packet = (SimplyPacket) { + .length = length, + .buffer = buffer, + }; + list1_append(&self->queue, &packet->node); + send_msg_retry(self); + return packet; } -bool simply_msg_window_show(uint32_t id) { - DictionaryIterator *iter = NULL; - if (app_message_outbox_begin(&iter) != APP_MSG_OK) { +static bool send_click(SimplyMsg *self, SimplyACmd type, ButtonId button) { + size_t length = dict_calc_buffer_size(2, 1, 1); + void *buffer = malloc0(length); + if (!buffer) { return false; } - dict_write_uint8(iter, 0, SimplyACmd_windowShow); - dict_write_uint32(iter, 1, id); - return (app_message_outbox_send() == APP_MSG_OK); + DictionaryIterator iter; + dict_write_begin(&iter, buffer, length); + dict_write_uint8(&iter, 0, type); + dict_write_uint8(&iter, 1, button); + return add_packet(self, buffer, length); } -static bool msg_window_hide(uint32_t id) { - DictionaryIterator *iter = NULL; - if (app_message_outbox_begin(&iter) != APP_MSG_OK) { +bool simply_msg_single_click(SimplyMsg *self, ButtonId button) { + return send_click(self, SimplyACmd_click, button); +} + +bool simply_msg_long_click(SimplyMsg *self, ButtonId button) { + return send_click(self, SimplyACmd_longClick, button); +} + +bool send_window(SimplyMsg *self, SimplyACmd type, uint32_t id) { + size_t length = dict_calc_buffer_size(2, 1, 4); + void *buffer = malloc0(length); + if (!buffer) { return false; } - dict_write_uint8(iter, 0, SimplyACmd_windowHide); - dict_write_uint32(iter, 1, id); - return (app_message_outbox_send() == APP_MSG_OK); + DictionaryIterator iter; + dict_write_begin(&iter, buffer, length); + dict_write_uint8(&iter, 0, type); + dict_write_uint32(&iter, 1, id); + return add_packet(self, buffer, length); } -static void window_hide_timer_callback(void *data) { - uint32_t id = (uintptr_t)(void*) data; - if (!msg_window_hide(id)) { - app_timer_register(10, window_hide_timer_callback, data); - } +bool simply_msg_window_show(SimplyMsg *self, uint32_t id) { + return send_window(self, SimplyACmd_windowShow, id); } -bool simply_msg_window_hide(uint32_t id) { - if (!id) { return true; } - window_hide_timer_callback((void*)(uintptr_t) id); - return true; +bool simply_msg_window_hide(SimplyMsg *self, uint32_t id) { + return send_window(self, SimplyACmd_windowHide, id); } -bool simply_msg_accel_tap(AccelAxisType axis, int32_t direction) { - DictionaryIterator *iter = NULL; - if (app_message_outbox_begin(&iter) != APP_MSG_OK) { +bool simply_msg_accel_tap(SimplyMsg *self, AccelAxisType axis, int32_t direction) { + size_t length = dict_calc_buffer_size(3, 1, 1, 1); + void *buffer = malloc0(length); + if (!buffer) { return false; } - dict_write_uint8(iter, 0, SimplyACmd_accelTap); - dict_write_uint8(iter, 1, axis); - dict_write_int8(iter, 2, direction); - return (app_message_outbox_send() == APP_MSG_OK); + DictionaryIterator iter; + dict_write_begin(&iter, buffer, length); + dict_write_uint8(&iter, 0, SimplyACmd_accelTap); + dict_write_uint8(&iter, 1, axis); + dict_write_int8(&iter, 2, direction); + return add_packet(self, buffer, length); } -bool simply_msg_accel_data(AccelData *data, uint32_t num_samples, int32_t transaction_id) { +bool simply_msg_accel_data(SimplyMsg *self, AccelData *data, uint32_t num_samples, int32_t transaction_id) { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { return false; @@ -692,35 +750,45 @@ bool simply_msg_accel_data(AccelData *data, uint32_t num_samples, int32_t transa return (app_message_outbox_send() == APP_MSG_OK); } -bool simply_msg_menu_get_section(uint16_t index) { +static void write_menu_item(DictionaryIterator *iter, SimplyACmd type, uint16_t section, uint16_t index) { + dict_write_uint8(iter, 0, type); + dict_write_uint16(iter, 1, section); + dict_write_uint16(iter, 2, index); +} + +static bool send_menu_item(SimplyMsg *self, SimplyACmd type, uint16_t section, uint16_t index) { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { return false; } - dict_write_uint8(iter, 0, SimplyACmd_getMenuSection); - dict_write_uint16(iter, 1, index); + write_menu_item(iter, type, section, index); return (app_message_outbox_send() == APP_MSG_OK); } -static bool send_menu_item(SimplyACmd type, uint16_t section, uint16_t index) { - DictionaryIterator *iter = NULL; - if (app_message_outbox_begin(&iter) != APP_MSG_OK) { +static bool send_menu_item_retry(SimplyMsg *self, SimplyACmd type, uint16_t section, uint16_t index) { + size_t length = dict_calc_buffer_size(3, 1, 2, 2); + void *buffer = malloc0(length); + if (!buffer) { return false; } - dict_write_uint8(iter, 0, type); - dict_write_uint16(iter, 1, section); - dict_write_uint16(iter, 2, index); - return (app_message_outbox_send() == APP_MSG_OK); + DictionaryIterator iter; + dict_write_begin(&iter, buffer, length); + write_menu_item(&iter, type, section, index); + return add_packet(self, buffer, length); +} + +bool simply_msg_menu_get_section(SimplyMsg *self, uint16_t index) { + return send_menu_item(self, SimplyACmd_getMenuSection, index, 0); } -bool simply_msg_menu_get_item(uint16_t section, uint16_t index) { - return send_menu_item(SimplyACmd_getMenuItem, section, index); +bool simply_msg_menu_get_item(SimplyMsg *self, uint16_t section, uint16_t index) { + return send_menu_item(self, SimplyACmd_getMenuItem, section, index); } -bool simply_msg_menu_select_click(uint16_t section, uint16_t index) { - return send_menu_item(SimplyACmd_menuSelect, section, index); +bool simply_msg_menu_select_click(SimplyMsg *self, uint16_t section, uint16_t index) { + return send_menu_item_retry(self, SimplyACmd_menuSelect, section, index); } -bool simply_msg_menu_select_long_click(uint16_t section, uint16_t index) { - return send_menu_item(SimplyACmd_menuLongSelect, section, index); +bool simply_msg_menu_select_long_click(SimplyMsg *self, uint16_t section, uint16_t index) { + return send_menu_item_retry(self, SimplyACmd_menuLongSelect, section, index); } diff --git a/src/simply/simply_msg.h b/src/simply/simply_msg.h index f4a6fbf6..ba1d1e4e 100644 --- a/src/simply/simply_msg.h +++ b/src/simply/simply_msg.h @@ -2,25 +2,42 @@ #include "simply.h" +#include "util/list1.h" + #include #define TRANSACTION_ID_INVALID (-1) -void simply_msg_init(Simply *simply); -void simply_msg_deinit(); +typedef struct SimplyMsg SimplyMsg; + +struct SimplyMsg { + Simply *simply; + List1Node *queue; +}; + +typedef struct SimplyPacket SimplyPacket; + +struct SimplyPacket { + List1Node node; + size_t length; + void *buffer; +}; + +SimplyMsg *simply_msg_create(Simply *simply); +void simply_msg_destroy(SimplyMsg *self); bool simply_msg_has_communicated(); -bool simply_msg_single_click(ButtonId button); -bool simply_msg_long_click(ButtonId button); +bool simply_msg_single_click(SimplyMsg *self, ButtonId button); +bool simply_msg_long_click(SimplyMsg *self, ButtonId button); -bool simply_msg_window_show(uint32_t id); -bool simply_msg_window_hide(uint32_t id); +bool simply_msg_window_show(SimplyMsg *self, uint32_t id); +bool simply_msg_window_hide(SimplyMsg *self, uint32_t id); -bool simply_msg_accel_tap(AccelAxisType axis, int32_t direction); -bool simply_msg_accel_data(AccelData *accel, uint32_t num_samples, int32_t transaction_id); +bool simply_msg_accel_tap(SimplyMsg *self, AccelAxisType axis, int32_t direction); +bool simply_msg_accel_data(SimplyMsg *self, AccelData *accel, uint32_t num_samples, int32_t transaction_id); -bool simply_msg_menu_get_section(uint16_t index); -bool simply_msg_menu_get_item(uint16_t section, uint16_t index); -bool simply_msg_menu_select_click(uint16_t section, uint16_t index); -bool simply_msg_menu_select_long_click(uint16_t section, uint16_t index); -bool simply_msg_menu_hide(uint16_t section, uint16_t index); +bool simply_msg_menu_get_section(SimplyMsg *self, uint16_t index); +bool simply_msg_menu_get_item(SimplyMsg *self, uint16_t section, uint16_t index); +bool simply_msg_menu_select_click(SimplyMsg *self, uint16_t section, uint16_t index); +bool simply_msg_menu_select_long_click(SimplyMsg *self, uint16_t section, uint16_t index); +bool simply_msg_menu_hide(SimplyMsg *self, uint16_t section, uint16_t index); diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index b9cfaa61..511bec00 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -306,14 +306,14 @@ static void window_load(Window *window) { static void window_appear(Window *window) { SimplyStage *self = window_get_user_data(window); - simply_msg_window_show(self->window.id); + simply_msg_window_show(self->window.simply->msg, self->window.id); simply_stage_update_ticker(self); } static void window_disappear(Window *window) { SimplyStage *self = window_get_user_data(window); - simply_msg_window_hide(self->window.id); + simply_msg_window_hide(self->window.simply->msg, self->window.id); simply_stage_clear(self); } diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index ab9538f4..ce1c493f 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -2,7 +2,7 @@ #include "simply_window.h" -#include "simply/simply.h" +#include "simply.h" #include "util/list1.h" diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 0b0b7f96..192bdee7 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -255,12 +255,12 @@ static void window_load(Window *window) { static void window_appear(Window *window) { SimplyUi *self = window_get_user_data(window); - simply_msg_window_show(self->window.id); + simply_msg_window_show(self->window.simply->msg, self->window.id); } static void window_disappear(Window *window) { SimplyUi *self = window_get_user_data(window); - simply_msg_window_hide(self->window.id); + simply_msg_window_hide(self->window.simply->msg, self->window.id); } static void window_unload(Window *window) { diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 61187a3d..033d7d51 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -125,14 +125,14 @@ static void single_click_handler(ClickRecognizerRef recognizer, void *context) { bool is_enabled = (self->button_mask & (1 << button)); if (button == BUTTON_ID_BACK && !is_enabled) { if (simply_msg_has_communicated()) { - simply_msg_window_hide(self->id); + simply_msg_window_hide(self->simply->msg, self->id); } else { bool animated = true; window_stack_pop(animated); } } if (is_enabled) { - simply_msg_single_click(button); + simply_msg_single_click(self->simply->msg, button); } } @@ -141,7 +141,7 @@ static void long_click_handler(ClickRecognizerRef recognizer, void *context) { ButtonId button = click_recognizer_get_button_id(recognizer); bool is_enabled = (self->button_mask & (1 << button)); if (is_enabled) { - simply_msg_long_click(button); + simply_msg_long_click(self->simply->msg, button); } } From e1d83784f086a59df884f71e6d258198359dd36e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 06:59:00 -0700 Subject: [PATCH 282/791] Rename menu item image property to icon --- src/js/ui/simply-pebble.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 50fff1bf..d63578dc 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -267,7 +267,7 @@ var commands = [{ name: 'subtitle', type: String, }, { - name: 'image', + name: 'icon', type: Image, }], }, { From 07a8dfcc64b6fe0c73426859430e31432267b4ee Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 06:59:12 -0700 Subject: [PATCH 283/791] Update demo app.js wording and content --- src/js/app.js | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index a2080e75..0c5af5bc 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -10,19 +10,25 @@ var Vector2 = require('vector2'); var main = new UI.Card({ title: 'Pebble.js', icon: 'images/menu_icon.png', - body: 'Saying Hello World' + subtitle: 'Hello World!', + body: 'Press any button.' }); main.show(); main.on('click', 'up', function(e) { - var menu = new UI.Menu(); - menu.items(0, [{ - title: 'Hello World!', - subtitle: 'Subtitle text' - }, { - title: 'Item #2' - }]); + var menu = new UI.Menu({ + sections: [{ + items: [{ + title: 'Pebble.js', + icon: 'images/menu_icon.png', + subtitle: 'Can do Menus' + }, { + title: 'Hello World!', + subtitle: 'Subtitle Text' + }] + }] + }); menu.on('select', function(e) { console.log('Selected item: ' + e.section + ' ' + e.item); }); @@ -32,9 +38,11 @@ main.on('click', 'up', function(e) { main.on('click', 'select', function(e) { var wind = new UI.Window(); var textfield = new UI.Text({ - text: 'Yo!', - position: new Vector2(10, 10), - size: new Vector2(100, 30), + position: new Vector2(0, 50), + size: new Vector2(144, 30), + font: 'gothic-24-bold', + text: 'Hello World!', + textAlign: 'center' }); wind.add(textfield); wind.show(); @@ -42,8 +50,8 @@ main.on('click', 'select', function(e) { main.on('click', 'down', function(e) { var card = new UI.Card(); - card.title('A Pebble.js Card'); - card.subtitle('With subtitle'); - card.body('This is the simplest window you can push to the screen.'); + card.title('A Card'); + card.subtitle('Is a Window'); + card.body('The simplest window type in Pebble.js.'); card.show(); }); From 164359ea820a32520c86c40473ad9e47654dd57d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 09:08:54 -0700 Subject: [PATCH 284/791] Add list1 index --- src/util/list1.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/util/list1.h b/src/util/list1.h index 0b5357d3..e962ab75 100644 --- a/src/util/list1.h +++ b/src/util/list1.h @@ -53,6 +53,17 @@ static inline List1Node *list1_append(List1Node **head, List1Node *node) { return node; } +static inline int list1_index(List1Node *head, List1Node *node) { + List1Node *walk = head; + for (int i = 0; walk; ++i) { + if (walk == node) { + return i; + } + walk = walk->next; + } + return -1; +} + static inline List1Node *list1_insert(List1Node **head, int index, List1Node *node) { List1Node **next_ref = head; List1Node *walk = *head; From 679ee5bfbf881e6ffa110ba0c1a0ade4873dd923 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 09:12:28 -0700 Subject: [PATCH 285/791] Fix simply stage animations to use teardown to free --- src/simply/simply_stage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 511bec00..75706f7d 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -43,7 +43,6 @@ static void destroy_element(SimplyStage *self, SimplyElementCommon *element) { static void destroy_animation(SimplyStage *self, SimplyAnimation *animation) { if (!animation) { return; } list1_remove(&self->stage_layer.animations, &animation->node); - property_animation_destroy(animation->animation); free(animation); } @@ -256,6 +255,7 @@ SimplyAnimation *simply_stage_animate_element(SimplyStage *self, static const PropertyAnimationImplementation implementation = { .base = { .update = (AnimationUpdateImplementation) property_animation_update_grect, + .teardown = (AnimationTeardownImplementation) free, }, .accessors = { .setter = { .grect = (const GRectSetter) element_frame_setter }, From 30536b7b922509e6d6f387db140c431eab03376d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 09:19:27 -0700 Subject: [PATCH 286/791] Add simply msg animate element done --- src/simply/simply_msg.c | 14 ++++++++++++++ src/simply/simply_msg.h | 2 ++ src/simply/simply_stage.c | 8 ++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 5bd6d9da..eeb0299f 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -42,6 +42,7 @@ enum SimplyACmd { SimplyACmd_stageElement, SimplyACmd_stageRemove, SimplyACmd_stageAnimate, + SimplyACmd_stageAnimateDone, }; typedef enum SimplySetWindowParam SimplySetWindowParam; @@ -795,3 +796,16 @@ bool simply_msg_menu_select_click(SimplyMsg *self, uint16_t section, uint16_t in bool simply_msg_menu_select_long_click(SimplyMsg *self, uint16_t section, uint16_t index) { return send_menu_item_retry(self, SimplyACmd_menuLongSelect, section, index); } + +bool simply_msg_animate_element_done(SimplyMsg *self, uint16_t index) { + size_t length = dict_calc_buffer_size(2, 1, 2); + void *buffer = malloc0(length); + if (!buffer) { + return false; + } + DictionaryIterator iter; + dict_write_begin(&iter, buffer, length); + dict_write_uint8(&iter, 0, SimplyACmd_stageAnimateDone); + dict_write_uint16(&iter, 1, index); + return add_packet(self, buffer, length); +} diff --git a/src/simply/simply_msg.h b/src/simply/simply_msg.h index ba1d1e4e..88e42bb5 100644 --- a/src/simply/simply_msg.h +++ b/src/simply/simply_msg.h @@ -41,3 +41,5 @@ bool simply_msg_menu_get_item(SimplyMsg *self, uint16_t section, uint16_t index) bool simply_msg_menu_select_click(SimplyMsg *self, uint16_t section, uint16_t index); bool simply_msg_menu_select_long_click(SimplyMsg *self, uint16_t section, uint16_t index); bool simply_msg_menu_hide(SimplyMsg *self, uint16_t section, uint16_t index); + +bool simply_msg_animate_element_done(SimplyMsg *self, uint16_t index); diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 75706f7d..bd9f221b 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -233,9 +233,13 @@ static void animation_stopped(Animation *base_animation, bool finished, void *co SimplyStage *self = context; SimplyAnimation *animation = (SimplyAnimation*) list1_find( self->stage_layer.animations, animation_filter, base_animation); - if (animation) { - destroy_animation(self, animation); + if (!animation) { + return; } + SimplyElementCommon *element = animation->element; + destroy_animation(self, animation); + simply_msg_animate_element_done(self->window.simply->msg, + list1_index(self->stage_layer.elements, &element->node)); } SimplyAnimation *simply_stage_animate_element(SimplyStage *self, From de85533f94bdb9eedafc5df82dc5c5fba658daed Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 09:20:07 -0700 Subject: [PATCH 287/791] Add stage element animation queue --- src/js/ui/element.js | 33 ++++++++++++++++++++++++++++++++- src/js/ui/simply-pebble.js | 9 +++++++++ src/js/ui/stage.js | 5 +++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/js/ui/element.js b/src/js/ui/element.js index 42e23404..d171f7fb 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -19,12 +19,17 @@ var nextId = 1; var StageElement = function(elementDef) { this.state = elementDef || {}; this.state.id = nextId++; + this._queue = []; }; util2.copy(Propable.prototype, StageElement.prototype); Propable.makeAccessors(accessorProps, StageElement.prototype); +StageElement.prototype._reset = function() { + this._queue = []; +}; + StageElement.prototype._id = function() { return this.state.id; }; @@ -68,8 +73,34 @@ StageElement.prototype.animate = function(field, value, duration) { } var animateDef = myutil.toObject(field, value); util2.copy(animateDef, this.state); - this._animate(animateDef, duration); + function animate() { + this._animate(animateDef, duration); + } + if (this._queue.length === 0) { + animate.call(this); + } else { + this.queue(animate); + } return this; }; +StageElement.prototype.queue = function(callback) { + this._queue.push(callback); +}; + +StageElement.prototype.dequeue = function() { + var callback = this._queue.shift(); + if (!callback) { return; } + callback.call(this, this.dequeue.bind(this)); +}; + +StageElement.emitAnimateDone = function(index) { + if (index === -1) { return; } + var wind = WindowStack.top(); + if (!wind || !wind._dynamic) { return; } + var element = wind.at(index); + if (!element) { return; } + element.dequeue(); +}; + module.exports = StageElement; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index d63578dc..e1ecec7c 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -6,6 +6,7 @@ var ImageService = require('ui/imageservice'); var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); var Menu = require('ui/menu'); +var StageElement = require('ui/element'); var simply = require('ui/simply'); @@ -377,6 +378,11 @@ var commands = [{ name: 'easing', type: AnimationCurve, }], +}, { + name: 'stageAnimateDone', + params: [{ + name: 'index', + }], }]; // Build the commandMap and map each command to an integer. @@ -785,6 +791,9 @@ SimplyPebble.onAppMessage = function(e) { case 'menuLongSelect': Menu.emitSelect(command.name, payload[1], payload[2]); break; + case 'stageAnimateDone': + StageElement.emitAnimateDone(payload[1]); + break; } }; diff --git a/src/js/ui/stage.js b/src/js/ui/stage.js index fe5563f0..c3b21c84 100644 --- a/src/js/ui/stage.js +++ b/src/js/ui/stage.js @@ -12,6 +12,7 @@ util2.copy(Emitter.prototype, Stage.prototype); Stage.prototype._show = function() { this.each(function(element, index) { + element._reset(); this._insert(index, element); }.bind(this)); }; @@ -27,6 +28,10 @@ Stage.prototype.each = function(callback) { return this; }; +Stage.prototype.at = function(index) { + return this._items[index]; +}; + Stage.prototype.index = function(element) { return this._items.indexOf(element); }; From 3673f2ca5f09035f26d68a61ad268072dbc9f51b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 10:18:58 -0700 Subject: [PATCH 288/791] Add element queue and dequeue docs --- README.md | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d6be2f10..b04c440a 100644 --- a/README.md +++ b/README.md @@ -564,7 +564,11 @@ size.addSelf(size); element.animate({ position: pos, size: size }); ```` -When calling animate, the new values save immediately to the [Element]. +Animations are queued when `Element.animate` is called multiple times at once. The animations will occur in order, and the first animation will occur immediately. + +When an animation begins, its destination values are saved immediately to the [Element]. + +`Element.animate` is chainable. #### Element.animate(field, value, [duration=400]) @@ -576,6 +580,29 @@ pos.y += 20; element.animate('position', pos, 1000); ```` + +#### Element.queue(callback(next)) + +`Element.queue` can be used to perform tasks that are dependent upon an animation completing, such as preparing the element for a different animation. It is recommended to use `Element.queue` instead of a timeout if the same element will be animated after the custom task. + +The `callback` you pass to `Element.queue` will be called with a function `next` as the first parameter. When `next` is called, the next item in the animation queue will begin. Items includes callbacks added by `Element.queue` or animations added by `Element.animate` before an animation is complete. Calling `next` is equivalent to calling `Element.dequeue`. + +````js +element + .animate('position', new Vector2(0, 0) + .queue(function(next) { + this.backgroundColor('white'); + next(); + }) + .animate('position', new Vector2(0, 50) +```` + +`Element.queue` is chainable. + +#### Element.dequeue() + +`Element.dequeue` can be used to continue executing items in the animation queue. It is useful in cases where the `next` function passed in `Element.queue` callbacks is not available. See [Element.queue(callback(next))] for more information on the animation queue. + #### Element.position(position) Accessor to the `position` property. See [Element]. @@ -775,7 +802,6 @@ For more information, please refer to the [Vector2 class documentation in the th [Window]: #window [Card]: #card [Menu]: #menu -[Stage]: #stage [Element]: #element [Circle]: #circle [Image]: #image @@ -783,4 +809,5 @@ For more information, please refer to the [Vector2 class documentation in the th [Text]: #text [Window.show()]: #window-show [Window.hide()]: #window-hide +[Element.queue(callback(next))]: #element-queue-callback-next [Menu.on('select, callback)]: #menu-on-select-callback From 41c98b13c5ffcaa78e5e10bc9c10c83f14b6ff09 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 10:19:17 -0700 Subject: [PATCH 289/791] Fix window dynamic and actiondef docs wording --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b04c440a..3cdd6222 100644 --- a/README.md +++ b/README.md @@ -288,9 +288,10 @@ Pebble.js provides three types of Windows: | `fullscreen` | boolean | false | When true, the Pebble status bar will not be visible and the window will use the entire screen. | | `scrollable` | boolean | false | When true, the up and down button will scroll the content of this Card. | -#### actionDef + +#### Window actionDef -A `Window` action bar can be displayed by setting its Window `action` property to an `actionDef`. +A `Window` action bar can be displayed by setting its Window `action` property to an `actionDef`: | Name | Type | Default | Description | | ---- | :-------: | --------- | ------------- | @@ -308,7 +309,7 @@ var card = new UI.Card({ }); ```` -You will need to add images to your project following the [Using Images] guide to display action bar icons. +You will need to add images to your project according to the [Using Images] guide in order to display action bar icons. #### Window.show() @@ -356,7 +357,7 @@ Just like `Window.on('click', button, handler)` but for 'longClick' events. #### Window.action(actionDef) -This is a special nested accessor to the `action` property which takes an `actionDef`. It can be used to set a new `actionDef`. See [Window] for details on `actionDef`. +This is a special nested accessor to the `action` property which takes an `actionDef`. It can be used to set a new `actionDef`. See [Window actionDef]. ````js card.action({ @@ -367,7 +368,7 @@ card.action({ #### Window.action(field, value) -You may also call `Window.action` with two arguments to set specific fields of the window's `action` propery. +You may also call `Window.action` with two arguments, `field` and `value`, to set specific fields of the window's `action` propery. `field` is a string refering to the [actionDef] property to change. `value` is the new property value to set. ````js card.action('up', 'images/action_icon_plus.png'); @@ -379,7 +380,7 @@ Accessor to the `fullscreen` property. See [Window]. ### Window (dynamic) -A [Window] instantiated directly is dynamic window that can display a completely customizable user interface on the screen. When you initialize it, a window is empty and you will need to create instances of [Element] and add them to the window. [Card] and [Menu] will not display elements added to them in this way. +A [Window] instantiated directly is a dynamic window that can display a completely customizable user interface on the screen. Dynamic windows are initialized empty and will need [Element]s added to it. [Card] and [Menu] will not display elements added to them in this way. ````js // Create a dynamic window @@ -807,6 +808,7 @@ For more information, please refer to the [Vector2 class documentation in the th [Image]: #image [Rect]: #rect [Text]: #text +[Window actionDef]: #window-actiondef [Window.show()]: #window-show [Window.hide()]: #window-hide [Element.queue(callback(next))]: #element-queue-callback-next From 24e2e6dc33c76d0a862ccc5b3d80829c88d89d8a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 10:26:34 -0700 Subject: [PATCH 290/791] Diversify demo app.js text --- src/js/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 0c5af5bc..929fdc74 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -24,7 +24,7 @@ main.on('click', 'up', function(e) { icon: 'images/menu_icon.png', subtitle: 'Can do Menus' }, { - title: 'Hello World!', + title: 'Second Item', subtitle: 'Subtitle Text' }] }] @@ -41,7 +41,7 @@ main.on('click', 'select', function(e) { position: new Vector2(0, 50), size: new Vector2(144, 30), font: 'gothic-24-bold', - text: 'Hello World!', + text: 'Text Anywhere!', textAlign: 'center' }); wind.add(textfield); From ae8562f3b13da8935a90c3d33a89d64e4209ec95 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 10:31:30 -0700 Subject: [PATCH 291/791] Change Vector2 external link to use a reference --- README.md | 3 ++- src/js/lib/vector2.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3cdd6222..f510edbb 100644 --- a/README.md +++ b/README.md @@ -793,7 +793,7 @@ var Vector2 = require('vector2'); var vec = new Vector2(144, 168); ```` -For more information, please refer to the [Vector2 class documentation in the three.js library](http://threejs.org/docs/#Reference/Math/Vector2). +For more information, see [Vector2 in the three.js reference documentation][three.js Vector2]. [API Reference]: #api-reference @@ -813,3 +813,4 @@ For more information, please refer to the [Vector2 class documentation in the th [Window.hide()]: #window-hide [Element.queue(callback(next))]: #element-queue-callback-next [Menu.on('select, callback)]: #menu-on-select-callback +[three.js Vector2]: http://threejs.org/docs/#Reference/Math/Vector2 diff --git a/src/js/lib/vector2.js b/src/js/lib/vector2.js index 53fbe78f..b8c3a79a 100644 --- a/src/js/lib/vector2.js +++ b/src/js/lib/vector2.js @@ -1,5 +1,5 @@ /** - * Vector2 from Three.js + * Vector2 from three.js * https://github.com/mrdoob/three.js * * @author mr.doob / http://mrdoob.com/ From e9cca2a3ffa97e29ea0f8dda61fba38583c16313 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Mon, 2 Jun 2014 16:09:42 -0700 Subject: [PATCH 292/791] Try/Catch around json body parsing --- src/js/ajax.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/js/ajax.js b/src/js/ajax.js index b366bb16..c0909b9a 100644 --- a/src/js/ajax.js +++ b/src/js/ajax.js @@ -81,10 +81,16 @@ var ajax = function(opt, success, failure) { req.onreadystatechange = function(e) { if (req.readyState == 4) { var body = req.responseText; + var failed = req.status != 200; if (opt.type == 'json') { - body = JSON.parse(body); + try { + body = JSON.parse(body); + } + catch (err) { + failed = true; + } } - var callback = req.status == 200 ? success : failure; + var callback = !failed ? success : failure; if (callback) { callback(body, req.status, req); } From 4cc0bf57cb49af9c48fd9104cccb5a15397a00aa Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Mon, 2 Jun 2014 16:09:42 -0700 Subject: [PATCH 293/791] Try/Catch around json body parsing --- src/js/lib/ajax.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/js/lib/ajax.js b/src/js/lib/ajax.js index 42e47fdb..49b2d24a 100644 --- a/src/js/lib/ajax.js +++ b/src/js/lib/ajax.js @@ -81,10 +81,16 @@ var ajax = function(opt, success, failure) { req.onreadystatechange = function(e) { if (req.readyState == 4) { var body = req.responseText; + var failed = req.status != 200; if (opt.type == 'json') { - body = JSON.parse(body); + try { + body = JSON.parse(body); + } + catch (err) { + failed = true; + } } - var callback = req.status == 200 ? success : failure; + var callback = !failed ? success : failure; if (callback) { callback(body, req.status, req); } From e079182b4612cfdccf956032f9018d7464bd25d2 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 Jun 2014 21:03:52 -0700 Subject: [PATCH 294/791] Fix nested stages by adding missing message stage clear param --- src/js/ui/simply-pebble.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index e1ecec7c..7d280b29 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -661,9 +661,13 @@ SimplyPebble.image = function(id, gbitmap) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.stage = function(stageDef) { +SimplyPebble.stage = function(stageDef, clear) { var command = commandMap.setStage; var packet = makePacket(command, stageDef); + if (clear) { + clear = toClearFlags(clear); + packet[command.paramMap.clear.id] = clear; + } setActionPacket(packet, command, stageDef.action); SimplyPebble.sendPacket(packet); }; From 6575d97af2852f5014414e21488821e8c6451f70 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 11 Apr 2014 20:43:54 -0700 Subject: [PATCH 295/791] Fix ajax cache buster to modify the url before opening A previous bugfix required opening the request earlier, but accidentally neglected to move up url modification as well. --- src/js/ajax.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/js/ajax.js b/src/js/ajax.js index c0909b9a..f3b177f5 100644 --- a/src/js/ajax.js +++ b/src/js/ajax.js @@ -53,6 +53,11 @@ var ajax = function(opt, success, failure) { if (failure) { failure = onHandler('failure', failure); } } + if (opt.cache === false) { + var appendSymbol = url.indexOf('?') === -1 ? '?' : '&'; + url += appendSymbol + '_=' + new Date().getTime(); + } + var req = new XMLHttpRequest(); req.open(method.toUpperCase(), url, opt.async !== false); @@ -73,11 +78,6 @@ var ajax = function(opt, success, failure) { } } - if (opt.cache === false) { - var appendSymbol = url.indexOf('?') === -1 ? '?' : '&'; - url += appendSymbol + '_=' + new Date().getTime(); - } - req.onreadystatechange = function(e) { if (req.readyState == 4) { var body = req.responseText; From fb17f6fa26460248ebf20e2e9b4f96e3e63ef6c7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 11 Apr 2014 20:47:30 -0700 Subject: [PATCH 296/791] Bump version to v0.3.4 --- appinfo.json | 2 +- releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw | Bin 0 -> 78010 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw diff --git a/appinfo.json b/appinfo.json index 84554e23..9a4bbda0 100644 --- a/appinfo.json +++ b/appinfo.json @@ -4,7 +4,7 @@ "longName": "Simply.js", "companyName": "Meiguro", "versionCode": 1, - "versionLabel": "0.3.3", + "versionLabel": "0.3.4", "capabilities": [ "configurable" ], "watchapp": { "watchface": false diff --git a/releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw b/releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw new file mode 100644 index 0000000000000000000000000000000000000000..28a29f8593c552420b5309af8d46e7d0f8350d8b GIT binary patch literal 78010 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 Date: Mon, 2 Jun 2014 21:12:08 -0700 Subject: [PATCH 297/791] Add debug window stack debug log lines --- src/js/ui/menu.js | 2 +- src/js/ui/window.js | 4 ++++ src/js/ui/windowstack.js | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index f2199988..3b72fb3c 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -11,7 +11,7 @@ var Menu = function(menuDef) { this._sections = {}; }; -Menu.prototype._codeName = 'menu'; +Menu._codeName = 'menu'; util2.inherit(Menu, Window); diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 04e24e2d..5e431caf 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -252,6 +252,10 @@ Window.prototype._buttonAutoConfig = function() { } }; +Window.prototype._toString = function() { + return '[' + this.constructor._codeName + ' ' + this._id() + ']'; +}; + Window.emit = function(type, subtype, e, klass) { var wind = e.window = WindowStack.top(); if (klass) { diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index a0f30402..84693aed 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -66,6 +66,7 @@ WindowStack.prototype.push = function(item) { this._items.push(item); this._show(item); this._hide(prevTop, false); + console.log('(+) ' + item._toString() + ' : ' + this._toString()); }; WindowStack.prototype.pop = function(broadcast) { @@ -86,6 +87,7 @@ WindowStack.prototype.remove = function(item, broadcast) { this._show(top); this._hide(item, top && top.constructor === item.constructor ? false : broadcast); } + console.log('(-) ' + item._toString() + ' : ' + this._toString()); return item; }; @@ -105,4 +107,8 @@ WindowStack.prototype.emitHide = function(windowId) { this.remove(wind); }; +WindowStack.prototype._toString = function() { + return this._items.map(function(x){ return x._toString(); }).join(','); +}; + module.exports = new WindowStack(); From fc77a3777c708c5b3815c9685b2ffe6bce4a5be8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 00:58:01 -0700 Subject: [PATCH 298/791] Add simply window stack which manages window manipulation --- src/simply/simply_window_stack.c | 71 ++++++++++++++++++++++++++++++++ src/simply/simply_window_stack.h | 25 +++++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/simply/simply_window_stack.c create mode 100644 src/simply/simply_window_stack.h diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c new file mode 100644 index 00000000..0a5b3ba0 --- /dev/null +++ b/src/simply/simply_window_stack.c @@ -0,0 +1,71 @@ +#include "simply_window_stack.h" + +#include "simply_window.h" +#include "simply_msg.h" + +#include "simply.h" + +#include + +void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, bool is_push) { + self->is_showing = true; + window_stack_pop_all(true); + self->is_showing = false; + + Window *temp_window = NULL; + if (is_push) { + temp_window = window_create(); + window_stack_push(temp_window, false); + } + + simply_window_show(window); + + if (is_push) { + window_stack_remove(temp_window, false); + window_destroy(temp_window); + } +} + +void simply_window_stack_pop(SimplyWindowStack *self, SimplyWindow *window) { + self->is_hiding = true; + simply_window_hide(window); + self->is_hiding = false; +} + +void simply_window_stack_back(SimplyWindowStack *self, SimplyWindow *window) { + self->is_hiding = true; + simply_window_stack_send_hide(self, window); + self->is_hiding = false; +} + +void simply_window_stack_send_show(SimplyWindowStack *self, SimplyWindow *window) { + if (self->is_showing) { + return; + } + simply_msg_window_show(self->simply->msg, window->id); +} + +void simply_window_stack_send_hide(SimplyWindowStack *self, SimplyWindow *window) { + if (self->is_showing) { + return; + } + simply_msg_window_hide(self->simply->msg, window->id); + if (!self->is_hiding) { + window_stack_push(window->window, false); + } +} + +SimplyWindowStack *simply_window_stack_create(Simply *simply) { + SimplyWindowStack *self = malloc(sizeof(*self)); + *self = (SimplyWindowStack) { .simply = simply }; + + return self; +} + +void simply_window_stack_destroy(SimplyWindowStack *self) { + if (!self) { + return; + } + + free(self); +} diff --git a/src/simply/simply_window_stack.h b/src/simply/simply_window_stack.h new file mode 100644 index 00000000..f1e453b2 --- /dev/null +++ b/src/simply/simply_window_stack.h @@ -0,0 +1,25 @@ +#pragma once + +#include "simply_window.h" + +#include "simply.h" + +#include + +typedef struct SimplyWindowStack SimplyWindowStack; + +struct SimplyWindowStack { + Simply *simply; + bool is_showing:1; + bool is_hiding:1; +}; + +SimplyWindowStack *simply_window_stack_create(Simply *simply); +void simply_window_stack_destroy(SimplyWindowStack *self); + +void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, bool is_push); +void simply_window_stack_pop(SimplyWindowStack *self, SimplyWindow *window); +void simply_window_stack_back(SimplyWindowStack *self, SimplyWindow *window); + +void simply_window_stack_send_show(SimplyWindowStack *self, SimplyWindow *window); +void simply_window_stack_send_hide(SimplyWindowStack *self, SimplyWindow *window); From 5b15f89a87c7ab9a966da3a61318362b2f55daa1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 00:59:26 -0700 Subject: [PATCH 299/791] Use simply window stack for window transitions --- src/js/ui/simply-pebble.js | 19 +++++++++++---- src/js/ui/window.js | 6 ++--- src/js/ui/windowstack.js | 6 ++--- src/simply/simply.c | 5 +++- src/simply/simply.h | 1 + src/simply/simply_menu.c | 12 +++++++--- src/simply/simply_msg.c | 49 +++++++++++++++++--------------------- src/simply/simply_stage.c | 5 ++-- src/simply/simply_ui.c | 8 ++++--- src/simply/simply_window.c | 3 ++- 10 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 7d280b29..2733dff4 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -107,9 +107,11 @@ var AnimationCurve = function(x) { var setWindowParams = [{ name: 'clear', - type: Boolean, }, { name: 'id', +}, { + name: 'pushing', + type: Boolean, }, { name: 'action', type: Boolean, @@ -582,13 +584,16 @@ SimplyPebble.windowHide = function(windowId) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.card = function(cardDef, clear) { +SimplyPebble.card = function(cardDef, clear, pushing) { var command = commandMap.setCard; var packet = makePacket(command, cardDef); if (clear) { clear = toClearFlags(clear); packet[command.paramMap.clear.id] = clear; } + if (pushing) { + packet[command.paramMap.pushing.id] = pushing; + } setActionPacket(packet, command, cardDef.action); SimplyPebble.sendPacket(packet); }; @@ -616,7 +621,7 @@ SimplyPebble.accelPeek = function(callback) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.menu = function(menuDef, clear) { +SimplyPebble.menu = function(menuDef, clear, pushing) { var command = commandMap.setMenu; var packetDef = util2.copy(menuDef); if (packetDef.sections instanceof Array) { @@ -630,6 +635,9 @@ SimplyPebble.menu = function(menuDef, clear) { clear = toClearFlags(clear); packet[command.paramMap.clear.id] = clear; } + if (pushing) { + packet[command.paramMap.pushing.id] = pushing; + } SimplyPebble.sendPacket(packet); }; @@ -661,13 +669,16 @@ SimplyPebble.image = function(id, gbitmap) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.stage = function(stageDef, clear) { +SimplyPebble.stage = function(stageDef, clear, pushing) { var command = commandMap.setStage; var packet = makePacket(command, stageDef); if (clear) { clear = toClearFlags(clear); packet[command.paramMap.clear.id] = clear; } + if (pushing) { + packet[command.paramMap.pushing.id] = pushing; + } setActionPacket(packet, command, stageDef.action); SimplyPebble.sendPacket(packet); }; diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 5e431caf..8b64b0f0 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -79,10 +79,10 @@ Window.prototype.hide = function() { return this; }; -Window.prototype._show = function() { - this._prop(this.state, true); +Window.prototype._show = function(pushing) { + this._prop(this.state, true, pushing); if (this._dynamic) { - Stage.prototype._show.call(this); + Stage.prototype._show.call(this, pushing); } }; diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index 84693aed..bd468667 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -39,10 +39,10 @@ WindowStack.prototype._emitHide = function(item) { this.emit('hide', e); }; -WindowStack.prototype._show = function(item) { +WindowStack.prototype._show = function(item, pushing) { if (!item) { return; } this._emitShow(item); - item._show(); + item._show(pushing); }; WindowStack.prototype._hide = function(item, broadcast) { @@ -64,7 +64,7 @@ WindowStack.prototype.push = function(item) { this.remove(item); var prevTop = this.top(); this._items.push(item); - this._show(item); + this._show(item, true); this._hide(prevTop, false); console.log('(+) ' + item._toString() + ' : ' + this._toString()); }; diff --git a/src/simply/simply.c b/src/simply/simply.c index bb0a2e1d..dfb10090 100644 --- a/src/simply/simply.c +++ b/src/simply/simply.c @@ -5,8 +5,9 @@ #include "simply_splash.h" #include "simply_stage.h" #include "simply_menu.h" -#include "simply_ui.h" #include "simply_msg.h" +#include "simply_ui.h" +#include "simply_window_stack.h" #include @@ -19,6 +20,7 @@ Simply *simply_init(void) { simply->menu = simply_menu_create(simply); simply->msg = simply_msg_create(simply); simply->ui = simply_ui_create(simply); + simply->window_stack = simply_window_stack_create(simply); bool animated = false; window_stack_push(simply->splash->window, animated); @@ -27,6 +29,7 @@ Simply *simply_init(void) { } void simply_deinit(Simply *simply) { + simply_window_stack_destroy(simply->window_stack); simply_ui_destroy(simply->ui); simply_msg_destroy(simply->msg); simply_menu_destroy(simply->menu); diff --git a/src/simply/simply.h b/src/simply/simply.h index 3b913867..c45a6fa7 100644 --- a/src/simply/simply.h +++ b/src/simply/simply.h @@ -12,6 +12,7 @@ struct Simply { struct SimplyMenu *menu; struct SimplyMsg *msg; struct SimplyUi *ui; + struct SimplyWindowStack *window_stack; }; Simply *simply_init(); diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index a16be894..c4050849 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -1,8 +1,8 @@ #include "simply_menu.h" #include "simply_res.h" - #include "simply_msg.h" +#include "simply_window_stack.h" #include "simply.h" @@ -262,12 +262,11 @@ static void window_load(Window *window) { static void window_appear(Window *window) { SimplyMenu *self = window_get_user_data(window); - simply_msg_window_show(self->window.simply->msg, self->window.id); + simply_window_stack_send_show(self->window.simply->window_stack, &self->window); } static void window_disappear(Window *window) { SimplyMenu *self = window_get_user_data(window); - simply_msg_window_hide(self->window.simply->msg, self->window.id); simply_menu_clear(self); } @@ -279,9 +278,16 @@ static void window_unload(Window *window) { self->menu_layer.menu_layer = NULL; simply_window_unload(&self->window); + + simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); } void simply_menu_clear(SimplyMenu *self) { + if (self->menu_layer.get_timer) { + app_timer_cancel(self->menu_layer.get_timer); + self->menu_layer.get_timer = NULL; + } + while (self->menu_layer.sections) { destroy_section(self, (SimplyMenuSection*) self->menu_layer.sections); } diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index eeb0299f..af7d54aa 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -5,6 +5,7 @@ #include "simply_stage.h" #include "simply_menu.h" #include "simply_ui.h" +#include "simply_window_stack.h" #include "simply.h" @@ -50,6 +51,7 @@ typedef enum SimplySetWindowParam SimplySetWindowParam; enum SimplySetWindowParam { SetWindow_clear = 1, SetWindow_id, + SetWindow_pushing, SetWindow_action, SetWindow_actionUp, SetWindow_actionSelect, @@ -64,8 +66,6 @@ enum SimplySetWindowParam { typedef enum SimplySetUiParam SimplySetUiParam; enum SimplySetUiParam { - SetUi_clear = SetWindow_clear, - SetUi_id, SetUi_title = SetWindowLast, SetUi_subtitle, SetUi_body, @@ -78,18 +78,9 @@ enum SimplySetUiParam { typedef enum SimplySetMenuParam SimplySetMenuParam; enum SimplySetMenuParam { - SetMenu_clear = SetWindow_clear, - SetMenu_id, SetMenu_sections = SetWindowLast, }; -typedef enum SimplySetStageParam SimplySetStageParam; - -enum SimplySetStageParam { - SetStage_clear = SetWindow_clear, - SetStage_id, -}; - typedef enum VibeType VibeType; enum VibeType { @@ -148,8 +139,21 @@ static void set_window(SimplyWindow *window, DictionaryIterator *iter, Simply *s simply_window_set_action_bar(window, false); simply_window_action_bar_clear(window); } + bool is_clear = false; + bool is_id = false; + bool is_push = false; for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { + case SetWindow_clear: + is_clear = true; + break; + case SetWindow_id: + is_id = true; + window->id = tuple->value->uint32; + break; + case SetWindow_pushing: + is_push = true; + break; case SetWindow_action: simply_window_set_action_bar(window, tuple->value->int32); break; @@ -172,6 +176,9 @@ static void set_window(SimplyWindow *window, DictionaryIterator *iter, Simply *s break; } } + if (is_clear && is_id) { + simply_window_stack_show(simply->window_stack, window, is_push); + } } static void handle_set_window(DictionaryIterator *iter, Simply *simply) { @@ -194,21 +201,18 @@ static void handle_hide_window(DictionaryIterator *iter, Simply *simply) { window_id = tuple->value->uint32; } if (window->id == window_id) { - simply_window_hide(window); + simply_window_stack_pop(simply->window_stack, window); } } static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { SimplyUi *ui = simply->ui; Tuple *tuple; - if ((tuple = dict_find(iter, SetUi_clear))) { + if ((tuple = dict_find(iter, SetWindow_clear))) { simply_ui_clear(ui, tuple->value->uint32); } for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { - case SetUi_id: - ui->window.id = tuple->value->uint32; - break; case SetUi_title: case SetUi_subtitle: case SetUi_body: @@ -225,7 +229,6 @@ static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { } } set_window(&ui->window, iter, simply); - simply_ui_show(ui); } static void handle_vibe(DictionaryIterator *iter, Simply *simply) { @@ -283,19 +286,15 @@ static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { - case SetMenu_clear: + case SetWindow_clear: simply_menu_clear(menu); break; - case SetMenu_id: - menu->window.id = tuple->value->uint32; - break; case SetMenu_sections: simply_menu_set_num_sections(menu, tuple->value->int32); break; } } set_window(&menu->window, iter, simply); - simply_menu_show(menu); } static void handle_set_menu_section(DictionaryIterator *iter, Simply *simply) { @@ -380,16 +379,12 @@ static void handle_set_stage(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { - case SetStage_clear: + case SetWindow_clear: simply_stage_clear(stage); break; - case SetStage_id: - stage->window.id = tuple->value->uint32; - break; } } set_window(&stage->window, iter, simply); - simply_stage_show(stage); } static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index bd9f221b..53e4cd7c 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -3,6 +3,7 @@ #include "simply_window.h" #include "simply_res.h" #include "simply_msg.h" +#include "simply_window_stack.h" #include "simply.h" @@ -310,14 +311,14 @@ static void window_load(Window *window) { static void window_appear(Window *window) { SimplyStage *self = window_get_user_data(window); - simply_msg_window_show(self->window.simply->msg, self->window.id); + simply_window_stack_send_show(self->window.simply->window_stack, &self->window); simply_stage_update_ticker(self); } static void window_disappear(Window *window) { SimplyStage *self = window_get_user_data(window); - simply_msg_window_hide(self->window.simply->msg, self->window.id); + simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); simply_stage_clear(self); } diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 192bdee7..7707cd3a 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -2,7 +2,7 @@ #include "simply_msg.h" #include "simply_res.h" -#include "simply_menu.h" +#include "simply_window_stack.h" #include "simply.h" @@ -255,12 +255,14 @@ static void window_load(Window *window) { static void window_appear(Window *window) { SimplyUi *self = window_get_user_data(window); - simply_msg_window_show(self->window.simply->msg, self->window.id); + simply_window_stack_send_show(self->window.simply->window_stack, &self->window); } static void window_disappear(Window *window) { SimplyUi *self = window_get_user_data(window); - simply_msg_window_hide(self->window.simply->msg, self->window.id); + simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); + + simply_ui_clear(self, ~0); } static void window_unload(Window *window) { diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 033d7d51..9b014b5c 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -3,6 +3,7 @@ #include "simply_msg.h" #include "simply_res.h" #include "simply_menu.h" +#include "simply_window_stack.h" #include "simply.h" @@ -125,7 +126,7 @@ static void single_click_handler(ClickRecognizerRef recognizer, void *context) { bool is_enabled = (self->button_mask & (1 << button)); if (button == BUTTON_ID_BACK && !is_enabled) { if (simply_msg_has_communicated()) { - simply_msg_window_hide(self->simply->msg, self->id); + simply_window_stack_back(self->simply->window_stack, self); } else { bool animated = true; window_stack_pop(animated); From 569a3690cb8314598713d634cf320955f533d470 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 01:00:22 -0700 Subject: [PATCH 300/791] Add simply msg exponential backoff to send packet retry --- src/simply/simply_msg.c | 6 +++++- src/simply/simply_msg.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index af7d54aa..32ffbcd0 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -16,6 +16,8 @@ #include +#define SEND_DELAY_MS 10 + typedef enum SimplyACmd SimplyACmd; enum SimplyACmd { @@ -657,11 +659,13 @@ static void send_msg_retry(void *data) { return; } if (!send_msg(packet)){ - app_timer_register(10, send_msg_retry, self); + self->send_delay_ms *= 2; + app_timer_register(self->send_delay_ms, send_msg_retry, self); return; } list1_remove(&self->queue, &packet->node); destroy_packet(packet); + self->send_delay_ms = SEND_DELAY_MS; } static SimplyPacket *add_packet(SimplyMsg *self, void *buffer, size_t length) { diff --git a/src/simply/simply_msg.h b/src/simply/simply_msg.h index 88e42bb5..e2723d6d 100644 --- a/src/simply/simply_msg.h +++ b/src/simply/simply_msg.h @@ -13,6 +13,7 @@ typedef struct SimplyMsg SimplyMsg; struct SimplyMsg { Simply *simply; List1Node *queue; + uint32_t send_delay_ms; }; typedef struct SimplyPacket SimplyPacket; From c098006d07b23ee17ac0f60b6c255ad53264ba3b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 01:20:38 -0700 Subject: [PATCH 301/791] Change to not animate when showing the first window --- src/simply/simply_menu.c | 10 ---------- src/simply/simply_menu.h | 1 - src/simply/simply_msg.c | 10 ++-------- src/simply/simply_stage.c | 10 ---------- src/simply/simply_stage.h | 1 - src/simply/simply_ui.c | 18 +++--------------- src/simply/simply_ui.h | 2 -- src/simply/simply_window.c | 17 ----------------- src/simply/simply_window_stack.c | 11 ++++++++--- 9 files changed, 13 insertions(+), 67 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index c4050849..04d720d8 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -299,16 +299,6 @@ void simply_menu_clear(SimplyMenu *self) { mark_dirty(self); } -void simply_menu_show(SimplyMenu *self) { - if (!self->window.window) { - return; - } - if (!window_stack_contains_window(self->window.window)) { - bool animated = !self->window.simply->splash; - window_stack_push(self->window.window, animated); - } -} - SimplyMenu *simply_menu_create(Simply *simply) { SimplyMenu *self = malloc(sizeof(*self)); *self = (SimplyMenu) { diff --git a/src/simply/simply_menu.h b/src/simply/simply_menu.h index f5699143..e0b3308c 100644 --- a/src/simply/simply_menu.h +++ b/src/simply/simply_menu.h @@ -70,7 +70,6 @@ struct SimplyMenuItem { SimplyMenu *simply_menu_create(Simply *simply); void simply_menu_destroy(SimplyMenu *self); -void simply_menu_show(SimplyMenu *self); void simply_menu_clear(SimplyMenu *self); void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 32ffbcd0..811e6e41 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -120,12 +120,6 @@ bool simply_msg_has_communicated() { return s_has_communicated; } -static void check_splash(Simply *simply) { - if (simply->splash) { - simply_ui_show(simply->ui); - } -} - static SimplyWindow *get_top_simply_window(Simply *simply) { Window *base_window = window_stack_get_top_window(); SimplyWindow *window = window_get_user_data(base_window); @@ -595,10 +589,10 @@ static void sent_callback(DictionaryIterator *iter, void *context) { static void failed_callback(DictionaryIterator *iter, AppMessageResult reason, Simply *simply) { SimplyUi *ui = simply->ui; if (reason == APP_MSG_NOT_CONNECTED) { + simply_ui_clear(ui, ~0); simply_ui_set_text(ui, UiSubtitle, "Disconnected"); simply_ui_set_text(ui, UiBody, "Run the Pebble Phone App"); - - check_splash(simply); + simply_window_stack_show(simply->window_stack, &ui->window, true); } } diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 53e4cd7c..9b22c20d 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -332,16 +332,6 @@ static void window_unload(Window *window) { simply_window_unload(&self->window); } -void simply_stage_show(SimplyStage *self) { - if (!self->window.window) { - return; - } - if (!window_stack_contains_window(self->window.window)) { - bool animated = !self->window.simply->splash; - window_stack_push(self->window.window, animated); - } -} - void simply_stage_update(SimplyStage *self) { if (self->stage_layer.layer) { layer_mark_dirty(self->stage_layer.layer); diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index f1fc9500..d8956b3d 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -113,7 +113,6 @@ struct SimplyAnimation { SimplyStage *simply_stage_create(Simply *simply); void simply_stage_destroy(SimplyStage *self); -void simply_stage_show(SimplyStage *self); void simply_stage_clear(SimplyStage *self); void simply_stage_update(SimplyStage *self); diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 7707cd3a..d151cafa 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -227,11 +227,11 @@ static void show_welcome_text(SimplyUi *self) { return; } - simply_ui_set_text(self, UiTitle, "Simply.js"); + simply_ui_set_text(self, UiTitle, "Pebble.js"); simply_ui_set_text(self, UiSubtitle, "Write apps with JS!"); - simply_ui_set_text(self, UiBody, "Visit simplyjs.io for details."); + simply_ui_set_text(self, UiBody, "pebble.github.io/pebblejs"); - simply_ui_show(self); + simply_window_stack_show(self->window.simply->window_stack, &self->window, true); } static void window_load(Window *window) { @@ -261,8 +261,6 @@ static void window_appear(Window *window) { static void window_disappear(Window *window) { SimplyUi *self = window_get_user_data(window); simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); - - simply_ui_clear(self, ~0); } static void window_unload(Window *window) { @@ -274,16 +272,6 @@ static void window_unload(Window *window) { simply_window_unload(&self->window); } -void simply_ui_show(SimplyUi *self) { - if (!self->window.window) { - return; - } - if (!window_stack_contains_window(self->window.window)) { - bool animated = !self->window.simply->splash; - window_stack_push(self->window.window, animated); - } -} - SimplyUi *simply_ui_create(Simply *simply) { SimplyUi *self = malloc(sizeof(*self)); *self = (SimplyUi) { .window.layer = NULL }; diff --git a/src/simply/simply_ui.h b/src/simply/simply_ui.h index 38998d5d..3fc44c7c 100644 --- a/src/simply/simply_ui.h +++ b/src/simply/simply_ui.h @@ -44,9 +44,7 @@ struct SimplyUi { }; SimplyUi *simply_ui_create(Simply *simply); - void simply_ui_destroy(SimplyUi *self); -void simply_ui_show(SimplyUi *self); void simply_ui_clear(SimplyUi *self, uint32_t clear_mask); diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 9b014b5c..915c7a47 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -183,23 +183,6 @@ void simply_window_unload(SimplyWindow *self) { self->scroll_layer = NULL; } -void simply_window_hide(SimplyWindow *self) { - if (self->window == window_stack_get_top_window()) { - bool animated = true; - window_stack_pop(animated); - } -} - -void simply_window_show(SimplyWindow *self) { - if (!self->window) { - return; - } - if (!window_stack_contains_window(self->window)) { - bool animated = true; - window_stack_push(self->window, animated); - } -} - SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { self->simply = simply; diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index 0a5b3ba0..17433a61 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -8,8 +8,10 @@ #include void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, bool is_push) { + bool animated = (self->simply->splash == NULL); + self->is_showing = true; - window_stack_pop_all(true); + window_stack_pop_all(false); self->is_showing = false; Window *temp_window = NULL; @@ -18,7 +20,7 @@ void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, boo window_stack_push(temp_window, false); } - simply_window_show(window); + window_stack_push(window->window, animated); if (is_push) { window_stack_remove(temp_window, false); @@ -28,7 +30,10 @@ void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, boo void simply_window_stack_pop(SimplyWindowStack *self, SimplyWindow *window) { self->is_hiding = true; - simply_window_hide(window); + if (window->window == window_stack_get_top_window()) { + bool animated = true; + window_stack_pop(animated); + } self->is_hiding = false; } From 5ac3421fc2db3cb8d05f070e17d531d53c8f2fc6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 01:35:35 -0700 Subject: [PATCH 302/791] Animate showing a window through popping --- src/simply/simply_window_stack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index 17433a61..eae8770f 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -11,7 +11,7 @@ void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, boo bool animated = (self->simply->splash == NULL); self->is_showing = true; - window_stack_pop_all(false); + window_stack_pop_all(!is_push); self->is_showing = false; Window *temp_window = NULL; From b5964ce3d57ca770e9e167ead269b2041c9c6905 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 02:06:09 -0700 Subject: [PATCH 303/791] Change menu to autocreate required missing objects when setting --- src/js/ui/menu.js | 170 ++++++++++++++++++++-------------------------- 1 file changed, 73 insertions(+), 97 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 3b72fb3c..c714a2a5 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -44,25 +44,15 @@ var getMetaSection = function(sectionIndex) { return (this._sections[sectionIndex] || ( this._sections[sectionIndex] = {} )); }; -var targetType = { - menu: 1, - section: 2, - item: 3, -}; - -var isEnumerable = function(x, isArray) { - if (isArray) { - return (x instanceof Array); - } else { - return (typeof x === 'number' || x instanceof Array); - } -}; - -var getSections = function(target) { +var getSections = function() { var sections = this.state.sections; - if (isEnumerable(sections, target > targetType.menu)) { + if (sections instanceof Array) { return sections; } + if (typeof sections === 'number') { + sections = new Array(sections); + return (this.state.sections = sections); + } if (typeof sections === 'function') { this.sectionsProvider = this.state.sections; delete this.state.sections; @@ -70,43 +60,41 @@ var getSections = function(target) { if (this.sectionsProvider) { sections = this.sectionsProvider.call(this); if (sections) { - return (this.state.sections = sections); + this.state.sections = sections; + return getSections.call(this); } } - if (!sections) { - return (this.state.sections = []); - } + return (this.state.sections = []); }; -var getSection = function(e) { - var sections = getSections.call(this, targetType.section); - var section; - if (sections) { - section = sections[e.section]; - if (section) { - return section; - } +var getSection = function(e, create) { + var sections = getSections.call(this); + var section = sections[e.section]; + if (section) { + return section; } if (this.sectionProvider) { section = this.sectionProvider.call(this, e); if (section) { - if (!(sections instanceof Array)) { - sections = this.state.sections = []; - } return (sections[e.section] = section); } } - if (sections && !sections[e.section]) { - return (sections[e.section] = {}); - } + if (!create) { return; } + return (sections[e.section] = {}); }; -var getItems = function(e, target) { - var section = getSection.call(this, e); - if (!section) { return; } - if (isEnumerable(section.items, target > targetType.section)) { +var getItems = function(e, create) { + var section = getSection.call(this, e, create); + if (!section) { + if (e.section > 0) { return; } + section = this.state.sections[0] = {}; + } + if (section.items instanceof Array) { return section.items; } + if (typeof section.items === 'number') { + return (section.items = new Array(section.items)); + } if (typeof section.items === 'function') { this._sections[e.section] = section.items; delete section.items; @@ -115,69 +103,51 @@ var getItems = function(e, target) { if (itemsProvider) { var items = itemsProvider.call(this, e); if (items) { - return (section.items = items); + section.items = items; + return getItems.call(this, e, create); } } + return (section.items = []); }; -var getItem = function(e) { - var items = getItems.call(this, e, targetType.item); - var item; - if (items) { - item = items[e.item]; - if (item) { - return item; - } +var getItem = function(e, create) { + var items = getItems.call(this, e, create); + var item = items[e.item]; + if (item) { + return item; } - var section = getSection.call(this, e); var itemProvider = getMetaSection.call(this, e.section).item || this.itemProvider; if (itemProvider) { item = itemProvider.call(this, e); if (item) { - if (!(section.items instanceof Array)) { - section.items = []; - } - return (section.items[e.item] = item); + return (items[e.item] = item); } } + if (!create) { return; } + return (items[e.item] = {}); }; Menu.prototype._resolveMenu = function() { - var sections = getSections.call(this, targetType.menu); - this.state.sections = sections; - if (isEnumerable(sections)) { - if (typeof sections === 'number') { - this.state.sections = new Array(sections); - } - if (this === WindowStack.top()) { - simply.impl.menu.call(this, this.state); - } + var sections = getSections.call(this); + if (this === WindowStack.top()) { + simply.impl.menu.call(this, this.state); } }; Menu.prototype._resolveSection = function(e) { var section = getSection.call(this, e); - if (section) { - if (!(section.items instanceof Array)) { - section.items = getItems.call(this, e, targetType.section); - } - if (isEnumerable(section.items)) { - if (typeof section.items === 'number') { - section.items = new Array(section.items); - } - if (this === WindowStack.top()) { - simply.impl.menuSection.call(this, e.section, section); - } - } + if (!section) { return; } + section.items = getItems.call(this, e); + if (this === WindowStack.top()) { + simply.impl.menuSection.call(this, e.section, section); } }; Menu.prototype._resolveItem = function(e) { var item = getItem.call(this, e); - if (item) { - if (this === WindowStack.top()) { - simply.impl.menuItem.call(this, e.section, e.item, item); - } + if (!item) { return; } + if (this === WindowStack.top()) { + simply.impl.menuItem.call(this, e.section, e.item, item); } }; @@ -221,11 +191,17 @@ Menu.prototype.section = function(sectionIndex, section) { this.sectionProvider = sectionIndex; return this; } - var sections = this.state.sections; - if (sections instanceof Array) { - sections[sectionIndex] = section; + var menuIndex = { section: sectionIndex }; + if (!section) { + return getSection.call(this, menuIndex); } - this._resolveSection({ section: sectionIndex }); + var sections = getSections.call(this); + var prevLength = sections.length; + sections[sectionIndex] = section; + if (sections.length !== prevLength) { + this._resolveMenu(); + } + this._resolveSection(menuIndex); return this; }; @@ -240,15 +216,13 @@ Menu.prototype.items = function(sectionIndex, items) { getMetaSection.call(this, sectionIndex).items = items; return this; } - var section = getSection.call(this, { section: sectionIndex }); - if (section) { - if (items) { - section.items = items; - } else { - return section.items; - } + var menuIndex = { section: sectionIndex }; + if (!items) { + return getItems.call(this, menuIndex); } - this._resolveSection({ section: sectionIndex }); + var section = getSection.call(this, menuIndex, true); + section.items = items; + this._resolveSection(menuIndex); return this; }; @@ -269,15 +243,17 @@ Menu.prototype.item = function(sectionIndex, itemIndex, item) { getMetaSection.call(this, sectionIndex).item = item; return this; } - if (item) { - var section = getSection.call(this, { section: sectionIndex }); - if (section.items instanceof Array) { - section.items[itemIndex] = item; - } - } else { - return getItem.call(this, { section: sectionIndex, item: itemIndex }); + var menuIndex = { section: sectionIndex, item: itemIndex }; + if (!item) { + return getItem.call(this, menuIndex); + } + var items = getItems.call(this, menuIndex, true); + var prevLength = items.length; + items[itemIndex] = item; + if (items.length !== prevLength) { + this._resolveSection(menuIndex); } - this._resolveItem({ section: sectionIndex, item: itemIndex }); + this._resolveItem(menuIndex); return this; }; From 4b1320f92644b03abb5cf96f497513f67b6d90d3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 02:16:17 -0700 Subject: [PATCH 304/791] Buffer simply menu pops with a blank window instead of itself --- src/simply/simply_window_stack.c | 14 ++++++++------ src/simply/simply_window_stack.h | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index eae8770f..69ce4c09 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -14,17 +14,14 @@ void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, boo window_stack_pop_all(!is_push); self->is_showing = false; - Window *temp_window = NULL; if (is_push) { - temp_window = window_create(); - window_stack_push(temp_window, false); + window_stack_push(self->pusher, false); } window_stack_push(window->window, animated); if (is_push) { - window_stack_remove(temp_window, false); - window_destroy(temp_window); + window_stack_remove(self->pusher, false); } } @@ -56,7 +53,7 @@ void simply_window_stack_send_hide(SimplyWindowStack *self, SimplyWindow *window } simply_msg_window_hide(self->simply->msg, window->id); if (!self->is_hiding) { - window_stack_push(window->window, false); + window_stack_push(self->pusher, false); } } @@ -64,6 +61,8 @@ SimplyWindowStack *simply_window_stack_create(Simply *simply) { SimplyWindowStack *self = malloc(sizeof(*self)); *self = (SimplyWindowStack) { .simply = simply }; + self->pusher = window_create(); + return self; } @@ -72,5 +71,8 @@ void simply_window_stack_destroy(SimplyWindowStack *self) { return; } + window_destroy(self->pusher); + self->pusher = NULL; + free(self); } diff --git a/src/simply/simply_window_stack.h b/src/simply/simply_window_stack.h index f1e453b2..fe7618d9 100644 --- a/src/simply/simply_window_stack.h +++ b/src/simply/simply_window_stack.h @@ -10,6 +10,7 @@ typedef struct SimplyWindowStack SimplyWindowStack; struct SimplyWindowStack { Simply *simply; + Window *pusher; bool is_showing:1; bool is_hiding:1; }; From 9b6040cf16114af238bff4e146977e4d220544df Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 02:40:52 -0700 Subject: [PATCH 305/791] Simplify settings option accessor --- src/js/settings/settings.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index 18a2790a..c566c92a 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -50,24 +50,18 @@ Settings.loadOptions = function(path) { Settings.option = function(key, value) { var options = state.options; - if (arguments.length >= 2) { - if (typeof value === 'undefined') { - delete options[key]; - } else { - try { - value = JSON.stringify(value); - } catch (e) {} - options[key] = '' + value; - } - Settings.saveOptions(); + if (arguments.length === 1) { + return options[key]; } - value = options[key]; - if (!isNaN(Number(value))) { - return Number(value); + if (typeof value === 'undefined' || value === null) { + delete options[key]; + } else { + try { + value = JSON.parse(value); + } catch (e) {} + options[key] = value; } - try { - value = JSON.parse(value); - } catch (e) {} + Settings.saveOptions(); return value; }; From 9fcfc15870e3b84340d9fb8f4d9603334a84724a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 02:44:01 -0700 Subject: [PATCH 306/791] Disable default settings configurable --- src/js/settings/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index c566c92a..ff7213df 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -88,7 +88,6 @@ Settings.config = function(opt, open, close) { }; Settings.onOpenConfig = function(e) { - console.log("Settings.onOpenConfig"); var options; var url; var listener = util2.last(state.listeners); @@ -104,6 +103,7 @@ Settings.onOpenConfig = function(e) { } else { url = Settings.settingsUrl; options = Settings.getBaseOptions(); + return; } var hash = encodeURIComponent(JSON.stringify(options)); Pebble.openURL(url + '#' + hash); From b18a157f38bccd2c921f6a607f9753dae4114219 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 15:37:40 -0700 Subject: [PATCH 307/791] Change settings to provide option and data accessors - option is used to sync with a configurable - data is not synced and is a convenience layer over localStorage --- src/js/settings/settings.js | 93 ++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index ff7213df..b00b0905 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -1,4 +1,6 @@ var util2 = require('util2'); +var myutil = require('myutil'); +var appinfo = require('appinfo'); var Settings = module.exports; @@ -7,13 +9,22 @@ var state; Settings.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; Settings.init = function() { + Settings.reset(); + + Settings.loadOptions(); + Settings.loadData(); + + // Register listeners for the Settings + Pebble.addEventListener('showConfiguration', Settings.onOpenConfig); + Pebble.addEventListener('webviewclosed', Settings.onCloseConfig); +}; + +Settings.reset = function() { state = Settings.state = { options: {}, + data: {}, listeners: [], }; - // Register listeners for the Settings - Pebble.addEventListener('showConfiguration', Settings.onOpenConfig); - Pebble.addEventListener('webviewclosed', Settings.onCloseConfig); }; Settings.mainScriptUrl = function(scriptUrl) { @@ -28,49 +39,67 @@ Settings.mainScriptUrl = function(scriptUrl) { return scriptUrl; }; -var getOptionsKey = function(path) { - return 'options:' + path; +Settings.getBaseOptions = function() { + return { + scriptUrl: Settings.mainScriptUrl(), + }; }; -Settings.saveOptions = function(path) { - var options = state.options; - localStorage.setItem(getOptionsKey(path), JSON.stringify(options)); +var getDataKey = function(path, field) { + path = path || appinfo.uuid; + return field + ':' + path; }; -Settings.loadOptions = function(path) { - state.options = {}; - var options = localStorage.getItem(getOptionsKey(path)); +Settings.saveData = function(path, field) { + field = field || 'data'; + var data = data || state[field]; + localStorage.setItem(getDataKey(path, field), JSON.stringify(data)); +}; + +Settings.loadData = function(path, field) { + field = field || 'data'; + state[field] = {}; + var data = localStorage.getItem(getDataKey(path, field)); try { - options = JSON.parse(options); + data = JSON.parse(data); } catch (e) {} - if (typeof options === 'object' && options !== null) { - state.options = options; + if (typeof data === 'object' && data !== null) { + state[field] = data; } }; -Settings.option = function(key, value) { - var options = state.options; - if (arguments.length === 1) { - return options[key]; - } - if (typeof value === 'undefined' || value === null) { - delete options[key]; - } else { - try { - value = JSON.parse(value); - } catch (e) {} - options[key] = value; - } - Settings.saveOptions(); - return value; +Settings.saveOptions = function(path) { + Settings.saveData(path, 'options'); }; -Settings.getBaseOptions = function() { - return { - scriptUrl: Settings.mainScriptUrl(), +Settings.loadOptions = function(path) { + Settings.loadData(path, 'options'); +}; + +var makeDataAccessor = function(type, path) { + return function(field, value) { + var data = state[type]; + if (arguments.length === 0) { + return data; + } + if (arguments.length === 1 && typeof field !== 'object') { + return data[field]; + } + if (typeof field !== 'object' && typeof value === 'undefined' || value === null) { + delete data[field]; + return; + } + var def = myutil.toObject(field, value); + util2.copy(def, data); + Settings.saveData(path, type); + return value; }; }; +Settings.option = makeDataAccessor('options'); + +Settings.data = makeDataAccessor('data'); + Settings.config = function(opt, open, close) { if (typeof opt === 'string') { opt = { url: opt }; From 95bb362e2cc879f82b84def28599f0524d79d8d8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 15:39:03 -0700 Subject: [PATCH 308/791] Load changes to options in case the open listener makes any changes --- src/js/settings/settings.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index b00b0905..c28fb544 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -121,14 +121,16 @@ Settings.onOpenConfig = function(e) { var url; var listener = util2.last(state.listeners); if (listener) { - url = listener.params.url; - options = state.options; e = { originalEvent: e, - options: options, + options: state.options, url: listener.params.url, }; - listener.open(e); + if (listener.open(e) === false) { + return; + } + url = listener.params.url; + options = state.options; } else { url = Settings.settingsUrl; options = Settings.getBaseOptions(); From a2f4afb6a860ff9359862a969dd9dc959ca038a2 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 15:39:40 -0700 Subject: [PATCH 309/791] Fix main.c comment formatting --- src/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.c b/src/main.c index c7f8abf3..19b50665 100644 --- a/src/main.c +++ b/src/main.c @@ -1,11 +1,11 @@ /** - * PebbleJS Project main file. + * Pebble.js Project main file. */ #include #include "simply/simply.h" -/* +/** * By default, we 'simply' load Simply and start running it. */ int main(void) { From 9feafe4ef0e71b1f7193720d814d35a543ed01a4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 16:26:25 -0700 Subject: [PATCH 310/791] Add ajax deformify for parsing form encoded strings --- src/js/lib/ajax.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/js/lib/ajax.js b/src/js/lib/ajax.js index 49b2d24a..346ebc97 100644 --- a/src/js/lib/ajax.js +++ b/src/js/lib/ajax.js @@ -7,11 +7,22 @@ var ajax = (function(){ var formify = function(data) { var params = [], i = 0; for (var name in data) { - params[i++] = encodeURI(name) + '=' + encodeURI(data[name]); + params[i++] = encodeURIComponent(name) + '=' + encodeURIComponent(data[name]); } return params.join('&'); }; +var deformify = function(form) { + var params = {}; + form.replace(/(?:([^=&]*)=?([^&]*)?)(?:&|$)/g, function(_, name, value) { + if (name) { + params[name] = value || true; + } + return _; + }); + return params; +}; + /** * ajax options. There are various properties with url being the only required property. * @typedef ajaxOptions @@ -101,6 +112,7 @@ var ajax = function(opt, success, failure) { }; ajax.formify = formify; +ajax.deformify = deformify; if (typeof module !== 'undefined') { module.exports = ajax; From 1a7ba250588753243295c3e09c1cf4e2a4e36c23 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 16:27:36 -0700 Subject: [PATCH 311/791] Add util2 count which counts the keys in an object --- src/js/lib/util2.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/js/lib/util2.js b/src/js/lib/util2.js index fdc82f89..090185f6 100644 --- a/src/js/lib/util2.js +++ b/src/js/lib/util2.js @@ -8,6 +8,12 @@ var util2 = {}; util2.noop = function() {}; +util2.count = function(o) { + var i = 0; + for (var k in o) { ++i; } + return i; +}; + util2.copy = function(a, b) { b = b || (a instanceof Array ? [] : {}); for (var k in a) { b[k] = a[k]; } From 2549bec791b1b5f08586a54c32d5214e92c49474 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 16:31:31 -0700 Subject: [PATCH 312/791] Add settings configurable response form format decoding --- src/js/settings/settings.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index c28fb544..9973f598 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -1,4 +1,5 @@ var util2 = require('util2'); +var ajax = require('ajax'); var myutil = require('myutil'); var appinfo = require('appinfo'); @@ -143,15 +144,34 @@ Settings.onOpenConfig = function(e) { Settings.onCloseConfig = function(e) { var listener = util2.last(state.listeners); var options = {}; + var format; if (e.response && e.response !== 'CANCELLED') { - options = JSON.parse(decodeURIComponent(e.response)); + try { + options = JSON.parse(decodeURIComponent(e.response)); + format = 'json'; + } catch (err) {} + if (!format && e.response.match(/(&|=)/)) { + options = ajax.deformify(e.response); + if (util2.count(options) > 0) { + format = 'form'; + } + } } if (listener) { e = { originalEvent: e, + response: e.response, + originalOptions: state.options, options: options, url: listener.params.url, + failed: !format, + format: format, }; + if (format && listener.params.autoSave !== false) { + e.originalOptions = util2.copy(state.options); + util2.copy(options, state.options); + Settings.saveOptions(); + } return listener.close(e); } }; From a96429f5622a65b5e82559269b272b22454721f9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 18:33:09 -0700 Subject: [PATCH 313/791] Add settings module docs --- README.md | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/README.md b/README.md index f510edbb..125e7eb0 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,170 @@ More specifically: If in doubt, please contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com). +## Settings + +The Settings module allows you to add a configurable web view to your application and share options with it. Settings also provides two data accessors `Settings.option` and `Settings.data` which are backed by localStorage. Data stored in `Settings.option` is automatically shared with the configurable web view. + +### Settings + +`Settings` provides a single module of the same name `Settings`. + +````js +var Settings = require('settings'); +```` + +#### Settings.config(options, [open,] close) + +`Settings.config` registers your configurable for use along with `open` and `close` handlers. + +`options` is an object with the following parameters: + +| Name | Type | Argument | Default | Description | +| ---- | :----: | :--------: | --------- | ------------- | +| `url` | string | | | The URL to the configurable. e.g. 'http://www.example.com?name=value' | +| `autoSave` | boolean | (optional) | true | Whether to automatically save the web view response to options | + +`open` is an optional callback used to perform any tasks before the webview is open, such as managing the options that will be passed to the web view. + +````js +// Set a configurable with the open callback +Settings.config( + { url: 'http://www.example.com' } + function(e) { + console.log('opening configurable'); + + // Reset color to red before opening the webview + Settings.option('color', 'red'); + } + function(e) { + console.log('closed configurable'); + } +); +```` + +`close` is a callback that is called when the webview is closed via `pebblejs://close`. Any arguments passed to `pebblejs://close` is parsed and passed as options to the handler. `Settings` will attempt to parse the response first as URI encoded json and second as form encoded data if the first fails. + +````js +// Set a configurable with just the close callback +Settings.config( + { url: 'http://www.example.com' } + function(e) { + console.log('closed configurable'); + + // Show the parsed response + console.log(JSON.stringify(e.options)); + + // Show the raw response if parsing failed + if (e.failed) { + console.log(e.response); + } + } +); +```` + +To pass options from your configurable to `Settings.config` `close` in your webview, URI encode your options json as the hash to `pebblejs://close`. This will close your configurable, so you would perform this action in response to the user submitting their changes. + +````js +var options = { color: 'white', border: true }; +document.location = 'pebblejs://close#' + encodeURIComponent(JSON.stringify(options)); +```` + +#### Settings.option + +`Settings.option` is a data accessor built on localStorage that shares the options with the configurable web view. + +#### Settings.option(field, value) + +Saves `value` to `field`. It is recommended that `value` be either a primitive, or an object whose data is retained after going through `JSON.stringify` and `JSON.parse`. + +````js +Settings.option('color', 'red'); +```` + +If value is undefined or null, the field will be deleted. + +````js +Settings.option('color', null); +```` + +#### Settings.option(field) + +Returns the value of the option in `field`. + +````js +var player = Settings.option('player'); +console.log(player.id); +```` + +#### Settings.option(options) + +Sets multiple options given an `options` object. + +````js +Settings.option({ + color: 'blue', + border: false, +}); +```` + +#### Settings.option() + +Returns all options. The options can be modified, but you must call `Settings.option` in a way that sets to save. + +````js +var options = Settings.option(); +console.log(JSON.stringify(options)); +```` + +#### Settings.data + +`Settings.data` is a data accessor similar to `Settings.option` except it saves your data in a separate space. This is provided as a way to save data or options that you don't want to pass to a configurable web view. + +While localStorage is still accessible, it is recommended to use `Settings.data`. + +#### Settings.data(field, value) + +Saves `value` to `field`. It is recommended that `value` be either a primitive, or an object whose data is retained after going through `JSON.stringify` and `JSON.parse`. + +````js +Settings.data('player', { id: 1, x: 10, y: 10 }); +```` + +If value is undefined or null, the field will be deleted. + +````js +Settings.data('player', null); +```` + +#### Settings.data(field) + +Returns the value of the data in `field`. + +````js +var player = Settings.data('player'); +console.log(player.id); +```` + +#### Settings.data(data) + +Sets multiple data given an `data` object. + +````js +Settings.data({ + name: 'Pebble', + player: { id: 1, x: 0, y: 0 }, +}); +```` + +#### Settings.data() + +Returns all data. The data can be modified, but you must call `Settings.data` in a way that sets to save. + +````js +var data = Settings.data(); +console.log(JSON.stringify(data)); +```` + ## UI The UI framework contains all the classes needed to build the user interface of your Pebble applications and interact with the user. From e126f518a503136248b4f9d64dff45f25b491062 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 20:48:47 -0700 Subject: [PATCH 314/791] Add timetext doc named anchor --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f510edbb..dfa1b8ce 100644 --- a/README.md +++ b/README.md @@ -808,6 +808,7 @@ For more information, see [Vector2 in the three.js reference documentation][thre [Image]: #image [Rect]: #rect [Text]: #text +[TimeText]: #timetext [Window actionDef]: #window-actiondef [Window.show()]: #window-show [Window.hide()]: #window-hide From 5ad760a597a6352574ad1226e4b02f1a43cbc0b7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 3 Jun 2014 20:49:20 -0700 Subject: [PATCH 315/791] Add settings index which automatically initializes settings --- src/js/settings/index.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/js/settings/index.js diff --git a/src/js/settings/index.js b/src/js/settings/index.js new file mode 100644 index 00000000..dd700ab8 --- /dev/null +++ b/src/js/settings/index.js @@ -0,0 +1,5 @@ +var Settings = require('./settings'); + +Settings.init(); + +module.exports = Settings; From ada1456d413c0feaa71dfc052bc4626525e97f83 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Wed, 4 Jun 2014 00:25:14 -0700 Subject: [PATCH 316/791] Export Vibe with the UI module --- src/js/ui/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/ui/index.js b/src/js/ui/index.js index e39b7a7f..ab46b1f5 100644 --- a/src/js/ui/index.js +++ b/src/js/ui/index.js @@ -10,5 +10,6 @@ UI.Text = require('ui/text'); UI.TimeText = require('ui/timetext'); UI.Image = require('ui/image'); UI.Inverter = require('ui/inverter'); +UI.Vibe = require('ui/vibe'); module.exports = UI; From 9dc32548d527b9e62c6989cb92d3a67fcccc8b25 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 6 Jun 2014 19:06:58 -0700 Subject: [PATCH 317/791] Update ajax doc example to include the requisite protol --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dfa1b8ce..cce7ec0c 100644 --- a/README.md +++ b/README.md @@ -753,14 +753,14 @@ var ajax = require('ajax'); ajax( { - url: 'api.theysaidso.com/qod.json', + url: 'http://api.theysaidso.com/qod.json', type: 'json' }, - function (data) { - console.log("Quote of the day is: " + data.contents.quote); + function(data) { + console.log('Quote of the day is: ' + data.contents.quote); }, - function (error) { - console.log("Something bad happened :(" + error); + function(error) { + console.log('The ajax request failed: ' + error); } ); ```` From 8a5a7f77ecf63e9038a9f84b328108af67dc6218 Mon Sep 17 00:00:00 2001 From: Eric Migicovsky Date: Mon, 9 Jun 2014 07:23:21 +0800 Subject: [PATCH 318/791] bug in example code: adding comma to line 469 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cce7ec0c..8e4b610f 100644 --- a/README.md +++ b/README.md @@ -466,7 +466,7 @@ A menu contains one or more sections. Each section has a title and contains zero ````js var menu = new UI.Menu({ sections: [{ - title: 'First section' + title: 'First section', items: [{ title: 'First Item', subtitle: 'Some subtitle', From a26e212ede2e2359eff4ec07dfd68195da4d5fc3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 13 Jun 2014 12:35:06 -0700 Subject: [PATCH 319/791] Add util menu layer click config provider helper functions --- src/js/app.js | 12 +++++++++++- src/util/menu_layer.h | 24 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/util/menu_layer.h diff --git a/src/js/app.js b/src/js/app.js index 929fdc74..8a68b33a 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -22,7 +22,17 @@ main.on('click', 'up', function(e) { items: [{ title: 'Pebble.js', icon: 'images/menu_icon.png', - subtitle: 'Can do Menus' + subtitle: 'Can do Menus', + select: function() { + var fMenu = new UI.Menu(); + fMenu.show(); + fMenu.item(0, 0, {title: 'one'}); + fMenu.item(0, 1, {title: 'two'}); + fMenu.item(0, 2, {title: 'three'}); + fMenu.on('select', function(e) { + console.log(e.item); + }); + } }, { title: 'Second Item', subtitle: 'Subtitle Text' diff --git a/src/util/menu_layer.h b/src/util/menu_layer.h new file mode 100644 index 00000000..d3d49e33 --- /dev/null +++ b/src/util/menu_layer.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "simply/simply.h" + +static inline ClickConfigProvider menu_layer_click_config_provider_accessor(ClickConfigProvider provider) { + static ClickConfigProvider s_provider; + if (provider) { + s_provider = provider; + } + return s_provider; +} + +static inline void menu_layer_click_config(void *context) { + menu_layer_click_config_provider_accessor(NULL)(context); +} + +static inline void menu_layer_set_click_config_provider_onto_window(MenuLayer *menu_layer, + ClickConfigProvider click_config_provider, Window *window) { + menu_layer_set_click_config_onto_window(menu_layer, window); + menu_layer_click_config_provider_accessor(window_get_click_config_provider(window)); + window_set_click_config_provider_with_context(window, click_config_provider, menu_layer); +} From 18b1de4d587dd2bb9cce0323a0f860cf2e63f44a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 13 Jun 2014 12:35:48 -0700 Subject: [PATCH 320/791] Change simply menu to subscribe the back button --- src/simply/simply_menu.c | 18 +++++++++++++++--- src/simply/simply_window.c | 4 ++-- src/simply/simply_window.h | 2 ++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 04d720d8..b75d4b51 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -6,6 +6,8 @@ #include "simply.h" +#include "util/menu_layer.h" + #include #define MAX_CACHED_SECTIONS 10 @@ -233,6 +235,17 @@ static void menu_select_long_click_callback(MenuLayer *menu_layer, MenuIndex *ce simply_msg_menu_select_long_click(self->window.simply->msg, cell_index->section, cell_index->row); } +static void single_click_handler(ClickRecognizerRef recognizer, void *context) { + Window *base_window = layer_get_window(context); + SimplyWindow *window = window_get_user_data(base_window); + simply_window_single_click_handler(recognizer, window); +} + +static void click_config_provider(void *context) { + window_single_click_subscribe(BUTTON_ID_BACK, single_click_handler); + menu_layer_click_config(context); +} + static void window_load(Window *window) { SimplyMenu *self = window_get_user_data(window); @@ -257,7 +270,7 @@ static void window_load(Window *window) { .select_long_click = menu_select_long_click_callback, }); - menu_layer_set_click_config_onto_window(menu_layer, window); + menu_layer_set_click_config_provider_onto_window(menu_layer, click_config_provider, window); } static void window_appear(Window *window) { @@ -267,6 +280,7 @@ static void window_appear(Window *window) { static void window_disappear(Window *window) { SimplyMenu *self = window_get_user_data(window); + simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); simply_menu_clear(self); } @@ -278,8 +292,6 @@ static void window_unload(Window *window) { self->menu_layer.menu_layer = NULL; simply_window_unload(&self->window); - - simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); } void simply_menu_clear(SimplyMenu *self) { diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 915c7a47..a6470f71 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -120,7 +120,7 @@ void simply_window_set_button(SimplyWindow *self, ButtonId button, bool enable) } } -static void single_click_handler(ClickRecognizerRef recognizer, void *context) { +void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *context) { SimplyWindow *self = context; ButtonId button = click_recognizer_get_button_id(recognizer); bool is_enabled = (self->button_mask & (1 << button)); @@ -151,7 +151,7 @@ static void click_config_provider(void *context) { for (int i = 0; i < NUM_BUTTONS; ++i) { if (!self->is_scrollable || (i != BUTTON_ID_UP && i != BUTTON_ID_DOWN)) { window_set_click_context(i, context); - window_single_click_subscribe(i, (ClickHandler) single_click_handler); + window_single_click_subscribe(i, simply_window_single_click_handler); window_long_click_subscribe(i, 500, (ClickHandler) long_click_handler, NULL); } } diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index 943cb88b..8202dc72 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -27,6 +27,8 @@ void simply_window_hide(SimplyWindow *self); void simply_window_load(SimplyWindow *self); void simply_window_unload(SimplyWindow *self); +void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *context); + void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable); void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen); void simply_window_set_background_color(SimplyWindow *self, GColor background_color); From 59d0b06c7cf118c59b3856c0248ea71cbd96d119 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 13 Jun 2014 23:31:05 -0700 Subject: [PATCH 321/791] Update wscript to bundle json files --- wscript | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wscript b/wscript index 29558d7a..7a7b97b4 100644 --- a/wscript +++ b/wscript @@ -29,7 +29,8 @@ def build(ctx): @conf def concat_javascript(self, *k, **kw): js_path = kw['js_path'] - js_nodes = self.path.ant_glob(js_path + '/**/*.js') + js_nodes = (self.path.ant_glob(js_path + '/**/*.js') + + self.path.ant_glob(js_path + '/**/*.json')) if not js_nodes: return [] @@ -51,6 +52,8 @@ def concat_javascript(self, *k, **kw): relpath = os.path.relpath(node.abspath(), js_path) with open(node.abspath(), 'r') as f: body = f.read() + if relpath.endswith('.json'): + body = JSON_TEMPLATE.format(body=body) if relpath == LOADER_PATH: sources.insert(0, body) elif relpath.startswith('vendor/'): From 624665e26095d195a226a70495d964824ebb1e6e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 14 Jun 2014 20:46:35 -0700 Subject: [PATCH 322/791] Change menu to clear section items upon new section items arrays --- src/js/ui/menu.js | 18 +++++++++--------- src/js/ui/simply-pebble.js | 8 ++++++-- src/simply/simply_menu.c | 26 +++++++++++++++++--------- src/simply/simply_menu.h | 6 +++--- src/simply/simply_msg.c | 13 ++++++++----- 5 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index c714a2a5..2e5e38be 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -134,12 +134,12 @@ Menu.prototype._resolveMenu = function() { } }; -Menu.prototype._resolveSection = function(e) { +Menu.prototype._resolveSection = function(e, clear) { var section = getSection.call(this, e); if (!section) { return; } section.items = getItems.call(this, e); if (this === WindowStack.top()) { - simply.impl.menuSection.call(this, e.section, section); + simply.impl.menuSection.call(this, e.section, section, clear); } }; @@ -186,7 +186,7 @@ Menu.prototype.sections = function(sections) { Menu.prototype.section = function(sectionIndex, section) { if (typeof sectionIndex === 'object') { - sectionIndex = sectionIndex.section; + sectionIndex = sectionIndex.section || 0; } else if (typeof sectionIndex === 'function') { this.sectionProvider = sectionIndex; return this; @@ -197,17 +197,17 @@ Menu.prototype.section = function(sectionIndex, section) { } var sections = getSections.call(this); var prevLength = sections.length; - sections[sectionIndex] = section; + sections[sectionIndex] = util2.copy(section, sections[sectionIndex]); if (sections.length !== prevLength) { this._resolveMenu(); } - this._resolveSection(menuIndex); + this._resolveSection(menuIndex, typeof section.items !== 'undefined'); return this; }; Menu.prototype.items = function(sectionIndex, items) { if (typeof sectionIndex === 'object') { - sectionIndex = sectionIndex.section; + sectionIndex = sectionIndex.section || 0; } else if (typeof sectionIndex === 'function') { this.itemsProvider = sectionIndex; return this; @@ -222,7 +222,7 @@ Menu.prototype.items = function(sectionIndex, items) { } var section = getSection.call(this, menuIndex, true); section.items = items; - this._resolveSection(menuIndex); + this._resolveSection(menuIndex, true); return this; }; @@ -230,7 +230,7 @@ Menu.prototype.item = function(sectionIndex, itemIndex, item) { if (typeof sectionIndex === 'object') { item = itemIndex || item; itemIndex = sectionIndex.item; - sectionIndex = sectionIndex.section; + sectionIndex = sectionIndex.section || 0; } else if (typeof sectionIndex === 'function') { this.itemProvider = sectionIndex; return this; @@ -249,7 +249,7 @@ Menu.prototype.item = function(sectionIndex, itemIndex, item) { } var items = getItems.call(this, menuIndex, true); var prevLength = items.length; - items[itemIndex] = item; + items[itemIndex] = util2.copy(item, items[itemIndex]); if (items.length !== prevLength) { this._resolveSection(menuIndex); } diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 2733dff4..9a8fad24 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -245,6 +245,9 @@ var commands = [{ }, { name: 'setMenuSection', params: [{ + name: 'clear', + type: Boolean, + }, { name: 'section', }, { name: 'items', @@ -467,7 +470,7 @@ SimplyPebble.init = function() { var toParam = function(param, v) { if (param.type === String) { - v = v.toString(); + v = typeof v !== 'undefined' ? v.toString() : ''; } else if (param.type === Boolean) { v = v ? 1 : 0; } else if (param.type === Image && typeof v !== 'number') { @@ -641,13 +644,14 @@ SimplyPebble.menu = function(menuDef, clear, pushing) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.menuSection = function(sectionIndex, sectionDef) { +SimplyPebble.menuSection = function(sectionIndex, sectionDef, clear) { var command = commandMap.setMenuSection; var packetDef = util2.copy(sectionDef); packetDef.section = sectionIndex; if (packetDef.items instanceof Array) { packetDef.items = packetDef.items.length; } + packetDef.clear = clear; var packet = makePacket(command, packetDef); SimplyPebble.sendPacket(packet); }; diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index b75d4b51..288b2ea3 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -19,9 +19,9 @@ static char EMPTY_TITLE[] = ""; static bool section_filter(List1Node *node, void *data) { - SimplyMenuSection *section = (SimplyMenuSection*) node; + SimplyMenuCommon *section = (SimplyMenuCommon*) node; uint16_t section_index = (uint16_t)(uintptr_t) data; - return (section->index == section_index); + return (section->section == section_index); } static bool item_filter(List1Node *node, void *data) { @@ -29,7 +29,7 @@ static bool item_filter(List1Node *node, void *data) { uint32_t cell_index = (uint32_t)(uintptr_t) data; uint16_t section_index = cell_index; uint16_t row = cell_index >> 16; - return (item->section == section_index && item->index == row); + return (item->section == section_index && item->item == row); } static bool request_filter(List1Node *node, void *data) { @@ -89,12 +89,12 @@ static void request_menu_node(void *data) { SimplyMenuSection *section = (SimplyMenuSection*) list1_find(self->menu_layer.sections, request_filter, NULL); bool found = false; if (section) { - simply_msg_menu_get_section(self->window.simply->msg, section->index); + simply_msg_menu_get_section(self->window.simply->msg, section->section); found = true; } SimplyMenuItem *item = (SimplyMenuItem*) list1_find(self->menu_layer.items, request_filter, NULL); if (item) { - simply_msg_menu_get_item(self->window.simply->msg, item->section, item->index); + simply_msg_menu_get_item(self->window.simply->msg, item->section, item->item); found = true; } if (found) { @@ -112,7 +112,7 @@ static void add_section(SimplyMenu *self, SimplyMenuSection *section) { if (list1_size(self->menu_layer.sections) >= MAX_CACHED_SECTIONS) { destroy_section(self, (SimplyMenuSection*) list1_last(self->menu_layer.sections)); } - destroy_section_by_index(self, section->index); + destroy_section_by_index(self, section->section); list1_append(&self->menu_layer.sections, §ion->node); } @@ -120,14 +120,14 @@ static void add_item(SimplyMenu *self, SimplyMenuItem *item) { if (list1_size(self->menu_layer.items) >= MAX_CACHED_ITEMS) { destroy_item(self, (SimplyMenuItem*) list1_last(self->menu_layer.items)); } - destroy_item_by_index(self, item->section, item->index); + destroy_item_by_index(self, item->section, item->item); list1_append(&self->menu_layer.items, &item->node); } static void request_menu_section(SimplyMenu *self, uint16_t section_index) { SimplyMenuSection *section = malloc(sizeof(*section)); *section = (SimplyMenuSection) { - .index = section_index, + .section = section_index, }; add_section(self, section); schedule_get_timer(self); @@ -137,7 +137,7 @@ static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t SimplyMenuItem *item = malloc(sizeof(*item)); *item = (SimplyMenuItem) { .section = section_index, - .index = item_index, + .item = item_index, }; add_item(self, item); schedule_get_timer(self); @@ -294,6 +294,14 @@ static void window_unload(Window *window) { simply_window_unload(&self->window); } +void simply_menu_clear_section_items(SimplyMenu *self, int section_index) { + SimplyMenuItem *item = NULL; + do { + item = (SimplyMenuItem*) list1_find(self->menu_layer.items, section_filter, (void*)(uintptr_t) section_index); + destroy_item(self, item); + } while (item); +} + void simply_menu_clear(SimplyMenu *self) { if (self->menu_layer.get_timer) { app_timer_cancel(self->menu_layer.get_timer); diff --git a/src/simply/simply_menu.h b/src/simply/simply_menu.h index e0b3308c..98eb0610 100644 --- a/src/simply/simply_menu.h +++ b/src/simply/simply_menu.h @@ -42,6 +42,7 @@ typedef struct SimplyMenuCommon SimplyMenuCommon; #define SimplyMenuCommonDef { \ List1Node node; \ + uint16_t section; \ char *title; \ } @@ -55,7 +56,6 @@ struct SimplyMenuCommon SimplyMenuCommonDef; struct SimplyMenuSection { SimplyMenuCommonMember; - uint16_t index; uint16_t num_items; }; @@ -63,13 +63,13 @@ struct SimplyMenuItem { SimplyMenuCommonMember; char *subtitle; uint32_t icon; - uint16_t section; - uint16_t index; + uint16_t item; }; SimplyMenu *simply_menu_create(Simply *simply); void simply_menu_destroy(SimplyMenu *self); +void simply_menu_clear_section_items(SimplyMenu *self, int section_index); void simply_menu_clear(SimplyMenu *self); void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 811e6e41..d5d28e12 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -298,18 +298,21 @@ static void handle_set_menu_section(DictionaryIterator *iter, Simply *simply) { uint16_t section_index = 0; uint16_t num_items = 1; char *title = NULL; - if ((tuple = dict_find(iter, 1))) { + if ((tuple = dict_find(iter, 2))) { section_index = tuple->value->uint16; } - if ((tuple = dict_find(iter, 2))) { + if ((tuple = dict_find(iter, 3))) { num_items = tuple->value->uint16; } - if ((tuple = dict_find(iter, 3))) { + if ((tuple = dict_find(iter, 4))) { title = tuple->value->cstring; } + if ((tuple = dict_find(iter, 1))) { + simply_menu_clear_section_items(simply->menu, section_index); + } SimplyMenuSection *section = malloc(sizeof(*section)); *section = (SimplyMenuSection) { - .index = section_index, + .section = section_index, .num_items = num_items, .title = strdup2(title), }; @@ -341,7 +344,7 @@ static void handle_set_menu_item(DictionaryIterator *iter, Simply *simply) { SimplyMenuItem *item = malloc(sizeof(*item)); *item = (SimplyMenuItem) { .section = section_index, - .index = row, + .item = row, .title = strdup2(title), .subtitle = strdup2(subtitle), .icon = icon, From 27977b7892b68e6336accaccc0ce01e495cf2b14 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 15 Jun 2014 03:03:01 -0700 Subject: [PATCH 323/791] Add simply msg get and set menu selection --- src/simply/simply_menu.c | 8 ++++++++ src/simply/simply_menu.h | 4 ++++ src/simply/simply_msg.c | 37 +++++++++++++++++++++++++++++++++++++ src/simply/simply_msg.h | 1 + 4 files changed, 50 insertions(+) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 288b2ea3..06c6eaab 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -174,6 +174,14 @@ void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item) { mark_dirty(self); } +MenuIndex simply_menu_get_selection(SimplyMenu *self) { + return menu_layer_get_selected_index(self->menu_layer.menu_layer); +} + +void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, MenuRowAlign align, bool animated) { + menu_layer_set_selected_index(self->menu_layer.menu_layer, menu_index, align, animated); +} + static uint16_t menu_get_num_sections_callback(MenuLayer *menu_layer, void *data) { SimplyMenu *self = data; return self->menu_layer.num_sections; diff --git a/src/simply/simply_menu.h b/src/simply/simply_menu.h index 98eb0610..be4f7243 100644 --- a/src/simply/simply_menu.h +++ b/src/simply/simply_menu.h @@ -75,3 +75,7 @@ void simply_menu_clear(SimplyMenu *self); void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections); void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section); void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item); + +MenuIndex simply_menu_get_selection(SimplyMenu *self); +void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, MenuRowAlign align, bool animated); + diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index d5d28e12..f2db3956 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -40,6 +40,7 @@ enum SimplyACmd { SimplyACmd_getMenuItem, SimplyACmd_menuSelect, SimplyACmd_menuLongSelect, + SimplyACmd_menuSelection, SimplyACmd_image, SimplyACmd_setStage, SimplyACmd_stageElement, @@ -81,6 +82,10 @@ typedef enum SimplySetMenuParam SimplySetMenuParam; enum SimplySetMenuParam { SetMenu_sections = SetWindowLast, + SetMenu_selectionSection, + SetMenu_selectionItem, + SetMenu_selectionAlign, + SetMenu_selectionAnimated, }; typedef enum VibeType VibeType; @@ -280,6 +285,10 @@ static void handle_set_accel_config(DictionaryIterator *iter, Simply *simply) { static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { SimplyMenu *menu = simply->menu; Tuple *tuple; + bool is_selection = false; + MenuIndex menu_index = { 0, 0 }; + MenuRowAlign align = MenuRowAlignCenter; + bool selection_animated = true; for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { case SetWindow_clear: @@ -288,9 +297,25 @@ static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { case SetMenu_sections: simply_menu_set_num_sections(menu, tuple->value->int32); break; + case SetMenu_selectionSection: + menu_index.section = tuple->value->uint16; + break; + case SetMenu_selectionItem: + is_selection = true; + menu_index.row = tuple->value->uint16; + break; + case SetMenu_selectionAlign: + align = tuple->value->uint8; + break; + case SetMenu_selectionAnimated: + selection_animated = tuple->value->uint8; + break; } } set_window(&menu->window, iter, simply); + if (is_selection) { + simply_menu_set_selection(menu, menu_index, align, selection_animated); + } } static void handle_set_menu_section(DictionaryIterator *iter, Simply *simply) { @@ -352,6 +377,10 @@ static void handle_set_menu_item(DictionaryIterator *iter, Simply *simply) { simply_menu_add_item(simply->menu, item); } +static void handle_get_menu_selection(DictionaryIterator *iter, Simply *simply) { + simply_msg_send_menu_selection(simply->msg); +} + static void handle_set_image(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; uint32_t id = 0; @@ -565,6 +594,8 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_setMenuItem: handle_set_menu_item(iter, context); break; + case SimplyACmd_menuSelection: + handle_get_menu_selection(iter, context); case SimplyACmd_image: handle_set_image(iter, context); break; @@ -793,6 +824,11 @@ bool simply_msg_menu_select_long_click(SimplyMsg *self, uint16_t section, uint16 return send_menu_item_retry(self, SimplyACmd_menuLongSelect, section, index); } +bool simply_msg_send_menu_selection(SimplyMsg *self) { + MenuIndex menu_index = simply_menu_get_selection(self->simply->menu); + return send_menu_item_retry(self, SimplyACmd_menuSelection, menu_index.section, menu_index.row); +} + bool simply_msg_animate_element_done(SimplyMsg *self, uint16_t index) { size_t length = dict_calc_buffer_size(2, 1, 2); void *buffer = malloc0(length); @@ -805,3 +841,4 @@ bool simply_msg_animate_element_done(SimplyMsg *self, uint16_t index) { dict_write_uint16(&iter, 1, index); return add_packet(self, buffer, length); } + diff --git a/src/simply/simply_msg.h b/src/simply/simply_msg.h index e2723d6d..4bf61dbd 100644 --- a/src/simply/simply_msg.h +++ b/src/simply/simply_msg.h @@ -42,5 +42,6 @@ bool simply_msg_menu_get_item(SimplyMsg *self, uint16_t section, uint16_t index) bool simply_msg_menu_select_click(SimplyMsg *self, uint16_t section, uint16_t index); bool simply_msg_menu_select_long_click(SimplyMsg *self, uint16_t section, uint16_t index); bool simply_msg_menu_hide(SimplyMsg *self, uint16_t section, uint16_t index); +bool simply_msg_send_menu_selection(SimplyMsg *self); bool simply_msg_animate_element_done(SimplyMsg *self, uint16_t index); From c36ce0b4e298b47345b07e0e9cb8529f9c9387df Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 15 Jun 2014 03:03:42 -0700 Subject: [PATCH 324/791] Add menu selection getter and event --- src/js/ui/menu.js | 32 +++++++++++++++++++---- src/js/ui/simply-pebble.js | 52 +++++++++++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 2e5e38be..117246e9 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -9,6 +9,8 @@ var Menu = function(menuDef) { Window.call(this, menuDef); this._dynamic = false; this._sections = {}; + this._selection = { section: 0, item: 0 }; + this._selections = []; }; Menu._codeName = 'menu'; @@ -22,9 +24,9 @@ Menu.prototype._show = function() { Window.prototype._show.apply(this, arguments); }; -Menu.prototype._prop = function() { +Menu.prototype._prop = function(state, clear, pushing) { if (this === WindowStack.top()) { - simply.impl.menu.apply(this, arguments); + simply.impl.menu.call(this, state, clear, pushing, this._selection); } }; @@ -152,23 +154,37 @@ Menu.prototype._resolveItem = function(e) { }; Menu.prototype._emitSelect = function(e) { + this._selection = e; var item = getItem.call(this, e); - if (!item) { return; } switch (e.type) { case 'select': - if (typeof item.select === 'function') { + if (item && typeof item.select === 'function') { if (item.select(e) === false) { return false; } } break; case 'longSelect': - if (typeof item.longSelect === 'function') { + if (item && typeof item.longSelect === 'function') { if (item.longSelect(e) === false) { return false; } } break; + case 'selection': + var handlers = this._selections; + this._selections = []; + if (item && typeof item.selected === 'function') { + if (item.selected(e) === false) { + return false; + } + } + for (var i = 0, ii = handlers.length; i < ii; ++i) { + if (handlers[i](e) === false) { + break; + } + } + break; } }; @@ -257,6 +273,11 @@ Menu.prototype.item = function(sectionIndex, itemIndex, item) { return this; }; +Menu.prototype.selection = function(callback) { + this._selections.push(callback); + simply.impl.menuSelection(); +}; + Menu.emit = function(type, subtype, e) { Window.emit(type, subtype, e, Menu); }; @@ -296,6 +317,7 @@ Menu.emitSelect = function(type, section, item) { switch (type) { case 'menuSelect': type = 'select'; break; case 'menuLongSelect': type = 'longSelect'; break; + case 'menuSelection': type = 'selection'; break; } if (Menu.emit(type, null, e) === false) { return false; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 9a8fad24..b59f94de 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -13,8 +13,7 @@ var simply = require('ui/simply'); /** * This package provides the underlying implementation for the ui/* classes. * - * This implementation uses PebbleKit JS AppMessage to send commands to a Pebble Watch. - */ + * This implementation uses PebbleKit JS AppMessage to send commands to a Pebble Watm */ /* Make JSHint happy */ if (typeof Image === 'undefined') { @@ -105,6 +104,16 @@ var AnimationCurve = function(x) { return Number(x); }; +var MenuRowAlign = function(x) { + switch(x) { + case 'none' : return 0; + case 'center' : return 1; + case 'top' : return 2; + case 'bottom' : return 3; + } + return x ? Number(x) : 0; +}; + var setWindowParams = [{ name: 'clear', }, { @@ -163,6 +172,18 @@ var setCardParams = setWindowParams.concat([{ var setMenuParams = setWindowParams.concat([{ name: 'sections', type: Number, +}, { + name: 'selectionSection', + type: Number, +}, { + name: 'selectionItem', + type: Number, +}, { + name: 'selectionAlign', + type: MenuRowAlign, +}, { + name: 'selectionAnimated', + type: Boolean, }]); var setStageParams = setWindowParams; @@ -297,6 +318,14 @@ var commands = [{ }, { name: 'item', }], +}, { + name: 'menuSelection', + params: [{ + name: 'section', + }, { + name: 'item', + }], +}, { }, { name: 'image', params: [{ @@ -450,6 +479,13 @@ var actionBarTypeMap = { backgroundColor: 'actionBackgroundColor', }; +var menuSelectionTypeMap = { + section: 'selectionSection', + item: 'selectionItem', + align: 'selectionAlign', + animated: 'selectionAnimated', +}; + /** * SimplyPebble object provides the actual methods to communicate with Pebble. @@ -624,7 +660,7 @@ SimplyPebble.accelPeek = function(callback) { SimplyPebble.sendPacket(packet); }; -SimplyPebble.menu = function(menuDef, clear, pushing) { +SimplyPebble.menu = function(menuDef, clear, pushing, selection) { var command = commandMap.setMenu; var packetDef = util2.copy(menuDef); if (packetDef.sections instanceof Array) { @@ -641,6 +677,9 @@ SimplyPebble.menu = function(menuDef, clear, pushing) { if (pushing) { packet[command.paramMap.pushing.id] = pushing; } + if (selection) { + setPacket(packet, command, selection, menuSelectionTypeMap); + } SimplyPebble.sendPacket(packet); }; @@ -665,6 +704,12 @@ SimplyPebble.menuItem = function(sectionIndex, itemIndex, itemDef) { SimplyPebble.sendPacket(packet); }; +SimplyPebble.menuSelection = function() { + var command = commandMap.menuSelection; + var packet = makePacket(command); + SimplyPebble.sendPacket(packet); +}; + SimplyPebble.image = function(id, gbitmap) { var command = commandMap.image; var packetDef = util2.copy(gbitmap); @@ -808,6 +853,7 @@ SimplyPebble.onAppMessage = function(e) { break; case 'menuSelect': case 'menuLongSelect': + case 'menuSelection': Menu.emitSelect(command.name, payload[1], payload[2]); break; case 'stageAnimateDone': From db6d92e6b120b7d6f273e38a41b4e8ce46b8b830 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 15 Jun 2014 06:26:53 -0700 Subject: [PATCH 325/791] Fix loader.js to normalize relative paths at root --- src/js/loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/loader.js b/src/js/loader.js index 5ee4f548..1a2142f7 100644 --- a/src/js/loader.js +++ b/src/js/loader.js @@ -23,7 +23,7 @@ var replace = function(a, regexp, b) { }; loader.normalize = function(path) { - path = replace(path, /(?:\/\.?\/)+/g, '/'); + path = replace(path, /(?:(^|\/)\.?\/)+/g, '$1'); path = replace(path, /[^\/]*\/\.\.\//, ''); return path; }; From aa6fead2780da964fc52bdd44702c7a439d6aef3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 15 Jun 2014 07:27:52 -0700 Subject: [PATCH 326/791] Add struct.js for handling structs in byte arrays --- src/js/lib/struct.js | 105 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/js/lib/struct.js diff --git a/src/js/lib/struct.js b/src/js/lib/struct.js new file mode 100644 index 00000000..a57bd952 --- /dev/null +++ b/src/js/lib/struct.js @@ -0,0 +1,105 @@ +/** + * struct.js - chainable ArrayBuffer DataView wrapper + * + * @author Meiguro / http://meiguro.com/ + * @license MIT + */ + +var struct = function(def) { + this._littleEndian = true; + this._offset = 0; + this._makeAccessors(def); + this._view = new DataView(new ArrayBuffer(this.size)); +}; + +struct.types = { + int8: { size: 1 }, + uint8: { size: 1 }, + int16: { size: 2 }, + uint16: { size: 2 }, + int32: { size: 4 }, + uint32: { size: 4 }, + int64: { size: 8 }, + uint64: { size: 8 }, + float32: { size: 2 }, + float64: { size: 4 }, +}; + +var capitalize = function(str) { + return str.charAt(0).toUpperCase() + str.substr(1); +}; + +struct.prototype._makeAccessor = function(index, type, name, transform) { + var getName = 'get' + capitalize(type); + var setName = 'set' + capitalize(type); + this[name] = function(value) { + if (arguments.length === 0) { + return this._view[getName](this._offset + index, this._littleEndian); + } + if (transform) { + value = transform(value); + } + this._view[setName](this._offset + index, value, this._littleEndian); + return this; + }; + return this; +}; + +struct.prototype._makeAccessors = function(def) { + var index = 0; + var fields = []; + for (var i = 0, ii = def.length; i < ii; ++i) { + var member = def[i]; + var type = member[0]; + var name = member[1]; + var transform = member[2]; + this._makeAccessor(index, type, name, transform); + fields.push({ + type: type, + name: name, + transform: transform, + }); + index += struct.types[type].size; + } + this.fields = fields; + this.size = index; + return this; +}; + +struct.prototype.prop = function(def) { + if (arguments.length === 0) { + var obj = {}; + var fields = this.fields; + for (var i = 0, ii = fields.length; i < ii; ++i) { + var name = fields[i].name; + obj[name] = this[name](); + } + return obj; + } + for (var k in def) { + this[k](def[k]); + } + return this; +}; + +struct.prototype.view = function(view) { + if (arguments.length === 0) { + return this._view; + } + if (view instanceof ArrayBuffer) { + view = new DataView(view); + } + this._view = view; + return this; +}; + +struct.prototype.offset = function(offset) { + if (arguments.length === 0) { + return this._offset; + } + this._offset = offset; + return this; +}; + +module.exports = struct; + From 8c5b2e0327788ec1ecf29a5554aee83ef95a8609 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 15 Jun 2014 07:37:01 -0700 Subject: [PATCH 327/791] Add struct.js support for nested structs --- src/js/lib/struct.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/js/lib/struct.js b/src/js/lib/struct.js index a57bd952..344a9ad5 100644 --- a/src/js/lib/struct.js +++ b/src/js/lib/struct.js @@ -5,11 +5,16 @@ * @license MIT */ +var capitalize = function(str) { + return str.charAt(0).toUpperCase() + str.substr(1); +}; + var struct = function(def) { this._littleEndian = true; this._offset = 0; this._makeAccessors(def); this._view = new DataView(new ArrayBuffer(this.size)); + this._def = def; }; struct.types = { @@ -25,10 +30,6 @@ struct.types = { float64: { size: 4 }, }; -var capitalize = function(str) { - return str.charAt(0).toUpperCase() + str.substr(1); -}; - struct.prototype._makeAccessor = function(index, type, name, transform) { var getName = 'get' + capitalize(type); var setName = 'set' + capitalize(type); @@ -45,13 +46,21 @@ struct.prototype._makeAccessor = function(index, type, name, transform) { return this; }; -struct.prototype._makeAccessors = function(def) { - var index = 0; - var fields = []; +struct.prototype._makeAccessors = function(def, index, fields, prefix) { + index = index || 0; + fields = fields || []; for (var i = 0, ii = def.length; i < ii; ++i) { var member = def[i]; var type = member[0]; var name = member[1]; + if (prefix) { + name = prefix + capitalize(name); + } + if (type instanceof struct) { + this._makeAccessors(type._def, index, fields, name); + index = this.size; + continue; + } var transform = member[2]; this._makeAccessor(index, type, name, transform); fields.push({ From 66e23adb05a9aedd7c211b92aa73a2e6b088982f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 15 Jun 2014 19:08:34 -0700 Subject: [PATCH 328/791] Add struct.js inline cstring support --- src/js/lib/struct.js | 112 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 17 deletions(-) diff --git a/src/js/lib/struct.js b/src/js/lib/struct.js index 344a9ad5..027e9d08 100644 --- a/src/js/lib/struct.js +++ b/src/js/lib/struct.js @@ -12,6 +12,7 @@ var capitalize = function(str) { var struct = function(def) { this._littleEndian = true; this._offset = 0; + this._cursor = 0; this._makeAccessors(def); this._view = new DataView(new ArrayBuffer(this.size)); this._def = def; @@ -28,30 +29,104 @@ struct.types = { uint64: { size: 8 }, float32: { size: 2 }, float64: { size: 4 }, + cstring: { size: 1, dynamic: true }, }; -struct.prototype._makeAccessor = function(index, type, name, transform) { - var getName = 'get' + capitalize(type); - var setName = 'set' + capitalize(type); - this[name] = function(value) { - if (arguments.length === 0) { - return this._view[getName](this._offset + index, this._littleEndian); +var makeDataViewAccessor = function(type, typeName) { + var getName = 'get' + capitalize(typeName); + var setName = 'set' + capitalize(typeName); + type.get = function(offset, little) { + this._advance = type.size; + return this._view[getName](offset, little); + }; + type.set = function(offset, value, little) { + this._advance = type.size; + this._view[setName](offset, value, little); + }; +}; + +for (var k in struct.types) { + var type = struct.types[k]; + makeDataViewAccessor(type, k); +} + +struct.types.bool = struct.types.uint8; + +struct.types.cstring.get = function(offset) { + var chars = []; + var buffer = this._view.buffer; + for (var i = offset, ii = buffer.byteLength, j = 0; i < ii && buffer[i] !== 0; ++i, ++j) { + chars[j] = String.fromCharCode(buffer[i]); + } + this._advance = chars.length + 1; + return chars.join(''); +}; + +struct.types.cstring.set = function(offset, value) { + this._grow(offset + value.length); + var i = offset; + var buffer = this._view.buffer; + for (var j = 0, jj = value.length; j < jj && value[i] !== 0; ++i, ++j) { + buffer[i] = value.charCodeAt(j); + } + buffer[i + 1] = '\0'; + this._advance = value.length + 1; +}; + +struct.prototype._grow = function(target) { + var buffer = this._view.buffer; + var size = buffer.byteLength; + if (target <= size) { return; } + while (size < target) { size *= 2; } + var copy = new ArrayBuffer(size); + for (var i = 0; i < buffer.byteLength; ++i) { + copy[i] = buffer[i]; + } + this._view = new DataView(copy); +}; + +struct.prototype._makeAccessor = function(field) { + this[field.name] = function(value) { + var type = field.type; + if (field.dynamic) { + var fieldIndex = this._fields.indexOf(field); + var prevField = this._fields[fieldIndex - 1]; + if (fieldIndex === 0) { + this._cursor = 0; + } else if (this._access === field) { + this._cursor -= this._advance; + } else if (this._access !== prevField) { + throw new Error('dynamic field requires sequential access'); + } + } else { + this._cursor = this._offset + field.index; } - if (transform) { - value = transform(value); + this._access = field; + var result = this; + if (arguments.length === 0) { + result = type.get.call(this, this._cursor, this._littleEndian); + } else { + if (field.transform) { + value = field.transform(value); + } + type.set.call(this, this._cursor, value, this._littleEndian); } - this._view[setName](this._offset + index, value, this._littleEndian); - return this; + this._cursor += this._advance; + return result; }; return this; }; struct.prototype._makeAccessors = function(def, index, fields, prefix) { index = index || 0; - fields = fields || []; + this._fields = ( fields = fields || [] ); + var prevField = fields[fields.length]; for (var i = 0, ii = def.length; i < ii; ++i) { var member = def[i]; var type = member[0]; + if (typeof type === 'string') { + type = struct.types[type]; + } var name = member[1]; if (prefix) { name = prefix + capitalize(name); @@ -62,15 +137,18 @@ struct.prototype._makeAccessors = function(def, index, fields, prefix) { continue; } var transform = member[2]; - this._makeAccessor(index, type, name, transform); - fields.push({ + var field = { + index: index, type: type, name: name, transform: transform, - }); - index += struct.types[type].size; + dynamic: type.dynamic || prevField && prevField.dynamic, + }; + this._makeAccessor(field); + fields.push(field); + index += type.size; + prevField = field; } - this.fields = fields; this.size = index; return this; }; @@ -78,7 +156,7 @@ struct.prototype._makeAccessors = function(def, index, fields, prefix) { struct.prototype.prop = function(def) { if (arguments.length === 0) { var obj = {}; - var fields = this.fields; + var fields = this._fields; for (var i = 0, ii = fields.length; i < ii; ++i) { var name = fields[i].name; obj[name] = this[name](); From ff8376485b8df8055fe93620d83154dbc2c56fcc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 15 Jun 2014 22:24:38 -0700 Subject: [PATCH 329/791] Change window messages to packets --- src/simply/simply.h | 13 ++- src/simply/simply_msg.c | 230 +++++++++++++++++++++++----------------- 2 files changed, 140 insertions(+), 103 deletions(-) diff --git a/src/simply/simply.h b/src/simply/simply.h index c45a6fa7..2fb2cf19 100644 --- a/src/simply/simply.h +++ b/src/simply/simply.h @@ -7,12 +7,17 @@ typedef struct Simply Simply; struct Simply { struct SimplyAccel *accel; struct SimplyRes *res; - struct SimplySplash *splash; - struct SimplyStage *stage; - struct SimplyMenu *menu; struct SimplyMsg *msg; - struct SimplyUi *ui; struct SimplyWindowStack *window_stack; + struct SimplySplash *splash; + union { + struct { + struct SimplyStage *stage; + struct SimplyMenu *menu; + struct SimplyUi *ui; + }; + struct SimplyWindow *windows[0]; + }; }; Simply *simply_init(); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index d5d28e12..42b18331 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -18,6 +18,65 @@ #define SEND_DELAY_MS 10 +typedef enum Command Command; + +enum Command { + CommandWindowShow = 1, + CommandWindowHide, + CommandWindowProps, + CommandWindowActionBar, +}; + +typedef enum WindowType WindowType; + +enum WindowType { + WindowTypeWindow = 0, + WindowTypeMenu, + WindowTypeCard, + WindowTypeLast, +}; + +typedef struct Packet Packet; + +struct __attribute__((__packed__)) Packet { + Command type:16; + uint16_t length; +}; + +typedef struct WindowShowPacket WindowShowPacket; + +struct __attribute__((__packed__)) WindowShowPacket { + Packet packet; + WindowType type:8; + bool pushing; +}; + +typedef struct WindowHidePacket WindowHidePacket; + +struct __attribute__((__packed__)) WindowHidePacket { + Packet packet; + uint32_t id; +}; + +typedef struct WindowPropsPacket WindowPropsPacket; + +struct __attribute__((__packed__)) WindowPropsPacket { + Packet packet; + uint32_t id; + GColor background_color:8; + bool fullscreen; + bool scrollable; +}; + +typedef struct WindowActionBarPacket WindowActionBarPacket; + +struct __attribute__((__packed__)) WindowActionBarPacket { + Packet packet; + uint32_t image[3]; + GColor background_color:8; + bool action; +}; + typedef enum SimplyACmd SimplyACmd; enum SimplyACmd { @@ -48,27 +107,11 @@ enum SimplyACmd { SimplyACmd_stageAnimateDone, }; -typedef enum SimplySetWindowParam SimplySetWindowParam; - -enum SimplySetWindowParam { - SetWindow_clear = 1, - SetWindow_id, - SetWindow_pushing, - SetWindow_action, - SetWindow_actionUp, - SetWindow_actionSelect, - SetWindow_actionDown, - SetWindow_actionBackgroundColor, - SetWindow_backgroundColor, - SetWindow_fullscreen, - SetWindow_scrollable, - SetWindowLast, -}; - typedef enum SimplySetUiParam SimplySetUiParam; enum SimplySetUiParam { - SetUi_title = SetWindowLast, + SetUi_clear = 1, + SetUi_title, SetUi_subtitle, SetUi_body, SetUi_icon, @@ -80,7 +123,8 @@ enum SimplySetUiParam { typedef enum SimplySetMenuParam SimplySetMenuParam; enum SimplySetMenuParam { - SetMenu_sections = SetWindowLast, + SetMenu_clear = 1, + SetMenu_sections, }; typedef enum VibeType VibeType; @@ -129,82 +173,10 @@ static SimplyWindow *get_top_simply_window(Simply *simply) { return window; } -static void set_window(SimplyWindow *window, DictionaryIterator *iter, Simply *simply) { - Tuple *tuple; - if (&simply->menu->window != window && (tuple = dict_find(iter, SetWindow_clear))) { - simply_window_set_action_bar(window, false); - simply_window_action_bar_clear(window); - } - bool is_clear = false; - bool is_id = false; - bool is_push = false; - for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { - switch (tuple->key) { - case SetWindow_clear: - is_clear = true; - break; - case SetWindow_id: - is_id = true; - window->id = tuple->value->uint32; - break; - case SetWindow_pushing: - is_push = true; - break; - case SetWindow_action: - simply_window_set_action_bar(window, tuple->value->int32); - break; - case SetWindow_actionUp: - case SetWindow_actionSelect: - case SetWindow_actionDown: - simply_window_set_action_bar_icon(window, tuple->key - SetWindow_action, tuple->value->int32); - break; - case SetWindow_actionBackgroundColor: - simply_window_set_action_bar_background_color(window, tuple->value->uint8); - break; - case SetWindow_backgroundColor: - simply_window_set_background_color(window, tuple->value->uint32); - break; - case SetWindow_fullscreen: - simply_window_set_fullscreen(window, tuple->value->int32); - break; - case SetWindow_scrollable: - simply_window_set_scrollable(window, tuple->value->int32); - break; - } - } - if (is_clear && is_id) { - simply_window_stack_show(simply->window_stack, window, is_push); - } -} - -static void handle_set_window(DictionaryIterator *iter, Simply *simply) { - SimplyWindow *window = get_top_simply_window(simply); - if (!window) { - return; - } - set_window(window, iter, simply); -} - -static void handle_hide_window(DictionaryIterator *iter, Simply *simply) { - Window *base_window = window_stack_get_top_window(); - SimplyWindow *window = window_get_user_data(base_window); - if (!window || (void*) window == simply->splash) { - return; - } - Tuple *tuple; - uint32_t window_id = 0; - if ((tuple = dict_find(iter, 1))) { - window_id = tuple->value->uint32; - } - if (window->id == window_id) { - simply_window_stack_pop(simply->window_stack, window); - } -} - static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { SimplyUi *ui = simply->ui; Tuple *tuple; - if ((tuple = dict_find(iter, SetWindow_clear))) { + if ((tuple = dict_find(iter, SetUi_clear))) { simply_ui_clear(ui, tuple->value->uint32); } for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { @@ -224,7 +196,6 @@ static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { break; } } - set_window(&ui->window, iter, simply); } static void handle_vibe(DictionaryIterator *iter, Simply *simply) { @@ -282,7 +253,7 @@ static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { - case SetWindow_clear: + case SetMenu_clear: simply_menu_clear(menu); break; case SetMenu_sections: @@ -290,7 +261,6 @@ static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { break; } } - set_window(&menu->window, iter, simply); } static void handle_set_menu_section(DictionaryIterator *iter, Simply *simply) { @@ -378,12 +348,11 @@ static void handle_set_stage(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { switch (tuple->key) { - case SetWindow_clear: + case 0: simply_stage_clear(stage); break; } } - set_window(&stage->window, iter, simply); } static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { @@ -526,6 +495,67 @@ static void handle_animate_stage_element(DictionaryIterator *iter, Simply *simpl simply_stage_animate_element(stage, element, animation, to_frame); } +static void handle_window_show_packet(Simply *simply, Packet *data) { + WindowShowPacket *packet = (WindowShowPacket*) data; + unsigned int i = packet->type < WindowTypeLast ? packet->type : 0; + SimplyWindow *window = simply->windows[i]; + simply_window_stack_show(simply->window_stack, window, packet->pushing); +} + +static void handle_window_hide_packet(Simply *simply, Packet *data) { + WindowHidePacket *packet = (WindowHidePacket*) data; + SimplyWindow *window = get_top_simply_window(simply); + if (!window) { + return; + } + if (window->id == packet->id) { + simply_window_stack_pop(simply->window_stack, window); + } +} + +static void handle_window_props_packet(Simply *simply, Packet *data) { + WindowPropsPacket *packet = (WindowPropsPacket*) data; + SimplyWindow *window = get_top_simply_window(simply); + if (!window) { + return; + } + window->id = packet->id; + simply_window_set_background_color(window, packet->background_color); + simply_window_set_fullscreen(window, packet->fullscreen); + simply_window_set_scrollable(window, packet->scrollable); +} + +static void handle_window_action_bar_packet(Simply *simply, Packet *data) { + WindowActionBarPacket *packet = (WindowActionBarPacket*) data; + SimplyWindow *window = get_top_simply_window(simply); + if (!window) { + return; + } + for (unsigned int i = 0; i < ARRAY_LENGTH(packet->image); ++i) { + simply_window_set_action_bar_icon(window, i, packet->image[i]); + } + simply_window_set_action_bar_background_color(window, packet->background_color); + simply_window_set_action_bar(window, packet->action); +} + +static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { + Packet *packet = (Packet*) buffer; + switch (packet->type) { + case CommandWindowShow: + handle_window_show_packet(simply, packet); + break; + case CommandWindowHide: + handle_window_hide_packet(simply, packet); + break; + case CommandWindowProps: + handle_window_props_packet(simply, packet); + break; + case CommandWindowActionBar: + handle_window_action_bar_packet(simply, packet); + break; + } +} + static void received_callback(DictionaryIterator *iter, void *context) { Tuple *tuple = dict_find(iter, 0); if (!tuple) { @@ -534,12 +564,14 @@ static void received_callback(DictionaryIterator *iter, void *context) { s_has_communicated = true; + if (tuple->length > sizeof(Packet)) { + handle_packet(context, tuple->value->data, tuple->length); + } + switch (tuple->value->uint8) { case SimplyACmd_setWindow: - handle_set_window(iter, context); break; case SimplyACmd_windowHide: - handle_hide_window(iter, context); break; case SimplyACmd_setUi: handle_set_ui(iter, context); From ccafe3976ef37f3048dde286706d51784d86416a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 15 Jun 2014 22:54:54 -0700 Subject: [PATCH 330/791] Rename simply pebble packet to message --- src/js/ui/simply-pebble.js | 195 +++++++++++++++++++------------------ 1 file changed, 98 insertions(+), 97 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 9a8fad24..ebf956c7 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1,3 +1,4 @@ +var struct = require('struct'); var util2 = require('util2'); var myutil = require('myutil'); var Resource = require('ui/resource'); @@ -10,7 +11,7 @@ var StageElement = require('ui/element'); var simply = require('ui/simply'); -/** +/** * This package provides the underlying implementation for the ui/* classes. * * This implementation uses PebbleKit JS AppMessage to send commands to a Pebble Watch. @@ -481,29 +482,29 @@ var toParam = function(param, v) { return v; }; -var setPacket = function(packet, command, def, typeMap) { +var setMessage = function(message, command, def, typeMap) { var paramMap = command.paramMap; for (var k in def) { var paramName = typeMap && typeMap[k] || k; if (!paramName) { continue; } var param = paramMap[paramName]; if (param) { - packet[param.id] = toParam(param, def[k]); + message[param.id] = toParam(param, def[k]); } } - return packet; + return message; }; -var makePacket = function(command, def) { - var packet = {}; - packet[0] = command.id; +var makeMessage = function(command, def) { + var message = {}; + message[0] = command.id; if (def) { - setPacket(packet, command, def); + setMessage(message, command, def); } - return packet; + return message; }; -SimplyPebble.sendPacket = (function() { +SimplyPebble.sendMessage = (function() { var queue = []; var sending = false; @@ -523,8 +524,8 @@ SimplyPebble.sendPacket = (function() { Pebble.sendAppMessage(head, consume, cycle); } - function send(packet) { - queue.push(packet); + function send(message) { + queue.push(message); if (sending) { return; } sending = true; cycle(); @@ -535,8 +536,8 @@ SimplyPebble.sendPacket = (function() { SimplyPebble.buttonConfig = function(buttonConf) { var command = commandMap.configButtons; - var packet = makePacket(command, buttonConf); - SimplyPebble.sendPacket(packet); + var message = makeMessage(command, buttonConf); + SimplyPebble.sendMessage(message); }; var toClearFlags = function(clear) { @@ -559,60 +560,60 @@ var toClearFlags = function(clear) { return clear; }; -var setActionPacket = function(packet, command, actionDef) { +var setActionMessage = function(message, command, actionDef) { if (actionDef) { if (typeof actionDef === 'boolean') { actionDef = { action: actionDef }; } - setPacket(packet, command, actionDef, actionBarTypeMap); + setMessage(message, command, actionDef, actionBarTypeMap); } - return packet; + return message; }; SimplyPebble.window = function(windowDef, clear) { var command = commandMap.setWindow; - var packet = makePacket(command, windowDef); + var message = makeMessage(command, windowDef); if (clear) { clear = toClearFlags(clear); - packet[command.paramMap.clear.id] = clear; + message[command.paramMap.clear.id] = clear; } - setActionPacket(packet, command, windowDef.action); - SimplyPebble.sendPacket(packet); + setActionMessage(message, command, windowDef.action); + SimplyPebble.sendMessage(message); }; SimplyPebble.windowHide = function(windowId) { var command = commandMap.windowHide; - var packet = makePacket(command); - packet[command.paramMap.id.id] = windowId; - SimplyPebble.sendPacket(packet); + var message = makeMessage(command); + message[command.paramMap.id.id] = windowId; + SimplyPebble.sendMessage(message); }; SimplyPebble.card = function(cardDef, clear, pushing) { var command = commandMap.setCard; - var packet = makePacket(command, cardDef); + var message = makeMessage(command, cardDef); if (clear) { clear = toClearFlags(clear); - packet[command.paramMap.clear.id] = clear; + message[command.paramMap.clear.id] = clear; } if (pushing) { - packet[command.paramMap.pushing.id] = pushing; + message[command.paramMap.pushing.id] = pushing; } - setActionPacket(packet, command, cardDef.action); - SimplyPebble.sendPacket(packet); + setActionMessage(message, command, cardDef.action); + SimplyPebble.sendMessage(message); }; SimplyPebble.vibe = function(type) { var command = commandMap.vibe; - var packet = makePacket(command); + var message = makeMessage(command); var vibeIndex = vibeTypes.indexOf(type); - packet[command.paramMap.type.id] = vibeIndex !== -1 ? vibeIndex : 0; - SimplyPebble.sendPacket(packet); + message[command.paramMap.type.id] = vibeIndex !== -1 ? vibeIndex : 0; + SimplyPebble.sendMessage(message); }; SimplyPebble.accelConfig = function(configDef) { var command = commandMap.configAccelData; - var packet = makePacket(command, configDef); - SimplyPebble.sendPacket(packet); + var message = makeMessage(command, configDef); + SimplyPebble.sendMessage(message); }; var accelListeners = []; @@ -620,127 +621,127 @@ var accelListeners = []; SimplyPebble.accelPeek = function(callback) { accelListeners.push(callback); var command = commandMap.getAccelData; - var packet = makePacket(command); - SimplyPebble.sendPacket(packet); + var message = makeMessage(command); + SimplyPebble.sendMessage(message); }; SimplyPebble.menu = function(menuDef, clear, pushing) { var command = commandMap.setMenu; - var packetDef = util2.copy(menuDef); - if (packetDef.sections instanceof Array) { - packetDef.sections = packetDef.sections.length; + var messageDef = util2.copy(menuDef); + if (messageDef.sections instanceof Array) { + messageDef.sections = messageDef.sections.length; } - if (!packetDef.sections) { - packetDef.sections = 1; + if (!messageDef.sections) { + messageDef.sections = 1; } - var packet = makePacket(command, packetDef); + var message = makeMessage(command, messageDef); if (clear) { clear = toClearFlags(clear); - packet[command.paramMap.clear.id] = clear; + message[command.paramMap.clear.id] = clear; } if (pushing) { - packet[command.paramMap.pushing.id] = pushing; + message[command.paramMap.pushing.id] = pushing; } - SimplyPebble.sendPacket(packet); + SimplyPebble.sendMessage(message); }; SimplyPebble.menuSection = function(sectionIndex, sectionDef, clear) { var command = commandMap.setMenuSection; - var packetDef = util2.copy(sectionDef); - packetDef.section = sectionIndex; - if (packetDef.items instanceof Array) { - packetDef.items = packetDef.items.length; + var messageDef = util2.copy(sectionDef); + messageDef.section = sectionIndex; + if (messageDef.items instanceof Array) { + messageDef.items = messageDef.items.length; } - packetDef.clear = clear; - var packet = makePacket(command, packetDef); - SimplyPebble.sendPacket(packet); + messageDef.clear = clear; + var message = makeMessage(command, messageDef); + SimplyPebble.sendMessage(message); }; SimplyPebble.menuItem = function(sectionIndex, itemIndex, itemDef) { var command = commandMap.setMenuItem; - var packetDef = util2.copy(itemDef); - packetDef.section = sectionIndex; - packetDef.item = itemIndex; - var packet = makePacket(command, packetDef); - SimplyPebble.sendPacket(packet); + var messageDef = util2.copy(itemDef); + messageDef.section = sectionIndex; + messageDef.item = itemIndex; + var message = makeMessage(command, messageDef); + SimplyPebble.sendMessage(message); }; SimplyPebble.image = function(id, gbitmap) { var command = commandMap.image; - var packetDef = util2.copy(gbitmap); - packetDef.id = id; - var packet = makePacket(command, packetDef); - SimplyPebble.sendPacket(packet); + var messageDef = util2.copy(gbitmap); + messageDef.id = id; + var message = makeMessage(command, messageDef); + SimplyPebble.sendMessage(message); }; SimplyPebble.stage = function(stageDef, clear, pushing) { var command = commandMap.setStage; - var packet = makePacket(command, stageDef); + var message = makeMessage(command, stageDef); if (clear) { clear = toClearFlags(clear); - packet[command.paramMap.clear.id] = clear; + message[command.paramMap.clear.id] = clear; } if (pushing) { - packet[command.paramMap.pushing.id] = pushing; + message[command.paramMap.pushing.id] = pushing; } - setActionPacket(packet, command, stageDef.action); - SimplyPebble.sendPacket(packet); + setActionMessage(message, command, stageDef.action); + SimplyPebble.sendMessage(message); }; -var toFramePacket = function(packetDef) { - if (packetDef.position) { - var position = packetDef.position; - delete packetDef.position; - packetDef.x = position.x; - packetDef.y = position.y; +var toFrameMessage = function(messageDef) { + if (messageDef.position) { + var position = messageDef.position; + delete messageDef.position; + messageDef.x = position.x; + messageDef.y = position.y; } - if (packetDef.size) { - var size = packetDef.size; - delete packetDef.size; - packetDef.width = size.x; - packetDef.height = size.y; + if (messageDef.size) { + var size = messageDef.size; + delete messageDef.size; + messageDef.width = size.x; + messageDef.height = size.y; } - return packetDef; + return messageDef; }; SimplyPebble.stageElement = function(elementId, elementType, elementDef, index) { var command = commandMap.stageElement; - var packetDef = util2.copy(elementDef); - packetDef.id = elementId; - packetDef.type = elementType; - packetDef.index = index; - packetDef = toFramePacket(packetDef); - var packet = makePacket(command, packetDef); - SimplyPebble.sendPacket(packet); + var messageDef = util2.copy(elementDef); + messageDef.id = elementId; + messageDef.type = elementType; + messageDef.index = index; + messageDef = toFrameMessage(messageDef); + var message = makeMessage(command, messageDef); + SimplyPebble.sendMessage(message); }; SimplyPebble.stageRemove = function(elementId) { var command = commandMap.stageRemove; - var packet = makePacket(command); - packet[command.paramMap.id.id] = elementId; - SimplyPebble.sendPacket(packet); + var message = makeMessage(command); + message[command.paramMap.id.id] = elementId; + SimplyPebble.sendMessage(message); }; SimplyPebble.stageAnimate = function(elementId, animateDef, duration, easing) { var command = commandMap.stageAnimate; - var packetDef = util2.copy(animateDef); - packetDef.id = elementId; + var messageDef = util2.copy(animateDef); + messageDef.id = elementId; if (duration) { - packetDef.duration = duration; + messageDef.duration = duration; } if (easing) { - packetDef.easing = easing; + messageDef.easing = easing; } - packetDef = toFramePacket(packetDef); - var packet = makePacket(command, packetDef); - SimplyPebble.sendPacket(packet); + messageDef = toFrameMessage(messageDef); + var message = makeMessage(command, messageDef); + SimplyPebble.sendMessage(message); }; -var readInt = function(packet, width, pos, signed) { +var readInt = function(message, width, pos, signed) { var value = 0; pos = pos || 0; for (var i = 0; i < width; ++i) { - value += (packet[pos + i] & 0xFF) << (i * 8); + value += (message[pos + i] & 0xFF) << (i * 8); } if (signed) { var mask = 1 << (width * 8 - 1); From 0207a5f1c4e51d9c22016ddd0ea1a546c73ea505 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 15 Jun 2014 22:55:15 -0700 Subject: [PATCH 331/791] Change simply pebble image type to a function type --- src/js/ui/simply-pebble.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index ebf956c7..a077fe94 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -17,15 +17,17 @@ var simply = require('ui/simply'); * This implementation uses PebbleKit JS AppMessage to send commands to a Pebble Watch. */ -/* Make JSHint happy */ -if (typeof Image === 'undefined') { - window.Image = function(){}; -} - /** * First part of this file is defining the commands and types that we will use later. */ +var ImageType = function(x) { + if (typeof x !== 'number') { + return ImageService.resolve(x); + } + return x; +}; + var Color = function(x) { switch (x) { case 'clear': return ~0; @@ -118,13 +120,13 @@ var setWindowParams = [{ type: Boolean, }, { name: 'actionUp', - type: Image, + type: ImageType, }, { name: 'actionSelect', - type: Image, + type: ImageType, }, { name: 'actionDown', - type: Image, + type: ImageType, }, { name: 'actionBackgroundColor', type: Color, @@ -150,13 +152,13 @@ var setCardParams = setWindowParams.concat([{ type: String, }, { name: 'icon', - type: Image, + type: ImageType, }, { name: 'subicon', - type: Image, + type: ImageType, }, { name: 'image', - type: Image, + type: ImageType, }, { name: 'style' }]); @@ -275,7 +277,7 @@ var commands = [{ type: String, }, { name: 'icon', - type: Image, + type: ImageType, }], }, { name: 'getMenuItem', @@ -356,7 +358,7 @@ var commands = [{ type: TimeUnits, }, { name: 'image', - type: Image, + type: ImageType, }, { name: 'compositing', type: CompositingOp, @@ -474,8 +476,6 @@ var toParam = function(param, v) { v = typeof v !== 'undefined' ? v.toString() : ''; } else if (param.type === Boolean) { v = v ? 1 : 0; - } else if (param.type === Image && typeof v !== 'number') { - v = ImageService.resolve(v); } else if (typeof param.type === 'function') { v = param.type(v); } From 68d60d9febcf5a9fb978058fca1292eb9ceb3877 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 01:38:50 -0700 Subject: [PATCH 332/791] Add defaults to window class --- src/js/ui/window.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 8b64b0f0..033c1f9c 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -45,10 +45,16 @@ var actionProps = [ var accessorProps = configProps; +var defaults = { + backgroundColor: 'black', + fullscreen: false, + scrollable: false, +}; + var nextId = 1; var Window = function(windowDef) { - this.state = windowDef || {}; + this.state = myutil.shadow(defaults, windowDef || {}); this.state.id = nextId++; this._buttonInit(); this._items = []; From c6716fcdbe9082921d4ac78f286fbf1483597fcd Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 01:39:01 -0700 Subject: [PATCH 333/791] Add defaults to card class --- src/js/ui/card.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/js/ui/card.js b/src/js/ui/card.js index 83518e01..579b94bb 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -62,8 +62,12 @@ var configProps = [ var accessorProps = textProps.concat(imageProps).concat(configProps); var clearableProps = textProps.concat(imageProps); +var defaults = { + backgroundColor: 'white', +}; + var Card = function(cardDef) { - Window.call(this, cardDef); + Window.call(this, myutil.shadow(defaults, cardDef || {})); this._dynamic = false; }; From 52f21779795f0edc0134be90ace66000a405a251 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 01:40:10 -0700 Subject: [PATCH 334/791] Change simply window setters to be idempotent --- src/simply/simply_window.c | 12 ++++++++++++ src/simply/simply_window.h | 1 + 2 files changed, 13 insertions(+) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index a6470f71..591d3975 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -15,6 +15,10 @@ static void click_config_provider(void *data); void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { + if (self->is_scrollable == is_scrollable) { + return; + } + self->is_scrollable = is_scrollable; if (!self->scroll_layer) { @@ -39,6 +43,10 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { } void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { + if (self->is_fullscreen == is_fullscreen) { + return; + } + window_set_fullscreen(self->window, is_fullscreen); if (!self->layer) { @@ -68,6 +76,10 @@ void simply_window_set_background_color(SimplyWindow *self, GColor background_co } void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { + if (self->is_action_bar == is_action_bar) { + return; + } + self->is_action_bar = is_action_bar; if (!self->action_bar_layer) { diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index 8202dc72..6f54f644 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -15,6 +15,7 @@ struct SimplyWindow { uint32_t id; ButtonId button_mask:4; GColor background_color:2; + bool is_fullscreen:1; bool is_scrollable:1; bool is_action_bar:1; }; From 320a0e45c746de5450b6335144bfc047b27f88d0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 01:40:45 -0700 Subject: [PATCH 335/791] Add simply pebble window packets --- src/js/ui/simply-pebble.js | 195 ++++++++++++++++++++----------- src/simply/simply_window_stack.c | 3 + 2 files changed, 128 insertions(+), 70 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index a077fe94..d411f9cb 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -21,6 +21,10 @@ var simply = require('ui/simply'); * First part of this file is defining the commands and types that we will use later. */ +var BoolType = function(x) { + return x ? 1 : 0; +}; + var ImageType = function(x) { if (typeof x !== 'number') { return ImageService.resolve(x); @@ -108,40 +112,66 @@ var AnimationCurve = function(x) { return Number(x); }; -var setWindowParams = [{ +var makeArrayType = function(types) { + return function(x) { + var index = types.indexOf(x); + if (index !== -1) { + return index; + } + return Number(x); + }; +}; + +var windowTypes = [ + 'window', + 'menu', + 'card', +]; + +var WindowType = makeArrayType(windowTypes); + +var Packet = new struct([ + ['uint16', 'type'], + ['uint16', 'length'], +]); + +var WindowShowPacket = new struct([ + [Packet, 'packet'], + ['uint8', 'type', WindowType], + ['bool', 'pushing', BoolType], +]); + +var WindowHidePacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], +]); + +var WindowPropsPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + ['uint8', 'backgroundColor', Color], + ['bool', 'fullscreen', BoolType], + ['bool', 'scrollable', BoolType], +]); + +var WindowActionBarPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'upImage'], + ['uint32', 'selectImage'], + ['uint32', 'downImage'], +]); + +var CommandPackets = [ + Packet, + WindowShowPacket, + WindowHidePacket, + WindowPropsPacket, + WindowActionBarPacket, +]; + +var setCardParams = [{ name: 'clear', }, { - name: 'id', -}, { - name: 'pushing', - type: Boolean, -}, { - name: 'action', - type: Boolean, -}, { - name: 'actionUp', - type: ImageType, -}, { - name: 'actionSelect', - type: ImageType, -}, { - name: 'actionDown', - type: ImageType, -}, { - name: 'actionBackgroundColor', - type: Color, -}, { - name: 'backgroundColor', - type: Color, -}, { - name: 'fullscreen', - type: Boolean, -}, { - name: 'scrollable', - type: Boolean, -}]; - -var setCardParams = setWindowParams.concat([{ name: 'title', type: String, }, { @@ -161,28 +191,25 @@ var setCardParams = setWindowParams.concat([{ type: ImageType, }, { name: 'style' -}]); +}]; -var setMenuParams = setWindowParams.concat([{ +var setMenuParams = [{ + name: 'clear', +}, { name: 'sections', type: Number, -}]); +}]; -var setStageParams = setWindowParams; +var setStageParams = [{ + name: 'clear', +}]; var commands = [{ name: 'setWindow', - params: setWindowParams, }, { name: 'windowShow', - params: [{ - name: 'id' - }] }, { name: 'windowHide', - params: [{ - name: 'id' - }] }, { name: 'setCard', params: setCardParams, @@ -534,6 +561,49 @@ SimplyPebble.sendMessage = (function() { return send; })(); +var toByteArray = function(packet) { + var size = Math.max(packet.size, packet._cursor); + packet.packetType(CommandPackets.indexOf(packet)); + packet.packetLength(size); + + var byteArray = new Array(size); + var dataView = packet._view; + for (var i = 0; i < size; ++i) { + byteArray[i] = dataView.getUint8(i); + } + + return byteArray; +}; + +SimplyPebble.sendPacket = function(packet) { + SimplyPebble.sendMessage({ 0: toByteArray(packet) }); +}; + +var setPacket = function(packet, def) { + packet._fields.forEach(function(field) { + if (field.name in def) { + packet[field.name](def[field.name]); + } + }); + return packet; +}; + +SimplyPebble.windowProps = function(def) { + SimplyPebble.sendPacket(setPacket(WindowPropsPacket, def)); +}; + +SimplyPebble.windowActionBar = function(def) { + SimplyPebble.sendPacket(setPacket(WindowActionBarPacket, def)); +}; + +SimplyPebble.windowShow = function(def) { + SimplyPebble.sendPacket(setPacket(WindowShowPacket, def)); +}; + +SimplyPebble.windowHide = function(id) { + SimplyPebble.sendPacket(WindowHidePacket.id(id)); +}; + SimplyPebble.buttonConfig = function(buttonConf) { var command = commandMap.configButtons; var message = makeMessage(command, buttonConf); @@ -570,34 +640,17 @@ var setActionMessage = function(message, command, actionDef) { return message; }; -SimplyPebble.window = function(windowDef, clear) { - var command = commandMap.setWindow; - var message = makeMessage(command, windowDef); - if (clear) { - clear = toClearFlags(clear); - message[command.paramMap.clear.id] = clear; - } - setActionMessage(message, command, windowDef.action); - SimplyPebble.sendMessage(message); -}; - -SimplyPebble.windowHide = function(windowId) { - var command = commandMap.windowHide; - var message = makeMessage(command); - message[command.paramMap.id.id] = windowId; - SimplyPebble.sendMessage(message); -}; - SimplyPebble.card = function(cardDef, clear, pushing) { + if (arguments.length === 3) { + SimplyPebble.windowShow({ type: 'card', pushing: pushing }); + } + SimplyPebble.windowProps(cardDef); var command = commandMap.setCard; var message = makeMessage(command, cardDef); if (clear) { clear = toClearFlags(clear); message[command.paramMap.clear.id] = clear; } - if (pushing) { - message[command.paramMap.pushing.id] = pushing; - } setActionMessage(message, command, cardDef.action); SimplyPebble.sendMessage(message); }; @@ -626,6 +679,10 @@ SimplyPebble.accelPeek = function(callback) { }; SimplyPebble.menu = function(menuDef, clear, pushing) { + if (arguments.length === 3) { + SimplyPebble.windowShow({ type: 'menu', pushing: pushing }); + } + SimplyPebble.windowProps(menuDef); var command = commandMap.setMenu; var messageDef = util2.copy(menuDef); if (messageDef.sections instanceof Array) { @@ -639,9 +696,6 @@ SimplyPebble.menu = function(menuDef, clear, pushing) { clear = toClearFlags(clear); message[command.paramMap.clear.id] = clear; } - if (pushing) { - message[command.paramMap.pushing.id] = pushing; - } SimplyPebble.sendMessage(message); }; @@ -675,15 +729,16 @@ SimplyPebble.image = function(id, gbitmap) { }; SimplyPebble.stage = function(stageDef, clear, pushing) { + if (arguments.length === 3) { + SimplyPebble.windowShow({ type: 'window', pushing: pushing }); + } + SimplyPebble.windowProps(stageDef); var command = commandMap.setStage; var message = makeMessage(command, stageDef); if (clear) { clear = toClearFlags(clear); message[command.paramMap.clear.id] = clear; } - if (pushing) { - message[command.paramMap.pushing.id] = pushing; - } setActionMessage(message, command, stageDef.action); SimplyPebble.sendMessage(message); }; diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index 69ce4c09..9d20bf9c 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -48,6 +48,9 @@ void simply_window_stack_send_show(SimplyWindowStack *self, SimplyWindow *window } void simply_window_stack_send_hide(SimplyWindowStack *self, SimplyWindow *window) { + if (!window->id) { + return; + } if (self->is_showing) { return; } From 143ee05014ecad0154e8afb395e65f9183ae32f7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 03:29:14 -0700 Subject: [PATCH 336/791] Fix struct.js to be compatible with mobile Safari --- src/js/lib/struct.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/js/lib/struct.js b/src/js/lib/struct.js index 027e9d08..1e79f1d2 100644 --- a/src/js/lib/struct.js +++ b/src/js/lib/struct.js @@ -54,35 +54,35 @@ struct.types.bool = struct.types.uint8; struct.types.cstring.get = function(offset) { var chars = []; - var buffer = this._view.buffer; - for (var i = offset, ii = buffer.byteLength, j = 0; i < ii && buffer[i] !== 0; ++i, ++j) { - chars[j] = String.fromCharCode(buffer[i]); + var buffer = this._view; + for (var i = offset, ii = buffer.byteLength, j = 0; i < ii && buffer.getUint8(i) !== 0; ++i, ++j) { + chars[j] = String.fromCharCode(buffer.getUint8(i)); } this._advance = chars.length + 1; return chars.join(''); }; struct.types.cstring.set = function(offset, value) { - this._grow(offset + value.length); + this._grow(offset + value.length + 1); var i = offset; - var buffer = this._view.buffer; - for (var j = 0, jj = value.length; j < jj && value[i] !== 0; ++i, ++j) { - buffer[i] = value.charCodeAt(j); + var buffer = this._view; + for (var j = 0, jj = value.length; j < jj && value[i] !== '\0'; ++i, ++j) { + buffer.setUint8(i, value.charCodeAt(j)); } - buffer[i + 1] = '\0'; + buffer.setUint8(i, 0); this._advance = value.length + 1; }; struct.prototype._grow = function(target) { - var buffer = this._view.buffer; + var buffer = this._view; var size = buffer.byteLength; if (target <= size) { return; } while (size < target) { size *= 2; } - var copy = new ArrayBuffer(size); + var copy = new DataView(new ArrayBuffer(size)); for (var i = 0; i < buffer.byteLength; ++i) { - copy[i] = buffer[i]; + copy.setUint8(i, buffer.getUint8(i)); } - this._view = new DataView(copy); + this._view = copy; }; struct.prototype._makeAccessor = function(field) { From c355fd184fc8ce4a010b46b24ce0a06cfb28f584 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 03:29:35 -0700 Subject: [PATCH 337/791] Add util math with min and max macros --- src/util/math.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/util/math.h diff --git a/src/util/math.h b/src/util/math.h new file mode 100644 index 00000000..6b6cb616 --- /dev/null +++ b/src/util/math.h @@ -0,0 +1,13 @@ +#pragma once + +#define MAX(a, b) ({ \ + __typeof__(a) __max_tmp_a = (a); \ + __typeof__(a) __max_tmp_b = (b); \ + (__max_tmp_a >= __max_tmp_b ? __max_tmp_a : __max_tmp_b); \ +}) + +#define MIN(a, b) ({ \ + __typeof__(a) __min_tmp_a = (a); \ + __typeof__(a) __min_tmp_b = (b); \ + (__min_tmp_a <= __min_tmp_b ? __min_tmp_a : __min_tmp_b); \ +}) From e463c4da68ce58c42591ca9e59e4edb097ee17c1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 03:30:21 -0700 Subject: [PATCH 338/791] Convert set card message to packets --- src/js/ui/simply-pebble.js | 163 +++++++++++++++++++++++-------------- src/simply/simply_msg.c | 98 +++++++++++++++------- 2 files changed, 174 insertions(+), 87 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index d411f9cb..2f3abadb 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -130,6 +130,30 @@ var windowTypes = [ var WindowType = makeArrayType(windowTypes); +var cardTextTypes = [ + 'title', + 'subtitle', + 'body', +]; + +var CardTextType = makeArrayType(cardTextTypes); + +var cardImageTypes = [ + 'icon', + 'subicon', + 'banner', +]; + +var CardImageType = makeArrayType(cardImageTypes); + +var cardStyleTypes = [ + 'small', + 'large', + 'mono', +]; + +var CardStyleType = makeArrayType(cardStyleTypes); + var Packet = new struct([ ['uint16', 'type'], ['uint16', 'length'], @@ -156,9 +180,32 @@ var WindowPropsPacket = new struct([ var WindowActionBarPacket = new struct([ [Packet, 'packet'], - ['uint32', 'upImage'], - ['uint32', 'selectImage'], - ['uint32', 'downImage'], + ['uint32', 'up', ImageType], + ['uint32', 'select', ImageType], + ['uint32', 'down', ImageType], + ['uint8', 'action', BoolType], +]); + +var CardClearPacket = new struct([ + [Packet, 'packet'], + ['uint8', 'flags'], +]); + +var CardTextPacket = new struct([ + [Packet, 'packet'], + ['uint8', 'index', CardTextType], + ['cstring', 'text'], +]); + +var CardImagePacket = new struct([ + [Packet, 'packet'], + ['uint32', 'image', ImageType], + ['uint8', 'index', CardImageType], +]); + +var CardStylePacket = new struct([ + [Packet, 'packet'], + ['uint8', 'style', CardStyleType], ]); var CommandPackets = [ @@ -167,32 +214,12 @@ var CommandPackets = [ WindowHidePacket, WindowPropsPacket, WindowActionBarPacket, + CardClearPacket, + CardTextPacket, + CardImagePacket, + CardStylePacket, ]; -var setCardParams = [{ - name: 'clear', -}, { - name: 'title', - type: String, -}, { - name: 'subtitle', - type: String, -}, { - name: 'body', - type: String, -}, { - name: 'icon', - type: ImageType, -}, { - name: 'subicon', - type: ImageType, -}, { - name: 'image', - type: ImageType, -}, { - name: 'style' -}]; - var setMenuParams = [{ name: 'clear', }, { @@ -212,7 +239,6 @@ var commands = [{ name: 'windowHide', }, { name: 'setCard', - params: setCardParams, }, { name: 'click', params: [{ @@ -461,12 +487,6 @@ var vibeTypes = [ 'double', ]; -var styleTypes = [ - 'small', - 'large', - 'mono', -]; - var clearFlagMap = { action: (1 << 0), text: (1 << 1), @@ -562,14 +582,15 @@ SimplyPebble.sendMessage = (function() { })(); var toByteArray = function(packet) { + var type = CommandPackets.indexOf(packet); var size = Math.max(packet.size, packet._cursor); - packet.packetType(CommandPackets.indexOf(packet)); + packet.packetType(type); packet.packetLength(size); + var buffer = packet._view; var byteArray = new Array(size); - var dataView = packet._view; for (var i = 0; i < size; ++i) { - byteArray[i] = dataView.getUint8(i); + byteArray[i] = buffer.getUint8(i); } return byteArray; @@ -592,8 +613,15 @@ SimplyPebble.windowProps = function(def) { SimplyPebble.sendPacket(setPacket(WindowPropsPacket, def)); }; +var toActionDef = function(actionDef) { + if (typeof actionDef === 'boolean') { + actionDef = { action: actionDef }; + } + return actionDef; +}; + SimplyPebble.windowActionBar = function(def) { - SimplyPebble.sendPacket(setPacket(WindowActionBarPacket, def)); + SimplyPebble.sendPacket(setPacket(WindowActionBarPacket, toActionDef(def))); }; SimplyPebble.windowShow = function(def) { @@ -604,12 +632,6 @@ SimplyPebble.windowHide = function(id) { SimplyPebble.sendPacket(WindowHidePacket.id(id)); }; -SimplyPebble.buttonConfig = function(buttonConf) { - var command = commandMap.configButtons; - var message = makeMessage(command, buttonConf); - SimplyPebble.sendMessage(message); -}; - var toClearFlags = function(clear) { if (clear === true) { clear = 'all'; @@ -630,6 +652,44 @@ var toClearFlags = function(clear) { return clear; }; +SimplyPebble.cardClear = function(clear) { + SimplyPebble.sendPacket(CardClearPacket.flags(toClearFlags(clear))); +}; + +SimplyPebble.cardText = function(field, text) { + SimplyPebble.sendPacket(CardTextPacket.index(field).text(text)); +}; + +SimplyPebble.cardImage = function(field, image) { + SimplyPebble.sendPacket(CardImagePacket.index(field).image(image)); +}; + +SimplyPebble.card = function(def, clear, pushing) { + if (arguments.length === 3) { + SimplyPebble.windowShow({ type: 'card', pushing: pushing }); + } + if (clear !== undefined) { + SimplyPebble.cardClear(clear); + } + SimplyPebble.windowProps(def); + if (def.action !== undefined) { + SimplyPebble.windowActionBar(def.action); + } + for (var k in def) { + if (cardTextTypes.indexOf(k) !== -1) { + SimplyPebble.cardText(k, def[k]); + } else if (cardImageTypes.indexOf(k) !== -1) { + SimplyPebble.cardImage(k, def[k]); + } + } +}; + +SimplyPebble.buttonConfig = function(buttonConf) { + var command = commandMap.configButtons; + var message = makeMessage(command, buttonConf); + SimplyPebble.sendMessage(message); +}; + var setActionMessage = function(message, command, actionDef) { if (actionDef) { if (typeof actionDef === 'boolean') { @@ -640,21 +700,6 @@ var setActionMessage = function(message, command, actionDef) { return message; }; -SimplyPebble.card = function(cardDef, clear, pushing) { - if (arguments.length === 3) { - SimplyPebble.windowShow({ type: 'card', pushing: pushing }); - } - SimplyPebble.windowProps(cardDef); - var command = commandMap.setCard; - var message = makeMessage(command, cardDef); - if (clear) { - clear = toClearFlags(clear); - message[command.paramMap.clear.id] = clear; - } - setActionMessage(message, command, cardDef.action); - SimplyPebble.sendMessage(message); -}; - SimplyPebble.vibe = function(type) { var command = commandMap.vibe; var message = makeMessage(command); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 42b18331..8b597fe0 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -11,8 +11,10 @@ #include "util/dict.h" #include "util/list1.h" +#include "util/math.h" #include "util/memory.h" #include "util/string.h" +#include "util/window.h" #include @@ -25,6 +27,10 @@ enum Command { CommandWindowHide, CommandWindowProps, CommandWindowActionBar, + CommandCardClear, + CommandCardText, + CommandCardImage, + CommandCardStyle, }; typedef enum WindowType WindowType; @@ -77,6 +83,36 @@ struct __attribute__((__packed__)) WindowActionBarPacket { bool action; }; +typedef struct CardClearPacket CardClearPacket; + +struct __attribute__((__packed__)) CardClearPacket { + Packet packet; + uint8_t flags; +}; + +typedef struct CardTextPacket CardTextPacket; + +struct __attribute__((__packed__)) CardTextPacket { + Packet packet; + uint8_t index; + char text[]; +}; + +typedef struct CardImagePacket CardImagePacket; + +struct __attribute__((__packed__)) CardImagePacket { + Packet packet; + uint32_t image; + uint8_t index; +}; + +typedef struct CardStylePacket CardStylePacket; + +struct __attribute__((__packed__)) CardStylePacket { + Packet packet; + uint8_t style; +}; + typedef enum SimplyACmd SimplyACmd; enum SimplyACmd { @@ -173,31 +209,6 @@ static SimplyWindow *get_top_simply_window(Simply *simply) { return window; } -static void handle_set_ui(DictionaryIterator *iter, Simply *simply) { - SimplyUi *ui = simply->ui; - Tuple *tuple; - if ((tuple = dict_find(iter, SetUi_clear))) { - simply_ui_clear(ui, tuple->value->uint32); - } - for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { - switch (tuple->key) { - case SetUi_title: - case SetUi_subtitle: - case SetUi_body: - simply_ui_set_text(ui, tuple->key - SetUi_title, tuple->value->cstring); - break; - case SetUi_icon: - case SetUi_subicon: - case SetUi_image: - ui->ui_layer.imagefields[tuple->key - SetUi_icon] = tuple->value->uint32; - break; - case SetUi_style: - simply_ui_set_style(simply->ui, tuple->value->int32); - break; - } - } -} - static void handle_vibe(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; if ((tuple = dict_find(iter, 1))) { @@ -497,8 +508,7 @@ static void handle_animate_stage_element(DictionaryIterator *iter, Simply *simpl static void handle_window_show_packet(Simply *simply, Packet *data) { WindowShowPacket *packet = (WindowShowPacket*) data; - unsigned int i = packet->type < WindowTypeLast ? packet->type : 0; - SimplyWindow *window = simply->windows[i]; + SimplyWindow *window = simply->windows[MIN(WindowTypeLast - 1, packet->type)]; simply_window_stack_show(simply->window_stack, window, packet->pushing); } @@ -538,6 +548,27 @@ static void handle_window_action_bar_packet(Simply *simply, Packet *data) { simply_window_set_action_bar(window, packet->action); } +static void handle_card_clear_packet(Simply *simply, Packet *data) { + CardClearPacket *packet = (CardClearPacket*) data; + simply_ui_clear(simply->ui, packet->flags); +} + +static void handle_card_text_packet(Simply *simply, Packet *data) { + CardTextPacket *packet = (CardTextPacket*) data; + simply_ui_set_text(simply->ui, MIN(NumUiTextfields - 1, packet->index), packet->text); +} + +static void handle_card_image_packet(Simply *simply, Packet *data) { + CardImagePacket *packet = (CardImagePacket*) data; + simply->ui->ui_layer.imagefields[MIN(NumUiImagefields - 1, packet->index)] = packet->image; + window_stack_schedule_top_window_render(); +} + +static void handle_card_style_packet(Simply *simply, Packet *data) { + CardStylePacket *packet = (CardStylePacket*) data; + simply_ui_set_style(simply->ui, packet->style); +} + static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { Packet *packet = (Packet*) buffer; switch (packet->type) { @@ -553,6 +584,18 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandWindowActionBar: handle_window_action_bar_packet(simply, packet); break; + case CommandCardClear: + handle_card_clear_packet(simply, packet); + break; + case CommandCardText: + handle_card_text_packet(simply, packet); + break; + case CommandCardImage: + handle_card_image_packet(simply, packet); + break; + case CommandCardStyle: + handle_card_style_packet(simply, packet); + break; } } @@ -574,7 +617,6 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_windowHide: break; case SimplyACmd_setUi: - handle_set_ui(iter, context); break; case SimplyACmd_vibe: handle_vibe(iter, context); From 4f5365bd8963df69442560d75573d52b09cb805e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 03:39:54 -0700 Subject: [PATCH 339/791] Convert vibe message to packet --- src/js/ui/simply-pebble.js | 32 ++++++++++++---------- src/simply/simply_msg.c | 56 +++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 2f3abadb..28abf1a1 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -154,6 +154,14 @@ var cardStyleTypes = [ var CardStyleType = makeArrayType(cardStyleTypes); +var vibeTypes = [ + 'short', + 'long', + 'double', +]; + +var VibeType = makeArrayType(vibeTypes); + var Packet = new struct([ ['uint16', 'type'], ['uint16', 'length'], @@ -208,6 +216,11 @@ var CardStylePacket = new struct([ ['uint8', 'style', CardStyleType], ]); +var VibePacket = new struct([ + [Packet, 'packet'], + ['uint8', 'type', VibeType], +]); + var CommandPackets = [ Packet, WindowShowPacket, @@ -218,6 +231,7 @@ var CommandPackets = [ CardTextPacket, CardImagePacket, CardStylePacket, + VibePacket, ]; var setMenuParams = [{ @@ -481,12 +495,6 @@ var accelAxes = [ 'z', ]; -var vibeTypes = [ - 'short', - 'long', - 'double', -]; - var clearFlagMap = { action: (1 << 0), text: (1 << 1), @@ -684,6 +692,10 @@ SimplyPebble.card = function(def, clear, pushing) { } }; +SimplyPebble.vibe = function(type) { + SimplyPebble.sendPacket(VibePacket.type(type)); +}; + SimplyPebble.buttonConfig = function(buttonConf) { var command = commandMap.configButtons; var message = makeMessage(command, buttonConf); @@ -700,14 +712,6 @@ var setActionMessage = function(message, command, actionDef) { return message; }; -SimplyPebble.vibe = function(type) { - var command = commandMap.vibe; - var message = makeMessage(command); - var vibeIndex = vibeTypes.indexOf(type); - message[command.paramMap.type.id] = vibeIndex !== -1 ? vibeIndex : 0; - SimplyPebble.sendMessage(message); -}; - SimplyPebble.accelConfig = function(configDef) { var command = commandMap.configAccelData; var message = makeMessage(command, configDef); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 8b597fe0..384c1009 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -31,6 +31,7 @@ enum Command { CommandCardText, CommandCardImage, CommandCardStyle, + CommandVibe, }; typedef enum WindowType WindowType; @@ -42,6 +43,14 @@ enum WindowType { WindowTypeLast, }; +typedef enum VibeType VibeType; + +enum VibeType { + VibeShort = 0, + VibeLong = 1, + VibeDouble = 2, +}; + typedef struct Packet Packet; struct __attribute__((__packed__)) Packet { @@ -113,6 +122,13 @@ struct __attribute__((__packed__)) CardStylePacket { uint8_t style; }; +typedef struct VibePacket VibePacket; + +struct __attribute__((__packed__)) VibePacket { + Packet packet; + VibeType type:8; +}; + typedef enum SimplyACmd SimplyACmd; enum SimplyACmd { @@ -163,14 +179,6 @@ enum SimplySetMenuParam { SetMenu_sections, }; -typedef enum VibeType VibeType; - -enum VibeType { - VibeShort = 0, - VibeLong = 1, - VibeDouble = 2, -}; - typedef enum ElementParam ElementParam; enum ElementParam { @@ -209,17 +217,6 @@ static SimplyWindow *get_top_simply_window(Simply *simply) { return window; } -static void handle_vibe(DictionaryIterator *iter, Simply *simply) { - Tuple *tuple; - if ((tuple = dict_find(iter, 1))) { - switch ((VibeType) tuple->value->int32) { - case VibeShort: vibes_short_pulse(); break; - case VibeLong: vibes_short_pulse(); break; - case VibeDouble: vibes_double_pulse(); break; - } - } -} - static void handle_config_buttons(DictionaryIterator *iter, Simply *simply) { SimplyWindow *window = get_top_simply_window(simply); if (!window) { @@ -569,6 +566,15 @@ static void handle_card_style_packet(Simply *simply, Packet *data) { simply_ui_set_style(simply->ui, packet->style); } +static void handle_vibe_packet(Simply *simply, Packet *data) { + VibePacket *packet = (VibePacket*) data; + switch (packet->type) { + case VibeShort: vibes_short_pulse(); break; + case VibeLong: vibes_short_pulse(); break; + case VibeDouble: vibes_double_pulse(); break; + } +} + static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { Packet *packet = (Packet*) buffer; switch (packet->type) { @@ -596,6 +602,9 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandCardStyle: handle_card_style_packet(simply, packet); break; + case CommandVibe: + handle_vibe_packet(simply, packet); + break; } } @@ -612,15 +621,6 @@ static void received_callback(DictionaryIterator *iter, void *context) { } switch (tuple->value->uint8) { - case SimplyACmd_setWindow: - break; - case SimplyACmd_windowHide: - break; - case SimplyACmd_setUi: - break; - case SimplyACmd_vibe: - handle_vibe(iter, context); - break; case SimplyACmd_getAccelData: handle_get_accel_data(iter, context); break; From 4e8b936c4c6ca082b48354520689d19453f609d0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 04:08:13 -0700 Subject: [PATCH 340/791] Convert accel peek and accel config messages to packets --- src/js/ui/accel.js | 4 +-- src/js/ui/simply-pebble.js | 39 ++++++++++++-------- src/simply/simply_accel.c | 15 -------- src/simply/simply_accel.h | 6 ++-- src/simply/simply_msg.c | 73 +++++++++++++++++++++----------------- 5 files changed, 68 insertions(+), 69 deletions(-) diff --git a/src/js/ui/accel.js b/src/js/ui/accel.js index 5612e2a8..44e4d922 100644 --- a/src/js/ui/accel.js +++ b/src/js/ui/accel.js @@ -70,7 +70,7 @@ Accel.autoSubscribe = function() { * @param {simply.accelConfig} accelConf - An object defining the accelerometer configuration. */ Accel.config = function(opt, auto) { - if (typeof opt === 'undefined') { + if (arguments.length === 0) { return { rate: state.rate, samples: state.samples, @@ -85,7 +85,7 @@ Accel.config = function(opt, auto) { } state[k] = opt[k]; } - return simply.impl.accelConfig.apply(this, arguments); + return simply.impl.accelConfig(Accel.config()); }; /** diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 28abf1a1..a2368a39 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -221,6 +221,17 @@ var VibePacket = new struct([ ['uint8', 'type', VibeType], ]); +var AccelPeekPacket = new struct([ + [Packet, 'packet'], +]); + +var AccelConfigPacket = new struct([ + [Packet, 'packet'], + ['uint16', 'samples'], + ['uint8', 'rate'], + ['bool', 'subscribe', BoolType], +]); + var CommandPackets = [ Packet, WindowShowPacket, @@ -232,6 +243,8 @@ var CommandPackets = [ CardImagePacket, CardStylePacket, VibePacket, + AccelPeekPacket, + AccelConfigPacket, ]; var setMenuParams = [{ @@ -696,6 +709,17 @@ SimplyPebble.vibe = function(type) { SimplyPebble.sendPacket(VibePacket.type(type)); }; +var accelListeners = []; + +SimplyPebble.accelPeek = function(callback) { + accelListeners.push(callback); + SimplyPebble.sendPacket(AccelPeekPacket); +}; + +SimplyPebble.accelConfig = function(def) { + SimplyPebble.sendPacket(setPacket(AccelConfigPacket, def)); +}; + SimplyPebble.buttonConfig = function(buttonConf) { var command = commandMap.configButtons; var message = makeMessage(command, buttonConf); @@ -712,21 +736,6 @@ var setActionMessage = function(message, command, actionDef) { return message; }; -SimplyPebble.accelConfig = function(configDef) { - var command = commandMap.configAccelData; - var message = makeMessage(command, configDef); - SimplyPebble.sendMessage(message); -}; - -var accelListeners = []; - -SimplyPebble.accelPeek = function(callback) { - accelListeners.push(callback); - var command = commandMap.getAccelData; - var message = makeMessage(command); - SimplyPebble.sendMessage(message); -}; - SimplyPebble.menu = function(menuDef, clear, pushing) { if (arguments.length === 3) { SimplyPebble.windowShow({ type: 'menu', pushing: pushing }); diff --git a/src/simply/simply_accel.c b/src/simply/simply_accel.c index 31d47dd1..6ce9e9fb 100644 --- a/src/simply/simply_accel.c +++ b/src/simply/simply_accel.c @@ -12,21 +12,6 @@ static void handle_accel_data(AccelData *data, uint32_t num_samples) { simply_msg_accel_data(s_accel->simply->msg, data, num_samples, TRANSACTION_ID_INVALID); } -void simply_accel_set_data_rate(SimplyAccel *self, AccelSamplingRate rate) { - self->rate = rate; - accel_service_set_sampling_rate(rate); -} - -void simply_accel_set_data_samples(SimplyAccel *self, uint32_t num_samples) { - self->num_samples = num_samples; - accel_service_set_samples_per_update(num_samples); - if (!self->data_subscribed) { - return; - } - simply_accel_set_data_subscribe(self, false); - simply_accel_set_data_subscribe(self, true); -} - void simply_accel_set_data_subscribe(SimplyAccel *self, bool subscribe) { if (self->data_subscribed == subscribe) { return; diff --git a/src/simply/simply_accel.h b/src/simply/simply_accel.h index 525c4028..ce529533 100644 --- a/src/simply/simply_accel.h +++ b/src/simply/simply_accel.h @@ -8,16 +8,14 @@ typedef struct SimplyAccel SimplyAccel; struct SimplyAccel { Simply *simply; + uint16_t num_samples; + AccelSamplingRate rate:8; bool data_subscribed; - AccelSamplingRate rate; - uint32_t num_samples; }; SimplyAccel *simply_accel_create(Simply *simply); void simply_accel_destroy(SimplyAccel *self); -void simply_accel_set_data_rate(SimplyAccel *self, AccelSamplingRate rate); -void simply_accel_set_data_samples(SimplyAccel *self, uint32_t num_samples); void simply_accel_set_data_subscribe(SimplyAccel *self, bool subscribe); void simply_accel_peek(SimplyAccel *self, AccelData *data); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 384c1009..4e6334fb 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -32,6 +32,8 @@ enum Command { CommandCardImage, CommandCardStyle, CommandVibe, + CommandAccelPeek, + CommandAccelConfig, }; typedef enum WindowType WindowType; @@ -129,6 +131,17 @@ struct __attribute__((__packed__)) VibePacket { VibeType type:8; }; +typedef Packet AccelPeekPacket; + +typedef struct AccelConfigPacket AccelConfigPacket; + +struct __attribute__((__packed__)) AccelConfigPacket { + Packet packet; + uint16_t num_samples; + AccelSamplingRate rate:8; + bool data_subscribed; +}; + typedef enum SimplyACmd SimplyACmd; enum SimplyACmd { @@ -230,32 +243,6 @@ static void handle_config_buttons(DictionaryIterator *iter, Simply *simply) { } } -static void get_accel_data_timer_callback(void *context) { - Simply *simply = context; - AccelData data = { .x = 0 }; - simply_accel_peek(simply->accel, &data); - if (!simply_msg_accel_data(simply->msg, &data, 1, 0)) { - app_timer_register(10, get_accel_data_timer_callback, simply); - } -} - -static void handle_get_accel_data(DictionaryIterator *iter, Simply *simply) { - app_timer_register(10, get_accel_data_timer_callback, simply); -} - -static void handle_set_accel_config(DictionaryIterator *iter, Simply *simply) { - Tuple *tuple; - if ((tuple = dict_find(iter, 1))) { - simply_accel_set_data_rate(simply->accel, tuple->value->int32); - } - if ((tuple = dict_find(iter, 2))) { - simply_accel_set_data_samples(simply->accel, tuple->value->int32); - } - if ((tuple = dict_find(iter, 3))) { - simply_accel_set_data_subscribe(simply->accel, tuple->value->int32); - } -} - static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { SimplyMenu *menu = simply->menu; Tuple *tuple; @@ -575,6 +562,26 @@ static void handle_vibe_packet(Simply *simply, Packet *data) { } } +static void accel_peek_timer_callback(void *context) { + Simply *simply = context; + AccelData data = { .x = 0 }; + simply_accel_peek(simply->accel, &data); + if (!simply_msg_accel_data(simply->msg, &data, 1, 0)) { + app_timer_register(10, accel_peek_timer_callback, simply); + } +} + +static void handle_accel_peek_packet(Simply *simply, Packet *data) { + app_timer_register(10, accel_peek_timer_callback, simply); +} + +static void handle_accel_config_packet(Simply *simply, Packet *data) { + AccelConfigPacket *packet = (AccelConfigPacket*) data; + simply->accel->num_samples = packet->num_samples; + simply->accel->rate = packet->rate; + simply_accel_set_data_subscribe(simply->accel, packet->data_subscribed); +} + static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { Packet *packet = (Packet*) buffer; switch (packet->type) { @@ -605,6 +612,12 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandVibe: handle_vibe_packet(simply, packet); break; + case CommandAccelPeek: + handle_accel_peek_packet(simply, packet); + break; + case CommandAccelConfig: + handle_accel_config_packet(simply, packet); + break; } } @@ -616,17 +629,11 @@ static void received_callback(DictionaryIterator *iter, void *context) { s_has_communicated = true; - if (tuple->length > sizeof(Packet)) { + if (tuple->value->uint32 > 0xFFFF) { handle_packet(context, tuple->value->data, tuple->length); } switch (tuple->value->uint8) { - case SimplyACmd_getAccelData: - handle_get_accel_data(iter, context); - break; - case SimplyACmd_configAccelData: - handle_set_accel_config(iter, context); - break; case SimplyACmd_configButtons: handle_config_buttons(iter, context); break; From 7cb3caad6ff53aed5ed2c985187645b0a3435c20 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 05:55:02 -0700 Subject: [PATCH 341/791] Convert menu messages to packets --- src/js/ui/menu.js | 9 +- src/js/ui/simply-pebble.js | 214 ++++++++++++++++++--------------- src/simply/simply_msg.c | 238 ++++++++++++++++++++----------------- 3 files changed, 256 insertions(+), 205 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 117246e9..53aa115e 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -26,7 +26,11 @@ Menu.prototype._show = function() { Menu.prototype._prop = function(state, clear, pushing) { if (this === WindowStack.top()) { - simply.impl.menu.call(this, state, clear, pushing, this._selection); + simply.impl.menu.call(this, state, clear, pushing); + var select = this._selection; + if (this._resolveSection(select)) { + simply.impl.menuSelection(select.section, select.item); + } } }; @@ -133,6 +137,7 @@ Menu.prototype._resolveMenu = function() { var sections = getSections.call(this); if (this === WindowStack.top()) { simply.impl.menu.call(this, this.state); + return true; } }; @@ -142,6 +147,7 @@ Menu.prototype._resolveSection = function(e, clear) { section.items = getItems.call(this, e); if (this === WindowStack.top()) { simply.impl.menuSection.call(this, e.section, section, clear); + return true; } }; @@ -150,6 +156,7 @@ Menu.prototype._resolveItem = function(e) { if (!item) { return; } if (this === WindowStack.top()) { simply.impl.menuItem.call(this, e.section, e.item, item); + return true; } }; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index abbac99b..ff680723 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -24,11 +24,22 @@ var BoolType = function(x) { return x ? 1 : 0; }; +var StringType = function(x) { + return x || ''; +}; + +var EnumerableType = function(x) { + if (x && x.hasOwnProperty('length')) { + return x.length; + } + return x ? Number(x) : 0; +}; + var ImageType = function(x) { - if (typeof x !== 'number') { + if (x && typeof x !== 'number') { return ImageService.resolve(x); } - return x; + return x ? Number(x) : 0; }; var Color = function(x) { @@ -241,6 +252,51 @@ var AccelConfigPacket = new struct([ ['bool', 'subscribe', BoolType], ]); +var MenuClearPacket = new struct([ + [Packet, 'packet'], +]); + +var MenuClearSectionPacket = new struct([ + [Packet, 'packet'], + ['uint16', 'section'], +]); + +var MenuPropsPacket = new struct([ + [Packet, 'packet'], + ['uint16', 'sections', EnumerableType], +]); + +var MenuSectionPacket = new struct([ + [Packet, 'packet'], + ['uint16', 'section'], + ['uint16', 'items', EnumerableType], + ['uint16', 'titleLength', EnumerableType], + ['cstring', 'title', StringType], +]); + +var MenuItemPacket = new struct([ + [Packet, 'packet'], + ['uint16', 'section'], + ['uint16', 'item'], + ['uint32', 'icon', ImageType], + ['uint16', 'titleLength', EnumerableType], + ['uint16', 'subtitleLength', EnumerableType], + ['cstring', 'title', StringType], + ['cstring', 'subtitle', StringType], +]); + +var MenuGetSelectionPacket = new struct([ + [Packet, 'packet'], +]); + +var MenuSetSelectionPacket = new struct([ + [Packet, 'packet'], + ['uint16', 'section'], + ['uint16', 'item'], + ['uint8', 'align', MenuRowAlign], + ['bool', 'animated', BoolType], +]); + var CommandPackets = [ Packet, WindowShowPacket, @@ -254,6 +310,13 @@ var CommandPackets = [ VibePacket, AccelPeekPacket, AccelConfigPacket, + MenuClearPacket, + MenuClearSectionPacket, + MenuPropsPacket, + MenuSectionPacket, + MenuItemPacket, + MenuGetSelectionPacket, + MenuSetSelectionPacket, ]; var setMenuParams = [{ @@ -320,18 +383,8 @@ var commands = [{ }], }, { name: 'getAccelData', - params: [{ - name: 'transactionId', - }], }, { name: 'configAccelData', - params: [{ - name: 'rate', - }, { - name: 'samples', - }, { - name: 'subscribe', - }], }, { name: 'configButtons', params: [{ @@ -345,20 +398,8 @@ var commands = [{ }], }, { name: 'setMenu', - params: setMenuParams, }, { name: 'setMenuSection', - params: [{ - name: 'clear', - type: Boolean, - }, { - name: 'section', - }, { - name: 'items', - }, { - name: 'title', - type: String, - }], }, { name: 'getMenuSection', params: [{ @@ -366,20 +407,6 @@ var commands = [{ }], }, { name: 'setMenuItem', - params: [{ - name: 'section', - }, { - name: 'item', - }, { - name: 'title', - type: String, - }, { - name: 'subtitle', - type: String, - }, { - name: 'icon', - type: ImageType, - }], }, { name: 'getMenuItem', params: [{ @@ -403,12 +430,6 @@ var commands = [{ }], }, { name: 'menuSelection', - params: [{ - name: 'section', - }, { - name: 'item', - }], -}, { }, { name: 'image', params: [{ @@ -756,71 +777,74 @@ SimplyPebble.accelConfig = function(def) { SimplyPebble.sendPacket(setPacket(AccelConfigPacket, def)); }; -SimplyPebble.buttonConfig = function(buttonConf) { - var command = commandMap.configButtons; - var message = makeMessage(command, buttonConf); - SimplyPebble.sendMessage(message); +SimplyPebble.menuClear = function() { + SimplyPebble.sendPacket(MenuClearPacket); }; -var setActionMessage = function(message, command, actionDef) { - if (actionDef) { - if (typeof actionDef === 'boolean') { - actionDef = { action: actionDef }; - } - setMessage(message, command, actionDef, actionBarTypeMap); - } - return message; +SimplyPebble.menuClearSection = function(section) { + SimplyPebble.sendPacket(MenuClearSectionPacket.section(section)); }; -SimplyPebble.menu = function(menuDef, clear, pushing, selection) { - if (arguments.length === 3) { - SimplyPebble.windowShow({ type: 'menu', pushing: pushing }); - } - SimplyPebble.windowProps(menuDef); - var command = commandMap.setMenu; - var messageDef = util2.copy(menuDef); - if (messageDef.sections instanceof Array) { - messageDef.sections = messageDef.sections.length; - } - if (!messageDef.sections) { - messageDef.sections = 1; - } - var message = makeMessage(command, messageDef); - if (clear) { - clear = toClearFlags(clear); - message[command.paramMap.clear.id] = clear; +SimplyPebble.menuProps = function(def) { + SimplyPebble.sendPacket(setPacket(MenuPropsPacket, def)); +}; + +SimplyPebble.menuSection = function(section, def, clear) { + if (clear !== undefined) { + SimplyPebble.menuClearSection(section); } - if (selection) { - setMessage(packet, command, selection, menuSelectionTypeMap); + MenuSectionPacket + .section(section) + .items(def.items) + .titleLength(def.title) + .title(def.title); + SimplyPebble.sendPacket(MenuSectionPacket); +}; + +SimplyPebble.menuItem = function(section, item, def) { + MenuItemPacket + .section(section) + .item(item) + .icon(def.icon) + .titleLength(def.title) + .subtitleLength(def.subtitle) + .title(def.title) + .subtitle(def.subtitle); + SimplyPebble.sendPacket(MenuItemPacket); +}; + +SimplyPebble.menuSelection = function(section, item) { + if (arguments.length === 0) { + SimplyPebble.sendPacket(MenuGetSelectionPacket); } - SimplyPebble.sendMessage(message); + SimplyPebble.sendPacket(MenuSetSelectionPacket.section(section).item(item)); }; -SimplyPebble.menuSection = function(sectionIndex, sectionDef, clear) { - var command = commandMap.setMenuSection; - var messageDef = util2.copy(sectionDef); - messageDef.section = sectionIndex; - if (messageDef.items instanceof Array) { - messageDef.items = messageDef.items.length; +SimplyPebble.menu = function(def, clear, pushing) { + if (arguments.length === 3) { + SimplyPebble.windowShow({ type: 'menu', pushing: pushing }); } - messageDef.clear = clear; - var message = makeMessage(command, messageDef); - SimplyPebble.sendMessage(message); + if (clear !== undefined) { + SimplyPebble.menuClear(); + } + SimplyPebble.windowProps(def); + SimplyPebble.menuProps(def); }; -SimplyPebble.menuItem = function(sectionIndex, itemIndex, itemDef) { - var command = commandMap.setMenuItem; - var messageDef = util2.copy(itemDef); - messageDef.section = sectionIndex; - messageDef.item = itemIndex; - var message = makeMessage(command, messageDef); +SimplyPebble.buttonConfig = function(buttonConf) { + var command = commandMap.configButtons; + var message = makeMessage(command, buttonConf); SimplyPebble.sendMessage(message); }; -SimplyPebble.menuSelection = function() { - var command = commandMap.menuSelection; - var packet = makePacket(command); - SimplyPebble.sendPacket(packet); +var setActionMessage = function(message, command, actionDef) { + if (actionDef) { + if (typeof actionDef === 'boolean') { + actionDef = { action: actionDef }; + } + setMessage(message, command, actionDef, actionBarTypeMap); + } + return message; }; SimplyPebble.image = function(id, gbitmap) { diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index a623fc21..6459da89 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -34,6 +34,13 @@ enum Command { CommandVibe, CommandAccelPeek, CommandAccelConfig, + CommandMenuClear, + CommandMenuClearSection, + CommandMenuProps, + CommandMenuSection, + CommandMenuItem, + CommandMenuGetSelection, + CommandMenuSetSelection, }; typedef enum WindowType WindowType; @@ -142,6 +149,56 @@ struct __attribute__((__packed__)) AccelConfigPacket { bool data_subscribed; }; +typedef Packet MenuClearPacket; + +typedef struct MenuClearSectionPacket MenuClearSectionPacket; + +struct __attribute__((__packed__)) MenuClearSectionPacket { + Packet packet; + uint16_t section; +}; + +typedef struct MenuPropsPacket MenuPropsPacket; + +struct __attribute__((__packed__)) MenuPropsPacket { + Packet packet; + uint16_t num_sections; +}; + +typedef struct MenuSectionPacket MenuSectionPacket; + +struct __attribute__((__packed__)) MenuSectionPacket { + Packet packet; + uint16_t section; + uint16_t num_items; + uint16_t title_length; + char title[]; +}; + +typedef struct MenuItemPacket MenuItemPacket; + +struct __attribute__((__packed__)) MenuItemPacket { + Packet packet; + uint16_t section; + uint16_t item; + uint32_t icon; + uint16_t title_length; + uint16_t subtitle_length; + char buffer[]; +}; + +typedef Packet MenuGetSelectionPacket; + +typedef struct MenuSetSelectionPacket MenuSetSelectionPacket; + +struct __attribute__((__packed__)) MenuSetSelectionPacket { + Packet packet; + uint16_t section; + uint16_t item; + MenuRowAlign align:8; + bool animated; +}; + typedef enum SimplyACmd SimplyACmd; enum SimplyACmd { @@ -248,104 +305,6 @@ static void handle_config_buttons(DictionaryIterator *iter, Simply *simply) { } } -static void handle_set_menu(DictionaryIterator *iter, Simply *simply) { - SimplyMenu *menu = simply->menu; - Tuple *tuple; - bool is_selection = false; - MenuIndex menu_index = { 0, 0 }; - MenuRowAlign align = MenuRowAlignCenter; - bool selection_animated = true; - for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { - switch (tuple->key) { - case SetMenu_clear: - simply_menu_clear(menu); - break; - case SetMenu_sections: - simply_menu_set_num_sections(menu, tuple->value->int32); - break; - case SetMenu_selectionSection: - menu_index.section = tuple->value->uint16; - break; - case SetMenu_selectionItem: - is_selection = true; - menu_index.row = tuple->value->uint16; - break; - case SetMenu_selectionAlign: - align = tuple->value->uint8; - break; - case SetMenu_selectionAnimated: - selection_animated = tuple->value->uint8; - break; - } - } - if (is_selection) { - simply_menu_set_selection(menu, menu_index, align, selection_animated); - } -} - -static void handle_set_menu_section(DictionaryIterator *iter, Simply *simply) { - Tuple *tuple; - uint16_t section_index = 0; - uint16_t num_items = 1; - char *title = NULL; - if ((tuple = dict_find(iter, 2))) { - section_index = tuple->value->uint16; - } - if ((tuple = dict_find(iter, 3))) { - num_items = tuple->value->uint16; - } - if ((tuple = dict_find(iter, 4))) { - title = tuple->value->cstring; - } - if ((tuple = dict_find(iter, 1))) { - simply_menu_clear_section_items(simply->menu, section_index); - } - SimplyMenuSection *section = malloc(sizeof(*section)); - *section = (SimplyMenuSection) { - .section = section_index, - .num_items = num_items, - .title = strdup2(title), - }; - simply_menu_add_section(simply->menu, section); -} - -static void handle_set_menu_item(DictionaryIterator *iter, Simply *simply) { - Tuple *tuple; - uint16_t section_index = 0; - uint16_t row = 0; - uint32_t icon = 0; - char *title = NULL; - char *subtitle = NULL; - if ((tuple = dict_find(iter, 1))) { - section_index = tuple->value->uint16; - } - if ((tuple = dict_find(iter, 2))) { - row = tuple->value->uint16; - } - if ((tuple = dict_find(iter, 3))) { - title = tuple->value->cstring; - } - if ((tuple = dict_find(iter, 4))) { - subtitle = tuple->value->cstring; - } - if ((tuple = dict_find(iter, 5))) { - icon = tuple->value->uint32; - } - SimplyMenuItem *item = malloc(sizeof(*item)); - *item = (SimplyMenuItem) { - .section = section_index, - .item = row, - .title = strdup2(title), - .subtitle = strdup2(subtitle), - .icon = icon, - }; - simply_menu_add_item(simply->menu, item); -} - -static void handle_get_menu_selection(DictionaryIterator *iter, Simply *simply) { - simply_msg_send_menu_selection(simply->msg); -} - static void handle_set_image(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; uint32_t id = 0; @@ -611,6 +570,57 @@ static void handle_accel_config_packet(Simply *simply, Packet *data) { simply_accel_set_data_subscribe(simply->accel, packet->data_subscribed); } +static void handle_menu_clear_packet(Simply *simply, Packet *data) { + simply_menu_clear(simply->menu); +} + +static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { + MenuClearSectionPacket *packet = (MenuClearSectionPacket*) data; + simply_menu_clear_section_items(simply->menu, packet->section); +} + +static void handle_menu_props_packet(Simply *simply, Packet *data) { + MenuPropsPacket *packet = (MenuPropsPacket*) data; + simply_menu_set_num_sections(simply->menu, packet->num_sections); +} + +static void handle_menu_section_packet(Simply *simply, Packet *data) { + MenuSectionPacket *packet = (MenuSectionPacket*) data; + SimplyMenuSection *section = malloc(sizeof(*section)); + *section = (SimplyMenuSection) { + .section = packet->section, + .num_items = packet->num_items, + .title = packet->title_length ? strdup2(packet->title) : NULL, + }; + simply_menu_add_section(simply->menu, section); +} + +static void handle_menu_item_packet(Simply *simply, Packet *data) { + MenuItemPacket *packet = (MenuItemPacket*) data; + SimplyMenuItem *item = malloc(sizeof(*item)); + *item = (SimplyMenuItem) { + .section = packet->section, + .item = packet->item, + .title = packet->title_length ? strdup2(packet->buffer) : NULL, + .subtitle = packet->subtitle_length ? strdup2(packet->buffer + packet->title_length + 1) : NULL, + .icon = packet->icon, + }; + simply_menu_add_item(simply->menu, item); +} + +static void handle_menu_get_selection_packet(Simply *simply, Packet *data) { + simply_msg_send_menu_selection(simply->msg); +} + +static void handle_menu_set_selection_packet(Simply *simply, Packet *data) { + MenuSetSelectionPacket *packet = (MenuSetSelectionPacket*) data; + MenuIndex menu_index = { + .section = packet->section, + .row = packet->item, + }; + simply_menu_set_selection(simply->menu, menu_index, packet->align, packet->animated); +} + static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { Packet *packet = (Packet*) buffer; switch (packet->type) { @@ -647,6 +657,27 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandAccelConfig: handle_accel_config_packet(simply, packet); break; + case CommandMenuClear: + handle_menu_clear_packet(simply, packet); + break; + case CommandMenuClearSection: + handle_menu_clear_section_packet(simply, packet); + break; + case CommandMenuProps: + handle_menu_props_packet(simply, packet); + break; + case CommandMenuSection: + handle_menu_section_packet(simply, packet); + break; + case CommandMenuItem: + handle_menu_item_packet(simply, packet); + break; + case CommandMenuGetSelection: + handle_menu_get_selection_packet(simply, packet); + break; + case CommandMenuSetSelection: + handle_menu_set_selection_packet(simply, packet); + break; } } @@ -666,17 +697,6 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_configButtons: handle_config_buttons(iter, context); break; - case SimplyACmd_setMenu: - handle_set_menu(iter, context); - break; - case SimplyACmd_setMenuSection: - handle_set_menu_section(iter, context); - break; - case SimplyACmd_setMenuItem: - handle_set_menu_item(iter, context); - break; - case SimplyACmd_menuSelection: - handle_get_menu_selection(iter, context); case SimplyACmd_image: handle_set_image(iter, context); break; From 9972a4d8588bcba73c2b7312102f390c34606c15 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 06:50:06 -0700 Subject: [PATCH 342/791] Convert button config message to packet --- src/js/ui/menu.js | 2 -- src/js/ui/simply-pebble.js | 69 ++++++++++++++++++++------------------ src/js/ui/window.js | 33 +++++++++--------- src/simply/simply_msg.c | 36 +++++++++++--------- 4 files changed, 74 insertions(+), 66 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 53aa115e..08c56ae4 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -38,8 +38,6 @@ Menu.prototype.action = function() { throw new Error("Menus don't support action bars."); }; -Menu.prototype._buttonInit = function() {}; - Menu.prototype.buttonConfig = function() { throw new Error("Menus don't support changing button configurations."); }; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index ff680723..1759d1a2 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -142,6 +142,20 @@ var makeArrayType = function(types) { }; }; +var makeFlagsType = function(types) { + return function(x) { + var z = 0; + for (var k in x) { + if (!x[k]) { continue; } + var index = types.indexOf(k); + if (index !== -1) { + z |= 1 << index; + } + } + return z; + }; +}; + var windowTypes = [ 'window', 'menu', @@ -150,6 +164,17 @@ var windowTypes = [ var WindowType = makeArrayType(windowTypes); +var buttonTypes = [ + 'back', + 'up', + 'select', + 'down', +]; + +var ButtonType = makeArrayType(buttonTypes); + +var ButtonFlagsType = makeFlagsType(buttonTypes); + var cardTextTypes = [ 'title', 'subtitle', @@ -206,6 +231,11 @@ var WindowPropsPacket = new struct([ ['bool', 'scrollable', BoolType], ]); +var WindowButtonConfigPacket = new struct([ + [Packet, 'packet'], + ['uint8', 'buttonMask', ButtonFlagsType], +]); + var WindowActionBarPacket = new struct([ [Packet, 'packet'], ['uint32', 'up', ImageType], @@ -302,6 +332,7 @@ var CommandPackets = [ WindowShowPacket, WindowHidePacket, WindowPropsPacket, + WindowButtonConfigPacket, WindowActionBarPacket, CardClearPacket, CardTextPacket, @@ -319,25 +350,6 @@ var CommandPackets = [ MenuSetSelectionPacket, ]; -var setMenuParams = [{ - name: 'clear', -}, { - name: 'sections', - type: Number, -}, { - name: 'selectionSection', - type: Number, -}, { - name: 'selectionItem', - type: Number, -}, { - name: 'selectionAlign', - type: MenuRowAlign, -}, { - name: 'selectionAnimated', - type: Boolean, -}]; - var setStageParams = [{ name: 'clear', }]; @@ -545,13 +557,6 @@ for (var i = 0, ii = commands.length; i < ii; ++i) { } } -var buttons = [ - 'back', - 'up', - 'select', - 'down', -]; - var accelAxes = [ 'x', 'y', @@ -691,6 +696,10 @@ SimplyPebble.windowProps = function(def) { SimplyPebble.sendPacket(setPacket(WindowPropsPacket, def)); }; +SimplyPebble.windowButtonConfig = function(def) { + SimplyPebble.sendPacket(WindowButtonConfigPacket.buttonMask(def)); +}; + var toActionDef = function(actionDef) { if (typeof actionDef === 'boolean') { actionDef = { action: actionDef }; @@ -831,12 +840,6 @@ SimplyPebble.menu = function(def, clear, pushing) { SimplyPebble.menuProps(def); }; -SimplyPebble.buttonConfig = function(buttonConf) { - var command = commandMap.configButtons; - var message = makeMessage(command, buttonConf); - SimplyPebble.sendMessage(message); -}; - var setActionMessage = function(message, command, actionDef) { if (actionDef) { if (typeof actionDef === 'boolean') { @@ -950,7 +953,7 @@ SimplyPebble.onAppMessage = function(e) { break; case 'click': case 'longClick': - var button = buttons[payload[1]]; + var button = buttonTypes[payload[1]]; Window.emitClick(command.name, button); break; case 'accelTap': diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 033c1f9c..fcb7043f 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -87,6 +87,7 @@ Window.prototype.hide = function() { Window.prototype._show = function(pushing) { this._prop(this.state, true, pushing); + this._buttonConfig({}); if (this._dynamic) { Stage.prototype._show.call(this, pushing); } @@ -192,14 +193,14 @@ Window.prototype.onRemoveHandler = function(type, subtype) { }; Window.prototype._buttonInit = function() { - this.state.button = { + this._button = { config: {}, configMode: 'auto', }; for (var i = 0, ii = buttons.length; i < ii; i++) { var button = buttons[i]; if (button !== 'back') { - this.state.button.config[buttons[i]] = true; + this._button.config[buttons[i]] = true; } } }; @@ -221,40 +222,42 @@ Window.prototype._buttonInit = function() { * @memberOf simply * @param {simply.buttonConfig} buttonConf - An object defining the button configuration. */ -Window.prototype.buttonConfig = function(buttonConf, auto) { - var buttonState = this.state.button; - if (typeof buttonConf === 'undefined') { +Window.prototype._buttonConfig = function(buttonConf, auto) { + if (buttonConf === undefined) { var config = {}; for (var i = 0, ii = buttons.length; i < ii; ++i) { var name = buttons[i]; - config[name] = buttonConf.config[name]; + config[name] = this._button.config[name]; } return config; } for (var k in buttonConf) { if (buttons.indexOf(k) !== -1) { if (k === 'back') { - buttonState.configMode = buttonConf.back && !auto ? 'manual' : 'auto'; + this._button.configMode = buttonConf.back && !auto ? 'manual' : 'auto'; } - buttonState.config[k] = buttonConf[k]; + this._button.config[k] = buttonConf[k]; } } - if (simply.impl.buttonConfig) { - return simply.impl.buttonConfig(buttonState.config); + if (simply.impl.windowButtonConfig) { + return simply.impl.windowButtonConfig(this._button.config); } }; +Window.prototype.buttonConfig = function(buttonConf) { + this._buttonConfig(buttonConf); +}; + Window.prototype._buttonAutoConfig = function() { - var buttonState = this.state.button; - if (!buttonState || buttonState.configMode !== 'auto') { + if (!this._button || this._button.configMode !== 'auto') { return; } var singleBackCount = this.listenerCount('click', 'back'); var longBackCount = this.listenerCount('longClick', 'back'); var useBack = singleBackCount + longBackCount > 0; - if (useBack !== buttonState.config.back) { - buttonState.config.back = useBack; - return this.buttonConfig(buttonState.config, true); + if (useBack !== this._button.config.back) { + this._button.config.back = useBack; + return this._buttonConfig(this._button.config, true); } }; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 6459da89..1376da2d 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -26,6 +26,7 @@ enum Command { CommandWindowShow = 1, CommandWindowHide, CommandWindowProps, + CommandWindowButtonConfig, CommandWindowActionBar, CommandCardClear, CommandCardText, @@ -92,6 +93,13 @@ struct __attribute__((__packed__)) WindowPropsPacket { bool scrollable; }; +typedef struct WindowButtonConfigPacket WindowButtonConfigPacket; + +struct __attribute__((__packed__)) WindowButtonConfigPacket { + Packet packet; + uint8_t button_mask; +}; + typedef struct WindowActionBarPacket WindowActionBarPacket; struct __attribute__((__packed__)) WindowActionBarPacket { @@ -292,19 +300,6 @@ static SimplyWindow *get_top_simply_window(Simply *simply) { return window; } -static void handle_config_buttons(DictionaryIterator *iter, Simply *simply) { - SimplyWindow *window = get_top_simply_window(simply); - if (!window) { - return; - } - Tuple *tuple; - for (int i = 0; i < NUM_BUTTONS; ++i) { - if ((tuple = dict_find(iter, i + 1))) { - simply_window_set_button(window, i, tuple->value->int32); - } - } -} - static void handle_set_image(DictionaryIterator *iter, Simply *simply) { Tuple *tuple; uint32_t id = 0; @@ -507,6 +502,15 @@ static void handle_window_props_packet(Simply *simply, Packet *data) { simply_window_set_scrollable(window, packet->scrollable); } +static void handle_window_button_config_packet(Simply *simply, Packet *data) { + WindowButtonConfigPacket *packet = (WindowButtonConfigPacket*) data; + SimplyWindow *window = get_top_simply_window(simply); + if (!window) { + return; + } + window->button_mask = packet->button_mask; +} + static void handle_window_action_bar_packet(Simply *simply, Packet *data) { WindowActionBarPacket *packet = (WindowActionBarPacket*) data; SimplyWindow *window = get_top_simply_window(simply); @@ -633,6 +637,9 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandWindowProps: handle_window_props_packet(simply, packet); break; + case CommandWindowButtonConfig: + handle_window_button_config_packet(simply, packet); + break; case CommandWindowActionBar: handle_window_action_bar_packet(simply, packet); break; @@ -694,9 +701,6 @@ static void received_callback(DictionaryIterator *iter, void *context) { } switch (tuple->value->uint8) { - case SimplyACmd_configButtons: - handle_config_buttons(iter, context); - break; case SimplyACmd_image: handle_set_image(iter, context); break; From e291064e3718b15f6457b0658c4c9f0e0df94887 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 09:13:23 -0700 Subject: [PATCH 343/791] Change struct.js prop to set fields in definition order --- src/js/lib/struct.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/js/lib/struct.js b/src/js/lib/struct.js index 1e79f1d2..9953c49e 100644 --- a/src/js/lib/struct.js +++ b/src/js/lib/struct.js @@ -14,7 +14,7 @@ var struct = function(def) { this._offset = 0; this._cursor = 0; this._makeAccessors(def); - this._view = new DataView(new ArrayBuffer(this.size)); + this._view = new DataView(new ArrayBuffer(this._size)); this._def = def; }; @@ -107,7 +107,7 @@ struct.prototype._makeAccessor = function(field) { result = type.get.call(this, this._cursor, this._littleEndian); } else { if (field.transform) { - value = field.transform(value); + value = field.transform(value, field); } type.set.call(this, this._cursor, value, this._littleEndian); } @@ -133,7 +133,7 @@ struct.prototype._makeAccessors = function(def, index, fields, prefix) { } if (type instanceof struct) { this._makeAccessors(type._def, index, fields, name); - index = this.size; + index = this._size; continue; } var transform = member[2]; @@ -149,22 +149,26 @@ struct.prototype._makeAccessors = function(def, index, fields, prefix) { index += type.size; prevField = field; } - this.size = index; + this._size = index; return this; }; struct.prototype.prop = function(def) { + var fields = this._fields; + var i = 0, ii = fields.length, name; if (arguments.length === 0) { var obj = {}; - var fields = this._fields; - for (var i = 0, ii = fields.length; i < ii; ++i) { - var name = fields[i].name; + for (; i < ii; ++i) { + name = fields[i].name; obj[name] = this[name](); } return obj; } - for (var k in def) { - this[k](def[k]); + for (; i < ii; ++i) { + name = fields[i].name; + if (name in def) { + this[name](def[name]); + } } return this; }; From 762bc601c49f837f23336f474298a35cc5868afc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 09:14:12 -0700 Subject: [PATCH 344/791] Add struct.js optional nested struct transform setter --- src/js/lib/struct.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/js/lib/struct.js b/src/js/lib/struct.js index 9953c49e..666d1ad7 100644 --- a/src/js/lib/struct.js +++ b/src/js/lib/struct.js @@ -117,6 +117,13 @@ struct.prototype._makeAccessor = function(field) { return this; }; +struct.prototype._makeMetaAccessor = function(name, transform) { + this[name] = function(value, field) { + transform.call(this, value, field); + return this; + }; +}; + struct.prototype._makeAccessors = function(def, index, fields, prefix) { index = index || 0; this._fields = ( fields = fields || [] ); @@ -131,12 +138,15 @@ struct.prototype._makeAccessors = function(def, index, fields, prefix) { if (prefix) { name = prefix + capitalize(name); } + var transform = member[2]; if (type instanceof struct) { + if (transform) { + this._makeMetaAccessor(name, transform); + } this._makeAccessors(type._def, index, fields, name); index = this._size; continue; } - var transform = member[2]; var field = { index: index, type: type, From e2c14910d093915d8d35b701cf6f53142460cdcd Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 09:17:14 -0700 Subject: [PATCH 345/791] Change stage and element messages to packets --- src/js/ui/circle.js | 5 +- src/js/ui/element.js | 20 ++- src/js/ui/image.js | 5 +- src/js/ui/inverter.js | 2 +- src/js/ui/rect.js | 5 +- src/js/ui/simply-pebble.js | 273 ++++++++++++++++++++------------ src/js/ui/stage.js | 6 + src/js/ui/text.js | 5 +- src/simply/simply_msg.c | 310 ++++++++++++++++++++++--------------- 9 files changed, 389 insertions(+), 242 deletions(-) diff --git a/src/js/ui/circle.js b/src/js/ui/circle.js index d08a8a47..03332d41 100644 --- a/src/js/ui/circle.js +++ b/src/js/ui/circle.js @@ -8,9 +8,8 @@ var defaults = { }; var Circle = function(elementDef) { - StageElement.call(this, elementDef); - myutil.shadow(defaults, this.state); - this.state.type = 2; + StageElement.call(this, myutil.shadow(defaults, elementDef || {})); + this.state.type = StageElement.CircleType; }; util2.inherit(Circle, StageElement); diff --git a/src/js/ui/element.js b/src/js/ui/element.js index d171f7fb..e0b5d274 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -19,9 +19,21 @@ var nextId = 1; var StageElement = function(elementDef) { this.state = elementDef || {}; this.state.id = nextId++; + if (!this.state.position) { + this.state.position = new Vector2(); + } + if (!this.state.size) { + this.state.size = new Vector2(); + } this._queue = []; }; +StageElement.RectType = 1; +StageElement.CircleType = 2; +StageElement.TextType = 3; +StageElement.ImageType = 4; +StageElement.InverterType = 5; + util2.copy(Propable.prototype, StageElement.prototype); Propable.makeAccessors(accessorProps, StageElement.prototype); @@ -39,14 +51,8 @@ StageElement.prototype._type = function() { }; StageElement.prototype._prop = function(elementDef) { - if (!this.state.position) { - this.state.position = new Vector2(); - } - if (!this.state.size) { - this.state.size = new Vector2(); - } if (this.parent === WindowStack.top()) { - simply.impl.stageElement(this._id(), this._type(), elementDef); + simply.impl.stageElement(this._id(), this._type(), this.state); } }; diff --git a/src/js/ui/image.js b/src/js/ui/image.js index 55466988..c97b2888 100644 --- a/src/js/ui/image.js +++ b/src/js/ui/image.js @@ -14,9 +14,8 @@ var defaults = { }; var ImageElement = function(elementDef) { - StageElement.call(this, elementDef); - myutil.shadow(defaults, this.state); - this.state.type = 4; + StageElement.call(this, myutil.shadow(defaults, elementDef || {})); + this.state.type = StageElement.ImageType; }; util2.inherit(ImageElement, StageElement); diff --git a/src/js/ui/inverter.js b/src/js/ui/inverter.js index e18ce7c1..6ff0b4fa 100644 --- a/src/js/ui/inverter.js +++ b/src/js/ui/inverter.js @@ -4,7 +4,7 @@ var StageElement = require('ui/element'); var Inverter = function(elementDef) { StageElement.call(this, elementDef); - this.state.type = 5; + this.state.type = StageElement.InverterType; }; util2.inherit(Inverter, StageElement); diff --git a/src/js/ui/rect.js b/src/js/ui/rect.js index 638b6211..d705c222 100644 --- a/src/js/ui/rect.js +++ b/src/js/ui/rect.js @@ -8,9 +8,8 @@ var defaults = { }; var Rect = function(elementDef) { - StageElement.call(this, elementDef); - myutil.shadow(defaults, this.state); - this.state.type = 1; + StageElement.call(this, myutil.shadow(defaults, elementDef || {})); + this.state.type = StageElement.RectType; }; util2.inherit(Rect, StageElement); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 1759d1a2..c3200e0c 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -42,6 +42,16 @@ var ImageType = function(x) { return x ? Number(x) : 0; }; +var PositionType = function(x) { + this.positionX(x.x); + this.positionY(x.y); +}; + +var SizeType = function(x) { + this.sizeW(x.x); + this.sizeH(x.y); +}; + var Color = function(x) { switch (x) { case 'clear': return ~0; @@ -327,6 +337,76 @@ var MenuSetSelectionPacket = new struct([ ['bool', 'animated', BoolType], ]); +var StageClearPacket = new struct([ + [Packet, 'packet'], +]); + +var ElementInsertPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + ['uint8', 'type'], + ['uint16', 'index'], +]); + +var ElementRemovePacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], +]); + +var GPoint = new struct([ + ['int16', 'x'], + ['int16', 'y'], +]); + +var GSize = new struct([ + ['int16', 'w'], + ['int16', 'h'], +]); + +var GRect = new struct([ + [GPoint, 'origin', PositionType], + [GSize, 'size', SizeType], +]); + +var ElementCommonPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + [GPoint, 'position', PositionType], + [GSize, 'size', SizeType], + ['uint8', 'backgroundColor', Color], + ['uint8', 'borderColor', Color], +]); + +var ElementRadiusPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + ['uint16', 'radius', EnumerableType], +]); + +var ElementTextPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + ['uint8', 'updateTimeUnits', TimeUnits], + ['cstring', 'text', StringType], +]); + +var ElementTextStylePacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + ['uint8', 'color', Color], + ['uint8', 'textOverflow', TextOverflowMode], + ['uint8', 'textAlign', TextAlignment], + ['uint32', 'customFont'], + ['cstring', 'systemFont', StringType], +]); + +var ElementImagePacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + ['uint32', 'image', ImageType], + ['uint8', 'compositing', CompositingOp], +]); + var CommandPackets = [ Packet, WindowShowPacket, @@ -348,6 +428,14 @@ var CommandPackets = [ MenuItemPacket, MenuGetSelectionPacket, MenuSetSelectionPacket, + StageClearPacket, + ElementInsertPacket, + ElementRemovePacket, + ElementCommonPacket, + ElementRadiusPacket, + ElementTextPacket, + ElementTextStylePacket, + ElementImagePacket, ]; var setStageParams = [{ @@ -458,58 +546,8 @@ var commands = [{ params: setStageParams, }, { name: 'stageElement', - params: [{ - name: 'id', - }, { - name: 'type', - }, { - name: 'index', - }, { - name: 'x', - }, { - name: 'y', - }, { - name: 'width', - }, { - name: 'height', - }, { - name: 'backgroundColor', - type: Color, - }, { - name: 'borderColor', - type: Color, - }, { - name: 'radius', - }, { - name: 'text', - type: String, - }, { - name: 'font', - type: Font, - }, { - name: 'color', - type: Color, - }, { - name: 'textOverflow', - type: TextOverflowMode, - }, { - name: 'textAlign', - type: TextAlignment, - }, { - name: 'updateTimeUnits', - type: TimeUnits, - }, { - name: 'image', - type: ImageType, - }, { - name: 'compositing', - type: CompositingOp, - }], }, { name: 'stageRemove', - params: [{ - name: 'id', - }], }, { name: 'stageAnimate', params: [{ @@ -666,7 +704,7 @@ SimplyPebble.sendMessage = (function() { var toByteArray = function(packet) { var type = CommandPackets.indexOf(packet); - var size = Math.max(packet.size, packet._cursor); + var size = Math.max(packet._size, packet._cursor); packet.packetType(type); packet.packetLength(size); @@ -683,17 +721,8 @@ SimplyPebble.sendPacket = function(packet) { SimplyPebble.sendMessage({ 0: toByteArray(packet) }); }; -var setPacket = function(packet, def) { - packet._fields.forEach(function(field) { - if (field.name in def) { - packet[field.name](def[field.name]); - } - }); - return packet; -}; - SimplyPebble.windowProps = function(def) { - SimplyPebble.sendPacket(setPacket(WindowPropsPacket, def)); + SimplyPebble.sendPacket(WindowPropsPacket.prop(def)); }; SimplyPebble.windowButtonConfig = function(def) { @@ -708,11 +737,11 @@ var toActionDef = function(actionDef) { }; SimplyPebble.windowActionBar = function(def) { - SimplyPebble.sendPacket(setPacket(WindowActionBarPacket, toActionDef(def))); + SimplyPebble.sendPacket(WindowActionBarPacket.prop(toActionDef(def))); }; SimplyPebble.windowShow = function(def) { - SimplyPebble.sendPacket(setPacket(WindowShowPacket, def)); + SimplyPebble.sendPacket(WindowShowPacket.prop(def)); }; SimplyPebble.windowHide = function(id) { @@ -783,7 +812,7 @@ SimplyPebble.accelPeek = function(callback) { }; SimplyPebble.accelConfig = function(def) { - SimplyPebble.sendPacket(setPacket(AccelConfigPacket, def)); + SimplyPebble.sendPacket(AccelConfigPacket.prop(def)); }; SimplyPebble.menuClear = function() { @@ -795,7 +824,7 @@ SimplyPebble.menuClearSection = function(section) { }; SimplyPebble.menuProps = function(def) { - SimplyPebble.sendPacket(setPacket(MenuPropsPacket, def)); + SimplyPebble.sendPacket(MenuPropsPacket.prop(def)); }; SimplyPebble.menuSection = function(section, def, clear) { @@ -840,6 +869,87 @@ SimplyPebble.menu = function(def, clear, pushing) { SimplyPebble.menuProps(def); }; +SimplyPebble.elementInsert = function(id, type, index) { + SimplyPebble.sendPacket(ElementInsertPacket.id(id).type(type).index(index)); +}; + +SimplyPebble.elementRemove = function(id) { + SimplyPebble.sendPacket(ElementRemovePacket.id(id)); +}; + +SimplyPebble.elementCommon = function(id, def) { + ElementCommonPacket + .id(id) + .position(def.position) + .size(def.size) + .prop(def); + SimplyPebble.sendPacket(ElementCommonPacket); +}; + +SimplyPebble.elementRadius = function(id, radius) { + SimplyPebble.sendPacket(ElementRadiusPacket.id(id).radius(radius)); +}; + +SimplyPebble.elementText = function(id, text, timeUnits) { + SimplyPebble.sendPacket(ElementTextPacket.id(id).updateTimeUnits(timeUnits).text(text)); +}; + +SimplyPebble.elementTextStyle = function(id, def) { + ElementTextStylePacket.id(id).prop(def); + var font = Font(def.font); + if (typeof font === 'number') { + ElementTextStylePacket.customFont(font).systemFont(''); + } else { + ElementTextStylePacket.customFont(0).systemFont(font); + } + SimplyPebble.sendPacket(ElementTextStylePacket); +}; + +SimplyPebble.elementImage = function(id, image, compositing) { + SimplyPebble.sendPacket(ElementImagePacket.id(id).image(image).compositing(compositing)); +}; + +SimplyPebble.stageClear = function() { + SimplyPebble.sendPacket(StageClearPacket); +}; + +SimplyPebble.stageElement = function(id, type, def, index) { + if (index !== undefined) { + SimplyPebble.elementInsert(id, type, index); + } + SimplyPebble.elementCommon(id, def); + switch (type) { + case StageElement.RectType: + case StageElement.CircleType: + SimplyPebble.elementRadius(id, def.radius); + break; + case StageElement.TextType: + SimplyPebble.elementRadius(id, def.radius); + SimplyPebble.elementTextStyle(id, def); + SimplyPebble.elementText(id, def.text, def.updateTimeUnits); + break; + case StageElement.ImageType: + SimplyPebble.elementRadius(id, def.radius); + SimplyPebble.elementImage(id, def.image, def.compositing); + break; + } +}; + +SimplyPebble.stageRemove = SimplyPebble.elementRemove; + +SimplyPebble.stage = function(def, clear, pushing) { + if (arguments.length === 3) { + SimplyPebble.windowShow({ type: 'window', pushing: pushing }); + } + SimplyPebble.windowProps(def); + if (clear !== undefined) { + SimplyPebble.stageClear(); + } + if (def.action !== undefined) { + SimplyPebble.windowActionBar(def.action); + } +}; + var setActionMessage = function(message, command, actionDef) { if (actionDef) { if (typeof actionDef === 'boolean') { @@ -858,21 +968,6 @@ SimplyPebble.image = function(id, gbitmap) { SimplyPebble.sendMessage(message); }; -SimplyPebble.stage = function(stageDef, clear, pushing) { - if (arguments.length === 3) { - SimplyPebble.windowShow({ type: 'window', pushing: pushing }); - } - SimplyPebble.windowProps(stageDef); - var command = commandMap.setStage; - var message = makeMessage(command, stageDef); - if (clear) { - clear = toClearFlags(clear); - message[command.paramMap.clear.id] = clear; - } - setActionMessage(message, command, stageDef.action); - SimplyPebble.sendMessage(message); -}; - var toFrameMessage = function(messageDef) { if (messageDef.position) { var position = messageDef.position; @@ -889,24 +984,6 @@ var toFrameMessage = function(messageDef) { return messageDef; }; -SimplyPebble.stageElement = function(elementId, elementType, elementDef, index) { - var command = commandMap.stageElement; - var messageDef = util2.copy(elementDef); - messageDef.id = elementId; - messageDef.type = elementType; - messageDef.index = index; - messageDef = toFrameMessage(messageDef); - var message = makeMessage(command, messageDef); - SimplyPebble.sendMessage(message); -}; - -SimplyPebble.stageRemove = function(elementId) { - var command = commandMap.stageRemove; - var message = makeMessage(command); - message[command.paramMap.id.id] = elementId; - SimplyPebble.sendMessage(message); -}; - SimplyPebble.stageAnimate = function(elementId, animateDef, duration, easing) { var command = commandMap.stageAnimate; var messageDef = util2.copy(animateDef); diff --git a/src/js/ui/stage.js b/src/js/ui/stage.js index c3b21c84..7123a171 100644 --- a/src/js/ui/stage.js +++ b/src/js/ui/stage.js @@ -8,6 +8,12 @@ var Stage = function(stageDef) { this._items = []; }; +Stage.RectType = 1; +Stage.CircleType = 2; +Stage.TextType = 3; +Stage.ImageType = 4; +Stage.InverterType = 5; + util2.copy(Emitter.prototype, Stage.prototype); Stage.prototype._show = function() { diff --git a/src/js/ui/text.js b/src/js/ui/text.js index 03aa153f..832bd85f 100644 --- a/src/js/ui/text.js +++ b/src/js/ui/text.js @@ -19,9 +19,8 @@ var defaults = { }; var Text = function(elementDef) { - StageElement.call(this, elementDef); - myutil.shadow(defaults, this.state); - this.state.type = 3; + StageElement.call(this, myutil.shadow(defaults, elementDef || {})); + this.state.type = StageElement.TextType; }; util2.inherit(Text, StageElement); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 1376da2d..e1a200b8 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -42,6 +42,14 @@ enum Command { CommandMenuItem, CommandMenuGetSelection, CommandMenuSetSelection, + CommandStageClear, + CommandElementInsert, + CommandElementRemove, + CommandElementCommon, + CommandElementRadius, + CommandElementText, + CommandElementTextStyle, + CommandElementImage, }; typedef enum WindowType WindowType; @@ -207,6 +215,72 @@ struct __attribute__((__packed__)) MenuSetSelectionPacket { bool animated; }; +typedef Packet StageClearPacket; + +typedef struct ElementInsertPacket ElementInsertPacket; + +struct __attribute__((__packed__)) ElementInsertPacket { + Packet packet; + uint32_t id; + SimplyElementType type:8; + uint16_t index; +}; + +typedef struct ElementRemovePacket ElementRemovePacket; + +struct __attribute__((__packed__)) ElementRemovePacket { + Packet packet; + uint32_t id; +}; + +typedef struct ElementCommonPacket ElementCommonPacket; + +struct __attribute__((__packed__)) ElementCommonPacket { + Packet packet; + uint32_t id; + GRect frame; + GColor background_color:8; + GColor border_color:8; +}; + +typedef struct ElementRadiusPacket ElementRadiusPacket; + +struct __attribute__((__packed__)) ElementRadiusPacket { + Packet packet; + uint32_t id; + uint16_t radius; +}; + +typedef struct ElementTextPacket ElementTextPacket; + +struct __attribute__((__packed__)) ElementTextPacket { + Packet packet; + uint32_t id; + TimeUnits time_units:8; + char text[]; +}; + +typedef struct ElementTextStylePacket ElementTextStylePacket; + +struct __attribute__((__packed__)) ElementTextStylePacket { + Packet packet; + uint32_t id; + GColor color:8; + GTextOverflowMode overflow_mode:8; + GTextAlignment alignment:8; + uint32_t custom_font; + char system_font[]; +}; + +typedef struct ElementImagePacket ElementImagePacket; + +struct __attribute__((__packed__)) ElementImagePacket { + Packet packet; + uint32_t id; + uint32_t image; + GCompOp compositing:8; +}; + typedef enum SimplyACmd SimplyACmd; enum SimplyACmd { @@ -321,121 +395,6 @@ static void handle_set_image(DictionaryIterator *iter, Simply *simply) { simply_res_add_image(simply->res, id, width, height, pixels); } -static void handle_set_stage(DictionaryIterator *iter, Simply *simply) { - SimplyStage *stage = simply->stage; - Tuple *tuple; - for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { - switch (tuple->key) { - case 0: - simply_stage_clear(stage); - break; - } - } -} - -static void handle_set_stage_element(DictionaryIterator *iter, Simply *simply) { - SimplyStage *stage = simply->stage; - Tuple *tuple; - uint32_t id = 0; - SimplyElementType type = SimplyElementTypeNone; - if ((tuple = dict_find(iter, ElementId))) { - id = tuple->value->uint32; - } - if ((tuple = dict_find(iter, ElementType))) { - type = tuple->value->int32; - } - SimplyElementCommon *element = simply_stage_auto_element(stage, id, type); - if (!element || element->type != type) { - return; - } - GRect frame = element->frame; - bool update_frame = false; - bool update_ticker = false; - for (tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { - switch (tuple->key) { - case ElementIndex: - simply_stage_insert_element(stage, tuple->value->uint16, element); - break; - case ElementX: - frame.origin.x = tuple->value->int16; - update_frame = true; - break; - case ElementY: - frame.origin.y = tuple->value->int16; - update_frame = true; - break; - case ElementWidth: - frame.size.w = tuple->value->uint16; - update_frame = true; - break; - case ElementHeight: - frame.size.h = tuple->value->uint16; - update_frame = true; - break; - case ElementBackgroundColor: - element->background_color = tuple->value->uint8; - break; - case ElementBorderColor: - element->border_color = tuple->value->uint8; - break; - case ElementRadius: - ((SimplyElementRect*) element)->radius = tuple->value->uint16; - break; - case ElementText: - strset(&((SimplyElementText*) element)->text, tuple->value->cstring); - break; - case ElementTextFont: - if (tuple->type == TUPLE_CSTRING) { - ((SimplyElementText*) element)->font = fonts_get_system_font(tuple->value->cstring); - } else { - ((SimplyElementText*) element)->font = simply_res_get_font(simply->res, tuple->value->uint32); - } - break; - case ElementTextColor: - ((SimplyElementText*) element)->text_color = tuple->value->uint8; - break; - case ElementTextOverflow: - ((SimplyElementText*) element)->overflow_mode = tuple->value->uint8; - break; - case ElementTextAlignment: - ((SimplyElementText*) element)->alignment = tuple->value->uint8; - break; - case ElementTextUpdateTimeUnit: - ((SimplyElementText*) element)->time_units = tuple->value->uint8; - update_ticker = true; - break; - case ElementImage: - ((SimplyElementImage*) element)->image = tuple->value->uint32; - break; - case ElementCompositing: - ((SimplyElementImage*) element)->compositing = tuple->value->uint8; - break; - } - } - if (update_frame) { - simply_stage_set_element_frame(stage, element, frame); - } - if (update_ticker) { - simply_stage_update_ticker(stage); - } - simply_stage_update(stage); -} - -static void handle_remove_stage_element(DictionaryIterator *iter, Simply *simply) { - SimplyStage *stage = simply->stage; - Tuple *tuple; - uint32_t id = 0; - if ((tuple = dict_find(iter, 1))) { - id = tuple->value->uint32; - } - SimplyElementCommon *element = simply_stage_get_element(stage, id); - if (!element) { - return; - } - simply_stage_remove_element(stage, element); - simply_stage_update(stage); -} - static void handle_animate_stage_element(DictionaryIterator *iter, Simply *simply) { SimplyStage *stage = simply->stage; Tuple *tuple; @@ -625,6 +584,94 @@ static void handle_menu_set_selection_packet(Simply *simply, Packet *data) { simply_menu_set_selection(simply->menu, menu_index, packet->align, packet->animated); } +static void handle_stage_clear_packet(Simply *simply, Packet *data) { + simply_stage_clear(simply->stage); +} + +static void handle_element_insert_packet(Simply *simply, Packet *data) { + ElementInsertPacket *packet = (ElementInsertPacket*) data; + SimplyElementCommon *element = simply_stage_auto_element(simply->stage, packet->id, packet->type); + if (!element) { + return; + } + simply_stage_insert_element(simply->stage, packet->index, element); + simply_stage_update(simply->stage); +} + +static void handle_element_remove_packet(Simply *simply, Packet *data) { + ElementInsertPacket *packet = (ElementInsertPacket*) data; + SimplyElementCommon *element = simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + simply_stage_remove_element(simply->stage, element); + simply_stage_update(simply->stage); +} + +static void handle_element_common_packet(Simply *simply, Packet *data) { + ElementCommonPacket *packet = (ElementCommonPacket*) data; + SimplyElementCommon *element = simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + simply_stage_set_element_frame(simply->stage, element, packet->frame); + element->background_color = packet->background_color; + element->border_color = packet->border_color; + simply_stage_update(simply->stage); +} + +static void handle_element_radius_packet(Simply *simply, Packet *data) { + ElementRadiusPacket *packet = (ElementRadiusPacket*) data; + SimplyElementRect *element = (SimplyElementRect*) simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + element->radius = packet->radius; + simply_stage_update(simply->stage); +}; + +static void handle_element_text_packet(Simply *simply, Packet *data) { + ElementTextPacket *packet = (ElementTextPacket*) data; + SimplyElementText *element = (SimplyElementText*) simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + if (element->time_units != packet->time_units) { + element->time_units = packet->time_units; + simply_stage_update_ticker(simply->stage); + } + strset(&element->text, packet->text); + simply_stage_update(simply->stage); +} + +static void handle_element_text_style_packet(Simply *simply, Packet *data) { + ElementTextStylePacket *packet = (ElementTextStylePacket*) data; + SimplyElementText *element = (SimplyElementText*) simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + element->text_color = packet->color; + element->overflow_mode = packet->overflow_mode; + element->alignment = packet->alignment; + if (packet->custom_font) { + element->font = simply_res_get_font(simply->res, packet->custom_font); + } else if (packet->system_font[0]) { + element->font = fonts_get_system_font(packet->system_font); + } + simply_stage_update(simply->stage); +} + +static void handle_element_image_packet(Simply *simply, Packet *data) { + ElementImagePacket *packet = (ElementImagePacket*) data; + SimplyElementImage *element = (SimplyElementImage*) simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + element->image = packet->image; + element->compositing = packet->compositing; + simply_stage_update(simply->stage); +} + static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { Packet *packet = (Packet*) buffer; switch (packet->type) { @@ -685,6 +732,30 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandMenuSetSelection: handle_menu_set_selection_packet(simply, packet); break; + case CommandStageClear: + handle_stage_clear_packet(simply, packet); + break; + case CommandElementInsert: + handle_element_insert_packet(simply, packet); + break; + case CommandElementRemove: + handle_element_remove_packet(simply, packet); + break; + case CommandElementCommon: + handle_element_common_packet(simply, packet); + break; + case CommandElementRadius: + handle_element_radius_packet(simply, packet); + break; + case CommandElementText: + handle_element_text_packet(simply, packet); + break; + case CommandElementTextStyle: + handle_element_text_style_packet(simply, packet); + break; + case CommandElementImage: + handle_element_image_packet(simply, packet); + break; } } @@ -704,15 +775,6 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_image: handle_set_image(iter, context); break; - case SimplyACmd_setStage: - handle_set_stage(iter, context); - break; - case SimplyACmd_stageElement: - handle_set_stage_element(iter, context); - break; - case SimplyACmd_stageRemove: - handle_remove_stage_element(iter, context); - break; case SimplyACmd_stageAnimate: handle_animate_stage_element(iter, context); break; From 8de0be6be122aeb57289686882c1941d483ae997 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 09:44:26 -0700 Subject: [PATCH 346/791] Change element animate message to packet --- src/js/ui/element.js | 3 +- src/js/ui/simply-pebble.js | 69 ++++++++++++-------------------------- src/simply/simply_msg.c | 66 ++++++++++++++---------------------- 3 files changed, 50 insertions(+), 88 deletions(-) diff --git a/src/js/ui/element.js b/src/js/ui/element.js index e0b5d274..b42745cc 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -69,7 +69,8 @@ StageElement.prototype.remove = function(broadcast) { StageElement.prototype._animate = function(animateDef, duration) { if (this.parent === WindowStack.top()) { - simply.impl.stageAnimate(this._id(), animateDef, duration || 400, animateDef.easing || 'easeInOut'); + simply.impl.stageAnimate(this._id(), this.state, + animateDef, duration || 400, animateDef.easing || 'easeInOut'); } }; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index c3200e0c..f1eb1c24 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -407,6 +407,15 @@ var ElementImagePacket = new struct([ ['uint8', 'compositing', CompositingOp], ]); +var ElementAnimatePacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + [GPoint, 'position', PositionType], + [GSize, 'size', SizeType], + ['uint32', 'duration'], + ['uint8', 'easing', AnimationCurve], +]); + var CommandPackets = [ Packet, WindowShowPacket, @@ -436,6 +445,7 @@ var CommandPackets = [ ElementTextPacket, ElementTextStylePacket, ElementImagePacket, + ElementAnimatePacket, ]; var setStageParams = [{ @@ -550,22 +560,6 @@ var commands = [{ name: 'stageRemove', }, { name: 'stageAnimate', - params: [{ - name: 'id', - }, { - name: 'x', - }, { - name: 'y', - }, { - name: 'width', - }, { - name: 'height', - }, { - name: 'duration', - }, { - name: 'easing', - type: AnimationCurve, - }], }, { name: 'stageAnimateDone', params: [{ @@ -909,6 +903,16 @@ SimplyPebble.elementImage = function(id, image, compositing) { SimplyPebble.sendPacket(ElementImagePacket.id(id).image(image).compositing(compositing)); }; +SimplyPebble.elementAnimate = function(id, def, animateDef, duration, easing) { + ElementAnimatePacket + .id(id) + .position(animateDef.position || def.position) + .size(animateDef.size || def.size) + .duration(duration) + .easing(easing); + SimplyPebble.sendPacket(ElementAnimatePacket); +}; + SimplyPebble.stageClear = function() { SimplyPebble.sendPacket(StageClearPacket); }; @@ -937,6 +941,8 @@ SimplyPebble.stageElement = function(id, type, def, index) { SimplyPebble.stageRemove = SimplyPebble.elementRemove; +SimplyPebble.stageAnimate = SimplyPebble.elementAnimate; + SimplyPebble.stage = function(def, clear, pushing) { if (arguments.length === 3) { SimplyPebble.windowShow({ type: 'window', pushing: pushing }); @@ -968,37 +974,6 @@ SimplyPebble.image = function(id, gbitmap) { SimplyPebble.sendMessage(message); }; -var toFrameMessage = function(messageDef) { - if (messageDef.position) { - var position = messageDef.position; - delete messageDef.position; - messageDef.x = position.x; - messageDef.y = position.y; - } - if (messageDef.size) { - var size = messageDef.size; - delete messageDef.size; - messageDef.width = size.x; - messageDef.height = size.y; - } - return messageDef; -}; - -SimplyPebble.stageAnimate = function(elementId, animateDef, duration, easing) { - var command = commandMap.stageAnimate; - var messageDef = util2.copy(animateDef); - messageDef.id = elementId; - if (duration) { - messageDef.duration = duration; - } - if (easing) { - messageDef.easing = easing; - } - messageDef = toFrameMessage(messageDef); - var message = makeMessage(command, messageDef); - SimplyPebble.sendMessage(message); -}; - var readInt = function(message, width, pos, signed) { var value = 0; pos = pos || 0; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index e1a200b8..75866832 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -50,6 +50,7 @@ enum Command { CommandElementText, CommandElementTextStyle, CommandElementImage, + CommandElementAnimate, }; typedef enum WindowType WindowType; @@ -281,6 +282,16 @@ struct __attribute__((__packed__)) ElementImagePacket { GCompOp compositing:8; }; +typedef struct ElementAnimatePacket ElementAnimatePacket; + +struct __attribute__((__packed__)) ElementAnimatePacket { + Packet packet; + uint32_t id; + GRect frame; + uint32_t duration; + AnimationCurve curve:8; +}; + typedef enum SimplyACmd SimplyACmd; enum SimplyACmd { @@ -395,43 +406,6 @@ static void handle_set_image(DictionaryIterator *iter, Simply *simply) { simply_res_add_image(simply->res, id, width, height, pixels); } -static void handle_animate_stage_element(DictionaryIterator *iter, Simply *simply) { - SimplyStage *stage = simply->stage; - Tuple *tuple; - uint32_t id = 0; - if ((tuple = dict_find(iter, 1))) { - id = tuple->value->uint32; - } - SimplyElementCommon *element = simply_stage_get_element(stage, id); - if (!element) { - return; - } - GRect to_frame = element->frame; - SimplyAnimation *animation = malloc0(sizeof(*animation)); - if (!animation) { - return; - } - if ((tuple = dict_find(iter, 2))) { - to_frame.origin.x = tuple->value->int16; - } - if ((tuple = dict_find(iter, 3))) { - to_frame.origin.y = tuple->value->int16; - } - if ((tuple = dict_find(iter, 4))) { - to_frame.size.w = tuple->value->int16; - } - if ((tuple = dict_find(iter, 5))) { - to_frame.size.h = tuple->value->int16; - } - if ((tuple = dict_find(iter, 6))) { - animation->duration = tuple->value->uint32; - } - if ((tuple = dict_find(iter, 7))) { - animation->curve = tuple->value->uint8; - } - simply_stage_animate_element(stage, element, animation, to_frame); -} - static void handle_window_show_packet(Simply *simply, Packet *data) { WindowShowPacket *packet = (WindowShowPacket*) data; SimplyWindow *window = simply->windows[MIN(WindowTypeLast - 1, packet->type)]; @@ -672,6 +646,18 @@ static void handle_element_image_packet(Simply *simply, Packet *data) { simply_stage_update(simply->stage); } +static void handle_element_animate_packet(Simply *simply, Packet *data) { + ElementAnimatePacket *packet = (ElementAnimatePacket*) data; + SimplyElementCommon *element = simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + SimplyAnimation *animation = malloc0(sizeof(*animation)); + animation->duration = packet->duration; + animation->curve = packet->curve; + simply_stage_animate_element(simply->stage, element, animation, packet->frame); +} + static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { Packet *packet = (Packet*) buffer; switch (packet->type) { @@ -756,6 +742,9 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandElementImage: handle_element_image_packet(simply, packet); break; + case CommandElementAnimate: + handle_element_animate_packet(simply, packet); + break; } } @@ -775,9 +764,6 @@ static void received_callback(DictionaryIterator *iter, void *context) { case SimplyACmd_image: handle_set_image(iter, context); break; - case SimplyACmd_stageAnimate: - handle_animate_stage_element(iter, context); - break; } } From 2af5bfea04a275450c058b51aef876a84c4cd01a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 12:36:26 -0700 Subject: [PATCH 347/791] Add struct.js arbitrary size data buffer type --- src/js/lib/struct.js | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/js/lib/struct.js b/src/js/lib/struct.js index 666d1ad7..be471006 100644 --- a/src/js/lib/struct.js +++ b/src/js/lib/struct.js @@ -30,6 +30,7 @@ struct.types = { float32: { size: 2 }, float64: { size: 4 }, cstring: { size: 1, dynamic: true }, + data: { size: 0, dynamic: true }, }; var makeDataViewAccessor = function(type, typeName) { @@ -73,6 +74,32 @@ struct.types.cstring.set = function(offset, value) { this._advance = value.length + 1; }; +struct.types.data.get = function(offset) { + var length = this._value; + this._cursor = offset; + var buffer = this._view; + var copy = new DataView(new ArrayBuffer(length)); + for (var i = 0; i < length; ++i) { + copy.setUint8(i, buffer.getUint8(i + offset)); + } + this._advance = length; + return copy; +}; + +struct.types.data.set = function(offset, value) { + var length = value.byteLength || value.length; + this._cursor = offset; + this._grow(offset + length); + var buffer = this._view; + if (value instanceof ArrayBuffer) { + value = new DataView(value); + } + for (var i = 0; i < length; ++i) { + buffer.setUint8(i + offset, value instanceof DataView ? value.getUint8(i) : value[i]); + } + this._advance = length; +}; + struct.prototype._grow = function(target) { var buffer = this._view; var size = buffer.byteLength; @@ -85,13 +112,18 @@ struct.prototype._grow = function(target) { this._view = copy; }; +struct.prototype._prevField = function(field) { + field = field || this._access; + var fieldIndex = this._fields.indexOf(field); + return this._fields[fieldIndex - 1]; +}; + struct.prototype._makeAccessor = function(field) { this[field.name] = function(value) { var type = field.type; if (field.dynamic) { - var fieldIndex = this._fields.indexOf(field); - var prevField = this._fields[fieldIndex - 1]; - if (fieldIndex === 0) { + var prevField = this._prevField(field); + if (prevField === undefined) { this._cursor = 0; } else if (this._access === field) { this._cursor -= this._advance; @@ -105,11 +137,13 @@ struct.prototype._makeAccessor = function(field) { var result = this; if (arguments.length === 0) { result = type.get.call(this, this._cursor, this._littleEndian); + this._value = result; } else { if (field.transform) { value = field.transform(value, field); } type.set.call(this, this._cursor, value, this._littleEndian); + this._value = value; } this._cursor += this._advance; return result; From 1d0a96337c7d947dbf2a53aa6103a7277a5b2700 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 12:37:09 -0700 Subject: [PATCH 348/791] Fix ImageService to require imagelib and change to init automatically --- src/js/ui/imageservice.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/js/ui/imageservice.js b/src/js/ui/imageservice.js index bba4247b..a9b96898 100644 --- a/src/js/ui/imageservice.js +++ b/src/js/ui/imageservice.js @@ -1,4 +1,4 @@ -var imagelib = require('image'); +var imagelib = require('lib/image'); var myutil = require('myutil'); var Resource = require('ui/resource'); var simply = require('ui/simply'); @@ -8,10 +8,10 @@ var ImageService = module.exports; var state; ImageService.init = function() { - state = Image.state = { + state = ImageService.state = { cache: {}, nextId: Resource.items.length + 1, - rootURL: null + rootUrl: undefined, }; }; @@ -61,7 +61,7 @@ ImageService.load = function(opt, reset, callback) { callback = reset; reset = null; } - var url = myutil.abspath(state.rootURL, opt.url); + var url = myutil.abspath(state.rootUrl, opt.url); var hash = makeImageHash(opt); var image = state.cache[hash]; if (image) { @@ -96,8 +96,8 @@ ImageService.load = function(opt, reset, callback) { return image.id; }; -ImageService.setRootURL = function(url) { - state.rootURL = url; +ImageService.setRootUrl = function(url) { + state.rootUrl = url; }; /** @@ -108,3 +108,5 @@ ImageService.resolve = function(opt) { var id = Resource.getId(opt); return typeof id !== 'undefined' ? id : ImageService.load(opt); }; + +ImageService.init(); From c10934eed2580aa0e118fcd6aa443fe9013943a6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 12:38:38 -0700 Subject: [PATCH 349/791] Change accel to init automatically --- src/js/ui/accel.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/ui/accel.js b/src/js/ui/accel.js index 44e4d922..ba73f4f9 100644 --- a/src/js/ui/accel.js +++ b/src/js/ui/accel.js @@ -153,3 +153,5 @@ Accel.emitAccelData = function(accels, callback) { } Accel.emit('data', e); }; + +Accel.init(); From 46351955eeda9ab0f6eccf05161686edae0566ab Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 16 Jun 2014 12:39:00 -0700 Subject: [PATCH 350/791] Change image message to packet --- src/js/ui/simply-pebble.js | 33 ++++++++++++++----------- src/simply/simply_msg.c | 50 +++++++++++++++----------------------- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index f1eb1c24..e68e62c2 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -254,6 +254,14 @@ var WindowActionBarPacket = new struct([ ['uint8', 'action', BoolType], ]); +var ImagePacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + ['int16', 'width'], + ['int16', 'height'], + ['data', 'pixels'], +]); + var CardClearPacket = new struct([ [Packet, 'packet'], ['uint8', 'flags'], @@ -423,6 +431,7 @@ var CommandPackets = [ WindowPropsPacket, WindowButtonConfigPacket, WindowActionBarPacket, + ImagePacket, CardClearPacket, CardTextPacket, CardImagePacket, @@ -715,6 +724,14 @@ SimplyPebble.sendPacket = function(packet) { SimplyPebble.sendMessage({ 0: toByteArray(packet) }); }; +SimplyPebble.windowShow = function(def) { + SimplyPebble.sendPacket(WindowShowPacket.prop(def)); +}; + +SimplyPebble.windowHide = function(id) { + SimplyPebble.sendPacket(WindowHidePacket.id(id)); +}; + SimplyPebble.windowProps = function(def) { SimplyPebble.sendPacket(WindowPropsPacket.prop(def)); }; @@ -734,12 +751,8 @@ SimplyPebble.windowActionBar = function(def) { SimplyPebble.sendPacket(WindowActionBarPacket.prop(toActionDef(def))); }; -SimplyPebble.windowShow = function(def) { - SimplyPebble.sendPacket(WindowShowPacket.prop(def)); -}; - -SimplyPebble.windowHide = function(id) { - SimplyPebble.sendPacket(WindowHidePacket.id(id)); +SimplyPebble.image = function(id, gbitmap) { + SimplyPebble.sendPacket(ImagePacket.id(id).prop(gbitmap)); }; var toClearFlags = function(clear) { @@ -966,14 +979,6 @@ var setActionMessage = function(message, command, actionDef) { return message; }; -SimplyPebble.image = function(id, gbitmap) { - var command = commandMap.image; - var messageDef = util2.copy(gbitmap); - messageDef.id = id; - var message = makeMessage(command, messageDef); - SimplyPebble.sendMessage(message); -}; - var readInt = function(message, width, pos, signed) { var value = 0; pos = pos || 0; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 75866832..04066a2d 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -28,6 +28,7 @@ enum Command { CommandWindowProps, CommandWindowButtonConfig, CommandWindowActionBar, + CommandImagePacket, CommandCardClear, CommandCardText, CommandCardImage, @@ -118,6 +119,16 @@ struct __attribute__((__packed__)) WindowActionBarPacket { bool action; }; +typedef struct ImagePacket ImagePacket; + +struct __attribute__((__packed__)) ImagePacket { + Packet packet; + uint32_t id; + int16_t width; + int16_t height; + uint32_t pixels[]; +}; + typedef struct CardClearPacket CardClearPacket; struct __attribute__((__packed__)) CardClearPacket { @@ -385,27 +396,6 @@ static SimplyWindow *get_top_simply_window(Simply *simply) { return window; } -static void handle_set_image(DictionaryIterator *iter, Simply *simply) { - Tuple *tuple; - uint32_t id = 0; - int16_t width = 0; - int16_t height = 0; - uint32_t *pixels = NULL; - if ((tuple = dict_find(iter, 1))) { - id = tuple->value->uint32; - } - if ((tuple = dict_find(iter, 2))) { - width = tuple->value->int16; - } - if ((tuple = dict_find(iter, 3))) { - height = tuple->value->int16; - } - if ((tuple = dict_find(iter, 4))) { - pixels = (uint32_t*) tuple->value->data; - } - simply_res_add_image(simply->res, id, width, height, pixels); -} - static void handle_window_show_packet(Simply *simply, Packet *data) { WindowShowPacket *packet = (WindowShowPacket*) data; SimplyWindow *window = simply->windows[MIN(WindowTypeLast - 1, packet->type)]; @@ -457,6 +447,11 @@ static void handle_window_action_bar_packet(Simply *simply, Packet *data) { simply_window_set_action_bar(window, packet->action); } +static void handle_image_packet(Simply *simply, Packet *data) { + ImagePacket *packet = (ImagePacket*) data; + simply_res_add_image(simply->res, packet->id, packet->width, packet->height, packet->pixels); +} + static void handle_card_clear_packet(Simply *simply, Packet *data) { CardClearPacket *packet = (CardClearPacket*) data; simply_ui_clear(simply->ui, packet->flags); @@ -676,6 +671,9 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandWindowActionBar: handle_window_action_bar_packet(simply, packet); break; + case CommandImagePacket: + handle_image_packet(simply, packet); + break; case CommandCardClear: handle_card_clear_packet(simply, packet); break; @@ -756,15 +754,7 @@ static void received_callback(DictionaryIterator *iter, void *context) { s_has_communicated = true; - if (tuple->value->uint32 > 0xFFFF) { - handle_packet(context, tuple->value->data, tuple->length); - } - - switch (tuple->value->uint8) { - case SimplyACmd_image: - handle_set_image(iter, context); - break; - } + handle_packet(context, tuple->value->data, tuple->length); } static void dropped_callback(AppMessageResult reason, void *context) { From 7beb96710f154d788b5db12c4d7353ffbf0ad993 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 01:27:02 -0700 Subject: [PATCH 351/791] Change click messages to packets --- src/js/ui/simply-pebble.js | 43 ++++++++++++++++++++++- src/simply/simply_msg.c | 70 +++++++++++++++++++++++++++++--------- src/simply/simply_msg.h | 3 +- 3 files changed, 97 insertions(+), 19 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index e68e62c2..0113a4d4 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -254,6 +254,16 @@ var WindowActionBarPacket = new struct([ ['uint8', 'action', BoolType], ]); +var ClickPacket = new struct([ + [Packet, 'packet'], + ['uint8', 'button', ButtonType], +]); + +var LongClickPacket = new struct([ + [Packet, 'packet'], + ['uint8', 'button', ButtonType], +]); + var ImagePacket = new struct([ [Packet, 'packet'], ['uint32', 'id'], @@ -431,6 +441,8 @@ var CommandPackets = [ WindowPropsPacket, WindowButtonConfigPacket, WindowActionBarPacket, + ClickPacket, + LongClickPacket, ImagePacket, CardClearPacket, CardTextPacket, @@ -994,10 +1006,39 @@ var readInt = function(message, width, pos, signed) { return value; }; +var toArrayBuffer = function(array, length) { + length = length || array.length; + var copy = new DataView(new ArrayBuffer(length)); + for (var i = 0; i < length; ++i) { + copy.setUint8(i, array[i]); + } + return copy; +}; + +SimplyPebble.onPacket = function(data) { + Packet._view = toArrayBuffer(data); + var packet = CommandPackets[Packet.type()]; + packet._view = Packet._view; + switch (packet) { + case ClickPacket: + Window.emitClick('click', buttonTypes[packet.button()]); + break; + case LongClickPacket: + Window.emitClick('longClick', buttonTypes[packet.button()]); + break; + } +}; + SimplyPebble.onAppMessage = function(e) { var payload = e.payload; var code = payload[0]; - var command = commands[code]; + var command; + + if (code instanceof Array) { + return SimplyPebble.onPacket(code); + } else { + command = commands[code]; + } if (!command) { console.log('Received unknown payload: ' + JSON.stringify(payload)); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 04066a2d..c751ef68 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -28,6 +28,8 @@ enum Command { CommandWindowProps, CommandWindowButtonConfig, CommandWindowActionBar, + CommandClick, + CommandLongClick, CommandImagePacket, CommandCardClear, CommandCardText, @@ -119,6 +121,15 @@ struct __attribute__((__packed__)) WindowActionBarPacket { bool action; }; +typedef struct ClickPacket ClickPacket; + +struct __attribute__((__packed__)) ClickPacket { + Packet packet; + ButtonId button:8; +}; + +typedef ClickPacket LongClickPacket; + typedef struct ImagePacket ImagePacket; struct __attribute__((__packed__)) ImagePacket { @@ -671,6 +682,10 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandWindowActionBar: handle_window_action_bar_packet(simply, packet); break; + case CommandClick: + break; + case CommandLongClick: + break; case CommandImagePacket: handle_image_packet(simply, packet); break; @@ -819,7 +834,11 @@ static bool send_msg(SimplyPacket *packet) { if (app_message_outbox_begin(&iter) != APP_MSG_OK) { return false; } - dict_copy_from_buffer(iter, packet->buffer, packet->length); + if (packet->is_dict) { + dict_copy_from_buffer(iter, packet->buffer, packet->length); + } else { + dict_write_data(iter, 0, packet->buffer, packet->length); + } return (app_message_outbox_send() == APP_MSG_OK); } @@ -839,12 +858,16 @@ static void send_msg_retry(void *data) { self->send_delay_ms = SEND_DELAY_MS; } -static SimplyPacket *add_packet(SimplyMsg *self, void *buffer, size_t length) { +static SimplyPacket *add_packet(SimplyMsg *self, Packet *buffer, Command type, size_t length) { SimplyPacket *packet = malloc0(sizeof(*packet)); if (!packet) { free(buffer); return NULL; } + *buffer = (Packet) { + .type = type, + .length = length, + }; *packet = (SimplyPacket) { .length = length, .buffer = buffer, @@ -854,25 +877,38 @@ static SimplyPacket *add_packet(SimplyMsg *self, void *buffer, size_t length) { return packet; } -static bool send_click(SimplyMsg *self, SimplyACmd type, ButtonId button) { - size_t length = dict_calc_buffer_size(2, 1, 1); - void *buffer = malloc0(length); - if (!buffer) { +static SimplyPacket *add_dict(SimplyMsg *self, void *buffer, size_t length) { + SimplyPacket *packet = malloc0(sizeof(*packet)); + if (!packet) { + free(buffer); + return NULL; + } + *packet = (SimplyPacket) { + .is_dict = true, + .length = length, + .buffer = buffer, + }; + list1_append(&self->queue, &packet->node); + send_msg_retry(self); + return packet; +} + +static bool send_click(SimplyMsg *self, Command type, ButtonId button) { + size_t length; + ClickPacket *packet = malloc0(length = sizeof(*packet)); + if (!packet) { return false; } - DictionaryIterator iter; - dict_write_begin(&iter, buffer, length); - dict_write_uint8(&iter, 0, type); - dict_write_uint8(&iter, 1, button); - return add_packet(self, buffer, length); + packet->button = button; + return add_packet(self, (Packet*) packet, type, length); } bool simply_msg_single_click(SimplyMsg *self, ButtonId button) { - return send_click(self, SimplyACmd_click, button); + return send_click(self, CommandClick, button); } bool simply_msg_long_click(SimplyMsg *self, ButtonId button) { - return send_click(self, SimplyACmd_longClick, button); + return send_click(self, CommandLongClick, button); } bool send_window(SimplyMsg *self, SimplyACmd type, uint32_t id) { @@ -885,7 +921,7 @@ bool send_window(SimplyMsg *self, SimplyACmd type, uint32_t id) { dict_write_begin(&iter, buffer, length); dict_write_uint8(&iter, 0, type); dict_write_uint32(&iter, 1, id); - return add_packet(self, buffer, length); + return add_dict(self, buffer, length); } bool simply_msg_window_show(SimplyMsg *self, uint32_t id) { @@ -907,7 +943,7 @@ bool simply_msg_accel_tap(SimplyMsg *self, AccelAxisType axis, int32_t direction dict_write_uint8(&iter, 0, SimplyACmd_accelTap); dict_write_uint8(&iter, 1, axis); dict_write_int8(&iter, 2, direction); - return add_packet(self, buffer, length); + return add_dict(self, buffer, length); } bool simply_msg_accel_data(SimplyMsg *self, AccelData *data, uint32_t num_samples, int32_t transaction_id) { @@ -948,7 +984,7 @@ static bool send_menu_item_retry(SimplyMsg *self, SimplyACmd type, uint16_t sect DictionaryIterator iter; dict_write_begin(&iter, buffer, length); write_menu_item(&iter, type, section, index); - return add_packet(self, buffer, length); + return add_dict(self, buffer, length); } bool simply_msg_menu_get_section(SimplyMsg *self, uint16_t index) { @@ -982,6 +1018,6 @@ bool simply_msg_animate_element_done(SimplyMsg *self, uint16_t index) { dict_write_begin(&iter, buffer, length); dict_write_uint8(&iter, 0, SimplyACmd_stageAnimateDone); dict_write_uint16(&iter, 1, index); - return add_packet(self, buffer, length); + return add_dict(self, buffer, length); } diff --git a/src/simply/simply_msg.h b/src/simply/simply_msg.h index 4bf61dbd..9096c81d 100644 --- a/src/simply/simply_msg.h +++ b/src/simply/simply_msg.h @@ -20,7 +20,8 @@ typedef struct SimplyPacket SimplyPacket; struct SimplyPacket { List1Node node; - size_t length; + uint16_t length; + bool is_dict; void *buffer; }; From 1e2ad376a9df7be66d0cd154f9df9db5ab1c12d6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 01:40:32 -0700 Subject: [PATCH 352/791] Change window show, hide event messages to packets --- src/js/ui/simply-pebble.js | 23 +++++++++++++++-------- src/simply/simply_msg.c | 37 ++++++++++++++++++++++++------------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 0113a4d4..2970777f 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -233,6 +233,16 @@ var WindowHidePacket = new struct([ ['uint32', 'id'], ]); +var WindowShowEventPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], +]); + +var WindowHideEventPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], +]); + var WindowPropsPacket = new struct([ [Packet, 'packet'], ['uint32', 'id'], @@ -438,6 +448,8 @@ var CommandPackets = [ Packet, WindowShowPacket, WindowHidePacket, + WindowShowEventPacket, + WindowHideEventPacket, WindowPropsPacket, WindowButtonConfigPacket, WindowActionBarPacket, @@ -1020,6 +1032,9 @@ SimplyPebble.onPacket = function(data) { var packet = CommandPackets[Packet.type()]; packet._view = Packet._view; switch (packet) { + case WindowHideEventPacket: + WindowStack.emitHide(packet.id()); + break; case ClickPacket: Window.emitClick('click', buttonTypes[packet.button()]); break; @@ -1046,14 +1061,6 @@ SimplyPebble.onAppMessage = function(e) { } switch (command.name) { - case 'windowHide': - WindowStack.emitHide(payload[1]); - break; - case 'click': - case 'longClick': - var button = buttonTypes[payload[1]]; - Window.emitClick(command.name, button); - break; case 'accelTap': var axis = accelAxes[payload[1]]; Accel.emitAccelTap(axis, payload[2]); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index c751ef68..54ad3c31 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -25,6 +25,8 @@ typedef enum Command Command; enum Command { CommandWindowShow = 1, CommandWindowHide, + CommandWindowShowEvent, + CommandWindowHideEvent, CommandWindowProps, CommandWindowButtonConfig, CommandWindowActionBar, @@ -88,13 +90,21 @@ struct __attribute__((__packed__)) WindowShowPacket { bool pushing; }; -typedef struct WindowHidePacket WindowHidePacket; +typedef struct WindowSignalPacket WindowSignalPacket; -struct __attribute__((__packed__)) WindowHidePacket { +struct __attribute__((__packed__)) WindowSignalPacket { Packet packet; uint32_t id; }; +typedef WindowSignalPacket WindowHidePacket; + +typedef WindowHidePacket WindowEventPacket; + +typedef WindowEventPacket WindowShowEventPacket; + +typedef WindowEventPacket WindowHideEventPacket; + typedef struct WindowPropsPacket WindowPropsPacket; struct __attribute__((__packed__)) WindowPropsPacket { @@ -673,6 +683,10 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandWindowHide: handle_window_hide_packet(simply, packet); break; + case CommandWindowShowEvent: + break; + case CommandWindowHideEvent: + break; case CommandWindowProps: handle_window_props_packet(simply, packet); break; @@ -911,25 +925,22 @@ bool simply_msg_long_click(SimplyMsg *self, ButtonId button) { return send_click(self, CommandLongClick, button); } -bool send_window(SimplyMsg *self, SimplyACmd type, uint32_t id) { - size_t length = dict_calc_buffer_size(2, 1, 4); - void *buffer = malloc0(length); - if (!buffer) { +bool send_window(SimplyMsg *self, Command type, uint32_t id) { + size_t length; + WindowEventPacket *packet = malloc0(length = sizeof(*packet)); + if (!packet) { return false; } - DictionaryIterator iter; - dict_write_begin(&iter, buffer, length); - dict_write_uint8(&iter, 0, type); - dict_write_uint32(&iter, 1, id); - return add_dict(self, buffer, length); + packet->id = id; + return add_packet(self, (Packet*) packet, type, length); } bool simply_msg_window_show(SimplyMsg *self, uint32_t id) { - return send_window(self, SimplyACmd_windowShow, id); + return send_window(self, CommandWindowShowEvent, id); } bool simply_msg_window_hide(SimplyMsg *self, uint32_t id) { - return send_window(self, SimplyACmd_windowHide, id); + return send_window(self, CommandWindowHideEvent, id); } bool simply_msg_accel_tap(SimplyMsg *self, AccelAxisType axis, int32_t direction) { From 35d03b31a8eb6cdfc427ed76615eca40c9fdfcb7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 01:53:15 -0700 Subject: [PATCH 353/791] Change accel tap message to packet --- src/js/ui/simply-pebble.js | 14 ++++++++++---- src/simply/simply_msg.c | 26 +++++++++++++++++--------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 2970777f..dc9ce165 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -309,6 +309,12 @@ var VibePacket = new struct([ ['uint8', 'type', VibeType], ]); +var AccelTapPacket = new struct([ + [Packet, 'packet'], + ['uint8', 'axis'], + ['int8', 'direction'], +]); + var AccelPeekPacket = new struct([ [Packet, 'packet'], ]); @@ -461,6 +467,7 @@ var CommandPackets = [ CardImagePacket, CardStylePacket, VibePacket, + AccelTapPacket, AccelPeekPacket, AccelConfigPacket, MenuClearPacket, @@ -1041,6 +1048,9 @@ SimplyPebble.onPacket = function(data) { case LongClickPacket: Window.emitClick('longClick', buttonTypes[packet.button()]); break; + case AccelTapPacket: + Accel.emitAccelTap(accelAxes[packet.axis()], packet.direction()); + break; } }; @@ -1061,10 +1071,6 @@ SimplyPebble.onAppMessage = function(e) { } switch (command.name) { - case 'accelTap': - var axis = accelAxes[payload[1]]; - Accel.emitAccelTap(axis, payload[2]); - break; case 'accelData': var transactionId = payload[1]; var samples = payload[2]; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 54ad3c31..46665da8 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -38,6 +38,7 @@ enum Command { CommandCardImage, CommandCardStyle, CommandVibe, + CommandAccelTap, CommandAccelPeek, CommandAccelConfig, CommandMenuClear, @@ -187,6 +188,14 @@ struct __attribute__((__packed__)) VibePacket { VibeType type:8; }; +typedef struct AccelTapPacket AccelTapPacket; + +struct __attribute__((__packed__)) AccelTapPacket { + Packet packet; + AccelAxisType axis:8; + int8_t direction; +}; + typedef Packet AccelPeekPacket; typedef struct AccelConfigPacket AccelConfigPacket; @@ -718,6 +727,8 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandVibe: handle_vibe_packet(simply, packet); break; + case CommandAccelTap: + break; case CommandAccelPeek: handle_accel_peek_packet(simply, packet); break; @@ -944,17 +955,14 @@ bool simply_msg_window_hide(SimplyMsg *self, uint32_t id) { } bool simply_msg_accel_tap(SimplyMsg *self, AccelAxisType axis, int32_t direction) { - size_t length = dict_calc_buffer_size(3, 1, 1, 1); - void *buffer = malloc0(length); - if (!buffer) { + size_t length; + AccelTapPacket *packet = malloc0(length = sizeof(*packet)); + if (!packet) { return false; } - DictionaryIterator iter; - dict_write_begin(&iter, buffer, length); - dict_write_uint8(&iter, 0, SimplyACmd_accelTap); - dict_write_uint8(&iter, 1, axis); - dict_write_int8(&iter, 2, direction); - return add_dict(self, buffer, length); + packet->axis = axis; + packet->direction = direction; + return add_packet(self, (Packet*) packet, CommandAccelTap, length); } bool simply_msg_accel_data(SimplyMsg *self, AccelData *data, uint32_t num_samples, int32_t transaction_id) { From 5b3b1f2d1e2f6f52ef0b814bee5ce723cad5b739 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 02:31:43 -0700 Subject: [PATCH 354/791] Change menu item event messages to packets --- src/js/ui/simply-pebble.js | 68 +++++++++++++++++++++++------- src/simply/simply_msg.c | 85 +++++++++++++++++++++++--------------- 2 files changed, 105 insertions(+), 48 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index dc9ce165..5b1a5d90 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -348,6 +348,11 @@ var MenuSectionPacket = new struct([ ['cstring', 'title', StringType], ]); +var MenuGetSectionPacket = new struct([ + [Packet, 'packet'], + ['uint16', 'section'], +]); + var MenuItemPacket = new struct([ [Packet, 'packet'], ['uint16', 'section'], @@ -359,11 +364,13 @@ var MenuItemPacket = new struct([ ['cstring', 'subtitle', StringType], ]); -var MenuGetSelectionPacket = new struct([ +var MenuGetItemPacket = new struct([ [Packet, 'packet'], + ['uint16', 'section'], + ['uint16', 'item'], ]); -var MenuSetSelectionPacket = new struct([ +var MenuSelectionPacket = new struct([ [Packet, 'packet'], ['uint16', 'section'], ['uint16', 'item'], @@ -371,6 +378,28 @@ var MenuSetSelectionPacket = new struct([ ['bool', 'animated', BoolType], ]); +var MenuGetSelectionPacket = new struct([ + [Packet, 'packet'], +]); + +var MenuSelectionEventPacket = new struct([ + [Packet, 'packet'], + ['uint16', 'section'], + ['uint16', 'item'], +]); + +var MenuSelectPacket = new struct([ + [Packet, 'packet'], + ['uint16', 'section'], + ['uint16', 'item'], +]); + +var MenuLongSelectPacket = new struct([ + [Packet, 'packet'], + ['uint16', 'section'], + ['uint16', 'item'], +]); + var StageClearPacket = new struct([ [Packet, 'packet'], ]); @@ -474,9 +503,14 @@ var CommandPackets = [ MenuClearSectionPacket, MenuPropsPacket, MenuSectionPacket, + MenuGetSectionPacket, MenuItemPacket, + MenuGetItemPacket, + MenuSelectionPacket, MenuGetSelectionPacket, - MenuSetSelectionPacket, + MenuSelectionEventPacket, + MenuSelectPacket, + MenuLongSelectPacket, StageClearPacket, ElementInsertPacket, ElementRemovePacket, @@ -893,7 +927,7 @@ SimplyPebble.menuSelection = function(section, item) { if (arguments.length === 0) { SimplyPebble.sendPacket(MenuGetSelectionPacket); } - SimplyPebble.sendPacket(MenuSetSelectionPacket.section(section).item(item)); + SimplyPebble.sendPacket(MenuSelectionPacket.section(section).item(item)); }; SimplyPebble.menu = function(def, clear, pushing) { @@ -1051,6 +1085,21 @@ SimplyPebble.onPacket = function(data) { case AccelTapPacket: Accel.emitAccelTap(accelAxes[packet.axis()], packet.direction()); break; + case MenuGetSectionPacket: + Menu.emitSection(packet.section()); + break; + case MenuGetItemPacket: + Menu.emitItem(packet.section(), packet.item()); + break; + case MenuSelectPacket: + Menu.emitSelect('menuSelect', packet.section(), packet.item()); + break; + case MenuLongSelectPacket: + Menu.emitSelect('menuLongSelect', packet.section(), packet.item()); + break; + case MenuSelectionEventPacket: + Menu.emitSelect('menuSelection', packet.section(), packet.item()); + break; } }; @@ -1097,17 +1146,6 @@ SimplyPebble.onAppMessage = function(e) { } } break; - case 'getMenuSection': - Menu.emitSection(payload[1]); - break; - case 'getMenuItem': - Menu.emitItem(payload[1], payload[2]); - break; - case 'menuSelect': - case 'menuLongSelect': - case 'menuSelection': - Menu.emitSelect(command.name, payload[1], payload[2]); - break; case 'stageAnimateDone': StageElement.emitAnimateDone(payload[1]); break; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 46665da8..88174189 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -45,9 +45,14 @@ enum Command { CommandMenuClearSection, CommandMenuProps, CommandMenuSection, + CommandMenuGetSection, CommandMenuItem, + CommandMenuGetItem, + CommandMenuSelection, CommandMenuGetSelection, - CommandMenuSetSelection, + CommandMenuSelectionEvent, + CommandMenuSelect, + CommandMenuLongSelect, CommandStageClear, CommandElementInsert, CommandElementRemove, @@ -245,11 +250,19 @@ struct __attribute__((__packed__)) MenuItemPacket { char buffer[]; }; +typedef struct MenuItemEventPacket MenuItemEventPacket; + +struct __attribute__((__packed__)) MenuItemEventPacket { + Packet packet; + uint16_t section; + uint16_t item; +}; + typedef Packet MenuGetSelectionPacket; -typedef struct MenuSetSelectionPacket MenuSetSelectionPacket; +typedef struct MenuSelectionPacket MenuSelectionPacket; -struct __attribute__((__packed__)) MenuSetSelectionPacket { +struct __attribute__((__packed__)) MenuSelectionPacket { Packet packet; uint16_t section; uint16_t item; @@ -574,8 +587,8 @@ static void handle_menu_get_selection_packet(Simply *simply, Packet *data) { simply_msg_send_menu_selection(simply->msg); } -static void handle_menu_set_selection_packet(Simply *simply, Packet *data) { - MenuSetSelectionPacket *packet = (MenuSetSelectionPacket*) data; +static void handle_menu_selection_packet(Simply *simply, Packet *data) { + MenuSelectionPacket *packet = (MenuSelectionPacket*) data; MenuIndex menu_index = { .section = packet->section, .row = packet->item, @@ -747,14 +760,24 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandMenuSection: handle_menu_section_packet(simply, packet); break; + case CommandMenuGetSection: + break; case CommandMenuItem: handle_menu_item_packet(simply, packet); break; + case CommandMenuGetItem: + break; + case CommandMenuSelection: + handle_menu_selection_packet(simply, packet); + break; case CommandMenuGetSelection: handle_menu_get_selection_packet(simply, packet); break; - case CommandMenuSetSelection: - handle_menu_set_selection_packet(simply, packet); + case CommandMenuSelectionEvent: + break; + case CommandMenuSelect: + break; + case CommandMenuLongSelect: break; case CommandStageClear: handle_stage_clear_packet(simply, packet); @@ -979,52 +1002,48 @@ bool simply_msg_accel_data(SimplyMsg *self, AccelData *data, uint32_t num_sample return (app_message_outbox_send() == APP_MSG_OK); } -static void write_menu_item(DictionaryIterator *iter, SimplyACmd type, uint16_t section, uint16_t index) { - dict_write_uint8(iter, 0, type); - dict_write_uint16(iter, 1, section); - dict_write_uint16(iter, 2, index); -} - -static bool send_menu_item(SimplyMsg *self, SimplyACmd type, uint16_t section, uint16_t index) { - DictionaryIterator *iter = NULL; - if (app_message_outbox_begin(&iter) != APP_MSG_OK) { - return false; - } - write_menu_item(iter, type, section, index); - return (app_message_outbox_send() == APP_MSG_OK); +static bool send_menu_item(SimplyMsg *self, Command type, uint16_t section, uint16_t item) { + MenuItemEventPacket packet = { + .packet.type = type, + .packet.length = sizeof(packet), + .section = section, + .item = item, + }; + SimplyPacket packet_node = { + .length = sizeof(packet), + .buffer = &packet, + }; + return send_msg(&packet_node); } -static bool send_menu_item_retry(SimplyMsg *self, SimplyACmd type, uint16_t section, uint16_t index) { - size_t length = dict_calc_buffer_size(3, 1, 2, 2); - void *buffer = malloc0(length); - if (!buffer) { +static bool send_menu_item_retry(SimplyMsg *self, Command type, uint16_t section, uint16_t index) { + size_t length; + MenuItemEventPacket *packet = malloc0(length = sizeof(*packet)); + if (!packet) { return false; } - DictionaryIterator iter; - dict_write_begin(&iter, buffer, length); - write_menu_item(&iter, type, section, index); - return add_dict(self, buffer, length); + return add_packet(self, (Packet*) packet, type, length); } bool simply_msg_menu_get_section(SimplyMsg *self, uint16_t index) { - return send_menu_item(self, SimplyACmd_getMenuSection, index, 0); + return send_menu_item(self, CommandMenuGetSection, index, 0); } bool simply_msg_menu_get_item(SimplyMsg *self, uint16_t section, uint16_t index) { - return send_menu_item(self, SimplyACmd_getMenuItem, section, index); + return send_menu_item(self, CommandMenuGetItem, section, index); } bool simply_msg_menu_select_click(SimplyMsg *self, uint16_t section, uint16_t index) { - return send_menu_item_retry(self, SimplyACmd_menuSelect, section, index); + return send_menu_item_retry(self, CommandMenuSelect, section, index); } bool simply_msg_menu_select_long_click(SimplyMsg *self, uint16_t section, uint16_t index) { - return send_menu_item_retry(self, SimplyACmd_menuLongSelect, section, index); + return send_menu_item_retry(self, CommandMenuLongSelect, section, index); } bool simply_msg_send_menu_selection(SimplyMsg *self) { MenuIndex menu_index = simply_menu_get_selection(self->simply->menu); - return send_menu_item_retry(self, SimplyACmd_menuSelection, menu_index.section, menu_index.row); + return send_menu_item_retry(self, CommandMenuSelectionEvent, menu_index.section, menu_index.row); } bool simply_msg_animate_element_done(SimplyMsg *self, uint16_t index) { From 1e731e970c0103df5650d16265b3c54765451e99 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 02:46:16 -0700 Subject: [PATCH 355/791] Change element animate done message to packet --- src/js/ui/element.js | 12 ++++++---- src/js/ui/simply-pebble.js | 18 ++++++++++++--- src/simply/simply_msg.c | 47 ++++++++++++++------------------------ src/simply/simply_msg.h | 3 +-- src/simply/simply_stage.c | 3 +-- 5 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/js/ui/element.js b/src/js/ui/element.js index b42745cc..ffdc27f2 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -101,13 +101,15 @@ StageElement.prototype.dequeue = function() { callback.call(this, this.dequeue.bind(this)); }; -StageElement.emitAnimateDone = function(index) { - if (index === -1) { return; } +StageElement.emitAnimateDone = function(id) { var wind = WindowStack.top(); if (!wind || !wind._dynamic) { return; } - var element = wind.at(index); - if (!element) { return; } - element.dequeue(); + wind.each(function(element) { + if (element._id() === id) { + element.dequeue(); + return false; + } + }); }; module.exports = StageElement; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 5b1a5d90..b4a9dd30 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -479,6 +479,11 @@ var ElementAnimatePacket = new struct([ ['uint8', 'easing', AnimationCurve], ]); +var ElementAnimateDonePacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], +]); + var CommandPackets = [ Packet, WindowShowPacket, @@ -520,6 +525,7 @@ var CommandPackets = [ ElementTextStylePacket, ElementImagePacket, ElementAnimatePacket, + ElementAnimateDonePacket, ]; var setStageParams = [{ @@ -1071,6 +1077,12 @@ var toArrayBuffer = function(array, length) { SimplyPebble.onPacket = function(data) { Packet._view = toArrayBuffer(data); var packet = CommandPackets[Packet.type()]; + + if (!packet) { + console.log('Received unknown packet: ' + JSON.stringify(data)); + return; + } + packet._view = Packet._view; switch (packet) { case WindowHideEventPacket: @@ -1100,6 +1112,9 @@ SimplyPebble.onPacket = function(data) { case MenuSelectionEventPacket: Menu.emitSelect('menuSelection', packet.section(), packet.item()); break; + case ElementAnimateDonePacket: + StageElement.emitAnimateDone(packet.id()); + break; } }; @@ -1146,9 +1161,6 @@ SimplyPebble.onAppMessage = function(e) { } } break; - case 'stageAnimateDone': - StageElement.emitAnimateDone(payload[1]); - break; } }; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 88174189..8998e23b 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -62,6 +62,7 @@ enum Command { CommandElementTextStyle, CommandElementImage, CommandElementAnimate, + CommandElementAnimateDone, }; typedef enum WindowType WindowType; @@ -346,6 +347,13 @@ struct __attribute__((__packed__)) ElementAnimatePacket { AnimationCurve curve:8; }; +typedef struct ElementAnimateDonePacket ElementAnimateDonePacket; + +struct __attribute__((__packed__)) ElementAnimateDonePacket { + Packet packet; + uint32_t id; +}; + typedef enum SimplyACmd SimplyACmd; enum SimplyACmd { @@ -806,6 +814,8 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandElementAnimate: handle_element_animate_packet(simply, packet); break; + case CommandElementAnimateDone: + break; } } @@ -882,11 +892,7 @@ static bool send_msg(SimplyPacket *packet) { if (app_message_outbox_begin(&iter) != APP_MSG_OK) { return false; } - if (packet->is_dict) { - dict_copy_from_buffer(iter, packet->buffer, packet->length); - } else { - dict_write_data(iter, 0, packet->buffer, packet->length); - } + dict_write_data(iter, 0, packet->buffer, packet->length); return (app_message_outbox_send() == APP_MSG_OK); } @@ -925,22 +931,6 @@ static SimplyPacket *add_packet(SimplyMsg *self, Packet *buffer, Command type, s return packet; } -static SimplyPacket *add_dict(SimplyMsg *self, void *buffer, size_t length) { - SimplyPacket *packet = malloc0(sizeof(*packet)); - if (!packet) { - free(buffer); - return NULL; - } - *packet = (SimplyPacket) { - .is_dict = true, - .length = length, - .buffer = buffer, - }; - list1_append(&self->queue, &packet->node); - send_msg_retry(self); - return packet; -} - static bool send_click(SimplyMsg *self, Command type, ButtonId button) { size_t length; ClickPacket *packet = malloc0(length = sizeof(*packet)); @@ -1046,16 +1036,13 @@ bool simply_msg_send_menu_selection(SimplyMsg *self) { return send_menu_item_retry(self, CommandMenuSelectionEvent, menu_index.section, menu_index.row); } -bool simply_msg_animate_element_done(SimplyMsg *self, uint16_t index) { - size_t length = dict_calc_buffer_size(2, 1, 2); - void *buffer = malloc0(length); - if (!buffer) { +bool simply_msg_animate_element_done(SimplyMsg *self, uint32_t id) { + size_t length; + ElementAnimateDonePacket *packet = malloc0(length = sizeof(*packet)); + if (!packet) { return false; } - DictionaryIterator iter; - dict_write_begin(&iter, buffer, length); - dict_write_uint8(&iter, 0, SimplyACmd_stageAnimateDone); - dict_write_uint16(&iter, 1, index); - return add_dict(self, buffer, length); + packet->id = id; + return add_packet(self, (Packet*) packet, CommandElementAnimateDone, length); } diff --git a/src/simply/simply_msg.h b/src/simply/simply_msg.h index 9096c81d..1973a6e5 100644 --- a/src/simply/simply_msg.h +++ b/src/simply/simply_msg.h @@ -21,7 +21,6 @@ typedef struct SimplyPacket SimplyPacket; struct SimplyPacket { List1Node node; uint16_t length; - bool is_dict; void *buffer; }; @@ -45,4 +44,4 @@ bool simply_msg_menu_select_long_click(SimplyMsg *self, uint16_t section, uint16 bool simply_msg_menu_hide(SimplyMsg *self, uint16_t section, uint16_t index); bool simply_msg_send_menu_selection(SimplyMsg *self); -bool simply_msg_animate_element_done(SimplyMsg *self, uint16_t index); +bool simply_msg_animate_element_done(SimplyMsg *self, uint32_t id); diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 9b22c20d..fd7b41eb 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -239,8 +239,7 @@ static void animation_stopped(Animation *base_animation, bool finished, void *co } SimplyElementCommon *element = animation->element; destroy_animation(self, animation); - simply_msg_animate_element_done(self->window.simply->msg, - list1_index(self->stage_layer.elements, &element->node)); + simply_msg_animate_element_done(self->window.simply->msg, element->id); } SimplyAnimation *simply_stage_animate_element(SimplyStage *self, From 0431e21017297dab74a1a1c450adfb56688c8e69 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 03:55:34 -0700 Subject: [PATCH 356/791] Add struct.js uint64 support --- src/js/lib/struct.js | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/js/lib/struct.js b/src/js/lib/struct.js index be471006..f168467f 100644 --- a/src/js/lib/struct.js +++ b/src/js/lib/struct.js @@ -53,6 +53,23 @@ for (var k in struct.types) { struct.types.bool = struct.types.uint8; +struct.types.uint64.get = function(offset, little) { + var buffer = this._view; + var a = buffer.getUint32(offset, little); + var b = buffer.getUint32(offset + 4, little); + this._advance = 8; + return ((little ? b : a) << 32) + (little ? a : b); +}; + +struct.types.uint64.set = function(offset, value, little) { + var a = value & 0xFFFFFFFF; + var b = (value >> 32) & 0xFFFFFFFF; + var buffer = this._view; + buffer.setUint32(offset, little ? a : b, little); + buffer.setUint32(offset + 4, little ? b : a, little); + this._advance = 8; +}; + struct.types.cstring.get = function(offset) { var chars = []; var buffer = this._view; @@ -131,18 +148,18 @@ struct.prototype._makeAccessor = function(field) { throw new Error('dynamic field requires sequential access'); } } else { - this._cursor = this._offset + field.index; + this._cursor = field.index; } this._access = field; var result = this; if (arguments.length === 0) { - result = type.get.call(this, this._cursor, this._littleEndian); + result = type.get.call(this, this._offset + this._cursor, this._littleEndian); this._value = result; } else { if (field.transform) { value = field.transform(value, field); } - type.set.call(this, this._cursor, value, this._littleEndian); + type.set.call(this, this._offset + this._cursor, value, this._littleEndian); this._value = value; } this._cursor += this._advance; From 7b284b86e2480ad5db8ca5faadf636dca36f395b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 03:55:59 -0700 Subject: [PATCH 357/791] Change accel data message to packet --- src/js/ui/simply-pebble.js | 78 ++++++++++++++++++++------------------ src/simply/simply_accel.c | 2 +- src/simply/simply_msg.c | 63 ++++++++++++++++++++---------- src/simply/simply_msg.h | 4 +- 4 files changed, 86 insertions(+), 61 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index b4a9dd30..5e1587a0 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -309,12 +309,6 @@ var VibePacket = new struct([ ['uint8', 'type', VibeType], ]); -var AccelTapPacket = new struct([ - [Packet, 'packet'], - ['uint8', 'axis'], - ['int8', 'direction'], -]); - var AccelPeekPacket = new struct([ [Packet, 'packet'], ]); @@ -326,6 +320,26 @@ var AccelConfigPacket = new struct([ ['bool', 'subscribe', BoolType], ]); +var AccelData = new struct([ + ['int16', 'x'], + ['int16', 'y'], + ['int16', 'z'], + ['bool', 'vibe'], + ['uint64', 'time'], +]); + +var AccelDataPacket = new struct([ + [Packet, 'packet'], + ['bool', 'peek'], + ['uint8', 'samples'], +]); + +var AccelTapPacket = new struct([ + [Packet, 'packet'], + ['uint8', 'axis'], + ['int8', 'direction'], +]); + var MenuClearPacket = new struct([ [Packet, 'packet'], ]); @@ -501,9 +515,10 @@ var CommandPackets = [ CardImagePacket, CardStylePacket, VibePacket, - AccelTapPacket, AccelPeekPacket, AccelConfigPacket, + AccelDataPacket, + AccelTapPacket, MenuClearPacket, MenuClearSectionPacket, MenuPropsPacket, @@ -1094,6 +1109,25 @@ SimplyPebble.onPacket = function(data) { case LongClickPacket: Window.emitClick('longClick', buttonTypes[packet.button()]); break; + case AccelDataPacket: + var samples = packet.samples(); + var accels = []; + AccelData._view = packet._view; + AccelData._offset = packet._size; + for (var i = 0; i < samples; ++i) { + accels.push(AccelData.prop()); + AccelData._offset += AccelData._size; + } + if (!packet.peek()) { + Accel.emitAccelData(accels); + } else { + var handlers = accelListeners; + accelListeners = []; + for (var j = 0, jj = handlers.length; j < jj; ++j) { + Accel.emitAccelData(accels, handlers[j]); + } + } + break; case AccelTapPacket: Accel.emitAccelTap(accelAxes[packet.axis()], packet.direction()); break; @@ -1133,35 +1167,7 @@ SimplyPebble.onAppMessage = function(e) { console.log('Received unknown payload: ' + JSON.stringify(payload)); return; } - - switch (command.name) { - case 'accelData': - var transactionId = payload[1]; - var samples = payload[2]; - var data = payload[3]; - var accels = []; - for (var i = 0; i < samples; i++) { - var pos = i * 15; - var accel = { - x: readInt(data, 2, pos, true), - y: readInt(data, 2, pos + 2, true), - z: readInt(data, 2, pos + 4, true), - vibe: readInt(data, 1, pos + 6) ? true : false, - time: readInt(data, 8, pos + 7), - }; - accels[i] = accel; - } - if (typeof transactionId === 'undefined') { - Accel.emitAccelData(accels); - } else { - var handlers = accelListeners; - accelListeners = []; - for (var j = 0, jj = handlers.length; j < jj; ++j) { - Accel.emitAccelData(accels, handlers[j]); - } - } - break; - } }; module.exports = SimplyPebble; + diff --git a/src/simply/simply_accel.c b/src/simply/simply_accel.c index 6ce9e9fb..5201a423 100644 --- a/src/simply/simply_accel.c +++ b/src/simply/simply_accel.c @@ -9,7 +9,7 @@ SimplyAccel *s_accel = NULL; static void handle_accel_data(AccelData *data, uint32_t num_samples) { - simply_msg_accel_data(s_accel->simply->msg, data, num_samples, TRANSACTION_ID_INVALID); + simply_msg_accel_data(s_accel->simply->msg, data, num_samples, false); } void simply_accel_set_data_subscribe(SimplyAccel *self, bool subscribe) { diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 8998e23b..98b2c6aa 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -38,9 +38,10 @@ enum Command { CommandCardImage, CommandCardStyle, CommandVibe, - CommandAccelTap, CommandAccelPeek, CommandAccelConfig, + CommandAccelData, + CommandAccelTap, CommandMenuClear, CommandMenuClearSection, CommandMenuProps, @@ -194,6 +195,17 @@ struct __attribute__((__packed__)) VibePacket { VibeType type:8; }; +typedef Packet AccelPeekPacket; + +typedef struct AccelConfigPacket AccelConfigPacket; + +struct __attribute__((__packed__)) AccelConfigPacket { + Packet packet; + uint16_t num_samples; + AccelSamplingRate rate:8; + bool data_subscribed; +}; + typedef struct AccelTapPacket AccelTapPacket; struct __attribute__((__packed__)) AccelTapPacket { @@ -202,15 +214,13 @@ struct __attribute__((__packed__)) AccelTapPacket { int8_t direction; }; -typedef Packet AccelPeekPacket; +typedef struct AccelDataPacket AccelDataPacket; -typedef struct AccelConfigPacket AccelConfigPacket; - -struct __attribute__((__packed__)) AccelConfigPacket { +struct __attribute__((__packed__)) AccelDataPacket { Packet packet; - uint16_t num_samples; - AccelSamplingRate rate:8; - bool data_subscribed; + bool is_peek; + uint8_t num_samples; + AccelData data[]; }; typedef Packet MenuClearPacket; @@ -537,7 +547,7 @@ static void accel_peek_timer_callback(void *context) { Simply *simply = context; AccelData data = { .x = 0 }; simply_accel_peek(simply->accel, &data); - if (!simply_msg_accel_data(simply->msg, &data, 1, 0)) { + if (!simply_msg_accel_data(simply->msg, &data, 1, true)) { app_timer_register(10, accel_peek_timer_callback, simply); } } @@ -748,14 +758,16 @@ static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { case CommandVibe: handle_vibe_packet(simply, packet); break; - case CommandAccelTap: - break; case CommandAccelPeek: handle_accel_peek_packet(simply, packet); break; case CommandAccelConfig: handle_accel_config_packet(simply, packet); break; + case CommandAccelData: + break; + case CommandAccelTap: + break; case CommandMenuClear: handle_menu_clear_packet(simply, packet); break; @@ -978,18 +990,27 @@ bool simply_msg_accel_tap(SimplyMsg *self, AccelAxisType axis, int32_t direction return add_packet(self, (Packet*) packet, CommandAccelTap, length); } -bool simply_msg_accel_data(SimplyMsg *self, AccelData *data, uint32_t num_samples, int32_t transaction_id) { - DictionaryIterator *iter = NULL; - if (app_message_outbox_begin(&iter) != APP_MSG_OK) { +bool simply_msg_accel_data(SimplyMsg *self, AccelData *data, uint32_t num_samples, bool is_peek) { + size_t data_length = sizeof(AccelData) * num_samples; + size_t length; + AccelDataPacket *packet = malloc(length = sizeof(AccelDataPacket) + data_length); + if (!packet) { return false; } - dict_write_uint8(iter, 0, SimplyACmd_accelData); - if (transaction_id >= 0) { - dict_write_int32(iter, 1, transaction_id); - } - dict_write_uint8(iter, 2, num_samples); - dict_write_data(iter, 3, (uint8_t*) data, sizeof(*data) * num_samples); - return (app_message_outbox_send() == APP_MSG_OK); + packet->packet = (Packet) { + .type = CommandAccelData, + .length = length, + }; + packet->is_peek = is_peek; + packet->num_samples = num_samples; + memcpy(packet->data, data, data_length); + SimplyPacket packet_node = { + .length = length, + .buffer = packet, + }; + bool result = send_msg(&packet_node); + free(packet); + return result; } static bool send_menu_item(SimplyMsg *self, Command type, uint16_t section, uint16_t item) { diff --git a/src/simply/simply_msg.h b/src/simply/simply_msg.h index 1973a6e5..8eeebef1 100644 --- a/src/simply/simply_msg.h +++ b/src/simply/simply_msg.h @@ -6,8 +6,6 @@ #include -#define TRANSACTION_ID_INVALID (-1) - typedef struct SimplyMsg SimplyMsg; struct SimplyMsg { @@ -35,7 +33,7 @@ bool simply_msg_window_show(SimplyMsg *self, uint32_t id); bool simply_msg_window_hide(SimplyMsg *self, uint32_t id); bool simply_msg_accel_tap(SimplyMsg *self, AccelAxisType axis, int32_t direction); -bool simply_msg_accel_data(SimplyMsg *self, AccelData *accel, uint32_t num_samples, int32_t transaction_id); +bool simply_msg_accel_data(SimplyMsg *self, AccelData *accel, uint32_t num_samples, bool is_peek); bool simply_msg_menu_get_section(SimplyMsg *self, uint16_t index); bool simply_msg_menu_get_item(SimplyMsg *self, uint16_t section, uint16_t index); From 2a53851ac293b07cb7067629eefc50a53d394ec0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 04:01:44 -0700 Subject: [PATCH 358/791] Remove legacy dictionary based messaging protocol --- src/js/ui/simply-pebble.js | 230 +------------------------------------ src/simply/simply_msg.c | 78 ------------- 2 files changed, 1 insertion(+), 307 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 5e1587a0..a9cf4a78 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -543,147 +543,6 @@ var CommandPackets = [ ElementAnimateDonePacket, ]; -var setStageParams = [{ - name: 'clear', -}]; - -var commands = [{ - name: 'setWindow', -}, { - name: 'windowShow', -}, { - name: 'windowHide', -}, { - name: 'setCard', -}, { - name: 'click', - params: [{ - name: 'button', - }], -}, { - name: 'longClick', - params: [{ - name: 'button', - }], -}, { - name: 'accelTap', - params: [{ - name: 'axis', - }, { - name: 'direction', - }], -}, { - name: 'vibe', - params: [{ - name: 'type', - }], -}, { - name: 'accelData', - params: [{ - name: 'transactionId', - }, { - name: 'numSamples', - }, { - name: 'accelData', - }], -}, { - name: 'getAccelData', -}, { - name: 'configAccelData', -}, { - name: 'configButtons', - params: [{ - name: 'back', - }, { - name: 'up', - }, { - name: 'select', - }, { - name: 'down', - }], -}, { - name: 'setMenu', -}, { - name: 'setMenuSection', -}, { - name: 'getMenuSection', - params: [{ - name: 'section', - }], -}, { - name: 'setMenuItem', -}, { - name: 'getMenuItem', - params: [{ - name: 'section', - }, { - name: 'item', - }], -}, { - name: 'menuSelect', - params: [{ - name: 'section', - }, { - name: 'item', - }], -}, { - name: 'menuLongSelect', - params: [{ - name: 'section', - }, { - name: 'item', - }], -}, { - name: 'menuSelection', -}, { - name: 'image', - params: [{ - name: 'id', - }, { - name: 'width', - }, { - name: 'height', - }, { - name: 'pixels', - }], -}, { - name: 'setStage', - params: setStageParams, -}, { - name: 'stageElement', -}, { - name: 'stageRemove', -}, { - name: 'stageAnimate', -}, { - name: 'stageAnimateDone', - params: [{ - name: 'index', - }], -}]; - -// Build the commandMap and map each command to an integer. - -var commandMap = {}; - -for (var i = 0, ii = commands.length; i < ii; ++i) { - var command = commands[i]; - commandMap[command.name] = command; - command.id = i; - - var params = command.params; - if (!params) { - continue; - } - - var paramMap = command.paramMap = {}; - for (var j = 0, jj = params.length; j < jj; ++j) { - var param = params[j]; - paramMap[param.name] = param; - param.id = j + 1; - } -} - var accelAxes = [ 'x', 'y', @@ -696,21 +555,6 @@ var clearFlagMap = { image: (1 << 2), }; -var actionBarTypeMap = { - up: 'actionUp', - select: 'actionSelect', - down: 'actionDown', - backgroundColor: 'actionBackgroundColor', -}; - -var menuSelectionTypeMap = { - section: 'selectionSection', - item: 'selectionItem', - align: 'selectionAlign', - animated: 'selectionAnimated', -}; - - /** * SimplyPebble object provides the actual methods to communicate with Pebble. * @@ -727,40 +571,6 @@ SimplyPebble.init = function() { simply.impl = SimplyPebble; }; - -var toParam = function(param, v) { - if (param.type === String) { - v = typeof v !== 'undefined' ? v.toString() : ''; - } else if (param.type === Boolean) { - v = v ? 1 : 0; - } else if (typeof param.type === 'function') { - v = param.type(v); - } - return v; -}; - -var setMessage = function(message, command, def, typeMap) { - var paramMap = command.paramMap; - for (var k in def) { - var paramName = typeMap && typeMap[k] || k; - if (!paramName) { continue; } - var param = paramMap[paramName]; - if (param) { - message[param.id] = toParam(param, def[k]); - } - } - return message; -}; - -var makeMessage = function(command, def) { - var message = {}; - message[0] = command.id; - if (def) { - setMessage(message, command, def); - } - return message; -}; - SimplyPebble.sendMessage = (function() { var queue = []; var sending = false; @@ -1055,31 +865,6 @@ SimplyPebble.stage = function(def, clear, pushing) { } }; -var setActionMessage = function(message, command, actionDef) { - if (actionDef) { - if (typeof actionDef === 'boolean') { - actionDef = { action: actionDef }; - } - setMessage(message, command, actionDef, actionBarTypeMap); - } - return message; -}; - -var readInt = function(message, width, pos, signed) { - var value = 0; - pos = pos || 0; - for (var i = 0; i < width; ++i) { - value += (message[pos + i] & 0xFF) << (i * 8); - } - if (signed) { - var mask = 1 << (width * 8 - 1); - if (value & mask) { - value = value - (((mask - 1) << 1) + 1); - } - } - return value; -}; - var toArrayBuffer = function(array, length) { length = length || array.length; var copy = new DataView(new ArrayBuffer(length)); @@ -1153,20 +938,7 @@ SimplyPebble.onPacket = function(data) { }; SimplyPebble.onAppMessage = function(e) { - var payload = e.payload; - var code = payload[0]; - var command; - - if (code instanceof Array) { - return SimplyPebble.onPacket(code); - } else { - command = commands[code]; - } - - if (!command) { - console.log('Received unknown payload: ' + JSON.stringify(payload)); - return; - } + SimplyPebble.onPacket(e.payload[0]); }; module.exports = SimplyPebble; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 98b2c6aa..da7bd668 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -364,84 +364,6 @@ struct __attribute__((__packed__)) ElementAnimateDonePacket { uint32_t id; }; -typedef enum SimplyACmd SimplyACmd; - -enum SimplyACmd { - SimplyACmd_setWindow = 0, - SimplyACmd_windowShow, - SimplyACmd_windowHide, - SimplyACmd_setUi, - SimplyACmd_click, - SimplyACmd_longClick, - SimplyACmd_accelTap, - SimplyACmd_vibe, - SimplyACmd_accelData, - SimplyACmd_getAccelData, - SimplyACmd_configAccelData, - SimplyACmd_configButtons, - SimplyACmd_setMenu, - SimplyACmd_setMenuSection, - SimplyACmd_getMenuSection, - SimplyACmd_setMenuItem, - SimplyACmd_getMenuItem, - SimplyACmd_menuSelect, - SimplyACmd_menuLongSelect, - SimplyACmd_menuSelection, - SimplyACmd_image, - SimplyACmd_setStage, - SimplyACmd_stageElement, - SimplyACmd_stageRemove, - SimplyACmd_stageAnimate, - SimplyACmd_stageAnimateDone, -}; - -typedef enum SimplySetUiParam SimplySetUiParam; - -enum SimplySetUiParam { - SetUi_clear = 1, - SetUi_title, - SetUi_subtitle, - SetUi_body, - SetUi_icon, - SetUi_subicon, - SetUi_image, - SetUi_style, -}; - -typedef enum SimplySetMenuParam SimplySetMenuParam; - -enum SimplySetMenuParam { - SetMenu_clear = 1, - SetMenu_sections, - SetMenu_selectionSection, - SetMenu_selectionItem, - SetMenu_selectionAlign, - SetMenu_selectionAnimated, -}; - -typedef enum ElementParam ElementParam; - -enum ElementParam { - ElementId = 1, - ElementType, - ElementIndex, - ElementX, - ElementY, - ElementWidth, - ElementHeight, - ElementBackgroundColor, - ElementBorderColor, - ElementRadius, - ElementText, - ElementTextFont, - ElementTextColor, - ElementTextOverflow, - ElementTextAlignment, - ElementTextUpdateTimeUnit, - ElementImage, - ElementCompositing, -}; - static bool s_has_communicated = false; bool simply_msg_has_communicated() { From 897e26435a63661dd547745a2245cd201de8d6e8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 04:12:53 -0700 Subject: [PATCH 359/791] Refactor simply pebble send message into message queue class --- src/js/ui/simply-pebble.js | 66 +++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index a9cf4a78..c46135d2 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -561,6 +561,8 @@ var clearFlagMap = { * It's an implementation of an abstract interface used by all the other classes. */ +var state; + var SimplyPebble = {}; SimplyPebble.init = function() { @@ -569,37 +571,55 @@ SimplyPebble.init = function() { // Register this implementation as the one currently in use simply.impl = SimplyPebble; + + state = SimplyPebble.state = {}; + + // Initialize the app message queue + state.messageQueue = new MessageQueue(); }; -SimplyPebble.sendMessage = (function() { - var queue = []; - var sending = false; +/** + * MessageQueue is an app message queue that guarantees delivery and order. + */ +var MessageQueue = function() { + this._queue = []; + this._sending = false; - function stop() { - sending = false; - } + this._consume = this.consume.bind(this); + this._cycle = this.cycle.bind(this); +}; - function consume() { - queue.splice(0, 1); - if (queue.length === 0) { return stop(); } - cycle(); - } +MessageQueue.prototype.stop = function() { + this._sending = false; +}; - function cycle() { - var head = queue[0]; - if (!head) { return stop(); } - Pebble.sendAppMessage(head, consume, cycle); +MessageQueue.prototype.consume = function() { + this._queue.splice(0, 1); + if (this._queue.length === 0) { + return this.stop(); } + this.cycle(); +}; - function send(message) { - queue.push(message); - if (sending) { return; } - sending = true; - cycle(); +MessageQueue.prototype.cycle = function() { + if (!this._sending) { + return; + } + var head = this._queue[0]; + if (!head) { + return this.stop(); } + Pebble.sendAppMessage(head, this._consume, this._cycle); +}; - return send; -})(); +MessageQueue.prototype.send = function(message) { + this._queue.push(message); + if (this._sending) { + return; + } + this._sending = true; + this.cycle(); +}; var toByteArray = function(packet) { var type = CommandPackets.indexOf(packet); @@ -617,7 +637,7 @@ var toByteArray = function(packet) { }; SimplyPebble.sendPacket = function(packet) { - SimplyPebble.sendMessage({ 0: toByteArray(packet) }); + state.messageQueue.send({ 0: toByteArray(packet) }); }; SimplyPebble.windowShow = function(def) { From 523dd527822951506c73151d7f4ccea706093b7d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 04:47:21 -0700 Subject: [PATCH 360/791] Add multiple packet per message support for js to c - Add PacketQueue class for handling packet bursts - Change c received_callback to handle multiple packets --- src/js/ui/simply-pebble.js | 32 +++++++++++++++++++++++++++++++- src/simply/simply_msg.c | 17 ++++++++++++++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index c46135d2..c6328603 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -576,6 +576,9 @@ SimplyPebble.init = function() { // Initialize the app message queue state.messageQueue = new MessageQueue(); + + // Initialize the packet queue + state.packetQueue = new PacketQueue(); }; /** @@ -636,8 +639,35 @@ var toByteArray = function(packet) { return byteArray; }; +/** + * PacketQueue is a packet queue that combines multiple packets into a single packet. + * This reduces latency caused by the time spacing between each app message. + */ +var PacketQueue = function() { + this._message = []; + + this._send = this.send.bind(this); +}; + +PacketQueue.prototype._maxPayloadSize = 2048 - 20; + +PacketQueue.prototype.add = function(packet) { + var byteArray = toByteArray(packet); + if (this._message.length + byteArray.length >= this._maxPayloadSize) { + this.send(); + } + Array.prototype.push.apply(this._message, byteArray); + clearTimeout(this._timeout); + this._timeout = setTimeout(this._send, 0); +}; + +PacketQueue.prototype.send = function() { + state.messageQueue.send({ 0: this._message }); + this._message = []; +}; + SimplyPebble.sendPacket = function(packet) { - state.messageQueue.send({ 0: toByteArray(packet) }); + state.packetQueue.add(packet); }; SimplyPebble.windowShow = function(def) { diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index da7bd668..1ef8a053 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -636,8 +636,7 @@ static void handle_element_animate_packet(Simply *simply, Packet *data) { simply_stage_animate_element(simply->stage, element, animation, packet->frame); } -static void handle_packet(Simply *simply, uint8_t *buffer, uint16_t length) { - Packet *packet = (Packet*) buffer; +static void handle_packet(Simply *simply, Packet *packet) { switch (packet->type) { case CommandWindowShow: handle_window_show_packet(simply, packet); @@ -761,7 +760,19 @@ static void received_callback(DictionaryIterator *iter, void *context) { s_has_communicated = true; - handle_packet(context, tuple->value->data, tuple->length); + size_t length = tuple->length; + uint8_t *buffer = tuple->value->data; + while (true) { + Packet *packet = (Packet*) buffer; + handle_packet(context, packet); + + length -= packet->length; + if (length == 0) { + break; + } + + buffer += packet->length; + } } static void dropped_callback(AppMessageResult reason, void *context) { From 12addf21d22deceefeacc2200af87e2c25405de9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 06:24:57 -0700 Subject: [PATCH 361/791] Add multiple packet per message support for c to js --- src/js/ui/simply-pebble.js | 21 ++++++++-- src/simply/simply_msg.c | 84 +++++++++++++++++++++++++++----------- src/simply/simply_msg.h | 3 ++ 3 files changed, 80 insertions(+), 28 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index c6328603..3462630b 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -924,16 +924,18 @@ var toArrayBuffer = function(array, length) { return copy; }; -SimplyPebble.onPacket = function(data) { - Packet._view = toArrayBuffer(data); +SimplyPebble.onPacket = function(buffer, offset) { + Packet._view = buffer; + Packet._offset = offset; var packet = CommandPackets[Packet.type()]; if (!packet) { - console.log('Received unknown packet: ' + JSON.stringify(data)); + console.log('Received unknown packet: ' + JSON.stringify(buffer)); return; } packet._view = Packet._view; + packet._offset = offset; switch (packet) { case WindowHideEventPacket: WindowStack.emitHide(packet.id()); @@ -988,7 +990,18 @@ SimplyPebble.onPacket = function(data) { }; SimplyPebble.onAppMessage = function(e) { - SimplyPebble.onPacket(e.payload[0]); + var data = e.payload[0]; + Packet._view = toArrayBuffer(data); + + var offset = 0; + var length = data.length; + + do { + SimplyPebble.onPacket(Packet._view, offset); + + Packet._offset = offset; + offset += Packet.length(); + } while (offset !== 0 && offset < length); }; module.exports = SimplyPebble; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 1ef8a053..648f095e 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -20,6 +20,10 @@ #define SEND_DELAY_MS 10 +static const size_t APP_MSG_SIZE_INBOUND = 2048; + +static const size_t APP_MSG_SIZE_OUTBOUND = 512; + typedef enum Command Command; enum Command { @@ -797,9 +801,7 @@ SimplyMsg *simply_msg_create(Simply *simply) { simply->msg = self; - const uint32_t size_inbound = 2048; - const uint32_t size_outbound = 512; - app_message_open(size_inbound, size_outbound); + app_message_open(APP_MSG_SIZE_INBOUND, APP_MSG_SIZE_OUTBOUND); app_message_set_context(simply); @@ -823,37 +825,72 @@ void simply_msg_destroy(SimplyMsg *self) { free(self); } -static void destroy_packet(SimplyPacket *packet) { +static void destroy_packet(SimplyMsg *self, SimplyPacket *packet) { if (!packet) { return; } + list1_remove(&self->queue, &packet->node); free(packet->buffer); packet->buffer = NULL; free(packet); } -static bool send_msg(SimplyPacket *packet) { +static bool send_msg(uint8_t *buffer, size_t length) { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { return false; } - dict_write_data(iter, 0, packet->buffer, packet->length); + dict_write_data(iter, 0, buffer, length); return (app_message_outbox_send() == APP_MSG_OK); } +static void make_multi_packet(SimplyMsg *self, SimplyPacket *packet) { + if (!packet) { + return; + } + size_t length = 0; + SimplyPacket *last; + for (SimplyPacket *walk = packet;;) { + length += walk->length; + SimplyPacket *next = (SimplyPacket*) walk->node.next; + if (!next || length + next->length > APP_MSG_SIZE_OUTBOUND - 2 * sizeof(Tuple)) { + last = next; + break; + } + walk = next; + } + uint8_t *buffer = malloc(length); + if (!buffer) { + return; + } + uint8_t *cursor = buffer; + for (SimplyPacket *walk = packet; walk && walk != last;) { + memcpy(cursor, walk->buffer, walk->length); + cursor += walk->length; + SimplyPacket *next = (SimplyPacket*) walk->node.next; + destroy_packet(self, walk); + walk = next; + } + self->send_buffer = buffer; + self->send_length = length; +} + static void send_msg_retry(void *data) { SimplyMsg *self = data; - SimplyPacket *packet = (SimplyPacket*) self->queue; - if (!packet) { + self->send_timer = NULL; + if (!self->send_buffer) { + make_multi_packet(self, (SimplyPacket*) self->queue); + } + if (!self->send_buffer) { return; } - if (!send_msg(packet)){ + if (!send_msg(self->send_buffer, self->send_length)){ self->send_delay_ms *= 2; - app_timer_register(self->send_delay_ms, send_msg_retry, self); + self->send_timer = app_timer_register(self->send_delay_ms, send_msg_retry, self); return; } - list1_remove(&self->queue, &packet->node); - destroy_packet(packet); + free(self->send_buffer); + self->send_buffer = NULL; self->send_delay_ms = SEND_DELAY_MS; } @@ -872,7 +909,12 @@ static SimplyPacket *add_packet(SimplyMsg *self, Packet *buffer, Command type, s .buffer = buffer, }; list1_append(&self->queue, &packet->node); - send_msg_retry(self); + if (self->send_delay_ms <= SEND_DELAY_MS) { + if (self->send_timer) { + app_timer_cancel(self->send_timer); + } + app_timer_register(SEND_DELAY_MS, send_msg_retry, self); + } return packet; } @@ -937,11 +979,7 @@ bool simply_msg_accel_data(SimplyMsg *self, AccelData *data, uint32_t num_sample packet->is_peek = is_peek; packet->num_samples = num_samples; memcpy(packet->data, data, data_length); - SimplyPacket packet_node = { - .length = length, - .buffer = packet, - }; - bool result = send_msg(&packet_node); + bool result = send_msg((uint8_t*) packet, length); free(packet); return result; } @@ -953,19 +991,17 @@ static bool send_menu_item(SimplyMsg *self, Command type, uint16_t section, uint .section = section, .item = item, }; - SimplyPacket packet_node = { - .length = sizeof(packet), - .buffer = &packet, - }; - return send_msg(&packet_node); + return send_msg((uint8_t*) &packet, sizeof(packet)); } -static bool send_menu_item_retry(SimplyMsg *self, Command type, uint16_t section, uint16_t index) { +static bool send_menu_item_retry(SimplyMsg *self, Command type, uint16_t section, uint16_t item) { size_t length; MenuItemEventPacket *packet = malloc0(length = sizeof(*packet)); if (!packet) { return false; } + packet->section = section; + packet->item = item; return add_packet(self, (Packet*) packet, type, length); } diff --git a/src/simply/simply_msg.h b/src/simply/simply_msg.h index 8eeebef1..5a8eb034 100644 --- a/src/simply/simply_msg.h +++ b/src/simply/simply_msg.h @@ -12,6 +12,9 @@ struct SimplyMsg { Simply *simply; List1Node *queue; uint32_t send_delay_ms; + AppTimer *send_timer; + uint8_t *send_buffer; + size_t send_length; }; typedef struct SimplyPacket SimplyPacket; From 9980a921b6eb310fc448c6bfd142742d440e7edc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 06:25:58 -0700 Subject: [PATCH 362/791] Change simply menu to use the simply msg packet queue --- src/simply/simply_menu.c | 56 +++++++++------------------------------- src/simply/simply_menu.h | 2 -- src/simply/simply_msg.c | 16 +++--------- 3 files changed, 15 insertions(+), 59 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 06c6eaab..48c49725 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -32,11 +32,6 @@ static bool item_filter(List1Node *node, void *data) { return (item->section == section_index && item->item == row); } -static bool request_filter(List1Node *node, void *data) { - SimplyMenuCommon *item = (SimplyMenuCommon*) node; - return (item->title == NULL); -} - static SimplyMenuSection *get_menu_section(SimplyMenu *self, int index) { return (SimplyMenuSection*) list1_find(self->menu_layer.sections, section_filter, (void*)(uintptr_t) index); } @@ -81,33 +76,6 @@ static void destroy_item_by_index(SimplyMenu *self, int section, int index) { self->menu_layer.items, item_filter, (void*)(uintptr_t) cell_index)); } -static void schedule_get_timer(SimplyMenu *self); - -static void request_menu_node(void *data) { - SimplyMenu *self = data; - self->menu_layer.get_timer = NULL; - SimplyMenuSection *section = (SimplyMenuSection*) list1_find(self->menu_layer.sections, request_filter, NULL); - bool found = false; - if (section) { - simply_msg_menu_get_section(self->window.simply->msg, section->section); - found = true; - } - SimplyMenuItem *item = (SimplyMenuItem*) list1_find(self->menu_layer.items, request_filter, NULL); - if (item) { - simply_msg_menu_get_item(self->window.simply->msg, item->section, item->item); - found = true; - } - if (found) { - schedule_get_timer(self); - } -} - -static void schedule_get_timer(SimplyMenu *self) { - if (self->menu_layer.get_timer) { return; } - self->menu_layer.get_timer = app_timer_register(self->menu_layer.request_delay_ms, request_menu_node, self); - self->menu_layer.request_delay_ms *= 2; -} - static void add_section(SimplyMenu *self, SimplyMenuSection *section) { if (list1_size(self->menu_layer.sections) >= MAX_CACHED_SECTIONS) { destroy_section(self, (SimplyMenuSection*) list1_last(self->menu_layer.sections)); @@ -125,29 +93,35 @@ static void add_item(SimplyMenu *self, SimplyMenuItem *item) { } static void request_menu_section(SimplyMenu *self, uint16_t section_index) { - SimplyMenuSection *section = malloc(sizeof(*section)); + SimplyMenuSection *section = get_menu_section(self, section_index); + if (section) { + return; + } + section = malloc(sizeof(*section)); *section = (SimplyMenuSection) { .section = section_index, }; add_section(self, section); - schedule_get_timer(self); + simply_msg_menu_get_section(self->window.simply->msg, section_index); } static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t item_index) { - SimplyMenuItem *item = malloc(sizeof(*item)); + SimplyMenuItem *item = get_menu_item(self, section_index, item_index); + if (item) { + return; + } + item = malloc(sizeof(*item)); *item = (SimplyMenuItem) { .section = section_index, .item = item_index, }; add_item(self, item); - schedule_get_timer(self); + simply_msg_menu_get_item(self->window.simply->msg, section_index, item_index); } static void mark_dirty(SimplyMenu *self) { if (!self->menu_layer.menu_layer) { return; } menu_layer_reload_data(self->menu_layer.menu_layer); - request_menu_node(self); - self->menu_layer.request_delay_ms = REQUEST_DELAY_MS; } void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections) { @@ -311,11 +285,6 @@ void simply_menu_clear_section_items(SimplyMenu *self, int section_index) { } void simply_menu_clear(SimplyMenu *self) { - if (self->menu_layer.get_timer) { - app_timer_cancel(self->menu_layer.get_timer); - self->menu_layer.get_timer = NULL; - } - while (self->menu_layer.sections) { destroy_section(self, (SimplyMenuSection*) self->menu_layer.sections); } @@ -332,7 +301,6 @@ SimplyMenu *simply_menu_create(Simply *simply) { *self = (SimplyMenu) { .window.simply = simply, .menu_layer.num_sections = 1, - .menu_layer.request_delay_ms = REQUEST_DELAY_MS, }; simply_window_init(&self->window, simply); diff --git a/src/simply/simply_menu.h b/src/simply/simply_menu.h index be4f7243..946ec7d4 100644 --- a/src/simply/simply_menu.h +++ b/src/simply/simply_menu.h @@ -28,8 +28,6 @@ struct SimplyMenuLayer { MenuLayer *menu_layer; List1Node *sections; List1Node *items; - AppTimer *get_timer; - uint32_t request_delay_ms; uint16_t num_sections; }; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 648f095e..4e5ce4f2 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -985,16 +985,6 @@ bool simply_msg_accel_data(SimplyMsg *self, AccelData *data, uint32_t num_sample } static bool send_menu_item(SimplyMsg *self, Command type, uint16_t section, uint16_t item) { - MenuItemEventPacket packet = { - .packet.type = type, - .packet.length = sizeof(packet), - .section = section, - .item = item, - }; - return send_msg((uint8_t*) &packet, sizeof(packet)); -} - -static bool send_menu_item_retry(SimplyMsg *self, Command type, uint16_t section, uint16_t item) { size_t length; MenuItemEventPacket *packet = malloc0(length = sizeof(*packet)); if (!packet) { @@ -1014,16 +1004,16 @@ bool simply_msg_menu_get_item(SimplyMsg *self, uint16_t section, uint16_t index) } bool simply_msg_menu_select_click(SimplyMsg *self, uint16_t section, uint16_t index) { - return send_menu_item_retry(self, CommandMenuSelect, section, index); + return send_menu_item(self, CommandMenuSelect, section, index); } bool simply_msg_menu_select_long_click(SimplyMsg *self, uint16_t section, uint16_t index) { - return send_menu_item_retry(self, CommandMenuLongSelect, section, index); + return send_menu_item(self, CommandMenuLongSelect, section, index); } bool simply_msg_send_menu_selection(SimplyMsg *self) { MenuIndex menu_index = simply_menu_get_selection(self->simply->menu); - return send_menu_item_retry(self, CommandMenuSelectionEvent, menu_index.section, menu_index.row); + return send_menu_item(self, CommandMenuSelectionEvent, menu_index.section, menu_index.row); } bool simply_msg_animate_element_done(SimplyMsg *self, uint32_t id) { From 95c103a030046e7710b049e7e6bfd0ac167f57d9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 06:32:29 -0700 Subject: [PATCH 363/791] Change menu to automatically preload the first three items --- src/js/ui/menu.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 08c56ae4..d2700d15 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -27,9 +27,13 @@ Menu.prototype._show = function() { Menu.prototype._prop = function(state, clear, pushing) { if (this === WindowStack.top()) { simply.impl.menu.call(this, state, clear, pushing); - var select = this._selection; + var select = util2.copy(this._selection); if (this._resolveSection(select)) { simply.impl.menuSelection(select.section, select.item); + for (var i = 0; i < 3; ++i) { + this._resolveItem(select); + select.item++; + } } } }; From 67451abed3f0a788596b4a1b22eaf9418df5de60 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Jun 2014 06:33:52 -0700 Subject: [PATCH 364/791] Remove nested test menu from app.js --- src/js/app.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 8a68b33a..929fdc74 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -22,17 +22,7 @@ main.on('click', 'up', function(e) { items: [{ title: 'Pebble.js', icon: 'images/menu_icon.png', - subtitle: 'Can do Menus', - select: function() { - var fMenu = new UI.Menu(); - fMenu.show(); - fMenu.item(0, 0, {title: 'one'}); - fMenu.item(0, 1, {title: 'two'}); - fMenu.item(0, 2, {title: 'three'}); - fMenu.on('select', function(e) { - console.log(e.item); - }); - } + subtitle: 'Can do Menus' }, { title: 'Second Item', subtitle: 'Subtitle Text' From 4f6263bf1fdb9c6ed49f273e7da84519fce83f64 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Jun 2014 05:36:04 -0700 Subject: [PATCH 365/791] Fix simply pebble card text to use an empty string by default --- src/js/ui/simply-pebble.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 3462630b..0d861b33 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -726,7 +726,7 @@ SimplyPebble.cardClear = function(clear) { }; SimplyPebble.cardText = function(field, text) { - SimplyPebble.sendPacket(CardTextPacket.index(field).text(text)); + SimplyPebble.sendPacket(CardTextPacket.index(field).text(text || '')); }; SimplyPebble.cardImage = function(field, image) { From 4d5583ceb56ef52ec75ce084ed69c4d2a43c65e4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Jun 2014 05:36:48 -0700 Subject: [PATCH 366/791] Update simply pebble menu selection to take menu alignment --- src/js/ui/simply-pebble.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 0d861b33..1e85b3a7 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -804,11 +804,11 @@ SimplyPebble.menuItem = function(section, item, def) { SimplyPebble.sendPacket(MenuItemPacket); }; -SimplyPebble.menuSelection = function(section, item) { +SimplyPebble.menuSelection = function(section, item, align) { if (arguments.length === 0) { SimplyPebble.sendPacket(MenuGetSelectionPacket); } - SimplyPebble.sendPacket(MenuSelectionPacket.section(section).item(item)); + SimplyPebble.sendPacket(MenuSelectionPacket.section(section).item(item).align(align || 'center')); }; SimplyPebble.menu = function(def, clear, pushing) { From e797e4a42486089f13242529fddda390eb3928a1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Jun 2014 05:37:16 -0700 Subject: [PATCH 367/791] Change menu to preload items before the selection as well --- src/js/ui/menu.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index d2700d15..8a813bd6 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -24,14 +24,19 @@ Menu.prototype._show = function() { Window.prototype._show.apply(this, arguments); }; +Menu.prototype._preload = 5; + Menu.prototype._prop = function(state, clear, pushing) { if (this === WindowStack.top()) { simply.impl.menu.call(this, state, clear, pushing); var select = util2.copy(this._selection); if (this._resolveSection(select)) { simply.impl.menuSelection(select.section, select.item); - for (var i = 0; i < 3; ++i) { - this._resolveItem(select); + select.item -= Math.max(0, Math.floor(this._preload / 2)); + for (var i = 0; i < this._preload; ++i) { + if (!this._resolveItem(select)) { + break; + } select.item++; } } From 7e78db8e7a87608476783dd2529fe60414969d51 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Jun 2014 06:01:26 -0700 Subject: [PATCH 368/791] Fix simply msg send retry to continue the queue after success --- src/simply/simply_msg.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 4e5ce4f2..8a98e8e6 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -884,14 +884,14 @@ static void send_msg_retry(void *data) { if (!self->send_buffer) { return; } - if (!send_msg(self->send_buffer, self->send_length)){ + if (send_msg(self->send_buffer, self->send_length)) { + free(self->send_buffer); + self->send_buffer = NULL; + self->send_delay_ms = SEND_DELAY_MS; + } else { self->send_delay_ms *= 2; - self->send_timer = app_timer_register(self->send_delay_ms, send_msg_retry, self); - return; } - free(self->send_buffer); - self->send_buffer = NULL; - self->send_delay_ms = SEND_DELAY_MS; + self->send_timer = app_timer_register(self->send_delay_ms, send_msg_retry, self); } static SimplyPacket *add_packet(SimplyMsg *self, Packet *buffer, Command type, size_t length) { From 77a20aabb157a95489363417fd3449de31afe77e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Jun 2014 06:17:22 -0700 Subject: [PATCH 369/791] Change menu to automatically preload items for sections --- src/js/ui/menu.js | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 8a813bd6..90fea058 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -24,22 +24,12 @@ Menu.prototype._show = function() { Window.prototype._show.apply(this, arguments); }; -Menu.prototype._preload = 5; +Menu.prototype._numPreloadItems = 5; Menu.prototype._prop = function(state, clear, pushing) { if (this === WindowStack.top()) { simply.impl.menu.call(this, state, clear, pushing); - var select = util2.copy(this._selection); - if (this._resolveSection(select)) { - simply.impl.menuSelection(select.section, select.item); - select.item -= Math.max(0, Math.floor(this._preload / 2)); - for (var i = 0; i < this._preload; ++i) { - if (!this._resolveItem(select)) { - break; - } - select.item++; - } - } + this._resolveSection(this._selection); } }; @@ -154,6 +144,10 @@ Menu.prototype._resolveSection = function(e, clear) { section.items = getItems.call(this, e); if (this === WindowStack.top()) { simply.impl.menuSection.call(this, e.section, section, clear); + var select = this._selection; + if (select.section === e.section) { + this._preloadSection(select); + } return true; } }; @@ -167,6 +161,20 @@ Menu.prototype._resolveItem = function(e) { } }; +Menu.prototype._preloadItems = function(e) { + var select = util2.copy(e); + select.item = Math.max(0, select.item - Math.floor(this._numPreloadItems / 2)); + for (var i = 0; i < this._numPreloadItems; ++i) { + this._resolveItem(select); + select.item++; + } +}; + +Menu.prototype._preloadSection = function(e) { + simply.impl.menuSelection(e.section, e.item); + this._preloadItems(e); +}; + Menu.prototype._emitSelect = function(e) { this._selection = e; var item = getItem.call(this, e); From b9d8dce89cb03f29b4cd7fcb0fcde83c77a4e2ed Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Jun 2014 06:20:51 -0700 Subject: [PATCH 370/791] Fix simply msg add packet to set the send timer --- src/simply/simply_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 8a98e8e6..b8f7e1bb 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -913,7 +913,7 @@ static SimplyPacket *add_packet(SimplyMsg *self, Packet *buffer, Command type, s if (self->send_timer) { app_timer_cancel(self->send_timer); } - app_timer_register(SEND_DELAY_MS, send_msg_retry, self); + self->send_timer = app_timer_register(SEND_DELAY_MS, send_msg_retry, self); } return packet; } From 0272b86bbdf2405cfd3c7540ed36875b198d14a7 Mon Sep 17 00:00:00 2001 From: Thomas Sarlandie Date: Tue, 24 Jun 2014 16:36:22 -0700 Subject: [PATCH 371/791] Fixing typo in the doc --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 089381c4..072aa29f 100644 --- a/README.md +++ b/README.md @@ -154,11 +154,11 @@ Exporting is possible by modifying or setting `module.exports` within the requir ### Pebble -The `Pebble` object from [PebbleKit JavaScript](https://developer.getpebble.com/2/guides/javascript-guide.html) is available as a global variable. It's usage is discouraged in Pebble.js, instead you should use the objects documented below who provide a cleaner object interface to the same functionalities. +The `Pebble` object from [PebbleKit JavaScript](https://developer.getpebble.com/2/guides/javascript-guide.html) is available as a global variable. Its usage is discouraged in Pebble.js, instead you should use the objects documented below who provide a cleaner object interface to the same functionalities. ### window -- browser -A `window` object is provided with a subset of the standard APIs you would find in a normal browser. It's direct usage is discouraged because available functionalities may differ between the iOS and Android runtime environment. +A `window` object is provided with a subset of the standard APIs you would find in a normal browser. Its direct usage is discouraged because available functionalities may differ between the iOS and Android runtime environment. More specifically: From 1629e076eff295072012df89ca6c53da611d3ae5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 26 Jun 2014 05:52:09 -0700 Subject: [PATCH 372/791] Fix Settings.config example usage --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 072aa29f..eccb11dd 100644 --- a/README.md +++ b/README.md @@ -195,13 +195,13 @@ var Settings = require('settings'); ````js // Set a configurable with the open callback Settings.config( - { url: 'http://www.example.com' } + { url: 'http://www.example.com' }, function(e) { console.log('opening configurable'); // Reset color to red before opening the webview Settings.option('color', 'red'); - } + }, function(e) { console.log('closed configurable'); } @@ -213,7 +213,7 @@ Settings.config( ````js // Set a configurable with just the close callback Settings.config( - { url: 'http://www.example.com' } + { url: 'http://www.example.com' }, function(e) { console.log('closed configurable'); From b52145ff0df7a368fbbc87df847cd2d38700c2c1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 26 Jun 2014 05:52:27 -0700 Subject: [PATCH 373/791] Add optional url returning for a Settings.config open callback --- src/js/settings/settings.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index 9973f598..dc0ff06b 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -127,10 +127,14 @@ Settings.onOpenConfig = function(e) { options: state.options, url: listener.params.url, }; - if (listener.open(e) === false) { - return; + var result; + if (listener.open) { + result = listener.open(e); + if (result === false) { + return; + } } - url = listener.params.url; + url = typeof result === 'string' ? result : listener.params.url; options = state.options; } else { url = Settings.settingsUrl; From b0673f649245c9510c5ad49a8aba57ba7af1a86c Mon Sep 17 00:00:00 2001 From: Ole Michaelis Date: Sat, 5 Jul 2014 16:32:18 +0200 Subject: [PATCH 374/791] fixed invalid javascript in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eccb11dd..97a2fd6f 100644 --- a/README.md +++ b/README.md @@ -377,7 +377,7 @@ The number of callbacks will depend on the configuration of the accelerometer. W Peeks at the current accelerometer value. The callback function will be called with the data point as an event. ````js -Accel.peek(function(e)) { +Accel.peek(function(e) { console.log('Current acceleration on axis are: X=' + e.accel.x + ' Y=' + e.accel.y + ' Z=' + e.accel.z); }); ```` From de913e80394fae744551716e4beee74740244f7e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Jun 2014 08:52:15 -0700 Subject: [PATCH 375/791] Add ajax.js form type for automatically decoding forms --- src/js/lib/ajax.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/js/lib/ajax.js b/src/js/lib/ajax.js index 346ebc97..75663388 100644 --- a/src/js/lib/ajax.js +++ b/src/js/lib/ajax.js @@ -90,18 +90,19 @@ var ajax = function(opt, success, failure) { } req.onreadystatechange = function(e) { - if (req.readyState == 4) { + if (req.readyState === 4) { var body = req.responseText; - var failed = req.status != 200; - if (opt.type == 'json') { - try { + var okay = req.status === 200; + try { + if (opt.type === 'json') { body = JSON.parse(body); + } else if (opt.type === 'form') { + body = deformify(body); } - catch (err) { - failed = true; - } + } catch (err) { + okay = false; } - var callback = !failed ? success : failure; + var callback = okay ? success : failure; if (callback) { callback(body, req.status, req); } From a56e3bdac2f4cf5e8441f1918cd91e6a2ec2caa1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 11 Jul 2014 18:56:33 -0700 Subject: [PATCH 376/791] Fix menu to prepend new items into cache --- src/simply/simply_menu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 48c49725..4313a86b 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -81,7 +81,7 @@ static void add_section(SimplyMenu *self, SimplyMenuSection *section) { destroy_section(self, (SimplyMenuSection*) list1_last(self->menu_layer.sections)); } destroy_section_by_index(self, section->section); - list1_append(&self->menu_layer.sections, §ion->node); + list1_prepend(&self->menu_layer.sections, §ion->node); } static void add_item(SimplyMenu *self, SimplyMenuItem *item) { @@ -89,7 +89,7 @@ static void add_item(SimplyMenu *self, SimplyMenuItem *item) { destroy_item(self, (SimplyMenuItem*) list1_last(self->menu_layer.items)); } destroy_item_by_index(self, item->section, item->item); - list1_append(&self->menu_layer.items, &item->node); + list1_prepend(&self->menu_layer.items, &item->node); } static void request_menu_section(SimplyMenu *self, uint16_t section_index) { From ee8e123572dd53b93fb72f348ccc033a3628b681 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 25 Jul 2014 01:20:04 -0700 Subject: [PATCH 377/791] Workaround the scroll layer animating on window destroy crash --- src/simply/simply_window.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 591d3975..981e960d 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -34,7 +34,8 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { if (!is_scrollable) { GRect bounds = layer_get_bounds(window_get_root_layer(self->window)); layer_set_bounds(self->layer, bounds); - const bool animated = true; + // TODO: change back to animated when a closing animated scroll doesn't cause a crash + const bool animated = false; scroll_layer_set_content_offset(self->scroll_layer, GPointZero, animated); scroll_layer_set_content_size(self->scroll_layer, bounds.size); } From c864a707bf7a922945df5410b17a3382ad354b74 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 25 Jul 2014 07:00:07 -0700 Subject: [PATCH 378/791] Change logo images to Pebble.js --- resources/images/logo_splash.png | Bin 19202 -> 15306 bytes resources/images/menu_icon.png | Bin 19261 -> 15463 bytes resources/images/tile_splash.png | Bin 18123 -> 18120 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/images/logo_splash.png b/resources/images/logo_splash.png index 4b9fc5dd733edb1ac89bf6e52d56392fa602f838..da4494e1064c96dd889236ba3cc74bf43d8d81d4 100644 GIT binary patch delta 895 zcmZpg#(1iHg1B-8BLff!%5&EPDb50q$YKTtZeb8+WSBKaf`P&E`bH~D#>uN0O(z#K zT1?hpcAWfx(PnZgljr12=Jd(YtbUW@SlK2ku<{6)>lzt{7@As{nphba=^B_%wqaGC ze4RxQBw-q2U}j}%Y-MTzlK8=*Jh`7mSQTBu%D})zzaqE5$~UznF(t7i(Iv4Yb@CBr zv&qNA%_c8nl~gfHGB-~&PBPauu&^-DH8CmOS|}tGPiAmaIJ2rD2pmlmFnJHF8X_&>P zGx-2V0c&zmYGTRc4I0yBi}a@tMq60@5eAQC;)qJHzk_gNk4LR+U$!rn>ZUZCCnHe#|G*!wB?*WC?=yGyCiGN ziWL*<-9oQ~efzm?S!Z5-){~P9H81)9{kvoRTI*L^cC~+4rcoK|_T1uU?U$SHjm7S_ zZ}@4a$he_ahT(?Ycl*K>s~=AIy~|oId#=v=MZQ1xE{Nu>Pr0~o&zS?OAFk)D*v9kV z-FLV5?f0+OtM5I( z)Ut1GySTmn!XK4gW&4UI9Zr2d@mXmx&qwX+`|TekRZRF?RWRxE8W}Uo_op^?$ZLF$ z%nK6JR)-_*b9e>2~|?o8d;x^s2sD+^9>pA$YOeQt4ZT;aUJ jdxdgEaq~Wftk1v4%M##vHugOzpLx3avAV2tN@xNA@5N80 delta 2475 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 Date: Fri, 25 Jul 2014 07:10:49 -0700 Subject: [PATCH 379/791] Update loader.js to provide the CommonJS exports local variable --- src/js/loader.js | 4 +++- wscript | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/js/loader.js b/src/js/loader.js index 1a2142f7..3cba762a 100644 --- a/src/js/loader.js +++ b/src/js/loader.js @@ -38,8 +38,10 @@ loader.require = function(path, requirer) { return module.exports; } + var require = function(path) { return loader.require(path, module); }; + module.exports = {}; - module.loader(module, function(path) { return loader.require(path, module); }); + module.loader(module.exports, module, require); module.loaded = true; return module.exports; diff --git a/wscript b/wscript index 7a7b97b4..b2bb67bc 100644 --- a/wscript +++ b/wscript @@ -37,7 +37,8 @@ def concat_javascript(self, *k, **kw): def concat_javascript_task(task): LOADER_PATH = "loader.js" - LOADER_TEMPLATE = "__loader.define({relpath}, {lineno}, function(module, require) {{\n{body}\n}});" + LOADER_TEMPLATE = ("__loader.define({relpath}, {lineno}, " + + "function(exports, module, require) {{\n{body}\n}});") JSON_TEMPLATE = "module.exports = {body};" APPINFO_PATH = "appinfo.json" From 6e884a9fc9aa3f6c5cf34c6abbf4b9724798dea4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 26 Jul 2014 02:30:54 -0700 Subject: [PATCH 380/791] Automatically prefix http:// to Settings config urls if missing --- src/js/settings/settings.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index dc0ff06b..8435481a 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -28,10 +28,15 @@ Settings.reset = function() { }; }; -Settings.mainScriptUrl = function(scriptUrl) { - if (typeof scriptUrl === 'string' && scriptUrl.length && !scriptUrl.match(/^(\w+:)?\/\//)) { - scriptUrl = 'http://' + scriptUrl; +var toHttpUrl = function(url) { + if (typeof url === 'string' && url.length && !url.match(/^(\w+:)?\/\//)) { + url = 'http://' + url; } + return url; +}; + +Settings.mainScriptUrl = function(scriptUrl) { + scriptUrl = toHttpUrl(scriptUrl); if (scriptUrl) { localStorage.setItem('mainJsUrl', scriptUrl); } else { @@ -86,7 +91,7 @@ var makeDataAccessor = function(type, path) { if (arguments.length === 1 && typeof field !== 'object') { return data[field]; } - if (typeof field !== 'object' && typeof value === 'undefined' || value === null) { + if (typeof field !== 'object' && value === undefined || value === null) { delete data[field]; return; } @@ -105,7 +110,8 @@ Settings.config = function(opt, open, close) { if (typeof opt === 'string') { opt = { url: opt }; } - if (typeof close === 'undefined') { + opt.url = toHttpUrl(opt.url); + if (close === undefined) { close = open; open = util2.noop; } From f82acae4708d6caa12e6a2d11e491c61a51d1a27 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 26 Jul 2014 02:31:46 -0700 Subject: [PATCH 381/791] Change Settings.config close to be optional --- src/js/settings/settings.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index 8435481a..3181746f 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -182,6 +182,8 @@ Settings.onCloseConfig = function(e) { util2.copy(options, state.options); Settings.saveOptions(); } - return listener.close(e); + if (listener.close) { + return listener.close(e); + } } }; From ba6bb11b5607d897e9d3ac8afea4759d0c30e7f4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 26 Jul 2014 02:32:13 -0700 Subject: [PATCH 382/791] Fix simply pebble menu selection get request to only get --- src/js/ui/simply-pebble.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 1e85b3a7..dc7e6ded 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -14,7 +14,8 @@ var simply = require('ui/simply'); /** * This package provides the underlying implementation for the ui/* classes. * - * This implementation uses PebbleKit JS AppMessage to send commands to a Pebble Watm */ + * This implementation uses PebbleKit JS AppMessage to send commands to a Pebble Watch. + */ /** * First part of this file is defining the commands and types that we will use later. @@ -805,8 +806,9 @@ SimplyPebble.menuItem = function(section, item, def) { }; SimplyPebble.menuSelection = function(section, item, align) { - if (arguments.length === 0) { + if (section === undefined) { SimplyPebble.sendPacket(MenuGetSelectionPacket); + return; } SimplyPebble.sendPacket(MenuSelectionPacket.section(section).item(item).align(align || 'center')); }; From 05a715c3f3f56d5c9cfb9fe70816203821b824dc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 27 Jul 2014 16:24:17 -0700 Subject: [PATCH 383/791] Change simply pebble StringType to convert values to strings --- src/js/ui/simply-pebble.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index dc7e6ded..a68f6a3f 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -26,7 +26,7 @@ var BoolType = function(x) { }; var StringType = function(x) { - return x || ''; + return '' + x; }; var EnumerableType = function(x) { From 91cb37853eea782fa9fa59e0875354f52a2727d3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 27 Jul 2014 16:28:37 -0700 Subject: [PATCH 384/791] Fix simply msg window action bar packet button image mapping --- src/simply/simply_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index b8f7e1bb..32d4dbd7 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -428,7 +428,7 @@ static void handle_window_action_bar_packet(Simply *simply, Packet *data) { return; } for (unsigned int i = 0; i < ARRAY_LENGTH(packet->image); ++i) { - simply_window_set_action_bar_icon(window, i, packet->image[i]); + simply_window_set_action_bar_icon(window, i + 1, packet->image[i]); } simply_window_set_action_bar_background_color(window, packet->background_color); simply_window_set_action_bar(window, packet->action); From fb3d31034a0565ce1bdddc4a060fc094ea02165b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 30 Jul 2014 16:31:12 -0700 Subject: [PATCH 385/791] Move action flag from window action bar to window props packet --- src/js/ui/simply-pebble.js | 11 +++++++++-- src/simply/simply_msg.c | 6 +++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index a68f6a3f..62782c63 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -250,6 +250,7 @@ var WindowPropsPacket = new struct([ ['uint8', 'backgroundColor', Color], ['bool', 'fullscreen', BoolType], ['bool', 'scrollable', BoolType], + ['uint8', 'action', BoolType], ]); var WindowButtonConfigPacket = new struct([ @@ -262,7 +263,7 @@ var WindowActionBarPacket = new struct([ ['uint32', 'up', ImageType], ['uint32', 'select', ImageType], ['uint32', 'down', ImageType], - ['uint8', 'action', BoolType], + ['uint8', 'backgroundColor', Color], ]); var ClickPacket = new struct([ @@ -680,6 +681,9 @@ SimplyPebble.windowHide = function(id) { }; SimplyPebble.windowProps = function(def) { + WindowPropsPacket + .prop(def) + .action('action' in def && def.action !== false); SimplyPebble.sendPacket(WindowPropsPacket.prop(def)); }; @@ -695,7 +699,10 @@ var toActionDef = function(actionDef) { }; SimplyPebble.windowActionBar = function(def) { - SimplyPebble.sendPacket(WindowActionBarPacket.prop(toActionDef(def))); + WindowActionBarPacket + .prop(toActionDef(def)) + .backgroundColor(def.backgroundColor || 'black'); + SimplyPebble.sendPacket(WindowActionBarPacket); }; SimplyPebble.image = function(id, gbitmap) { diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 32d4dbd7..2a09ef1e 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -125,6 +125,7 @@ struct __attribute__((__packed__)) WindowPropsPacket { GColor background_color:8; bool fullscreen; bool scrollable; + bool action; }; typedef struct WindowButtonConfigPacket WindowButtonConfigPacket; @@ -140,7 +141,6 @@ struct __attribute__((__packed__)) WindowActionBarPacket { Packet packet; uint32_t image[3]; GColor background_color:8; - bool action; }; typedef struct ClickPacket ClickPacket; @@ -410,6 +410,7 @@ static void handle_window_props_packet(Simply *simply, Packet *data) { simply_window_set_background_color(window, packet->background_color); simply_window_set_fullscreen(window, packet->fullscreen); simply_window_set_scrollable(window, packet->scrollable); + simply_window_set_action_bar(window, packet->action); } static void handle_window_button_config_packet(Simply *simply, Packet *data) { @@ -427,11 +428,10 @@ static void handle_window_action_bar_packet(Simply *simply, Packet *data) { if (!window) { return; } + simply_window_set_action_bar_background_color(window, packet->background_color); for (unsigned int i = 0; i < ARRAY_LENGTH(packet->image); ++i) { simply_window_set_action_bar_icon(window, i + 1, packet->image[i]); } - simply_window_set_action_bar_background_color(window, packet->background_color); - simply_window_set_action_bar(window, packet->action); } static void handle_image_packet(Simply *simply, Packet *data) { From fbff86a45832e725271c779814c9da0ef42467bf Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 30 Jul 2014 16:32:09 -0700 Subject: [PATCH 386/791] Fix Window _action to use windowActionBar --- src/js/ui/window.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/ui/window.js b/src/js/ui/window.js index fcb7043f..3313da48 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -140,9 +140,9 @@ Window.prototype.prop = function(field, value, clear) { return this; }; -Window.prototype._action = function(visible) { +Window.prototype._action = function(actionDef) { if (this === WindowStack.top()) { - simply.impl.window({ action: typeof visible === 'boolean' ? visible : this.state.action }, 'action'); + simply.impl.windowActionBar(actionDef); } }; From 6e3e9c9e226b407ab7e72b1bec54efd438ffda25 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 30 Jul 2014 16:32:35 -0700 Subject: [PATCH 387/791] Alias simply pebble window to stage --- src/js/ui/simply-pebble.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 62782c63..5387464c 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -924,6 +924,8 @@ SimplyPebble.stage = function(def, clear, pushing) { } }; +SimplyPebble.window = SimplyPebble.stage; + var toArrayBuffer = function(array, length) { length = length || array.length; var copy = new DataView(new ArrayBuffer(length)); From beb5c850046d72fbeeee6703dd9d9ef944426c3e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 Aug 2014 03:04:40 -0700 Subject: [PATCH 388/791] Add menu select event doc --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 97a2fd6f..890428f6 100644 --- a/README.md +++ b/README.md @@ -674,10 +674,19 @@ menu.item(0, 0, { title: 'A new item', subtitle: 'replacing the previous one' }) #### Menu.on('select', callback) -Registers a callback called when an item in the menu is selected. +Registers a callback called when an item in the menu is selected. The callback function will be passed an event with the following fields: + +* section: The section index of the section of the selected item. +* item: The item index of the selected item. **Note:** You can also register a callback for 'longSelect' event, triggered when the user long clicks on an item. +````js +menu.on('select', function(e) { + console.log('Selected item #' + e.item + ' of section #' + e.section); +}); +```` + #### Menu.on('longSelect', callback) See `Menu.on('select, callback)` From 9b2acf61acb0d6b4e43ad394dc7cb87018e285a6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 Aug 2014 08:22:24 -0700 Subject: [PATCH 389/791] Change menu to use itemIndex and sectionIndex property names This frees up the item and section fields to reference objects. --- README.md | 6 ++--- src/js/app.js | 2 +- src/js/ui/menu.js | 64 +++++++++++++++++++++++------------------------ 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 890428f6..9ee80d71 100644 --- a/README.md +++ b/README.md @@ -676,14 +676,14 @@ menu.item(0, 0, { title: 'A new item', subtitle: 'replacing the previous one' }) Registers a callback called when an item in the menu is selected. The callback function will be passed an event with the following fields: -* section: The section index of the section of the selected item. -* item: The item index of the selected item. +* sectionIndex: The section index of the section of the selected item. +* itemIndex: The item index of the selected item. **Note:** You can also register a callback for 'longSelect' event, triggered when the user long clicks on an item. ````js menu.on('select', function(e) { - console.log('Selected item #' + e.item + ' of section #' + e.section); + console.log('Selected item #' + e.itemIndex + ' of section #' + e.sectionIndex); }); ```` diff --git a/src/js/app.js b/src/js/app.js index 929fdc74..c16b2d3b 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -30,7 +30,7 @@ main.on('click', 'up', function(e) { }] }); menu.on('select', function(e) { - console.log('Selected item: ' + e.section + ' ' + e.item); + console.log('Selected item: ' + e.sectionIndex + ' ' + e.itemIndex); }); menu.show(); }); diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 90fea058..1fe12b77 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -9,7 +9,7 @@ var Menu = function(menuDef) { Window.call(this, menuDef); this._dynamic = false; this._sections = {}; - this._selection = { section: 0, item: 0 }; + this._selection = { sectionIndex: 0, itemIndex: 0 }; this._selections = []; }; @@ -72,24 +72,24 @@ var getSections = function() { var getSection = function(e, create) { var sections = getSections.call(this); - var section = sections[e.section]; + var section = sections[e.sectionIndex]; if (section) { return section; } if (this.sectionProvider) { section = this.sectionProvider.call(this, e); if (section) { - return (sections[e.section] = section); + return (sections[e.sectionIndex] = section); } } if (!create) { return; } - return (sections[e.section] = {}); + return (sections[e.sectionIndex] = {}); }; var getItems = function(e, create) { var section = getSection.call(this, e, create); if (!section) { - if (e.section > 0) { return; } + if (e.sectionIndex > 0) { return; } section = this.state.sections[0] = {}; } if (section.items instanceof Array) { @@ -99,10 +99,10 @@ var getItems = function(e, create) { return (section.items = new Array(section.items)); } if (typeof section.items === 'function') { - this._sections[e.section] = section.items; + this._sections[e.sectionIndex] = section.items; delete section.items; } - var itemsProvider = getMetaSection.call(this, e.section).items || this.itemsProvider; + var itemsProvider = getMetaSection.call(this, e.sectionIndex).items || this.itemsProvider; if (itemsProvider) { var items = itemsProvider.call(this, e); if (items) { @@ -115,19 +115,19 @@ var getItems = function(e, create) { var getItem = function(e, create) { var items = getItems.call(this, e, create); - var item = items[e.item]; + var item = items[e.itemIndex]; if (item) { return item; } - var itemProvider = getMetaSection.call(this, e.section).item || this.itemProvider; + var itemProvider = getMetaSection.call(this, e.sectionIndex).item || this.itemProvider; if (itemProvider) { item = itemProvider.call(this, e); if (item) { - return (items[e.item] = item); + return (items[e.itemIndex] = item); } } if (!create) { return; } - return (items[e.item] = {}); + return (items[e.itemIndex] = {}); }; Menu.prototype._resolveMenu = function() { @@ -143,9 +143,9 @@ Menu.prototype._resolveSection = function(e, clear) { if (!section) { return; } section.items = getItems.call(this, e); if (this === WindowStack.top()) { - simply.impl.menuSection.call(this, e.section, section, clear); + simply.impl.menuSection.call(this, e.sectionIndex, section, clear); var select = this._selection; - if (select.section === e.section) { + if (select.sectionIndex === e.sectionIndex) { this._preloadSection(select); } return true; @@ -156,22 +156,22 @@ Menu.prototype._resolveItem = function(e) { var item = getItem.call(this, e); if (!item) { return; } if (this === WindowStack.top()) { - simply.impl.menuItem.call(this, e.section, e.item, item); + simply.impl.menuItem.call(this, e.sectionIndex, e.itemIndex, item); return true; } }; Menu.prototype._preloadItems = function(e) { var select = util2.copy(e); - select.item = Math.max(0, select.item - Math.floor(this._numPreloadItems / 2)); + select.itemIndex = Math.max(0, select.itemIndex - Math.floor(this._numPreloadItems / 2)); for (var i = 0; i < this._numPreloadItems; ++i) { this._resolveItem(select); - select.item++; + select.itemIndex++; } }; Menu.prototype._preloadSection = function(e) { - simply.impl.menuSelection(e.section, e.item); + simply.impl.menuSelection(e.sectionIndex, e.itemIndex); this._preloadItems(e); }; @@ -224,12 +224,12 @@ Menu.prototype.sections = function(sections) { Menu.prototype.section = function(sectionIndex, section) { if (typeof sectionIndex === 'object') { - sectionIndex = sectionIndex.section || 0; + sectionIndex = sectionIndex.sectionIndex || 0; } else if (typeof sectionIndex === 'function') { this.sectionProvider = sectionIndex; return this; } - var menuIndex = { section: sectionIndex }; + var menuIndex = { sectionIndex: sectionIndex }; if (!section) { return getSection.call(this, menuIndex); } @@ -245,7 +245,7 @@ Menu.prototype.section = function(sectionIndex, section) { Menu.prototype.items = function(sectionIndex, items) { if (typeof sectionIndex === 'object') { - sectionIndex = sectionIndex.section || 0; + sectionIndex = sectionIndex.sectionIndex || 0; } else if (typeof sectionIndex === 'function') { this.itemsProvider = sectionIndex; return this; @@ -254,7 +254,7 @@ Menu.prototype.items = function(sectionIndex, items) { getMetaSection.call(this, sectionIndex).items = items; return this; } - var menuIndex = { section: sectionIndex }; + var menuIndex = { sectionIndex: sectionIndex }; if (!items) { return getItems.call(this, menuIndex); } @@ -267,8 +267,8 @@ Menu.prototype.items = function(sectionIndex, items) { Menu.prototype.item = function(sectionIndex, itemIndex, item) { if (typeof sectionIndex === 'object') { item = itemIndex || item; - itemIndex = sectionIndex.item; - sectionIndex = sectionIndex.section || 0; + itemIndex = sectionIndex.itemIndex; + sectionIndex = sectionIndex.sectionIndex || 0; } else if (typeof sectionIndex === 'function') { this.itemProvider = sectionIndex; return this; @@ -281,7 +281,7 @@ Menu.prototype.item = function(sectionIndex, itemIndex, item) { getMetaSection.call(this, sectionIndex).item = item; return this; } - var menuIndex = { section: sectionIndex, item: itemIndex }; + var menuIndex = { sectionIndex: sectionIndex, itemIndex: itemIndex }; if (!item) { return getItem.call(this, menuIndex); } @@ -304,11 +304,11 @@ Menu.emit = function(type, subtype, e) { Window.emit(type, subtype, e, Menu); }; -Menu.emitSection = function(section) { +Menu.emitSection = function(sectionIndex) { var menu = WindowStack.top(); if (!(menu instanceof Menu)) { return; } var e = { - section: section + sectionIndex: sectionIndex }; if (Menu.emit('section', null, e) === false) { return false; @@ -316,12 +316,12 @@ Menu.emitSection = function(section) { menu._resolveSection(e); }; -Menu.emitItem = function(section, item) { +Menu.emitItem = function(sectionIndex, itemIndex) { var menu = WindowStack.top(); if (!(menu instanceof Menu)) { return; } var e = { - section: section, - item: item, + sectionIndex: sectionIndex, + itemIndex: itemIndex, }; if (Menu.emit('item', null, e) === false) { return false; @@ -329,12 +329,12 @@ Menu.emitItem = function(section, item) { menu._resolveItem(e); }; -Menu.emitSelect = function(type, section, item) { +Menu.emitSelect = function(type, sectionIndex, itemIndex) { var menu = WindowStack.top(); if (!(menu instanceof Menu)) { return; } var e = { - section: section, - item: item, + sectionIndex: sectionIndex, + itemIndex: itemIndex, }; switch (type) { case 'menuSelect': type = 'select'; break; From c36e717d90e44ed585a919a345826a9e57e66143 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 Aug 2014 08:29:22 -0700 Subject: [PATCH 390/791] Change menu helper getters to private methods --- src/js/ui/menu.js | 50 +++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 1fe12b77..5e2485a5 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -43,11 +43,11 @@ Menu.prototype.buttonConfig = function() { Menu.prototype._buttonAutoConfig = function() {}; -var getMetaSection = function(sectionIndex) { +Menu.prototype._getMetaSection = function(sectionIndex) { return (this._sections[sectionIndex] || ( this._sections[sectionIndex] = {} )); }; -var getSections = function() { +Menu.prototype._getSections = function() { var sections = this.state.sections; if (sections instanceof Array) { return sections; @@ -64,14 +64,14 @@ var getSections = function() { sections = this.sectionsProvider.call(this); if (sections) { this.state.sections = sections; - return getSections.call(this); + return this._getSections(); } } return (this.state.sections = []); }; -var getSection = function(e, create) { - var sections = getSections.call(this); +Menu.prototype._getSection = function(e, create) { + var sections = this._getSections(); var section = sections[e.sectionIndex]; if (section) { return section; @@ -86,8 +86,8 @@ var getSection = function(e, create) { return (sections[e.sectionIndex] = {}); }; -var getItems = function(e, create) { - var section = getSection.call(this, e, create); +Menu.prototype._getItems = function(e, create) { + var section = this._getSection(e, create); if (!section) { if (e.sectionIndex > 0) { return; } section = this.state.sections[0] = {}; @@ -102,24 +102,24 @@ var getItems = function(e, create) { this._sections[e.sectionIndex] = section.items; delete section.items; } - var itemsProvider = getMetaSection.call(this, e.sectionIndex).items || this.itemsProvider; + var itemsProvider = this._getMetaSection(e.sectionIndex).items || this.itemsProvider; if (itemsProvider) { var items = itemsProvider.call(this, e); if (items) { section.items = items; - return getItems.call(this, e, create); + return this._getItems(e, create); } } return (section.items = []); }; -var getItem = function(e, create) { - var items = getItems.call(this, e, create); +Menu.prototype._getItem = function(e, create) { + var items = this._getItems(e, create); var item = items[e.itemIndex]; if (item) { return item; } - var itemProvider = getMetaSection.call(this, e.sectionIndex).item || this.itemProvider; + var itemProvider = this._getMetaSection(e.sectionIndex).item || this.itemProvider; if (itemProvider) { item = itemProvider.call(this, e); if (item) { @@ -131,7 +131,7 @@ var getItem = function(e, create) { }; Menu.prototype._resolveMenu = function() { - var sections = getSections.call(this); + var sections = this._getSections(this); if (this === WindowStack.top()) { simply.impl.menu.call(this, this.state); return true; @@ -139,9 +139,9 @@ Menu.prototype._resolveMenu = function() { }; Menu.prototype._resolveSection = function(e, clear) { - var section = getSection.call(this, e); + var section = this._getSection(e); if (!section) { return; } - section.items = getItems.call(this, e); + section.items = this._getItems(e); if (this === WindowStack.top()) { simply.impl.menuSection.call(this, e.sectionIndex, section, clear); var select = this._selection; @@ -153,7 +153,7 @@ Menu.prototype._resolveSection = function(e, clear) { }; Menu.prototype._resolveItem = function(e) { - var item = getItem.call(this, e); + var item = this._getItem(e); if (!item) { return; } if (this === WindowStack.top()) { simply.impl.menuItem.call(this, e.sectionIndex, e.itemIndex, item); @@ -177,7 +177,7 @@ Menu.prototype._preloadSection = function(e) { Menu.prototype._emitSelect = function(e) { this._selection = e; - var item = getItem.call(this, e); + var item = this._getItem(e); switch (e.type) { case 'select': if (item && typeof item.select === 'function') { @@ -231,9 +231,9 @@ Menu.prototype.section = function(sectionIndex, section) { } var menuIndex = { sectionIndex: sectionIndex }; if (!section) { - return getSection.call(this, menuIndex); + return this._getSection(menuIndex); } - var sections = getSections.call(this); + var sections = this._getSections(); var prevLength = sections.length; sections[sectionIndex] = util2.copy(section, sections[sectionIndex]); if (sections.length !== prevLength) { @@ -251,14 +251,14 @@ Menu.prototype.items = function(sectionIndex, items) { return this; } if (typeof items === 'function') { - getMetaSection.call(this, sectionIndex).items = items; + this._getMetaSection(sectionIndex).items = items; return this; } var menuIndex = { sectionIndex: sectionIndex }; if (!items) { - return getItems.call(this, menuIndex); + return this._getItems(menuIndex); } - var section = getSection.call(this, menuIndex, true); + var section = this._getSection(menuIndex, true); section.items = items; this._resolveSection(menuIndex, true); return this; @@ -278,14 +278,14 @@ Menu.prototype.item = function(sectionIndex, itemIndex, item) { itemIndex = null; } if (typeof item === 'function') { - getMetaSection.call(this, sectionIndex).item = item; + this._getMetaSection(sectionIndex).item = item; return this; } var menuIndex = { sectionIndex: sectionIndex, itemIndex: itemIndex }; if (!item) { - return getItem.call(this, menuIndex); + return this._getItem(menuIndex); } - var items = getItems.call(this, menuIndex, true); + var items = this._getItems(menuIndex, true); var prevLength = items.length; items[itemIndex] = util2.copy(item, items[itemIndex]); if (items.length !== prevLength) { From bf741acc47838cb88a429559a67e8972d58e925b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 Aug 2014 08:39:50 -0700 Subject: [PATCH 391/791] Add item and section references to menu events --- README.md | 5 ++++- src/js/app.js | 3 ++- src/js/ui/menu.js | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9ee80d71..7a8396ac 100644 --- a/README.md +++ b/README.md @@ -676,14 +676,17 @@ menu.item(0, 0, { title: 'A new item', subtitle: 'replacing the previous one' }) Registers a callback called when an item in the menu is selected. The callback function will be passed an event with the following fields: +* section: The menu section object. * sectionIndex: The section index of the section of the selected item. +* item: The menu item object. * itemIndex: The item index of the selected item. **Note:** You can also register a callback for 'longSelect' event, triggered when the user long clicks on an item. ````js menu.on('select', function(e) { - console.log('Selected item #' + e.itemIndex + ' of section #' + e.sectionIndex); + console.log('Selected item #' + e.itemIndex + ' of section #' + e.sectionIndex); + console.log('The item is titled "' + e.item.title + '"'); }); ```` diff --git a/src/js/app.js b/src/js/app.js index c16b2d3b..87d980dc 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -30,7 +30,8 @@ main.on('click', 'up', function(e) { }] }); menu.on('select', function(e) { - console.log('Selected item: ' + e.sectionIndex + ' ' + e.itemIndex); + console.log('Selected item #' + e.itemIndex + ' of section #' + e.sectionIndex); + console.log('The item is titled "' + e.item.title + '"'); }); menu.show(); }); diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 5e2485a5..220f588f 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -310,6 +310,7 @@ Menu.emitSection = function(sectionIndex) { var e = { sectionIndex: sectionIndex }; + e.section = menu._getSection(e); if (Menu.emit('section', null, e) === false) { return false; } @@ -323,6 +324,8 @@ Menu.emitItem = function(sectionIndex, itemIndex) { sectionIndex: sectionIndex, itemIndex: itemIndex, }; + e.section = menu._getSection(e); + e.item = menu._getItem(e); if (Menu.emit('item', null, e) === false) { return false; } @@ -336,6 +339,8 @@ Menu.emitSelect = function(type, sectionIndex, itemIndex) { sectionIndex: sectionIndex, itemIndex: itemIndex, }; + e.section = menu._getSection(e); + e.item = menu._getItem(e); switch (type) { case 'menuSelect': type = 'select'; break; case 'menuLongSelect': type = 'longSelect'; break; From 638f916d3f7482eb005665a3818207df7f8e2822 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 Aug 2014 08:45:24 -0700 Subject: [PATCH 392/791] Add a reference to menu to menu events. Menu events already reference the menu with `this`, but referencing menu is clearer and is safe from rebinded functions. --- README.md | 1 + src/js/ui/menu.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 7a8396ac..e2e60036 100644 --- a/README.md +++ b/README.md @@ -676,6 +676,7 @@ menu.item(0, 0, { title: 'A new item', subtitle: 'replacing the previous one' }) Registers a callback called when an item in the menu is selected. The callback function will be passed an event with the following fields: +* menu: The menu object. * section: The menu section object. * sectionIndex: The section index of the section of the selected item. * item: The menu item object. diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 220f588f..9364b31d 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -308,6 +308,7 @@ Menu.emitSection = function(sectionIndex) { var menu = WindowStack.top(); if (!(menu instanceof Menu)) { return; } var e = { + menu: menu, sectionIndex: sectionIndex }; e.section = menu._getSection(e); @@ -321,6 +322,7 @@ Menu.emitItem = function(sectionIndex, itemIndex) { var menu = WindowStack.top(); if (!(menu instanceof Menu)) { return; } var e = { + menu: menu, sectionIndex: sectionIndex, itemIndex: itemIndex, }; @@ -336,6 +338,7 @@ Menu.emitSelect = function(type, sectionIndex, itemIndex) { var menu = WindowStack.top(); if (!(menu instanceof Menu)) { return; } var e = { + menu: menu, sectionIndex: sectionIndex, itemIndex: itemIndex, }; From 74feedc700acc72201d87af1d010e19f4f51e920 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 Aug 2014 13:41:43 -0700 Subject: [PATCH 393/791] Add beta and usability notice --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e2e60036..3ba297ba 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Pebble.js lets you write beautiful Pebble applications completely in JavaScript. Pebble.js applications run on your phone. They have access to all the resources of your phone (Internet connectivity, GPS, almost unlimited memory, etc). Because they are written in JavaScript they are also perfect to make HTTP requests and connect your Pebble to the Internet. +**Note:** Pebble.js is still in beta meaning API changes are possible. Pebble.js is suited towards applications that inherently require heavy communication with the Phone such as using resources on the Internet. In other cases, please be aware of the additional usage of Bluetooth in order for JavaScript to perform actions on the Pebble. + > ![JSConf 2014](http://2014.jsconf.us/img/logo.png) > > Pebble.js was announced during JSConf 2014! From 76f587ad4370e30222efc2067eff23c33c638900 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 Aug 2014 13:53:37 -0700 Subject: [PATCH 394/791] Update menu accessor docs detailing retrieval --- README.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3ba297ba..e6a4c878 100644 --- a/README.md +++ b/README.md @@ -646,7 +646,7 @@ var menu = new UI.Menu({ #### Menu.section(sectionIndex, section) -Define the section to be displayed at sectionIndex. +Define the section to be displayed at `sectionIndex`. See [Menu] for the properties of a section. ````js var section = { @@ -658,31 +658,37 @@ var section = { menu.section(1, section); ```` +When called with no `section`, returns the section at the given `sectionIndex`. + #### Menu.items(sectionIndex, items) -Define the items to display in a specific section. +Define the items to display in a specific section. See [Menu] for the properties of an item. ````js menu.items(0, [ { title: 'new item1' }, { title: 'new item2' } ]); ```` +Whell called with no `items`, returns the items of the section at the given `sectionIndex`. + #### Menu.item(sectionIndex, itemIndex, item) -Define the item to display at index itemIndex in section sectionIndex. +Define the item to display at index `itemIndex` in section `sectionIndex`. See [Menu] for the properties of an item. ````js menu.item(0, 0, { title: 'A new item', subtitle: 'replacing the previous one' }); ```` +When called with no `item`, returns the item at the given `sectionIndex` and `itemIndex`. + #### Menu.on('select', callback) Registers a callback called when an item in the menu is selected. The callback function will be passed an event with the following fields: -* menu: The menu object. -* section: The menu section object. -* sectionIndex: The section index of the section of the selected item. -* item: The menu item object. -* itemIndex: The item index of the selected item. +* `menu`: The menu object. +* `section`: The menu section object. +* `sectionIndex`: The section index of the section of the selected item. +* `item`: The menu item object. +* `itemIndex`: The item index of the selected item. **Note:** You can also register a callback for 'longSelect' event, triggered when the user long clicks on an item. From e45743e7a5f4fcdbbd76b09f67affa0bdcb81569 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 Aug 2014 14:10:34 -0700 Subject: [PATCH 395/791] Clarify Bluetooth impact in usability notice --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6a4c878..2ad47b9c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Pebble.js lets you write beautiful Pebble applications completely in JavaScript. Pebble.js applications run on your phone. They have access to all the resources of your phone (Internet connectivity, GPS, almost unlimited memory, etc). Because they are written in JavaScript they are also perfect to make HTTP requests and connect your Pebble to the Internet. -**Note:** Pebble.js is still in beta meaning API changes are possible. Pebble.js is suited towards applications that inherently require heavy communication with the Phone such as using resources on the Internet. In other cases, please be aware of the additional usage of Bluetooth in order for JavaScript to perform actions on the Pebble. +**Note:** Pebble.js is still in beta meaning API changes are possible. Pebble.js is suited towards applications that inherently require heavy communication with the Phone such as using resources on the Internet. In other cases, please be aware of the additional power consumption and latency of Bluetooth in order for JavaScript to perform actions on the Pebble. > ![JSConf 2014](http://2014.jsconf.us/img/logo.png) > From aa17ab1f0905fa1c6d49fbe6acdd6a95ca0880df Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 Aug 2014 14:22:12 -0700 Subject: [PATCH 396/791] Fix disclaimer wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ad47b9c..17a358eb 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Pebble.js lets you write beautiful Pebble applications completely in JavaScript. Pebble.js applications run on your phone. They have access to all the resources of your phone (Internet connectivity, GPS, almost unlimited memory, etc). Because they are written in JavaScript they are also perfect to make HTTP requests and connect your Pebble to the Internet. -**Note:** Pebble.js is still in beta meaning API changes are possible. Pebble.js is suited towards applications that inherently require heavy communication with the Phone such as using resources on the Internet. In other cases, please be aware of the additional power consumption and latency of Bluetooth in order for JavaScript to perform actions on the Pebble. +**Note:** Pebble.js is still in beta meaning API changes are possible. Pebble.js is best suited for applications that inherently require heavy communication with the Phone such as using resources on the Internet. In other cases, please be aware of the additional power consumption and latency of Bluetooth in order for JavaScript to perform actions on the Pebble. > ![JSConf 2014](http://2014.jsconf.us/img/logo.png) > From 178a5a3b080bba1f22b5e5b6fc80eea877984afb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 Aug 2014 14:59:49 -0700 Subject: [PATCH 397/791] Clarify the underlying Bluetooth usage compared to native apps Thanks Katharine for this version of wording and Cherie for the template! --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 17a358eb..30116e6b 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ Pebble.js Pebble.js lets you write beautiful Pebble applications completely in JavaScript. -Pebble.js applications run on your phone. They have access to all the resources of your phone (Internet connectivity, GPS, almost unlimited memory, etc). Because they are written in JavaScript they are also perfect to make HTTP requests and connect your Pebble to the Internet. +Pebble.js applications run on your phone. They have access to all the resources of your phone (internet connectivity, GPS, almost unlimited memory, etc). Because they are written in JavaScript they are also perfect to make HTTP requests and connect your Pebble to the internet. -**Note:** Pebble.js is still in beta meaning API changes are possible. Pebble.js is best suited for applications that inherently require heavy communication with the Phone such as using resources on the Internet. In other cases, please be aware of the additional power consumption and latency of Bluetooth in order for JavaScript to perform actions on the Pebble. +**Warning:** Pebble.js is still in beta, so breaking API changes are possible. Pebble.js is best suited for prototyping and applications that inherently require communication in response to user actions, such as accessing the internet. Please be aware that as a result of Bluetooth round-trips for all actions, Pebble.js apps will use more power and respond slower to user interaction than a similar native app. > ![JSConf 2014](http://2014.jsconf.us/img/logo.png) > @@ -13,11 +13,11 @@ Pebble.js applications run on your phone. They have access to all the resources ## Getting Started - * In CloudPebble.net + * In CloudPebble - The easiest way to use Pebble.js is in [CloudPebble.net](http://www.cloudpebble.net). Select the 'Pebble.js' project type when creating a new project. + The easiest way to use Pebble.js is in [CloudPebble](https://cloudpebble.net). Select the 'Pebble.js' project type when creating a new project. - [Build a Pebble.js application now in CloudPebble.net >](http://www.cloudpebble.net) + [Build a Pebble.js application now in CloudPebble >](https://cloudpebble.net) * With the Pebble SDK From d4bb48fa92a75cd388f8331c2be4f4429ee27dbb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 9 Aug 2014 14:15:12 -0700 Subject: [PATCH 398/791] Revert change to simply window to be idempotent --- src/simply/simply_window.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 981e960d..9012fbf9 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -77,10 +77,6 @@ void simply_window_set_background_color(SimplyWindow *self, GColor background_co } void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { - if (self->is_action_bar == is_action_bar) { - return; - } - self->is_action_bar = is_action_bar; if (!self->action_bar_layer) { From c2e76b521fc63725f2a33c6941dd5b93c8a0adf4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 9 Aug 2014 16:33:57 -0700 Subject: [PATCH 399/791] Move action enabled bool back to window action props Toggling the state of the action bar causes the window's click config to be set to either an action bar click config or scroll layer click config, both of which are not desirable for a menu which requires the menu click config. For now, ensure that all window operations the menu requires do not in any way relate to the action bar, thus this property must be grouped with actions. As a result, windows are responsible for clearing the action bar themselves in their respective clearing procedures. --- src/js/ui/simply-pebble.js | 6 ++---- src/simply/simply_msg.c | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 5387464c..f6cda37d 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -250,7 +250,6 @@ var WindowPropsPacket = new struct([ ['uint8', 'backgroundColor', Color], ['bool', 'fullscreen', BoolType], ['bool', 'scrollable', BoolType], - ['uint8', 'action', BoolType], ]); var WindowButtonConfigPacket = new struct([ @@ -263,6 +262,7 @@ var WindowActionBarPacket = new struct([ ['uint32', 'up', ImageType], ['uint32', 'select', ImageType], ['uint32', 'down', ImageType], + ['uint8', 'action', BoolType], ['uint8', 'backgroundColor', Color], ]); @@ -681,9 +681,6 @@ SimplyPebble.windowHide = function(id) { }; SimplyPebble.windowProps = function(def) { - WindowPropsPacket - .prop(def) - .action('action' in def && def.action !== false); SimplyPebble.sendPacket(WindowPropsPacket.prop(def)); }; @@ -701,6 +698,7 @@ var toActionDef = function(actionDef) { SimplyPebble.windowActionBar = function(def) { WindowActionBarPacket .prop(toActionDef(def)) + .action(typeof def === 'boolean' ? def : true) .backgroundColor(def.backgroundColor || 'black'); SimplyPebble.sendPacket(WindowActionBarPacket); }; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 2a09ef1e..6a234549 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -125,7 +125,6 @@ struct __attribute__((__packed__)) WindowPropsPacket { GColor background_color:8; bool fullscreen; bool scrollable; - bool action; }; typedef struct WindowButtonConfigPacket WindowButtonConfigPacket; @@ -140,6 +139,7 @@ typedef struct WindowActionBarPacket WindowActionBarPacket; struct __attribute__((__packed__)) WindowActionBarPacket { Packet packet; uint32_t image[3]; + bool action; GColor background_color:8; }; @@ -410,7 +410,6 @@ static void handle_window_props_packet(Simply *simply, Packet *data) { simply_window_set_background_color(window, packet->background_color); simply_window_set_fullscreen(window, packet->fullscreen); simply_window_set_scrollable(window, packet->scrollable); - simply_window_set_action_bar(window, packet->action); } static void handle_window_button_config_packet(Simply *simply, Packet *data) { @@ -432,6 +431,7 @@ static void handle_window_action_bar_packet(Simply *simply, Packet *data) { for (unsigned int i = 0; i < ARRAY_LENGTH(packet->image); ++i) { simply_window_set_action_bar_icon(window, i + 1, packet->image[i]); } + simply_window_set_action_bar(window, packet->action); } static void handle_image_packet(Simply *simply, Packet *data) { From 42856497b0ffd91addfb33d36a9855ec906c850f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 9 Aug 2014 16:38:39 -0700 Subject: [PATCH 400/791] Add action bar clearing to simply ui clear --- src/simply/simply_ui.c | 3 +++ src/simply/simply_window.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index d151cafa..e86fbb06 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -43,6 +43,9 @@ static SimplyStyle STYLES[] = { }; void simply_ui_clear(SimplyUi *self, uint32_t clear_mask) { + if (clear_mask & (1 << ClearAction)) { + simply_window_action_bar_clear(&self->window); + } if (clear_mask & (1 << ClearText)) { for (SimplyUiTextfield textfield = 0; textfield < NumUiTextfields; ++textfield) { simply_ui_set_text(self, textfield, NULL); diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 9012fbf9..fa596f9d 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -116,6 +116,8 @@ void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor ba } void simply_window_action_bar_clear(SimplyWindow *self) { + simply_window_set_action_bar(self, false); + for (ButtonId button = BUTTON_ID_UP; button <= BUTTON_ID_DOWN; ++button) { action_bar_layer_clear_icon(self->action_bar_layer, button); } From 0ce42d546c10865df8c933705ff962e300c44250 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 9 Aug 2014 16:38:56 -0700 Subject: [PATCH 401/791] Add action bar clearing to simply stage clear --- src/simply/simply_stage.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index fd7b41eb..19d1a977 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -48,6 +48,8 @@ static void destroy_animation(SimplyStage *self, SimplyAnimation *animation) { } void simply_stage_clear(SimplyStage *self) { + simply_window_action_bar_clear(&self->window); + while (self->stage_layer.elements) { destroy_element(self, (SimplyElementCommon*) self->stage_layer.elements); } From 3b35c2f39ba063881230749a326b9438318e6fbf Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 Aug 2014 15:31:35 -0700 Subject: [PATCH 402/791] Change all flag to boolean true --- src/js/ui/card.js | 5 +++-- src/js/ui/propable.js | 2 +- src/js/ui/simply-pebble.js | 5 +---- src/js/ui/window.js | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/js/ui/card.js b/src/js/ui/card.js index 579b94bb..0b6271b6 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -87,9 +87,10 @@ Card.prototype._prop = function() { Card.prototype._clear = function(flags) { flags = myutil.toFlags(flags); - if (myutil.flag(flags, 'all')) { + if (flags === true) { clearableProps.forEach(myutil.unset.bind(this)); - } else if (myutil.flag(flags, 'action')) { + } + if (myutil.flag(flags, 'action')) { this._clearAction(); } }; diff --git a/src/js/ui/propable.js b/src/js/ui/propable.js index efecc503..c0244bdf 100644 --- a/src/js/ui/propable.js +++ b/src/js/ui/propable.js @@ -46,7 +46,7 @@ Propable.prototype.prop = function(field, value, clear) { clear = value; } if (clear) { - this._clear('all'); + this._clear(true); } var def = myutil.toObject(field, value); util2.copy(def, this.state); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index f6cda37d..18e3eeb8 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -708,10 +708,7 @@ SimplyPebble.image = function(id, gbitmap) { }; var toClearFlags = function(clear) { - if (clear === true) { - clear = 'all'; - } - if (clear === 'all') { + if (clear === true || clear === 'all') { clear = ~0; } else if (typeof clear === 'string') { clear = clearFlagMap[clear]; diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 3313da48..84d7cfe1 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -132,7 +132,7 @@ Window.prototype.prop = function(field, value, clear) { clear = value; } if (clear) { - this._clear('all'); + this._clear(true); } var windowDef = myutil.toObject(field, value); util2.copy(windowDef, this.state); From 7e6ee2c1ed4f06fec201609ce2fc07d3cf869bc5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 Aug 2014 15:32:29 -0700 Subject: [PATCH 403/791] Fix simply pebble window action bar to clear unset images --- src/js/ui/simply-pebble.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 18e3eeb8..1a3aa95e 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -696,10 +696,13 @@ var toActionDef = function(actionDef) { }; SimplyPebble.windowActionBar = function(def) { + var actionDef = toActionDef(def); WindowActionBarPacket - .prop(toActionDef(def)) + .up(actionDef.up) + .select(actionDef.select) + .down(actionDef.down) .action(typeof def === 'boolean' ? def : true) - .backgroundColor(def.backgroundColor || 'black'); + .backgroundColor(actionDef.backgroundColor || 'black'); SimplyPebble.sendPacket(WindowActionBarPacket); }; From d8add639fb24bb6c83318162a53600a28a978d35 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 27 Aug 2014 13:36:24 -0700 Subject: [PATCH 404/791] Fix disconnected notice to complete showing up --- src/simply/simply_msg.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 6a234549..92b1c512 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -370,12 +370,17 @@ struct __attribute__((__packed__)) ElementAnimateDonePacket { static bool s_has_communicated = false; +static bool s_broadcast_window = true; + bool simply_msg_has_communicated() { return s_has_communicated; } static SimplyWindow *get_top_simply_window(Simply *simply) { Window *base_window = window_stack_get_top_window(); + if (!base_window) { + return NULL; + } SimplyWindow *window = window_get_user_data(base_window); if (!window || (void*) window == simply->splash) { return NULL; @@ -791,7 +796,13 @@ static void failed_callback(DictionaryIterator *iter, AppMessageResult reason, S simply_ui_clear(ui, ~0); simply_ui_set_text(ui, UiSubtitle, "Disconnected"); simply_ui_set_text(ui, UiBody, "Run the Pebble Phone App"); - simply_window_stack_show(simply->window_stack, &ui->window, true); + + if (get_top_simply_window(simply) != &ui->window) { + bool was_broadcast = s_broadcast_window; + s_broadcast_window = false; + simply_window_stack_show(simply->window_stack, &ui->window, true); + s_broadcast_window = was_broadcast; + } } } @@ -937,6 +948,9 @@ bool simply_msg_long_click(SimplyMsg *self, ButtonId button) { } bool send_window(SimplyMsg *self, Command type, uint32_t id) { + if (!s_broadcast_window) { + return false; + } size_t length; WindowEventPacket *packet = malloc0(length = sizeof(*packet)); if (!packet) { From c298d176b0d7d7e70274f80574d23b6faffab7bf Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 27 Aug 2014 13:39:28 -0700 Subject: [PATCH 405/791] Fix disconnected screen to allow exiting with the back button --- src/simply/simply_msg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 92b1c512..9cf433f9 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -793,6 +793,8 @@ static void sent_callback(DictionaryIterator *iter, void *context) { static void failed_callback(DictionaryIterator *iter, AppMessageResult reason, Simply *simply) { SimplyUi *ui = simply->ui; if (reason == APP_MSG_NOT_CONNECTED) { + s_has_communicated = false; + simply_ui_clear(ui, ~0); simply_ui_set_text(ui, UiSubtitle, "Disconnected"); simply_ui_set_text(ui, UiBody, "Run the Pebble Phone App"); From 58370c383ec189cade5fd4590c3d340f25bdb21e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 27 Aug 2014 18:45:09 -0700 Subject: [PATCH 406/791] Change menu to only set the menu index on show --- src/js/ui/menu.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 9364b31d..f0a14233 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -22,6 +22,8 @@ util2.copy(Emitter.prototype, Menu.prototype); Menu.prototype._show = function() { this._resolveMenu(); Window.prototype._show.apply(this, arguments); + var select = this._selection; + simply.impl.menuSelection(select.sectionIndex, select.itemIndex); }; Menu.prototype._numPreloadItems = 5; @@ -146,7 +148,7 @@ Menu.prototype._resolveSection = function(e, clear) { simply.impl.menuSection.call(this, e.sectionIndex, section, clear); var select = this._selection; if (select.sectionIndex === e.sectionIndex) { - this._preloadSection(select); + this._preloadItems(select); } return true; } @@ -170,11 +172,6 @@ Menu.prototype._preloadItems = function(e) { } }; -Menu.prototype._preloadSection = function(e) { - simply.impl.menuSelection(e.sectionIndex, e.itemIndex); - this._preloadItems(e); -}; - Menu.prototype._emitSelect = function(e) { this._selection = e; var item = this._getItem(e); From 0999d723ed132591ac7ecab4083783f21b5f8a46 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 27 Aug 2014 19:13:08 -0700 Subject: [PATCH 407/791] Reduce app msg inbound size to actual maximum of 2044 --- src/simply/simply_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 9cf433f9..edf240c5 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -20,7 +20,7 @@ #define SEND_DELAY_MS 10 -static const size_t APP_MSG_SIZE_INBOUND = 2048; +static const size_t APP_MSG_SIZE_INBOUND = 2044; static const size_t APP_MSG_SIZE_OUTBOUND = 512; From ce71773c6c7e3cc7f28413bcd8209f20a25fbaa6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 28 Aug 2014 15:25:41 -0700 Subject: [PATCH 408/791] Change to show disconnected on communication timeout --- src/simply/simply_msg.c | 26 ++++++++++++++++---------- src/simply/simply_msg.h | 1 + src/simply/simply_ui.c | 6 +----- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index edf240c5..029fc242 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -791,20 +791,26 @@ static void sent_callback(DictionaryIterator *iter, void *context) { } static void failed_callback(DictionaryIterator *iter, AppMessageResult reason, Simply *simply) { - SimplyUi *ui = simply->ui; if (reason == APP_MSG_NOT_CONNECTED) { s_has_communicated = false; - simply_ui_clear(ui, ~0); - simply_ui_set_text(ui, UiSubtitle, "Disconnected"); - simply_ui_set_text(ui, UiBody, "Run the Pebble Phone App"); + simply_msg_show_disconnected(simply->msg); + } +} - if (get_top_simply_window(simply) != &ui->window) { - bool was_broadcast = s_broadcast_window; - s_broadcast_window = false; - simply_window_stack_show(simply->window_stack, &ui->window, true); - s_broadcast_window = was_broadcast; - } +void simply_msg_show_disconnected(SimplyMsg *self) { + Simply *simply = self->simply; + SimplyUi *ui = simply->ui; + + simply_ui_clear(ui, ~0); + simply_ui_set_text(ui, UiSubtitle, "Disconnected"); + simply_ui_set_text(ui, UiBody, "Run the Pebble Phone App"); + + if (get_top_simply_window(simply) != &ui->window) { + bool was_broadcast = s_broadcast_window; + s_broadcast_window = false; + simply_window_stack_show(simply->window_stack, &ui->window, true); + s_broadcast_window = was_broadcast; } } diff --git a/src/simply/simply_msg.h b/src/simply/simply_msg.h index 5a8eb034..db1d324d 100644 --- a/src/simply/simply_msg.h +++ b/src/simply/simply_msg.h @@ -28,6 +28,7 @@ struct SimplyPacket { SimplyMsg *simply_msg_create(Simply *simply); void simply_msg_destroy(SimplyMsg *self); bool simply_msg_has_communicated(); +void simply_msg_show_disconnected(SimplyMsg *self); bool simply_msg_single_click(SimplyMsg *self, ButtonId button); bool simply_msg_long_click(SimplyMsg *self, ButtonId button); diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index e86fbb06..d4b2bc71 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -230,11 +230,7 @@ static void show_welcome_text(SimplyUi *self) { return; } - simply_ui_set_text(self, UiTitle, "Pebble.js"); - simply_ui_set_text(self, UiSubtitle, "Write apps with JS!"); - simply_ui_set_text(self, UiBody, "pebble.github.io/pebblejs"); - - simply_window_stack_show(self->window.simply->window_stack, &self->window, true); + simply_msg_show_disconnected(self->window.simply->msg); } static void window_load(Window *window) { From 045da4102d1dbc925c36cce3258b068cc94e809b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 28 Aug 2014 17:47:44 -0700 Subject: [PATCH 409/791] Ignore extra Android cancelled webview closes The webview close callback is called an additional time with the cancelled event after a regular close. Ignore the next cancelled event for each regular close to function similar to iOS. --- src/js/settings/settings.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index 3181746f..77751043 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -25,6 +25,7 @@ Settings.reset = function() { options: {}, data: {}, listeners: [], + ignoreCancelled: 0, }; }; @@ -152,10 +153,18 @@ Settings.onOpenConfig = function(e) { }; Settings.onCloseConfig = function(e) { + // Work around for PebbleKit JS Android + // On Android, an extra cancelled event occurs after a normal close + if (e.response !== 'CANCELLED') { + state.ignoreCancelled++; + } else if (state.ignoreCancelled > 0) { + state.ignoreCancelled--; + return; + } var listener = util2.last(state.listeners); var options = {}; var format; - if (e.response && e.response !== 'CANCELLED') { + if (e.response) { try { options = JSON.parse(decodeURIComponent(e.response)); format = 'json'; From 3661d215f7f62438359fed8650ac81eb80b9adca Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 5 Sep 2014 16:46:57 -0700 Subject: [PATCH 410/791] Fix README examples to use the new operator for creating Vector2 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 30116e6b..da8d7474 100644 --- a/README.md +++ b/README.md @@ -120,8 +120,8 @@ var Vector2 = require('vector2'); var wind = new UI.Window(); var textfield = new UI.Text({ - position: Vector2(0, 0), - size: Vector2(144, 168), + position: new Vector2(0, 0), + size: new Vector2(144, 168), font: 'GOTHIC_18_BOLD', text: 'Gothic 18 Bold' }); @@ -553,7 +553,7 @@ A [Window] instantiated directly is a dynamic window that can display a complete var wind = new UI.Window(); // Add a rect element -var rect = new UI.Rect({ size: Vector2(20, 20) }); +var rect = new UI.Rect({ size: new Vector2(20, 20) }); wind.add(rect); wind.show(); From c0770adfdd4d6c3f02e5cb492470f2cdff218068 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 5 Sep 2014 18:05:49 -0700 Subject: [PATCH 411/791] Change README font guide to use lowercase with dashes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index da8d7474..181b66b3 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ card.icon('IMAGE_CHOOSE_A_UNIQUE_IDENTIFIER'); ## Using Fonts -You can use any of the Pebble system fonts in your Pebble.js applications. Please refer to [this Pebble Developer's blog post](https://developer.getpebble.com/blog/2013/07/24/Using-Pebble-System-Fonts/) for a list of all the Pebble system fonts. +You can use any of the Pebble system fonts in your Pebble.js applications. Please refer to [this Pebble Developer's blog post](https://developer.getpebble.com/blog/2013/07/24/Using-Pebble-System-Fonts/) for a list of all the Pebble system fonts. When referring to a font, using lowercase with dashes is recommended. For example, `GOTHIC_18_BOLD` becomes `gothic-18-bold`. ````js var Vector2 = require('vector2'); @@ -122,7 +122,7 @@ var wind = new UI.Window(); var textfield = new UI.Text({ position: new Vector2(0, 0), size: new Vector2(144, 168), - font: 'GOTHIC_18_BOLD', + font: 'gothic-18-bold', text: 'Gothic 18 Bold' }); wind.add(textfield); From 8da96e8f3bcf5d8e2e9af6005aed4707f5f871cb Mon Sep 17 00:00:00 2001 From: Christian Dullweber Date: Sat, 6 Sep 2014 21:11:53 -0700 Subject: [PATCH 412/791] don't throw errors on successful http requests --- src/js/lib/ajax.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/lib/ajax.js b/src/js/lib/ajax.js index 75663388..2aa47be1 100644 --- a/src/js/lib/ajax.js +++ b/src/js/lib/ajax.js @@ -92,7 +92,8 @@ var ajax = function(opt, success, failure) { req.onreadystatechange = function(e) { if (req.readyState === 4) { var body = req.responseText; - var okay = req.status === 200; + var okay = req.status >= 200 && req.status < 300 || req.status === 304; + try { if (opt.type === 'json') { body = JSON.parse(body); From 4e687b9ea886e74a55dadc0bdca28a5528f3ae68 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 8 Sep 2014 13:46:53 -0700 Subject: [PATCH 413/791] Disable changing the background color of a card --- src/js/ui/simply-pebble.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 1a3aa95e..92ffe356 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -680,8 +680,11 @@ SimplyPebble.windowHide = function(id) { SimplyPebble.sendPacket(WindowHidePacket.id(id)); }; -SimplyPebble.windowProps = function(def) { - SimplyPebble.sendPacket(WindowPropsPacket.prop(def)); +SimplyPebble.windowProps = function(def, backgroundColor) { + WindowPropsPacket + .prop(def) + .backgroundColor(backgroundColor); + SimplyPebble.sendPacket(WindowPropsPacket); }; SimplyPebble.windowButtonConfig = function(def) { @@ -746,7 +749,7 @@ SimplyPebble.card = function(def, clear, pushing) { if (clear !== undefined) { SimplyPebble.cardClear(clear); } - SimplyPebble.windowProps(def); + SimplyPebble.windowProps(def, 'white'); if (def.action !== undefined) { SimplyPebble.windowActionBar(def.action); } From 25c6cfee58c9089c85cdaf31a16d23684cfeae5a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 8 Sep 2014 13:48:45 -0700 Subject: [PATCH 414/791] Set the default font of a text element to gothic-24 --- src/js/ui/text.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/ui/text.js b/src/js/ui/text.js index 832bd85f..5b7864f8 100644 --- a/src/js/ui/text.js +++ b/src/js/ui/text.js @@ -16,6 +16,7 @@ var defaults = { backgroundColor: 'clear', borderColor: 'clear', color: 'white', + font: 'gothic-24', }; var Text = function(elementDef) { From 61ab157a201bae9be24f762053770283a56bd659 Mon Sep 17 00:00:00 2001 From: YuLun Shih Date: Tue, 16 Sep 2014 21:28:49 +0800 Subject: [PATCH 415/791] Fix the broken table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 181b66b3..164422b8 100644 --- a/README.md +++ b/README.md @@ -416,7 +416,7 @@ Subscribe to the accel 'data' event. The callback function will be passed an eve One accelerometer data point is an object with the following properties: | Property | Type | Description | -| -------- | : ---: | ------------ | +| -------- | :----: | ------------ | | `x` | Number | The acceleration across the x-axis (from left to right when facing your Pebble) | | `y` | Number | The acceleration across the y-axis (from the bottom of the screen to the top of the screen) | | `z` | Number | The acceleration across the z-axis (going through your Pebble from the back side of your Pebble to the front side - and then through your head if Pebble is facing you ;) | From af65cf59f003bc1411c7f553885338438ed42267 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 30 Sep 2014 11:55:43 -0700 Subject: [PATCH 416/791] Update to SDK v2.6 --- appinfo.json | 2 +- src/simply/simply_res.c | 16 ++++++++-------- src/simply/simply_res.h | 1 + src/simply/simply_stage.c | 2 +- src/simply/simply_ui.c | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/appinfo.json b/appinfo.json index 0694a652..9cbc1e2a 100644 --- a/appinfo.json +++ b/appinfo.json @@ -4,7 +4,7 @@ "longName": "Pebble.js", "companyName": "Meiguro", "versionCode": 1, - "versionLabel": "0.4.0", + "versionLabel": "0.4", "capabilities": [ "configurable" ], "watchapp": { "watchface": false diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 4b6a9c43..2e95a802 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -88,7 +88,7 @@ GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder if (image) { return &image->bitmap; } - if (id <= ARRAY_LENGTH(resource_crc_table)) { + if (id <= self->num_bundled_res) { return simply_res_add_bundled_image(self, id); } if (!image && is_placeholder) { @@ -103,12 +103,7 @@ GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id) { return NULL; } - ResHandle handle = resource_get_handle(id); - if (!handle) { - return NULL; - } - - GFont custom_font = fonts_load_custom_font(handle); + GFont custom_font = fonts_load_custom_font(id); if (!custom_font) { free(font); return NULL; @@ -131,7 +126,7 @@ GFont simply_res_auto_font(SimplyRes *self, uint32_t id) { if (font) { return font->font; } - if (id <= ARRAY_LENGTH(resource_crc_table)) { + if (id <= self->num_bundled_res) { return simply_res_add_custom_font(self, id); } return NULL; @@ -150,6 +145,11 @@ void simply_res_clear(SimplyRes *self) { SimplyRes *simply_res_create() { SimplyRes *self = malloc(sizeof(*self)); *self = (SimplyRes) { .images = NULL }; + + while (resource_get_handle(self->num_bundled_res + 1)) { + ++self->num_bundled_res; + } + return self; } diff --git a/src/simply/simply_res.h b/src/simply/simply_res.h index d21f317c..634038e7 100644 --- a/src/simply/simply_res.h +++ b/src/simply/simply_res.h @@ -13,6 +13,7 @@ typedef struct SimplyRes SimplyRes; struct SimplyRes { List1Node *images; List1Node *fonts; + uint32_t num_bundled_res; }; typedef struct SimplyResItemCommon SimplyResItemCommon; diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 19d1a977..8566ab7f 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -106,7 +106,7 @@ static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementTex if (element->time_units) { text = format_time(text); } - GFont font = element->font ? element->font : fonts_get_system_font(FONT_KEY_FONT_FALLBACK); + GFont font = element->font ? element->font : fonts_get_system_font(FONT_KEY_GOTHIC_14); graphics_context_set_text_color(ctx, element->text_color); graphics_draw_text(ctx, text, font, element->frame, element->overflow_mode, element->alignment, NULL); } diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index d4b2bc71..19d145db 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -64,7 +64,7 @@ void simply_ui_set_style(SimplyUi *self, int style_index) { self->ui_layer.style = &STYLES[style_index]; if (self->ui_layer.style->custom_body_font_id) { self->ui_layer.custom_body_font = fonts_load_custom_font( - resource_get_handle(self->ui_layer.style->custom_body_font_id)); + self->ui_layer.style->custom_body_font_id); } layer_mark_dirty(self->ui_layer.layer); } From 98a62fcb92fbae3dc76f33a185c13afb935dee81 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 30 Sep 2014 20:39:08 -0700 Subject: [PATCH 417/791] Continue to use resource_get_handle for custom fonts --- src/simply/simply_res.c | 7 ++++++- src/simply/simply_ui.c | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 2e95a802..13345154 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -103,7 +103,12 @@ GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id) { return NULL; } - GFont custom_font = fonts_load_custom_font(id); + ResHandle handle = resource_get_handle(id); + if (!handle) { + return NULL; + } + + GFont custom_font = fonts_load_custom_font(handle); if (!custom_font) { free(font); return NULL; diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 19d145db..d4b2bc71 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -64,7 +64,7 @@ void simply_ui_set_style(SimplyUi *self, int style_index) { self->ui_layer.style = &STYLES[style_index]; if (self->ui_layer.style->custom_body_font_id) { self->ui_layer.custom_body_font = fonts_load_custom_font( - self->ui_layer.style->custom_body_font_id); + resource_get_handle(self->ui_layer.style->custom_body_font_id)); } layer_mark_dirty(self->ui_layer.layer); } From 23d34b46b38dec2b809eaf8ea245da5e96fb40f0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 19 Oct 2014 17:48:44 -0700 Subject: [PATCH 418/791] Fix card to have a banner method --- src/js/ui/card.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ui/card.js b/src/js/ui/card.js index 0b6271b6..7156bc04 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -38,7 +38,7 @@ var textProps = [ var imageProps = [ 'icon', 'subicon', - 'image', + 'banner', ]; var actionProps = [ From 0ee9ed4ec4dba90508279411952204180d6c635f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 19 Oct 2014 18:38:16 -0700 Subject: [PATCH 419/791] Use the image size if an image element frame is zeroed --- src/simply/simply_stage.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 8566ab7f..fc9ec677 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -117,7 +117,11 @@ static void image_element_draw(GContext *ctx, SimplyStage *self, SimplyElementIm rect_element_draw_background(ctx, self, (SimplyElementRect*) element); GBitmap *bitmap = simply_res_get_image(self->window.simply->res, element->image); if (bitmap) { - graphics_draw_bitmap_centered(ctx, bitmap, element->frame); + GRect frame = element->frame; + if (frame.size.w == 0 && frame.size.h == 0) { + frame = bitmap->bounds; + } + graphics_draw_bitmap_centered(ctx, bitmap, frame); } rect_element_draw_border(ctx, self, (SimplyElementRect*) element); graphics_context_set_compositing_mode(ctx, GCompOpAssign); From 24d31761a90dbfb51b2ae7621215b38c0102eaae Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 19 Oct 2014 19:39:13 -0700 Subject: [PATCH 420/791] Add Image element docs. Fixes #5 --- README.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 164422b8..99f18619 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ To add an image in your application, edit the `appinfo.json` file and add your i } ```` -> If you are using CloudPebble, you can add images in your project configuration (coming soon!). +> If you are using CloudPebble, you can add images in your project configuration. To reference your image in Pebble.js, you can use the `name` field or the `file` field. @@ -111,6 +111,23 @@ card.icon('images/your_image.png'); card.icon('IMAGE_CHOOSE_A_UNIQUE_IDENTIFIER'); ```` +You can also display images with [Image] when using a dynamic [Window]. + +````js +// This is an example of using an image with Image and Window +var UI = require('ui'); +var Vector2 = require('vector2'); + +var wind = new UI.Window({ fullscreen: true }); +var image = new UI.Image({ + position: new Vector2(0, 0), + size: new Vector2(144, 168), + image: 'images/your_image.png' +}); +wind.add(image); +wind.show(); +```` + ## Using Fonts You can use any of the Pebble system fonts in your Pebble.js applications. Please refer to [this Pebble Developer's blog post](https://developer.getpebble.com/blog/2013/07/24/Using-Pebble-System-Fonts/) for a list of all the Pebble system fonts. When referring to a font, using lowercase with dashes is recommended. For example, `GOTHIC_18_BOLD` becomes `gothic-18-bold`. @@ -908,6 +925,36 @@ Sets the borderColor property. See [Text]. Sets the backgroundColor property. See [Text]. +### Image + +An [Element] that displays an image on the screen. + +The [Image] element has the following properties. Just like any other [Element] you can initialize those properties when creating the object or use the accessors. + +| Name | Type | Default | Description | +| ------------ | :-------: | --------- | ------------- | +| `image` | string | "" | The resource name or path to the image to display in this element. See [Using Images] for more information and how to add your own images. | +| `compositing` | string | "normal" | The compositing operation used to display the image. See [Image.compositing(compop)] for a list of possible compositing operations. | + + +#### Image.image(image) + +Sets the image property. See [Image]. + + +#### Image.compositing(compop) + +Sets the compositing operation to be used when rendering. Specify the compositing operation as a string such as `"invert"`. The following is a list of compositing operations available. + +| Compositing | Description | +| ----------- | :--------------------------------------------------------------------: | +| `"normal"` | Display the image normally. This is the default. | +| `"invert"` | Display the image with inverted colors. | +| `"or"` | White pixels are shown, black pixels are clear. | +| `"and"` | Black pixels are shown, white pixels are clear. | +| `"clear"` | The image's white pixels are painted as black, and the rest are clear. | +| `"set"` | The image's black pixels are painted as white, and the rest are clear. | + ### Vibe `Vibe` allows you to trigger vibration on the user wrist. @@ -998,5 +1045,6 @@ For more information, see [Vector2 in the three.js reference documentation][thre [Window.show()]: #window-show [Window.hide()]: #window-hide [Element.queue(callback(next))]: #element-queue-callback-next +[Image.compositing(compop)]: #image-compositing [Menu.on('select, callback)]: #menu-on-select-callback [three.js Vector2]: http://threejs.org/docs/#Reference/Math/Vector2 From a619bd4b668cd5e11010694222b09ae9b2309e57 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 29 Oct 2014 21:04:34 -0700 Subject: [PATCH 421/791] Remove releases --- releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw | Bin 78010 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw diff --git a/releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw b/releases/simply-js-v0.3.4-for-fw-v2.0.2.pbw deleted file mode 100644 index 28a29f8593c552420b5309af8d46e7d0f8350d8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78010 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 Date: Thu, 30 Oct 2014 01:21:31 -0700 Subject: [PATCH 422/791] Add scroll layer click config override helpers --- src/util/scroll_layer.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/util/scroll_layer.h diff --git a/src/util/scroll_layer.h b/src/util/scroll_layer.h new file mode 100644 index 00000000..73a4e2f6 --- /dev/null +++ b/src/util/scroll_layer.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "simply/simply.h" + +static inline ClickConfigProvider scroll_layer_click_config_provider_accessor(ClickConfigProvider provider) { + static ClickConfigProvider s_provider; + if (provider) { + s_provider = provider; + } + return s_provider; +} + +static inline void scroll_layer_click_config(void *context) { + window_set_click_context(BUTTON_ID_UP, context); + window_set_click_context(BUTTON_ID_DOWN, context); + scroll_layer_click_config_provider_accessor(NULL)(context); +} + +static inline void scroll_layer_set_click_config_provider_onto_window(ScrollLayer *scroll_layer, + ClickConfigProvider click_config_provider, Window *window, void *context) { + scroll_layer_set_click_config_onto_window(scroll_layer, window); + scroll_layer_click_config_provider_accessor(window_get_click_config_provider(window)); + window_set_click_config_provider_with_context(window, click_config_provider, context); +} From 04f926f137395a0ebd0faaab8b0722da9aa75a7d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 30 Oct 2014 01:22:05 -0700 Subject: [PATCH 423/791] Change simply window to allow scroll layer and action bar simultaneously --- src/simply/simply_window.c | 42 +++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index fa596f9d..90ecaf0f 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -8,24 +8,30 @@ #include "simply.h" #include "util/graphics.h" +#include "util/scroll_layer.h" #include "util/string.h" #include static void click_config_provider(void *data); -void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { - if (self->is_scrollable == is_scrollable) { +static void set_scroll_layer_click_config(SimplyWindow *self) { + if (!self->scroll_layer) { return; } - self->is_scrollable = is_scrollable; + scroll_layer_set_click_config_provider_onto_window( + self->scroll_layer, click_config_provider, self->window, self); +} - if (!self->scroll_layer) { +void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { + if (self->is_scrollable == is_scrollable) { return; } - scroll_layer_set_click_config_onto_window(self->scroll_layer, self->window); + self->is_scrollable = is_scrollable; + + set_scroll_layer_click_config(self); if (!self->layer) { return; @@ -84,12 +90,16 @@ void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { } action_bar_layer_remove_from_window(self->action_bar_layer); - if (is_action_bar) { - action_bar_layer_add_to_window(self->action_bar_layer, self->window); - action_bar_layer_set_click_config_provider(self->action_bar_layer, click_config_provider); - } else { - scroll_layer_set_click_config_onto_window(self->scroll_layer, self->window); + + set_scroll_layer_click_config(self); + + if (!is_action_bar) { + return; } + + action_bar_layer_set_context(self->action_bar_layer, self); + action_bar_layer_set_click_config_provider(self->action_bar_layer, click_config_provider); + action_bar_layer_add_to_window(self->action_bar_layer, self->window); } void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint32_t id) { @@ -166,6 +176,9 @@ static void click_config_provider(void *context) { window_long_click_subscribe(i, 500, (ClickHandler) long_click_handler, NULL); } } + if (self->is_scrollable) { + scroll_layer_click_config(self->scroll_layer); + } } void simply_window_load(SimplyWindow *self) { @@ -180,13 +193,8 @@ void simply_window_load(SimplyWindow *self) { layer_add_child(window_layer, scroll_base_layer); scroll_layer_set_context(scroll_layer, self); - scroll_layer_set_callbacks(scroll_layer, (ScrollLayerCallbacks) { - .click_config_provider = click_config_provider, - }); - if (self->is_action_bar) { - simply_window_set_action_bar(self, true); - } + simply_window_set_action_bar(self, self->is_action_bar); } void simply_window_unload(SimplyWindow *self) { @@ -205,7 +213,7 @@ SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { Window *window = self->window = window_create(); window_set_background_color(window, GColorClear); - window_set_click_config_provider(window, click_config_provider); + window_set_click_config_provider_with_context(window, click_config_provider, self); ActionBarLayer *action_bar_layer = self->action_bar_layer = action_bar_layer_create(); action_bar_layer_set_context(action_bar_layer, self); From 45f5f8e58d3832862c36970a7ff8ade73c7b438e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 30 Oct 2014 01:25:43 -0700 Subject: [PATCH 424/791] Clarify that scrollable also blocks long click events --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 99f18619..870bd521 100644 --- a/README.md +++ b/README.md @@ -469,7 +469,7 @@ Pebble.js provides three types of Windows: | `clear` | boolean | | | | `action` | actionDef | None | An action bar will be shown when configured with an `actionDef`. | | `fullscreen` | boolean | false | When true, the Pebble status bar will not be visible and the window will use the entire screen. | -| `scrollable` | boolean | false | When true, the up and down button will scroll the content of this Card. | +| `scrollable` | boolean | false | Whether the user can scroll this Window with the up and down button. When this is enabled, single and long click events on the up and down button will not be transmitted to your app. | #### Window actionDef @@ -625,7 +625,7 @@ The properties available on a [Card] are: | `icon` | Image | null | An image to display before the title text. Refer to [Using Images] for instructions on how to include images in your app. | | `subicon` | Image | null | An image to display before the subtitle text. Refer to [Using Images] for instructions on how to include images in your app. | | `banner` | Image | null | An image to display in the center of the screen. Refer to [Using Images] for instructions on how to include images in your app. | -| `scrollable` | boolean | false | Whether the user can scroll this card with the up and down button. When this is enabled, click events on the up and down button will not be transmitted to your app. | +| `scrollable` | boolean | false | Whether the user can scroll this card with the up and down button. When this is enabled, single and long click events on the up and down button will not be transmitted to your app. | | `style` | string | "small" | Selects the font used to display the body. This can be 'small', 'large' or 'mono' | The small and large styles correspond to the system notification styles. Mono sets a monospace font for the body textfield, enabling more complex text UIs or ASCII art. From 14b1868b053cdd5ecfd71bf6117b1817f093003f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 30 Oct 2014 01:43:59 -0700 Subject: [PATCH 425/791] Add simply stage scrollable support --- src/simply/simply_stage.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index fc9ec677..10ca82f5 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -130,11 +130,20 @@ static void image_element_draw(GContext *ctx, SimplyStage *self, SimplyElementIm static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyStage *self = *(void**) layer_get_data(layer); + GRect frame = layer_get_frame(layer); + frame.origin = scroll_layer_get_content_offset(self->window.scroll_layer); + frame.origin.x = -frame.origin.x; + frame.origin.y = -frame.origin.y; + graphics_context_set_fill_color(ctx, self->window.background_color); - graphics_fill_rect(ctx, layer_get_frame(layer), 0, GCornerNone); + graphics_fill_rect(ctx, frame, 0, GCornerNone); SimplyElementCommon *element = (SimplyElementCommon*) self->stage_layer.elements; while (element) { + int16_t max_y = element->frame.origin.y + element->frame.size.h; + if (max_y > frame.size.h) { + frame.size.h = max_y; + } switch (element->type) { case SimplyElementTypeNone: break; @@ -155,6 +164,12 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } element = (SimplyElementCommon*) element->node.next; } + + if (self->window.is_scrollable) { + frame.origin = GPointZero; + layer_set_frame(layer, frame); + scroll_layer_set_content_size(self->window.scroll_layer, frame.size); + } } static SimplyElementCommon *alloc_element(SimplyElementType type) { From 5ed1ee2f1c8859a57610956aed43002ce0d44c77 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 8 Dec 2014 14:09:29 -0800 Subject: [PATCH 426/791] Add missing card style support in new packet format. Fixes #12 --- src/js/ui/simply-pebble.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 92ffe356..0d70d240 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -742,6 +742,10 @@ SimplyPebble.cardImage = function(field, image) { SimplyPebble.sendPacket(CardImagePacket.index(field).image(image)); }; +SimplyPebble.cardStyle = function(field, style) { + SimplyPebble.sendPacket(CardStylePacket.style(style)); +}; + SimplyPebble.card = function(def, clear, pushing) { if (arguments.length === 3) { SimplyPebble.windowShow({ type: 'card', pushing: pushing }); @@ -758,6 +762,8 @@ SimplyPebble.card = function(def, clear, pushing) { SimplyPebble.cardText(k, def[k]); } else if (cardImageTypes.indexOf(k) !== -1) { SimplyPebble.cardImage(k, def[k]); + } else if (k === 'style') { + SimplyPebble.cardStyle(k, def[k]); } } }; From 5ef545aa53efbab5ab04eb1e32f5eb8d86216859 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 8 Dec 2014 14:21:02 -0800 Subject: [PATCH 427/791] Fix and better clarify element animation docs --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 870bd521..0ea2d785 100644 --- a/README.md +++ b/README.md @@ -752,7 +752,7 @@ Removes the element from its [Window]. #### Element.animate(animateDef, [duration=400]) -The `position` and `size` properties can be animated. An `animateDef` is object with any supported properties specified. See [Element] for a description of those properties. The default animation duration is 400 milliseconds. +The `position` and `size` are currently the only Element properties that can be animated. An `animateDef` is object with any supported properties specified. See [Element] for a description of those properties. The default animation duration is 400 milliseconds. ````js // Use the element's position and size to avoid allocating more vectors. @@ -767,7 +767,7 @@ size.addSelf(size); element.animate({ position: pos, size: size }); ```` -Animations are queued when `Element.animate` is called multiple times at once. The animations will occur in order, and the first animation will occur immediately. +Each element has its own animation queue. Animations are queued when `Element.animate` is called multiple times at once with the same element. The animations will occur in order, and the first animation will occur immediately. Note that because each element has its own queue, calling `Element.animate` across different elements will result all elements animating the same time. To queue animations across multiple elements, see [Element.queue(callback(next))]. When an animation begins, its destination values are saved immediately to the [Element]. @@ -786,7 +786,7 @@ element.animate('position', pos, 1000); #### Element.queue(callback(next)) -`Element.queue` can be used to perform tasks that are dependent upon an animation completing, such as preparing the element for a different animation. It is recommended to use `Element.queue` instead of a timeout if the same element will be animated after the custom task. +`Element.queue` can be used to perform tasks that are dependent upon an animation completing, such as preparing the element for a different animation. `Element.queue` can also be used to coordinate animations across different elements. It is recommended to use `Element.queue` instead of a timeout if the same element will be animated after the custom task. The `callback` you pass to `Element.queue` will be called with a function `next` as the first parameter. When `next` is called, the next item in the animation queue will begin. Items includes callbacks added by `Element.queue` or animations added by `Element.animate` before an animation is complete. Calling `next` is equivalent to calling `Element.dequeue`. @@ -797,7 +797,7 @@ element this.backgroundColor('white'); next(); }) - .animate('position', new Vector2(0, 50) + .animate('position', new Vector2(0, 50)); ```` `Element.queue` is chainable. From 7d5d6f1363335f042027d380938912d20559d378 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 19 Dec 2014 01:56:53 -0800 Subject: [PATCH 428/791] Fix struct.js to interpret strings as raw bytes for utf8 support Thanks songfei! --- src/js/lib/struct.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/lib/struct.js b/src/js/lib/struct.js index f168467f..3ebae271 100644 --- a/src/js/lib/struct.js +++ b/src/js/lib/struct.js @@ -81,6 +81,7 @@ struct.types.cstring.get = function(offset) { }; struct.types.cstring.set = function(offset, value) { + value = unescape(encodeURIComponent(value)); this._grow(offset + value.length + 1); var i = offset; var buffer = this._view; From 330927b394410362c28f53ec06cc26c52e451496 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 23 Dec 2014 03:32:32 -0800 Subject: [PATCH 429/791] Fix ajax.js to set the content mimetype when posting form data --- src/js/lib/ajax.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/js/lib/ajax.js b/src/js/lib/ajax.js index 2aa47be1..15a28bb5 100644 --- a/src/js/lib/ajax.js +++ b/src/js/lib/ajax.js @@ -79,12 +79,13 @@ var ajax = function(opt, success, failure) { } } - var data = null; - if (opt.data) { + var data = opt.data; + if (data) { if (opt.type === 'json') { req.setRequestHeader('Content-Type', 'application/json'); data = JSON.stringify(opt.data); - } else { + } else if (opt.type !== 'text') { + req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); data = formify(opt.data); } } From 71f3a21b51343da114835c95b7d38b57944d44da Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 4 Jan 2015 13:54:37 -0800 Subject: [PATCH 430/791] Fix to perform a long pulse on requesting a long vibe Thanks sarfata! --- src/simply/simply_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 029fc242..20923a2f 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -469,7 +469,7 @@ static void handle_vibe_packet(Simply *simply, Packet *data) { VibePacket *packet = (VibePacket*) data; switch (packet->type) { case VibeShort: vibes_short_pulse(); break; - case VibeLong: vibes_short_pulse(); break; + case VibeLong: vibes_long_pulse(); break; case VibeDouble: vibes_double_pulse(); break; } } From 45cdc570e96e92cc82ae77c1f863ae89949986b1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 6 Jan 2015 15:11:25 -0800 Subject: [PATCH 431/791] Fix safe.js to extract exception message from abnormal exceptions --- src/js/lib/safe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index d5357218..d2a8e2e1 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -93,7 +93,7 @@ safe.translateStack = function(stack) { safe.translateError = function(err) { var name = err.name; - var message = err.message; + var message = err.message || err.toString(); var stack = err.stack; var result = ['JavaScript Error:']; if (message && (!stack || !stack.match(message))) { From 950c50ff2d74ca642b73d04520bf43dbf8806ce8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 6 Jan 2015 15:51:20 -0800 Subject: [PATCH 432/791] Clear resources when switching windows --- src/simply/simply_menu.c | 1 + src/simply/simply_stage.c | 1 + src/simply/simply_ui.c | 2 ++ 3 files changed, 4 insertions(+) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 4313a86b..ba3c3816 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -264,6 +264,7 @@ static void window_disappear(Window *window) { SimplyMenu *self = window_get_user_data(window); simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); + simply_res_clear(self->window.simply->res); simply_menu_clear(self); } diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 10ca82f5..ada5cb16 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -340,6 +340,7 @@ static void window_disappear(Window *window) { SimplyStage *self = window_get_user_data(window); simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); + simply_res_clear(self->window.simply->res); simply_stage_clear(self); } diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index d4b2bc71..042ffb02 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -260,6 +260,8 @@ static void window_appear(Window *window) { static void window_disappear(Window *window) { SimplyUi *self = window_get_user_data(window); simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); + + simply_res_clear(self->window.simply->res); } static void window_unload(Window *window) { From 6c78f38abbf789486fb8d898a98f596617de27a5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 18 Jan 2015 21:13:20 -0800 Subject: [PATCH 433/791] Add simply msg segmented packet receiving --- src/simply/simply_msg.c | 106 ++++++++++++++++++++++++++++++++++------ src/simply/simply_msg.h | 3 +- 2 files changed, 93 insertions(+), 16 deletions(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 20923a2f..19171385 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -27,7 +27,8 @@ static const size_t APP_MSG_SIZE_OUTBOUND = 512; typedef enum Command Command; enum Command { - CommandWindowShow = 1, + CommandSegment = 1, + CommandWindowShow, CommandWindowHide, CommandWindowShowEvent, CommandWindowHideEvent, @@ -94,6 +95,14 @@ struct __attribute__((__packed__)) Packet { uint16_t length; }; +typedef struct SegmentPacket SegmentPacket; + +struct __attribute__((__packed__)) SegmentPacket { + Packet packet; + bool is_last; + uint8_t buffer[]; +}; + typedef struct WindowShowPacket WindowShowPacket; struct __attribute__((__packed__)) WindowShowPacket { @@ -372,6 +381,8 @@ static bool s_has_communicated = false; static bool s_broadcast_window = true; +static void handle_packet(Simply *simply, Packet *packet); + bool simply_msg_has_communicated() { return s_has_communicated; } @@ -388,6 +399,67 @@ static SimplyWindow *get_top_simply_window(Simply *simply) { return window; } +static void destroy_packet(SimplyMsg *self, SimplyPacket *packet) { + if (!packet) { + return; + } + free(packet->buffer); + packet->buffer = NULL; + free(packet); +} + +static void add_receive_packet(SimplyMsg *self, SegmentPacket *packet) { + size_t size = packet->packet.length; + Packet *copy = malloc(size); + memcpy(copy, packet, size); + SimplyPacket *node = malloc0(sizeof(*node)); + node->length = size; + node->buffer = copy; + list1_prepend(&self->receive_queue, &node->node); +} + +static void handle_receive_queue(SimplyMsg *self, SegmentPacket *packet) { + size_t total_length = packet->packet.length - sizeof(SegmentPacket); + for (List1Node *walk = self->receive_queue; walk; walk = walk->next) { + total_length += ((SimplyPacket*) walk)->length - sizeof(SegmentPacket); + } + + void *buffer = malloc(total_length); + void *cursor = buffer + total_length; + SegmentPacket *other = packet; + SimplyPacket *walk = NULL; + while (true) { + size_t copy_size = other->packet.length - sizeof(SegmentPacket); + cursor -= copy_size; + memcpy(cursor, other->buffer, copy_size); + + if (walk) { + list1_remove(&self->receive_queue, &walk->node); + destroy_packet(self, walk); + } + + walk = (SimplyPacket*) self->receive_queue; + if (!walk) { + break; + } + + other = walk->buffer; + } + + handle_packet(self->simply, buffer); + + free(buffer); +} + +static void handle_segment_packet(Simply *simply, Packet *data) { + SegmentPacket *packet = (SegmentPacket*) data; + if (packet->is_last) { + handle_receive_queue(simply->msg, packet); + } else { + add_receive_packet(simply->msg, packet); + } +} + static void handle_window_show_packet(Simply *simply, Packet *data) { WindowShowPacket *packet = (WindowShowPacket*) data; SimplyWindow *window = simply->windows[MIN(WindowTypeLast - 1, packet->type)]; @@ -647,6 +719,9 @@ static void handle_element_animate_packet(Simply *simply, Packet *data) { static void handle_packet(Simply *simply, Packet *packet) { switch (packet->type) { + case CommandSegment: + handle_segment_packet(simply, packet); + break; case CommandWindowShow: handle_window_show_packet(simply, packet); break; @@ -770,11 +845,19 @@ static void received_callback(DictionaryIterator *iter, void *context) { s_has_communicated = true; size_t length = tuple->length; + if (length == 0) { + return; + } + uint8_t *buffer = tuple->value->data; while (true) { Packet *packet = (Packet*) buffer; handle_packet(context, packet); + if (packet->length == 0) { + break; + } + length -= packet->length; if (length == 0) { break; @@ -790,7 +873,9 @@ static void dropped_callback(AppMessageResult reason, void *context) { static void sent_callback(DictionaryIterator *iter, void *context) { } -static void failed_callback(DictionaryIterator *iter, AppMessageResult reason, Simply *simply) { +static void failed_callback(DictionaryIterator *iter, AppMessageResult reason, void *context) { + Simply *simply = context; + if (reason == APP_MSG_NOT_CONNECTED) { s_has_communicated = false; @@ -827,7 +912,7 @@ SimplyMsg *simply_msg_create(Simply *simply) { app_message_register_inbox_received(received_callback); app_message_register_inbox_dropped(dropped_callback); app_message_register_outbox_sent(sent_callback); - app_message_register_outbox_failed((AppMessageOutboxFailed) failed_callback); + app_message_register_outbox_failed(failed_callback); return self; } @@ -844,16 +929,6 @@ void simply_msg_destroy(SimplyMsg *self) { free(self); } -static void destroy_packet(SimplyMsg *self, SimplyPacket *packet) { - if (!packet) { - return; - } - list1_remove(&self->queue, &packet->node); - free(packet->buffer); - packet->buffer = NULL; - free(packet); -} - static bool send_msg(uint8_t *buffer, size_t length) { DictionaryIterator *iter = NULL; if (app_message_outbox_begin(&iter) != APP_MSG_OK) { @@ -887,6 +962,7 @@ static void make_multi_packet(SimplyMsg *self, SimplyPacket *packet) { memcpy(cursor, walk->buffer, walk->length); cursor += walk->length; SimplyPacket *next = (SimplyPacket*) walk->node.next; + list1_remove(&self->send_queue, &walk->node); destroy_packet(self, walk); walk = next; } @@ -898,7 +974,7 @@ static void send_msg_retry(void *data) { SimplyMsg *self = data; self->send_timer = NULL; if (!self->send_buffer) { - make_multi_packet(self, (SimplyPacket*) self->queue); + make_multi_packet(self, (SimplyPacket*) self->send_queue); } if (!self->send_buffer) { return; @@ -927,7 +1003,7 @@ static SimplyPacket *add_packet(SimplyMsg *self, Packet *buffer, Command type, s .length = length, .buffer = buffer, }; - list1_append(&self->queue, &packet->node); + list1_append(&self->send_queue, &packet->node); if (self->send_delay_ms <= SEND_DELAY_MS) { if (self->send_timer) { app_timer_cancel(self->send_timer); diff --git a/src/simply/simply_msg.h b/src/simply/simply_msg.h index db1d324d..2b293b13 100644 --- a/src/simply/simply_msg.h +++ b/src/simply/simply_msg.h @@ -10,7 +10,8 @@ typedef struct SimplyMsg SimplyMsg; struct SimplyMsg { Simply *simply; - List1Node *queue; + List1Node *send_queue; + List1Node *receive_queue; uint32_t send_delay_ms; AppTimer *send_timer; uint8_t *send_buffer; From 610f2a6ee11e8b527c123a322eea063dd896c234 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 18 Jan 2015 21:13:48 -0800 Subject: [PATCH 434/791] Add SimplyPebble segmented packet sending for oversized packets --- src/js/ui/simply-pebble.js | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 0d70d240..9a0b9a5e 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -223,6 +223,12 @@ var Packet = new struct([ ['uint16', 'length'], ]); +var SegmentPacket = new struct([ + [Packet, 'packet'], + ['bool', 'isLast'], + ['data', 'buffer'], +]); + var WindowShowPacket = new struct([ [Packet, 'packet'], ['uint8', 'type', WindowType], @@ -502,6 +508,7 @@ var ElementAnimateDonePacket = new struct([ var CommandPackets = [ Packet, + SegmentPacket, WindowShowPacket, WindowHidePacket, WindowShowEventPacket, @@ -651,11 +658,11 @@ var PacketQueue = function() { this._send = this.send.bind(this); }; -PacketQueue.prototype._maxPayloadSize = 2048 - 20; +PacketQueue.prototype._maxPayloadSize = 2044 - 32; PacketQueue.prototype.add = function(packet) { var byteArray = toByteArray(packet); - if (this._message.length + byteArray.length >= this._maxPayloadSize) { + if (this._message.length + byteArray.length > this._maxPayloadSize) { this.send(); } Array.prototype.push.apply(this._message, byteArray); @@ -664,12 +671,31 @@ PacketQueue.prototype.add = function(packet) { }; PacketQueue.prototype.send = function() { + if (this._message.length === 0) { + return; + } state.messageQueue.send({ 0: this._message }); this._message = []; }; +SimplyPebble.sendMultiPacket = function(packet) { + var byteArray = toByteArray(packet); + var totalSize = byteArray.length; + var segmentSize = state.packetQueue._maxPayloadSize - Packet._size; + for (var i = 0; i < totalSize; i += segmentSize) { + var isLast = (i + segmentSize) >= totalSize; + var buffer = byteArray.slice(i, Math.min(totalSize, i + segmentSize)); + SegmentPacket.isLast((i + segmentSize) >= totalSize).buffer(buffer); + state.packetQueue.add(SegmentPacket); + } +}; + SimplyPebble.sendPacket = function(packet) { - state.packetQueue.add(packet); + if (packet._cursor < state.packetQueue._maxPayloadSize) { + state.packetQueue.add(packet); + } else { + SimplyPebble.sendMultiPacket(packet); + } }; SimplyPebble.windowShow = function(def) { From 1374eea15ca5b0012d426f0a99ad1874c619dd25 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 18 Jan 2015 21:34:26 -0800 Subject: [PATCH 435/791] Update ImageService and SimplyPebble to clear the image cache Windows now clear simply res on unload. Update the JS side to do the same so that it knows when to resend images when necessary. --- src/js/ui/imageservice.js | 36 ++++++++++++++++++++++++++---------- src/js/ui/simply-pebble.js | 1 + 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/js/ui/imageservice.js b/src/js/ui/imageservice.js index a9b96898..c1e4885c 100644 --- a/src/js/ui/imageservice.js +++ b/src/js/ui/imageservice.js @@ -64,25 +64,30 @@ ImageService.load = function(opt, reset, callback) { var url = myutil.abspath(state.rootUrl, opt.url); var hash = makeImageHash(opt); var image = state.cache[hash]; + var fetch = false; if (image) { if ((opt.width && image.width !== opt.width) || (opt.height && image.height !== opt.height) || (opt.dither && image.dither !== opt.dither)) { reset = true; } - if (reset !== true) { + if (reset !== true && image.loaded) { return image.id; } } - image = { - id: state.nextId++, - url: url, - width: opt.width, - height: opt.height, - dither: opt.dither, - }; + if (!image || reset === true) { + fetch = true; + image = { + id: state.nextId++, + url: url, + }; + } + image.width = opt.width; + image.height = opt.height; + image.dither = opt.dither; + image.loaded = true; state.cache[hash] = image; - imagelib.load(image, function() { + var onLoad = function() { simply.impl.image(image.id, image.gbitmap); if (callback) { var e = { @@ -92,7 +97,12 @@ ImageService.load = function(opt, reset, callback) { }; callback(e); } - }); + }; + if (fetch) { + imagelib.load(image, onLoad); + } else { + onLoad(); + } return image.id; }; @@ -109,4 +119,10 @@ ImageService.resolve = function(opt) { return typeof id !== 'undefined' ? id : ImageService.load(opt); }; +ImageService.markAllUnloaded = function() { + for (var k in state.cache) { + delete state.cache[k].loaded; + } +}; + ImageService.init(); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 9a0b9a5e..628bf652 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -982,6 +982,7 @@ SimplyPebble.onPacket = function(buffer, offset) { packet._offset = offset; switch (packet) { case WindowHideEventPacket: + ImageService.markAllUnloaded(); WindowStack.emitHide(packet.id()); break; case ClickPacket: From 1ca0d8a171f2acaa1d7e0c9d75fabbeac83593c4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 20 Jan 2015 12:24:25 -0800 Subject: [PATCH 436/791] Fix SimplyPebble to send the utf8 byte length for strings --- src/js/ui/simply-pebble.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 0d70d240..98991898 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -29,8 +29,14 @@ var StringType = function(x) { return '' + x; }; +var UTF8ByteLength = function(x) { + return unescape(encodeURIComponent(x)).length; +}; + var EnumerableType = function(x) { - if (x && x.hasOwnProperty('length')) { + if (typeof x === 'string') { + return UTF8ByteLength(x); + } else if (x && x.hasOwnProperty('length')) { return x.length; } return x ? Number(x) : 0; From 959b52ff9c67f51bac9dcfd75b0d63e8347ed35f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 20 Jan 2015 15:01:45 -0800 Subject: [PATCH 437/791] Add Window show and hide events. Implements #19 --- src/js/ui/menu.js | 4 +--- src/js/ui/window.js | 20 ++++++++++++++++---- src/js/ui/windowstack.js | 14 +++++++------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index f0a14233..df066c54 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -297,9 +297,7 @@ Menu.prototype.selection = function(callback) { simply.impl.menuSelection(); }; -Menu.emit = function(type, subtype, e) { - Window.emit(type, subtype, e, Menu); -}; +Menu.emit = Window.emit; Menu.emitSection = function(sectionIndex) { var menu = WindowStack.top(); diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 84d7cfe1..822c85c8 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -265,16 +265,28 @@ Window.prototype._toString = function() { return '[' + this.constructor._codeName + ' ' + this._id() + ']'; }; -Window.emit = function(type, subtype, e, klass) { - var wind = e.window = WindowStack.top(); +Window.prototype._emit = function(type, subtype, e) { + e.window = this; + var klass = this.constructor; if (klass) { - e[klass._codeName] = wind; + e[klass._codeName] = this; } - if (wind && wind.emit(type, subtype, e) === false) { + if (this.emit(type, subtype, e) === false) { return false; } }; +Window.prototype._emitShow = function(type) { + return this._emit(type, null, {}); +}; + +Window.emit = function(type, subtype, e) { + var wind = WindowStack.top(); + if (window) { + return wind._emit(type, subtype, e); + } +}; + /** * Simply.js button click event. This can either be a single click or long click. * Use the event type 'click' or 'longClick' to subscribe to these events. diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index bd468667..3f9c2024 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -13,12 +13,6 @@ WindowStack.prototype.init = function() { this.off(); this._items = []; - this.on('show', function(e) { - e.window.forEachListener(e.window.onAddHandler); - }); - this.on('hide', function(e) { - e.window.forEachListener(e.window.onRemoveHandler); - }); }; WindowStack.prototype.top = function() { @@ -26,6 +20,9 @@ WindowStack.prototype.top = function() { }; WindowStack.prototype._emitShow = function(item) { + item.forEachListener(item.onAddHandler); + item._emitShow('show'); + var e = { window: item }; @@ -37,12 +34,15 @@ WindowStack.prototype._emitHide = function(item) { window: item }; this.emit('hide', e); + + item._emitShow('hide'); + item.forEachListener(item.onRemoveHandler); }; WindowStack.prototype._show = function(item, pushing) { if (!item) { return; } - this._emitShow(item); item._show(pushing); + this._emitShow(item); }; WindowStack.prototype._hide = function(item, broadcast) { From d100c39ca748930b4409a18adddf6e3939f96205 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 20 Jan 2015 15:26:27 -0800 Subject: [PATCH 438/791] Add Window show and hide event docs --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 0ea2d785..54efd726 100644 --- a/README.md +++ b/README.md @@ -538,6 +538,32 @@ You can register a handler for the 'up', 'select', 'down', and 'back' buttons. Just like `Window.on('click', button, handler)` but for 'longClick' events. +#### Window.on('show', handler) + +Registers a handler to call when the window is shown. This is useful for knowing when a user returns to your window from another. This event is also emitted when programmatically showing the window. This does not include when a Pebble notification popup is exited, revealing your window. + +````js +// Define the handler before showing. +wind.on('show', function() { + console.log('Window is shown!'); +}); + +// The show event will emit, and the handler will be called. +wind.show(); +```` + +#### Window.on('hide', handler) + +Registers a handler to call when the window is hidden. This is useful for knowing when a user exits out of your window or when your window is no longer visible because a different window is pushed on top. This event is also emitted when programmatically hiding the window. This does not include when a Pebble notification popup obstructs your window. + +It is recommended to use this instead of overriding the back button when appropriate. + +````js +wind.on('hide', function() { + console.log('Window is hidden!'); +}); +```` + #### Window.action(actionDef) This is a special nested accessor to the `action` property which takes an `actionDef`. It can be used to set a new `actionDef`. See [Window actionDef]. From 8bb82ff5147dd82a0f78a5c6f61fee485f4d84d8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 23 Jan 2015 01:56:22 -0800 Subject: [PATCH 439/791] Update ajax.js type parameter doc --- README.md | 2 +- src/js/lib/ajax.js | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 54efd726..18c562bf 100644 --- a/README.md +++ b/README.md @@ -1031,7 +1031,7 @@ The supported options are: | ---- | :----: | :--------: | --------- | ------------- | | `url` | string | | | The URL to make the ajax request to. e.g. 'http://www.example.com?name=value' | | `method` | string | (optional) | get | The HTTP method to use: 'get', 'post', 'put', 'delete', 'options', or any other standard method supported by the running environment. | -| `type` | string | (optional) | | The expected response format. Specify `json` to have ajax parse the response as json and pass an object as the data parameter. +| `type` | string | (optional) | | The content and response format. By default, the content format is 'form' and response format is separately 'text'. Specifying 'json' will have ajax send `data` as json as well as parse the response as json. Specifying 'text' allows you to send custom formatted content and parse the raw response text. If you wish to send form encoded data and parse json, leave `type` undefined and use `JSON.decode` to parse the response data. | `data` | object | (optional) | | The request body, mainly to be used in combination with 'post' or 'put'. e.g. `{ username: 'guest' }` | `headers` | object | (optional) | | Custom HTTP headers. Specify additional headers. e.g. `{ 'x-extra': 'Extra Header' }` | `async` | boolean | (optional) | true | Whether the request will be asynchronous. Specify `false` for a blocking, synchronous request. diff --git a/src/js/lib/ajax.js b/src/js/lib/ajax.js index 15a28bb5..5d74f036 100644 --- a/src/js/lib/ajax.js +++ b/src/js/lib/ajax.js @@ -29,8 +29,11 @@ var deformify = function(form) { * @property {string} [method='get'] - The HTTP method to use: 'get', 'post', 'put', 'delete', 'options', * or any other standard method supported by the running environment. * @property {string} url - The URL to make the ajax request to. e.g. 'http://www.example.com?name=value' - * @property {string} [type='text'] - The expected response format. Specify 'json' to have ajax parse - * the response as json and pass an object as the data parameter. + * @property {string} [type] - The content and response format. By default, the content format + * is 'form' and response format is separately 'text'. Specifying 'json' will have ajax send `data` + * as json as well as parse the response as json. Specifying 'text' allows you to send custom + * formatted content and parse the raw response text. If you wish to send form encoded data and + * parse json, leave `type` undefined and use `JSON.decode` to parse the response data. * @property {object} [data] - The request body, mainly to be used in combination with 'post' or 'put'. * e.g. { username: 'guest' } * @property {object} headers - Custom HTTP headers. Specify additional headers. From c216c6ab74671e26b45f361414fbff9340a049b3 Mon Sep 17 00:00:00 2001 From: Matthew Nielsen Date: Wed, 28 Jan 2015 20:45:39 -0700 Subject: [PATCH 440/791] Add coffeescript -> javascript compilation to build process. --- wscript | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/wscript b/wscript index b2bb67bc..8409a598 100644 --- a/wscript +++ b/wscript @@ -1,6 +1,12 @@ import json import os +try: + import coffeescript + import re # only needed for coffeescript files +except ImportError: + pass # allow coffeescript module to be optional + from waflib.Configure import conf top = '.' @@ -30,7 +36,8 @@ def build(ctx): def concat_javascript(self, *k, **kw): js_path = kw['js_path'] js_nodes = (self.path.ant_glob(js_path + '/**/*.js') + - self.path.ant_glob(js_path + '/**/*.json')) + self.path.ant_glob(js_path + '/**/*.json') + + self.path.ant_glob(js_path + '/**/*.coffee')) if not js_nodes: return [] @@ -60,6 +67,17 @@ def concat_javascript(self, *k, **kw): elif relpath.startswith('vendor/'): sources.append(body) else: + if relpath.endswith('.coffee'): + try: + body = coffeescript.compile(body) + except NameError: + self.fatal(""" + Coffeescript file '%s' found but coffeescript module isn't installed. + You may try `pip install coffeescript` or `easy_install coffeescript`. + """ % (relpath)) + # change ".coffee" or ".js.coffee" extensions to ".js" + relpath = re.sub('(\.js)?\.coffee$', '.js', relpath) + sources.append({ 'relpath': relpath, 'body': body }) with open(APPINFO_PATH, 'r') as f: From a873279212cd112a5eee17c63c5c302ec98cd607 Mon Sep 17 00:00:00 2001 From: Alessio Bogon Date: Sat, 21 Feb 2015 00:46:06 +0100 Subject: [PATCH 441/791] Light: new module This allows the backlight control on the Pebble. --- src/js/ui/light.js | 14 ++++++++++++++ src/js/ui/simply-pebble.js | 18 ++++++++++++++++++ src/simply/simply_msg.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 src/js/ui/light.js diff --git a/src/js/ui/light.js b/src/js/ui/light.js new file mode 100644 index 00000000..2a513217 --- /dev/null +++ b/src/js/ui/light.js @@ -0,0 +1,14 @@ +var Light = module.exports; +var simply = require('ui/simply'); + +Light.on = function() { + simply.impl.light('on'); +}; + +Light.auto = function() { + simply.impl.light('auto'); +} + +Light.trigger = function() { + simply.impl.light('trigger'); +} diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 56b61b03..b213f3b7 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -224,6 +224,14 @@ var vibeTypes = [ var VibeType = makeArrayType(vibeTypes); +var LightTypes = [ + 'on', + 'auto', + 'trigger' +]; + +var LightType = makeArrayType(LightTypes); + var Packet = new struct([ ['uint16', 'type'], ['uint16', 'length'], @@ -323,6 +331,11 @@ var VibePacket = new struct([ ['uint8', 'type', VibeType], ]); +var LightPacket = new struct([ + [Packet, 'packet'], + ['uint8', 'type', LightType], +]); + var AccelPeekPacket = new struct([ [Packet, 'packet'], ]); @@ -530,6 +543,7 @@ var CommandPackets = [ CardImagePacket, CardStylePacket, VibePacket, + LightPacket, AccelPeekPacket, AccelConfigPacket, AccelDataPacket, @@ -804,6 +818,10 @@ SimplyPebble.vibe = function(type) { SimplyPebble.sendPacket(VibePacket.type(type)); }; +SimplyPebble.light = function(type) { + SimplyPebble.sendPacket(LightPacket.type(type)); +}; + var accelListeners = []; SimplyPebble.accelPeek = function(callback) { diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 19171385..4d29b05a 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -43,6 +43,7 @@ enum Command { CommandCardImage, CommandCardStyle, CommandVibe, + CommandLight, CommandAccelPeek, CommandAccelConfig, CommandAccelData, @@ -88,6 +89,14 @@ enum VibeType { VibeDouble = 2, }; +typedef enum LightType LightType; + +enum LightType { + LightOn = 0, + LightAuto = 1, + LightTrigger = 2, +}; + typedef struct Packet Packet; struct __attribute__((__packed__)) Packet { @@ -208,6 +217,13 @@ struct __attribute__((__packed__)) VibePacket { VibeType type:8; }; +typedef struct LightPacket LightPacket; + +struct __attribute__((__packed__)) LightPacket { + Packet packet; + LightType type:8; +}; + typedef Packet AccelPeekPacket; typedef struct AccelConfigPacket AccelConfigPacket; @@ -546,6 +562,15 @@ static void handle_vibe_packet(Simply *simply, Packet *data) { } } +static void handle_light_packet(Simply *simply, Packet *data) { + LightPacket *packet = (LightPacket*) data; + switch (packet->type) { + case LightOn: light_enable(true); break; + case LightAuto: light_enable(false); break; + case LightTrigger: light_enable_interaction(); break; + } +} + static void accel_peek_timer_callback(void *context) { Simply *simply = context; AccelData data = { .x = 0 }; @@ -763,6 +788,9 @@ static void handle_packet(Simply *simply, Packet *packet) { case CommandVibe: handle_vibe_packet(simply, packet); break; + case CommandLight: + handle_light_packet(simply, packet); + break; case CommandAccelPeek: handle_accel_peek_packet(simply, packet); break; From e787215cfeddffcbd69c8cf3b56e70caf1b52c66 Mon Sep 17 00:00:00 2001 From: Alessio Bogon Date: Sat, 21 Feb 2015 09:51:54 +0100 Subject: [PATCH 442/791] Readme: Update doc for "Light" --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 18c562bf..67ae70e0 100644 --- a/README.md +++ b/README.md @@ -998,6 +998,25 @@ Vibe.vibrate('long'); | ---- |:----:|:--------:|---------|-------------| | `type` | string | optional | `short` | The duration of the vibration. `short`, `long` or `double`. | +### Light + +`Light` allows you to control the Pebble's backlight. +````js +var Light = require('ui/light'); + +// Turn on the light +Light.on('long'); +```` + +#### Light.on() +Turn on the light indefinitely. + +#### Light.auto() +Restore the normal behaviour. + +#### Light.trigger() +Trigger the backlight, just like if the user shook his wrist. + ## Libraries Pebble.js includes several libraries to help you write applications. From 564d5f9d9ee92d7501a47546fc3c166b9bdd859f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 21 Feb 2015 06:11:45 -0800 Subject: [PATCH 443/791] Capitalize SimplyPebble types --- src/js/ui/simply-pebble.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index b213f3b7..dfadedf2 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -173,56 +173,56 @@ var makeFlagsType = function(types) { }; }; -var windowTypes = [ +var WindowTypes = [ 'window', 'menu', 'card', ]; -var WindowType = makeArrayType(windowTypes); +var WindowType = makeArrayType(WindowTypes); -var buttonTypes = [ +var ButtonTypes = [ 'back', 'up', 'select', 'down', ]; -var ButtonType = makeArrayType(buttonTypes); +var ButtonType = makeArrayType(ButtonTypes); -var ButtonFlagsType = makeFlagsType(buttonTypes); +var ButtonFlagsType = makeFlagsType(ButtonTypes); -var cardTextTypes = [ +var CardTextTypes = [ 'title', 'subtitle', 'body', ]; -var CardTextType = makeArrayType(cardTextTypes); +var CardTextType = makeArrayType(CardTextTypes); -var cardImageTypes = [ +var CardImageTypes = [ 'icon', 'subicon', 'banner', ]; -var CardImageType = makeArrayType(cardImageTypes); +var CardImageType = makeArrayType(CardImageTypes); -var cardStyleTypes = [ +var CardStyleTypes = [ 'small', 'large', 'mono', ]; -var CardStyleType = makeArrayType(cardStyleTypes); +var CardStyleType = makeArrayType(CardStyleTypes); -var vibeTypes = [ +var VibeTypes = [ 'short', 'long', 'double', ]; -var VibeType = makeArrayType(vibeTypes); +var VibeType = makeArrayType(VibeTypes); var LightTypes = [ 'on', @@ -804,9 +804,9 @@ SimplyPebble.card = function(def, clear, pushing) { SimplyPebble.windowActionBar(def.action); } for (var k in def) { - if (cardTextTypes.indexOf(k) !== -1) { + if (CardTextTypes.indexOf(k) !== -1) { SimplyPebble.cardText(k, def[k]); - } else if (cardImageTypes.indexOf(k) !== -1) { + } else if (CardImageTypes.indexOf(k) !== -1) { SimplyPebble.cardImage(k, def[k]); } else if (k === 'style') { SimplyPebble.cardStyle(k, def[k]); @@ -1010,10 +1010,10 @@ SimplyPebble.onPacket = function(buffer, offset) { WindowStack.emitHide(packet.id()); break; case ClickPacket: - Window.emitClick('click', buttonTypes[packet.button()]); + Window.emitClick('click', ButtonTypes[packet.button()]); break; case LongClickPacket: - Window.emitClick('longClick', buttonTypes[packet.button()]); + Window.emitClick('longClick', ButtonTypes[packet.button()]); break; case AccelDataPacket: var samples = packet.samples(); From a70b16d680604caedf00c3b4dc1ffe6998d2ed1c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 21 Feb 2015 20:57:19 -0800 Subject: [PATCH 444/791] Move module specific msg logic to their respective module files Previously, all app message logic was separated from each module, but this resulted in a monolithic app message file that handled differing things. This patch changes the organization such that app message logic is consolidated with the module it corresponds with. The simply msg module now is only responsible for generic app messages or functionality too simple to warrant its own module. This change also makes it simpler to create a new self-contained C modules that can interact through app message. --- src/simply/simply_accel.c | 102 +++- src/simply/simply_accel.h | 6 +- src/simply/simply_menu.c | 188 ++++++- src/simply/simply_menu.h | 13 +- src/simply/simply_msg.c | 838 ++----------------------------- src/simply/simply_msg.h | 30 +- src/simply/simply_msg_commands.h | 50 ++ src/simply/simply_stage.c | 243 ++++++++- src/simply/simply_stage.h | 16 +- src/simply/simply_ui.c | 71 +++ src/simply/simply_ui.h | 1 + src/simply/simply_window.c | 105 +++- src/simply/simply_window.h | 4 + src/simply/simply_window_stack.c | 107 +++- src/simply/simply_window_stack.h | 8 + 15 files changed, 920 insertions(+), 862 deletions(-) create mode 100644 src/simply/simply_msg_commands.h diff --git a/src/simply/simply_accel.c b/src/simply/simply_accel.c index 5201a423..1fd40f1c 100644 --- a/src/simply/simply_accel.c +++ b/src/simply/simply_accel.c @@ -6,13 +6,70 @@ #include -SimplyAccel *s_accel = NULL; +typedef Packet AccelPeekPacket; + +typedef struct AccelConfigPacket AccelConfigPacket; + +struct __attribute__((__packed__)) AccelConfigPacket { + Packet packet; + uint16_t num_samples; + AccelSamplingRate rate:8; + bool data_subscribed; +}; + +typedef struct AccelTapPacket AccelTapPacket; + +struct __attribute__((__packed__)) AccelTapPacket { + Packet packet; + AccelAxisType axis:8; + int8_t direction; +}; + +typedef struct AccelDataPacket AccelDataPacket; + +struct __attribute__((__packed__)) AccelDataPacket { + Packet packet; + bool is_peek; + uint8_t num_samples; + AccelData data[]; +}; + +static SimplyAccel *s_accel = NULL; + +static bool send_accel_tap(AccelAxisType axis, int32_t direction) { + AccelTapPacket packet = { + .packet.type = CommandAccelTap, + .packet.length = sizeof(packet), + .axis = axis, + .direction = direction, + }; + return simply_msg_send_packet(&packet.packet); +} + +static bool send_accel_data(SimplyMsg *self, AccelData *data, uint32_t num_samples, bool is_peek) { + size_t data_length = sizeof(AccelData) * num_samples; + size_t length = sizeof(AccelDataPacket) + data_length; + AccelDataPacket *packet = malloc(length); + if (!packet) { + return false; + } + packet->packet = (Packet) { + .type = CommandAccelData, + .length = length, + }; + packet->is_peek = is_peek; + packet->num_samples = num_samples; + memcpy(packet->data, data, data_length); + bool result = simply_msg_send((uint8_t*) packet, length); + free(packet); + return result; +} static void handle_accel_data(AccelData *data, uint32_t num_samples) { - simply_msg_accel_data(s_accel->simply->msg, data, num_samples, false); + send_accel_data(s_accel->simply->msg, data, num_samples, false); } -void simply_accel_set_data_subscribe(SimplyAccel *self, bool subscribe) { +static void set_data_subscribe(SimplyAccel *self, bool subscribe) { if (self->data_subscribed == subscribe) { return; } @@ -25,15 +82,42 @@ void simply_accel_set_data_subscribe(SimplyAccel *self, bool subscribe) { self->data_subscribed = subscribe; } -void simply_accel_peek(SimplyAccel *self, AccelData *data) { - if (self->data_subscribed) { - return; +static void handle_accel_tap(AccelAxisType axis, int32_t direction) { + send_accel_tap(axis, direction); +} + +static void accel_peek_timer_callback(void *context) { + Simply *simply = context; + AccelData data = { .x = 0 }; + if (s_accel->data_subscribed) { + accel_service_peek(&data); + } + if (!send_accel_data(simply->msg, &data, 1, true)) { + app_timer_register(10, accel_peek_timer_callback, simply); } - accel_service_peek(data); } -static void handle_accel_tap(AccelAxisType axis, int32_t direction) { - simply_msg_accel_tap(s_accel->simply->msg, axis, direction); +static void handle_accel_peek_packet(Simply *simply, Packet *data) { + app_timer_register(10, accel_peek_timer_callback, simply); +} + +static void handle_accel_config_packet(Simply *simply, Packet *data) { + AccelConfigPacket *packet = (AccelConfigPacket*) data; + s_accel->num_samples = packet->num_samples; + s_accel->rate = packet->rate; + set_data_subscribe(simply->accel, packet->data_subscribed); +} + +bool simply_accel_handle_packet(Simply *simply, Packet *packet) { + switch (packet->type) { + case CommandAccelPeek: + handle_accel_peek_packet(simply, packet); + return true; + case CommandAccelConfig: + handle_accel_config_packet(simply, packet); + return true; + } + return false; } SimplyAccel *simply_accel_create(Simply *simply) { diff --git a/src/simply/simply_accel.h b/src/simply/simply_accel.h index ce529533..3ebc1520 100644 --- a/src/simply/simply_accel.h +++ b/src/simply/simply_accel.h @@ -1,5 +1,7 @@ #pragma once +#include "simply_msg.h" + #include "simply.h" #include @@ -16,6 +18,4 @@ struct SimplyAccel { SimplyAccel *simply_accel_create(Simply *simply); void simply_accel_destroy(SimplyAccel *self); -void simply_accel_set_data_subscribe(SimplyAccel *self, bool subscribe); - -void simply_accel_peek(SimplyAccel *self, AccelData *data); +bool simply_accel_handle_packet(Simply *simply, Packet *packet); diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index ba3c3816..25224682 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -7,6 +7,7 @@ #include "simply.h" #include "util/menu_layer.h" +#include "util/string.h" #include @@ -16,8 +17,102 @@ #define REQUEST_DELAY_MS 10 +typedef Packet MenuClearPacket; + +typedef struct MenuClearSectionPacket MenuClearSectionPacket; + +struct __attribute__((__packed__)) MenuClearSectionPacket { + Packet packet; + uint16_t section; +}; + +typedef struct MenuPropsPacket MenuPropsPacket; + +struct __attribute__((__packed__)) MenuPropsPacket { + Packet packet; + uint16_t num_sections; +}; + +typedef struct MenuSectionPacket MenuSectionPacket; + +struct __attribute__((__packed__)) MenuSectionPacket { + Packet packet; + uint16_t section; + uint16_t num_items; + uint16_t title_length; + char title[]; +}; + +typedef struct MenuItemPacket MenuItemPacket; + +struct __attribute__((__packed__)) MenuItemPacket { + Packet packet; + uint16_t section; + uint16_t item; + uint32_t icon; + uint16_t title_length; + uint16_t subtitle_length; + char buffer[]; +}; + +typedef struct MenuItemEventPacket MenuItemEventPacket; + +struct __attribute__((__packed__)) MenuItemEventPacket { + Packet packet; + uint16_t section; + uint16_t item; +}; + +typedef Packet MenuGetSelectionPacket; + +typedef struct MenuSelectionPacket MenuSelectionPacket; + +struct __attribute__((__packed__)) MenuSelectionPacket { + Packet packet; + uint16_t section; + uint16_t item; + MenuRowAlign align:8; + bool animated; +}; + +static void simply_menu_clear_section_items(SimplyMenu *self, int section_index); +static void simply_menu_clear(SimplyMenu *self); + +static void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections); +static void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section); +static void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item); + +static MenuIndex simply_menu_get_selection(SimplyMenu *self); +static void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, MenuRowAlign align, bool animated); + static char EMPTY_TITLE[] = ""; +static bool send_menu_item(Command type, uint16_t section, uint16_t item) { + MenuItemEventPacket packet = { + .packet.type = type, + .packet.length = sizeof(packet), + .section = section, + .item = item, + }; + return simply_msg_send_packet(&packet.packet); +} + +static bool send_menu_get_section(uint16_t index) { + return send_menu_item(CommandMenuGetSection, index, 0); +} + +static bool send_menu_get_item(uint16_t section, uint16_t index) { + return send_menu_item(CommandMenuGetItem, section, index); +} + +static bool send_menu_select_click(uint16_t section, uint16_t index) { + return send_menu_item(CommandMenuSelect, section, index); +} + +static bool send_menu_select_long_click(uint16_t section, uint16_t index) { + return send_menu_item(CommandMenuLongSelect, section, index); +} + static bool section_filter(List1Node *node, void *data) { SimplyMenuCommon *section = (SimplyMenuCommon*) node; uint16_t section_index = (uint16_t)(uintptr_t) data; @@ -102,7 +197,7 @@ static void request_menu_section(SimplyMenu *self, uint16_t section_index) { .section = section_index, }; add_section(self, section); - simply_msg_menu_get_section(self->window.simply->msg, section_index); + send_menu_get_section(section_index); } static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t item_index) { @@ -116,7 +211,7 @@ static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t .item = item_index, }; add_item(self, item); - simply_msg_menu_get_item(self->window.simply->msg, section_index, item_index); + send_menu_get_item(section_index, item_index); } static void mark_dirty(SimplyMenu *self) { @@ -156,6 +251,11 @@ void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, MenuRowAl menu_layer_set_selected_index(self->menu_layer.menu_layer, menu_index, align, animated); } +static bool send_menu_selection(SimplyMenu *self) { + MenuIndex menu_index = simply_menu_get_selection(self); + return send_menu_item(CommandMenuSelectionEvent, menu_index.section, menu_index.row); +} + static uint16_t menu_get_num_sections_callback(MenuLayer *menu_layer, void *data) { SimplyMenu *self = data; return self->menu_layer.num_sections; @@ -208,13 +308,11 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI } static void menu_select_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { - SimplyMenu *self = data; - simply_msg_menu_select_click(self->window.simply->msg, cell_index->section, cell_index->row); + send_menu_select_click(cell_index->section, cell_index->row); } static void menu_select_long_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { - SimplyMenu *self = data; - simply_msg_menu_select_long_click(self->window.simply->msg, cell_index->section, cell_index->row); + send_menu_select_long_click(cell_index->section, cell_index->row); } static void single_click_handler(ClickRecognizerRef recognizer, void *context) { @@ -297,6 +395,84 @@ void simply_menu_clear(SimplyMenu *self) { mark_dirty(self); } +static void handle_menu_clear_packet(Simply *simply, Packet *data) { + simply_menu_clear(simply->menu); +} + +static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { + MenuClearSectionPacket *packet = (MenuClearSectionPacket*) data; + simply_menu_clear_section_items(simply->menu, packet->section); +} + +static void handle_menu_props_packet(Simply *simply, Packet *data) { + MenuPropsPacket *packet = (MenuPropsPacket*) data; + simply_menu_set_num_sections(simply->menu, packet->num_sections); +} + +static void handle_menu_section_packet(Simply *simply, Packet *data) { + MenuSectionPacket *packet = (MenuSectionPacket*) data; + SimplyMenuSection *section = malloc(sizeof(*section)); + *section = (SimplyMenuSection) { + .section = packet->section, + .num_items = packet->num_items, + .title = packet->title_length ? strdup2(packet->title) : NULL, + }; + simply_menu_add_section(simply->menu, section); +} + +static void handle_menu_item_packet(Simply *simply, Packet *data) { + MenuItemPacket *packet = (MenuItemPacket*) data; + SimplyMenuItem *item = malloc(sizeof(*item)); + *item = (SimplyMenuItem) { + .section = packet->section, + .item = packet->item, + .title = packet->title_length ? strdup2(packet->buffer) : NULL, + .subtitle = packet->subtitle_length ? strdup2(packet->buffer + packet->title_length + 1) : NULL, + .icon = packet->icon, + }; + simply_menu_add_item(simply->menu, item); +} + +static void handle_menu_get_selection_packet(Simply *simply, Packet *data) { + send_menu_selection(simply->menu); +} + +static void handle_menu_selection_packet(Simply *simply, Packet *data) { + MenuSelectionPacket *packet = (MenuSelectionPacket*) data; + MenuIndex menu_index = { + .section = packet->section, + .row = packet->item, + }; + simply_menu_set_selection(simply->menu, menu_index, packet->align, packet->animated); +} + +bool simply_menu_handle_packet(Simply *simply, Packet *packet) { + switch (packet->type) { + case CommandMenuClear: + handle_menu_clear_packet(simply, packet); + return true; + case CommandMenuClearSection: + handle_menu_clear_section_packet(simply, packet); + return true; + case CommandMenuProps: + handle_menu_props_packet(simply, packet); + return true; + case CommandMenuSection: + handle_menu_section_packet(simply, packet); + return true; + case CommandMenuItem: + handle_menu_item_packet(simply, packet); + return true; + case CommandMenuSelection: + handle_menu_selection_packet(simply, packet); + return true; + case CommandMenuGetSelection: + handle_menu_get_selection_packet(simply, packet); + return true; + } + return false; +} + SimplyMenu *simply_menu_create(Simply *simply) { SimplyMenu *self = malloc(sizeof(*self)); *self = (SimplyMenu) { diff --git a/src/simply/simply_menu.h b/src/simply/simply_menu.h index 946ec7d4..090640aa 100644 --- a/src/simply/simply_menu.h +++ b/src/simply/simply_menu.h @@ -2,6 +2,8 @@ #include "simply_window.h" +#include "simply_msg.h" + #include "simply.h" #include "util/list1.h" @@ -67,13 +69,4 @@ struct SimplyMenuItem { SimplyMenu *simply_menu_create(Simply *simply); void simply_menu_destroy(SimplyMenu *self); -void simply_menu_clear_section_items(SimplyMenu *self, int section_index); -void simply_menu_clear(SimplyMenu *self); - -void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections); -void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section); -void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item); - -MenuIndex simply_menu_get_selection(SimplyMenu *self); -void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, MenuRowAlign align, bool animated); - +bool simply_menu_handle_packet(Simply *simply, Packet *packet); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 4d29b05a..ec62f8ae 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -14,7 +14,6 @@ #include "util/math.h" #include "util/memory.h" #include "util/string.h" -#include "util/window.h" #include @@ -24,63 +23,6 @@ static const size_t APP_MSG_SIZE_INBOUND = 2044; static const size_t APP_MSG_SIZE_OUTBOUND = 512; -typedef enum Command Command; - -enum Command { - CommandSegment = 1, - CommandWindowShow, - CommandWindowHide, - CommandWindowShowEvent, - CommandWindowHideEvent, - CommandWindowProps, - CommandWindowButtonConfig, - CommandWindowActionBar, - CommandClick, - CommandLongClick, - CommandImagePacket, - CommandCardClear, - CommandCardText, - CommandCardImage, - CommandCardStyle, - CommandVibe, - CommandLight, - CommandAccelPeek, - CommandAccelConfig, - CommandAccelData, - CommandAccelTap, - CommandMenuClear, - CommandMenuClearSection, - CommandMenuProps, - CommandMenuSection, - CommandMenuGetSection, - CommandMenuItem, - CommandMenuGetItem, - CommandMenuSelection, - CommandMenuGetSelection, - CommandMenuSelectionEvent, - CommandMenuSelect, - CommandMenuLongSelect, - CommandStageClear, - CommandElementInsert, - CommandElementRemove, - CommandElementCommon, - CommandElementRadius, - CommandElementText, - CommandElementTextStyle, - CommandElementImage, - CommandElementAnimate, - CommandElementAnimateDone, -}; - -typedef enum WindowType WindowType; - -enum WindowType { - WindowTypeWindow = 0, - WindowTypeMenu, - WindowTypeCard, - WindowTypeLast, -}; - typedef enum VibeType VibeType; enum VibeType { @@ -97,13 +39,6 @@ enum LightType { LightTrigger = 2, }; -typedef struct Packet Packet; - -struct __attribute__((__packed__)) Packet { - Command type:16; - uint16_t length; -}; - typedef struct SegmentPacket SegmentPacket; struct __attribute__((__packed__)) SegmentPacket { @@ -112,64 +47,6 @@ struct __attribute__((__packed__)) SegmentPacket { uint8_t buffer[]; }; -typedef struct WindowShowPacket WindowShowPacket; - -struct __attribute__((__packed__)) WindowShowPacket { - Packet packet; - WindowType type:8; - bool pushing; -}; - -typedef struct WindowSignalPacket WindowSignalPacket; - -struct __attribute__((__packed__)) WindowSignalPacket { - Packet packet; - uint32_t id; -}; - -typedef WindowSignalPacket WindowHidePacket; - -typedef WindowHidePacket WindowEventPacket; - -typedef WindowEventPacket WindowShowEventPacket; - -typedef WindowEventPacket WindowHideEventPacket; - -typedef struct WindowPropsPacket WindowPropsPacket; - -struct __attribute__((__packed__)) WindowPropsPacket { - Packet packet; - uint32_t id; - GColor background_color:8; - bool fullscreen; - bool scrollable; -}; - -typedef struct WindowButtonConfigPacket WindowButtonConfigPacket; - -struct __attribute__((__packed__)) WindowButtonConfigPacket { - Packet packet; - uint8_t button_mask; -}; - -typedef struct WindowActionBarPacket WindowActionBarPacket; - -struct __attribute__((__packed__)) WindowActionBarPacket { - Packet packet; - uint32_t image[3]; - bool action; - GColor background_color:8; -}; - -typedef struct ClickPacket ClickPacket; - -struct __attribute__((__packed__)) ClickPacket { - Packet packet; - ButtonId button:8; -}; - -typedef ClickPacket LongClickPacket; - typedef struct ImagePacket ImagePacket; struct __attribute__((__packed__)) ImagePacket { @@ -180,36 +57,6 @@ struct __attribute__((__packed__)) ImagePacket { uint32_t pixels[]; }; -typedef struct CardClearPacket CardClearPacket; - -struct __attribute__((__packed__)) CardClearPacket { - Packet packet; - uint8_t flags; -}; - -typedef struct CardTextPacket CardTextPacket; - -struct __attribute__((__packed__)) CardTextPacket { - Packet packet; - uint8_t index; - char text[]; -}; - -typedef struct CardImagePacket CardImagePacket; - -struct __attribute__((__packed__)) CardImagePacket { - Packet packet; - uint32_t image; - uint8_t index; -}; - -typedef struct CardStylePacket CardStylePacket; - -struct __attribute__((__packed__)) CardStylePacket { - Packet packet; - uint8_t style; -}; - typedef struct VibePacket VibePacket; struct __attribute__((__packed__)) VibePacket { @@ -224,197 +71,24 @@ struct __attribute__((__packed__)) LightPacket { LightType type:8; }; -typedef Packet AccelPeekPacket; - -typedef struct AccelConfigPacket AccelConfigPacket; - -struct __attribute__((__packed__)) AccelConfigPacket { - Packet packet; - uint16_t num_samples; - AccelSamplingRate rate:8; - bool data_subscribed; -}; - -typedef struct AccelTapPacket AccelTapPacket; - -struct __attribute__((__packed__)) AccelTapPacket { - Packet packet; - AccelAxisType axis:8; - int8_t direction; -}; - -typedef struct AccelDataPacket AccelDataPacket; - -struct __attribute__((__packed__)) AccelDataPacket { - Packet packet; - bool is_peek; - uint8_t num_samples; - AccelData data[]; -}; - -typedef Packet MenuClearPacket; - -typedef struct MenuClearSectionPacket MenuClearSectionPacket; - -struct __attribute__((__packed__)) MenuClearSectionPacket { - Packet packet; - uint16_t section; -}; - -typedef struct MenuPropsPacket MenuPropsPacket; - -struct __attribute__((__packed__)) MenuPropsPacket { - Packet packet; - uint16_t num_sections; -}; - -typedef struct MenuSectionPacket MenuSectionPacket; - -struct __attribute__((__packed__)) MenuSectionPacket { - Packet packet; - uint16_t section; - uint16_t num_items; - uint16_t title_length; - char title[]; -}; - -typedef struct MenuItemPacket MenuItemPacket; - -struct __attribute__((__packed__)) MenuItemPacket { - Packet packet; - uint16_t section; - uint16_t item; - uint32_t icon; - uint16_t title_length; - uint16_t subtitle_length; - char buffer[]; -}; - -typedef struct MenuItemEventPacket MenuItemEventPacket; - -struct __attribute__((__packed__)) MenuItemEventPacket { - Packet packet; - uint16_t section; - uint16_t item; -}; - -typedef Packet MenuGetSelectionPacket; - -typedef struct MenuSelectionPacket MenuSelectionPacket; - -struct __attribute__((__packed__)) MenuSelectionPacket { - Packet packet; - uint16_t section; - uint16_t item; - MenuRowAlign align:8; - bool animated; -}; - -typedef Packet StageClearPacket; - -typedef struct ElementInsertPacket ElementInsertPacket; - -struct __attribute__((__packed__)) ElementInsertPacket { - Packet packet; - uint32_t id; - SimplyElementType type:8; - uint16_t index; -}; - -typedef struct ElementRemovePacket ElementRemovePacket; - -struct __attribute__((__packed__)) ElementRemovePacket { - Packet packet; - uint32_t id; -}; - -typedef struct ElementCommonPacket ElementCommonPacket; - -struct __attribute__((__packed__)) ElementCommonPacket { - Packet packet; - uint32_t id; - GRect frame; - GColor background_color:8; - GColor border_color:8; -}; - -typedef struct ElementRadiusPacket ElementRadiusPacket; - -struct __attribute__((__packed__)) ElementRadiusPacket { - Packet packet; - uint32_t id; - uint16_t radius; -}; - -typedef struct ElementTextPacket ElementTextPacket; - -struct __attribute__((__packed__)) ElementTextPacket { - Packet packet; - uint32_t id; - TimeUnits time_units:8; - char text[]; -}; - -typedef struct ElementTextStylePacket ElementTextStylePacket; - -struct __attribute__((__packed__)) ElementTextStylePacket { - Packet packet; - uint32_t id; - GColor color:8; - GTextOverflowMode overflow_mode:8; - GTextAlignment alignment:8; - uint32_t custom_font; - char system_font[]; -}; - -typedef struct ElementImagePacket ElementImagePacket; - -struct __attribute__((__packed__)) ElementImagePacket { - Packet packet; - uint32_t id; - uint32_t image; - GCompOp compositing:8; -}; - -typedef struct ElementAnimatePacket ElementAnimatePacket; +static SimplyMsg *s_msg = NULL; -struct __attribute__((__packed__)) ElementAnimatePacket { - Packet packet; - uint32_t id; - GRect frame; - uint32_t duration; - AnimationCurve curve:8; -}; +static bool s_has_communicated = false; -typedef struct ElementAnimateDonePacket ElementAnimateDonePacket; +typedef struct CommandHandlerEntry CommandHandlerEntry; -struct __attribute__((__packed__)) ElementAnimateDonePacket { - Packet packet; - uint32_t id; +struct CommandHandlerEntry { + int16_t start_type; + int16_t end_type; + PacketHandler handler; }; -static bool s_has_communicated = false; - -static bool s_broadcast_window = true; - static void handle_packet(Simply *simply, Packet *packet); bool simply_msg_has_communicated() { return s_has_communicated; } -static SimplyWindow *get_top_simply_window(Simply *simply) { - Window *base_window = window_stack_get_top_window(); - if (!base_window) { - return NULL; - } - SimplyWindow *window = window_get_user_data(base_window); - if (!window || (void*) window == simply->splash) { - return NULL; - } - return window; -} - static void destroy_packet(SimplyMsg *self, SimplyPacket *packet) { if (!packet) { return; @@ -476,83 +150,11 @@ static void handle_segment_packet(Simply *simply, Packet *data) { } } -static void handle_window_show_packet(Simply *simply, Packet *data) { - WindowShowPacket *packet = (WindowShowPacket*) data; - SimplyWindow *window = simply->windows[MIN(WindowTypeLast - 1, packet->type)]; - simply_window_stack_show(simply->window_stack, window, packet->pushing); -} - -static void handle_window_hide_packet(Simply *simply, Packet *data) { - WindowHidePacket *packet = (WindowHidePacket*) data; - SimplyWindow *window = get_top_simply_window(simply); - if (!window) { - return; - } - if (window->id == packet->id) { - simply_window_stack_pop(simply->window_stack, window); - } -} - -static void handle_window_props_packet(Simply *simply, Packet *data) { - WindowPropsPacket *packet = (WindowPropsPacket*) data; - SimplyWindow *window = get_top_simply_window(simply); - if (!window) { - return; - } - window->id = packet->id; - simply_window_set_background_color(window, packet->background_color); - simply_window_set_fullscreen(window, packet->fullscreen); - simply_window_set_scrollable(window, packet->scrollable); -} - -static void handle_window_button_config_packet(Simply *simply, Packet *data) { - WindowButtonConfigPacket *packet = (WindowButtonConfigPacket*) data; - SimplyWindow *window = get_top_simply_window(simply); - if (!window) { - return; - } - window->button_mask = packet->button_mask; -} - -static void handle_window_action_bar_packet(Simply *simply, Packet *data) { - WindowActionBarPacket *packet = (WindowActionBarPacket*) data; - SimplyWindow *window = get_top_simply_window(simply); - if (!window) { - return; - } - simply_window_set_action_bar_background_color(window, packet->background_color); - for (unsigned int i = 0; i < ARRAY_LENGTH(packet->image); ++i) { - simply_window_set_action_bar_icon(window, i + 1, packet->image[i]); - } - simply_window_set_action_bar(window, packet->action); -} - static void handle_image_packet(Simply *simply, Packet *data) { ImagePacket *packet = (ImagePacket*) data; simply_res_add_image(simply->res, packet->id, packet->width, packet->height, packet->pixels); } -static void handle_card_clear_packet(Simply *simply, Packet *data) { - CardClearPacket *packet = (CardClearPacket*) data; - simply_ui_clear(simply->ui, packet->flags); -} - -static void handle_card_text_packet(Simply *simply, Packet *data) { - CardTextPacket *packet = (CardTextPacket*) data; - simply_ui_set_text(simply->ui, MIN(NumUiTextfields - 1, packet->index), packet->text); -} - -static void handle_card_image_packet(Simply *simply, Packet *data) { - CardImagePacket *packet = (CardImagePacket*) data; - simply->ui->ui_layer.imagefields[MIN(NumUiImagefields - 1, packet->index)] = packet->image; - window_stack_schedule_top_window_render(); -} - -static void handle_card_style_packet(Simply *simply, Packet *data) { - CardStylePacket *packet = (CardStylePacket*) data; - simply_ui_set_style(simply->ui, packet->style); -} - static void handle_vibe_packet(Simply *simply, Packet *data) { VibePacket *packet = (VibePacket*) data; switch (packet->type) { @@ -571,297 +173,32 @@ static void handle_light_packet(Simply *simply, Packet *data) { } } -static void accel_peek_timer_callback(void *context) { - Simply *simply = context; - AccelData data = { .x = 0 }; - simply_accel_peek(simply->accel, &data); - if (!simply_msg_accel_data(simply->msg, &data, 1, true)) { - app_timer_register(10, accel_peek_timer_callback, simply); - } -} - -static void handle_accel_peek_packet(Simply *simply, Packet *data) { - app_timer_register(10, accel_peek_timer_callback, simply); -} - -static void handle_accel_config_packet(Simply *simply, Packet *data) { - AccelConfigPacket *packet = (AccelConfigPacket*) data; - simply->accel->num_samples = packet->num_samples; - simply->accel->rate = packet->rate; - simply_accel_set_data_subscribe(simply->accel, packet->data_subscribed); -} - -static void handle_menu_clear_packet(Simply *simply, Packet *data) { - simply_menu_clear(simply->menu); -} - -static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { - MenuClearSectionPacket *packet = (MenuClearSectionPacket*) data; - simply_menu_clear_section_items(simply->menu, packet->section); -} - -static void handle_menu_props_packet(Simply *simply, Packet *data) { - MenuPropsPacket *packet = (MenuPropsPacket*) data; - simply_menu_set_num_sections(simply->menu, packet->num_sections); -} - -static void handle_menu_section_packet(Simply *simply, Packet *data) { - MenuSectionPacket *packet = (MenuSectionPacket*) data; - SimplyMenuSection *section = malloc(sizeof(*section)); - *section = (SimplyMenuSection) { - .section = packet->section, - .num_items = packet->num_items, - .title = packet->title_length ? strdup2(packet->title) : NULL, - }; - simply_menu_add_section(simply->menu, section); -} - -static void handle_menu_item_packet(Simply *simply, Packet *data) { - MenuItemPacket *packet = (MenuItemPacket*) data; - SimplyMenuItem *item = malloc(sizeof(*item)); - *item = (SimplyMenuItem) { - .section = packet->section, - .item = packet->item, - .title = packet->title_length ? strdup2(packet->buffer) : NULL, - .subtitle = packet->subtitle_length ? strdup2(packet->buffer + packet->title_length + 1) : NULL, - .icon = packet->icon, - }; - simply_menu_add_item(simply->menu, item); -} - -static void handle_menu_get_selection_packet(Simply *simply, Packet *data) { - simply_msg_send_menu_selection(simply->msg); -} - -static void handle_menu_selection_packet(Simply *simply, Packet *data) { - MenuSelectionPacket *packet = (MenuSelectionPacket*) data; - MenuIndex menu_index = { - .section = packet->section, - .row = packet->item, - }; - simply_menu_set_selection(simply->menu, menu_index, packet->align, packet->animated); -} - -static void handle_stage_clear_packet(Simply *simply, Packet *data) { - simply_stage_clear(simply->stage); -} - -static void handle_element_insert_packet(Simply *simply, Packet *data) { - ElementInsertPacket *packet = (ElementInsertPacket*) data; - SimplyElementCommon *element = simply_stage_auto_element(simply->stage, packet->id, packet->type); - if (!element) { - return; - } - simply_stage_insert_element(simply->stage, packet->index, element); - simply_stage_update(simply->stage); -} - -static void handle_element_remove_packet(Simply *simply, Packet *data) { - ElementInsertPacket *packet = (ElementInsertPacket*) data; - SimplyElementCommon *element = simply_stage_get_element(simply->stage, packet->id); - if (!element) { - return; - } - simply_stage_remove_element(simply->stage, element); - simply_stage_update(simply->stage); -} - -static void handle_element_common_packet(Simply *simply, Packet *data) { - ElementCommonPacket *packet = (ElementCommonPacket*) data; - SimplyElementCommon *element = simply_stage_get_element(simply->stage, packet->id); - if (!element) { - return; - } - simply_stage_set_element_frame(simply->stage, element, packet->frame); - element->background_color = packet->background_color; - element->border_color = packet->border_color; - simply_stage_update(simply->stage); -} - -static void handle_element_radius_packet(Simply *simply, Packet *data) { - ElementRadiusPacket *packet = (ElementRadiusPacket*) data; - SimplyElementRect *element = (SimplyElementRect*) simply_stage_get_element(simply->stage, packet->id); - if (!element) { - return; - } - element->radius = packet->radius; - simply_stage_update(simply->stage); -}; - -static void handle_element_text_packet(Simply *simply, Packet *data) { - ElementTextPacket *packet = (ElementTextPacket*) data; - SimplyElementText *element = (SimplyElementText*) simply_stage_get_element(simply->stage, packet->id); - if (!element) { - return; - } - if (element->time_units != packet->time_units) { - element->time_units = packet->time_units; - simply_stage_update_ticker(simply->stage); - } - strset(&element->text, packet->text); - simply_stage_update(simply->stage); -} - -static void handle_element_text_style_packet(Simply *simply, Packet *data) { - ElementTextStylePacket *packet = (ElementTextStylePacket*) data; - SimplyElementText *element = (SimplyElementText*) simply_stage_get_element(simply->stage, packet->id); - if (!element) { - return; - } - element->text_color = packet->color; - element->overflow_mode = packet->overflow_mode; - element->alignment = packet->alignment; - if (packet->custom_font) { - element->font = simply_res_get_font(simply->res, packet->custom_font); - } else if (packet->system_font[0]) { - element->font = fonts_get_system_font(packet->system_font); - } - simply_stage_update(simply->stage); -} - -static void handle_element_image_packet(Simply *simply, Packet *data) { - ElementImagePacket *packet = (ElementImagePacket*) data; - SimplyElementImage *element = (SimplyElementImage*) simply_stage_get_element(simply->stage, packet->id); - if (!element) { - return; - } - element->image = packet->image; - element->compositing = packet->compositing; - simply_stage_update(simply->stage); -} - -static void handle_element_animate_packet(Simply *simply, Packet *data) { - ElementAnimatePacket *packet = (ElementAnimatePacket*) data; - SimplyElementCommon *element = simply_stage_get_element(simply->stage, packet->id); - if (!element) { - return; - } - SimplyAnimation *animation = malloc0(sizeof(*animation)); - animation->duration = packet->duration; - animation->curve = packet->curve; - simply_stage_animate_element(simply->stage, element, animation, packet->frame); -} - -static void handle_packet(Simply *simply, Packet *packet) { +static bool simply_base_handle_packet(Simply *simply, Packet *packet) { switch (packet->type) { case CommandSegment: handle_segment_packet(simply, packet); - break; - case CommandWindowShow: - handle_window_show_packet(simply, packet); - break; - case CommandWindowHide: - handle_window_hide_packet(simply, packet); - break; - case CommandWindowShowEvent: - break; - case CommandWindowHideEvent: - break; - case CommandWindowProps: - handle_window_props_packet(simply, packet); - break; - case CommandWindowButtonConfig: - handle_window_button_config_packet(simply, packet); - break; - case CommandWindowActionBar: - handle_window_action_bar_packet(simply, packet); - break; - case CommandClick: - break; - case CommandLongClick: - break; + return true; case CommandImagePacket: handle_image_packet(simply, packet); - break; - case CommandCardClear: - handle_card_clear_packet(simply, packet); - break; - case CommandCardText: - handle_card_text_packet(simply, packet); - break; - case CommandCardImage: - handle_card_image_packet(simply, packet); - break; - case CommandCardStyle: - handle_card_style_packet(simply, packet); - break; + return true; case CommandVibe: handle_vibe_packet(simply, packet); - break; + return true; case CommandLight: handle_light_packet(simply, packet); - break; - case CommandAccelPeek: - handle_accel_peek_packet(simply, packet); - break; - case CommandAccelConfig: - handle_accel_config_packet(simply, packet); - break; - case CommandAccelData: - break; - case CommandAccelTap: - break; - case CommandMenuClear: - handle_menu_clear_packet(simply, packet); - break; - case CommandMenuClearSection: - handle_menu_clear_section_packet(simply, packet); - break; - case CommandMenuProps: - handle_menu_props_packet(simply, packet); - break; - case CommandMenuSection: - handle_menu_section_packet(simply, packet); - break; - case CommandMenuGetSection: - break; - case CommandMenuItem: - handle_menu_item_packet(simply, packet); - break; - case CommandMenuGetItem: - break; - case CommandMenuSelection: - handle_menu_selection_packet(simply, packet); - break; - case CommandMenuGetSelection: - handle_menu_get_selection_packet(simply, packet); - break; - case CommandMenuSelectionEvent: - break; - case CommandMenuSelect: - break; - case CommandMenuLongSelect: - break; - case CommandStageClear: - handle_stage_clear_packet(simply, packet); - break; - case CommandElementInsert: - handle_element_insert_packet(simply, packet); - break; - case CommandElementRemove: - handle_element_remove_packet(simply, packet); - break; - case CommandElementCommon: - handle_element_common_packet(simply, packet); - break; - case CommandElementRadius: - handle_element_radius_packet(simply, packet); - break; - case CommandElementText: - handle_element_text_packet(simply, packet); - break; - case CommandElementTextStyle: - handle_element_text_style_packet(simply, packet); - break; - case CommandElementImage: - handle_element_image_packet(simply, packet); - break; - case CommandElementAnimate: - handle_element_animate_packet(simply, packet); - break; - case CommandElementAnimateDone: - break; + return true; } + return false; +} + +static void handle_packet(Simply *simply, Packet *packet) { + if (simply_base_handle_packet(simply, packet)) { return; } + if (simply_window_stack_handle_packet(simply, packet)) { return; } + if (simply_window_handle_packet(simply, packet)) { return; } + if (simply_ui_handle_packet(simply, packet)) { return; } + if (simply_accel_handle_packet(simply, packet)) { return; } + if (simply_menu_handle_packet(simply, packet)) { return; } + if (simply_stage_handle_packet(simply, packet)) { return; } } static void received_callback(DictionaryIterator *iter, void *context) { @@ -919,17 +256,21 @@ void simply_msg_show_disconnected(SimplyMsg *self) { simply_ui_set_text(ui, UiSubtitle, "Disconnected"); simply_ui_set_text(ui, UiBody, "Run the Pebble Phone App"); - if (get_top_simply_window(simply) != &ui->window) { - bool was_broadcast = s_broadcast_window; - s_broadcast_window = false; + if (window_stack_get_top_window() != ui->window.window) { + bool was_broadcast = simply_window_stack_set_broadcast(false); simply_window_stack_show(simply->window_stack, &ui->window, true); - s_broadcast_window = was_broadcast; + simply_window_stack_set_broadcast(was_broadcast); } } SimplyMsg *simply_msg_create(Simply *simply) { + if (s_msg) { + return s_msg; + } + SimplyMsg *self = malloc(sizeof(*self)); *self = (SimplyMsg) { .simply = simply }; + s_msg = self; simply->msg = self; @@ -966,6 +307,10 @@ static bool send_msg(uint8_t *buffer, size_t length) { return (app_message_outbox_send() == APP_MSG_OK); } +bool simply_msg_send(uint8_t *buffer, size_t length) { + return send_msg(buffer, length); +} + static void make_multi_packet(SimplyMsg *self, SimplyPacket *packet) { if (!packet) { return; @@ -1041,114 +386,11 @@ static SimplyPacket *add_packet(SimplyMsg *self, Packet *buffer, Command type, s return packet; } -static bool send_click(SimplyMsg *self, Command type, ButtonId button) { - size_t length; - ClickPacket *packet = malloc0(length = sizeof(*packet)); - if (!packet) { +bool simply_msg_send_packet(Packet *packet) { + Packet *copy = malloc(packet->length); + if (!copy) { return false; } - packet->button = button; - return add_packet(self, (Packet*) packet, type, length); -} - -bool simply_msg_single_click(SimplyMsg *self, ButtonId button) { - return send_click(self, CommandClick, button); -} - -bool simply_msg_long_click(SimplyMsg *self, ButtonId button) { - return send_click(self, CommandLongClick, button); + memcpy(copy, packet, packet->length); + return add_packet(s_msg, copy, packet->type, packet->length); } - -bool send_window(SimplyMsg *self, Command type, uint32_t id) { - if (!s_broadcast_window) { - return false; - } - size_t length; - WindowEventPacket *packet = malloc0(length = sizeof(*packet)); - if (!packet) { - return false; - } - packet->id = id; - return add_packet(self, (Packet*) packet, type, length); -} - -bool simply_msg_window_show(SimplyMsg *self, uint32_t id) { - return send_window(self, CommandWindowShowEvent, id); -} - -bool simply_msg_window_hide(SimplyMsg *self, uint32_t id) { - return send_window(self, CommandWindowHideEvent, id); -} - -bool simply_msg_accel_tap(SimplyMsg *self, AccelAxisType axis, int32_t direction) { - size_t length; - AccelTapPacket *packet = malloc0(length = sizeof(*packet)); - if (!packet) { - return false; - } - packet->axis = axis; - packet->direction = direction; - return add_packet(self, (Packet*) packet, CommandAccelTap, length); -} - -bool simply_msg_accel_data(SimplyMsg *self, AccelData *data, uint32_t num_samples, bool is_peek) { - size_t data_length = sizeof(AccelData) * num_samples; - size_t length; - AccelDataPacket *packet = malloc(length = sizeof(AccelDataPacket) + data_length); - if (!packet) { - return false; - } - packet->packet = (Packet) { - .type = CommandAccelData, - .length = length, - }; - packet->is_peek = is_peek; - packet->num_samples = num_samples; - memcpy(packet->data, data, data_length); - bool result = send_msg((uint8_t*) packet, length); - free(packet); - return result; -} - -static bool send_menu_item(SimplyMsg *self, Command type, uint16_t section, uint16_t item) { - size_t length; - MenuItemEventPacket *packet = malloc0(length = sizeof(*packet)); - if (!packet) { - return false; - } - packet->section = section; - packet->item = item; - return add_packet(self, (Packet*) packet, type, length); -} - -bool simply_msg_menu_get_section(SimplyMsg *self, uint16_t index) { - return send_menu_item(self, CommandMenuGetSection, index, 0); -} - -bool simply_msg_menu_get_item(SimplyMsg *self, uint16_t section, uint16_t index) { - return send_menu_item(self, CommandMenuGetItem, section, index); -} - -bool simply_msg_menu_select_click(SimplyMsg *self, uint16_t section, uint16_t index) { - return send_menu_item(self, CommandMenuSelect, section, index); -} - -bool simply_msg_menu_select_long_click(SimplyMsg *self, uint16_t section, uint16_t index) { - return send_menu_item(self, CommandMenuLongSelect, section, index); -} - -bool simply_msg_send_menu_selection(SimplyMsg *self) { - MenuIndex menu_index = simply_menu_get_selection(self->simply->menu); - return send_menu_item(self, CommandMenuSelectionEvent, menu_index.section, menu_index.row); -} - -bool simply_msg_animate_element_done(SimplyMsg *self, uint32_t id) { - size_t length; - ElementAnimateDonePacket *packet = malloc0(length = sizeof(*packet)); - if (!packet) { - return false; - } - packet->id = id; - return add_packet(self, (Packet*) packet, CommandElementAnimateDone, length); -} - diff --git a/src/simply/simply_msg.h b/src/simply/simply_msg.h index 2b293b13..35675579 100644 --- a/src/simply/simply_msg.h +++ b/src/simply/simply_msg.h @@ -1,5 +1,7 @@ #pragma once +#include "simply_msg_commands.h" + #include "simply.h" #include "util/list1.h" @@ -26,25 +28,19 @@ struct SimplyPacket { void *buffer; }; +typedef struct Packet Packet; + +struct __attribute__((__packed__)) Packet { + uint16_t type; + uint16_t length; +}; + +typedef void (*PacketHandler)(Simply *simply, Packet *packet); + SimplyMsg *simply_msg_create(Simply *simply); void simply_msg_destroy(SimplyMsg *self); bool simply_msg_has_communicated(); void simply_msg_show_disconnected(SimplyMsg *self); -bool simply_msg_single_click(SimplyMsg *self, ButtonId button); -bool simply_msg_long_click(SimplyMsg *self, ButtonId button); - -bool simply_msg_window_show(SimplyMsg *self, uint32_t id); -bool simply_msg_window_hide(SimplyMsg *self, uint32_t id); - -bool simply_msg_accel_tap(SimplyMsg *self, AccelAxisType axis, int32_t direction); -bool simply_msg_accel_data(SimplyMsg *self, AccelData *accel, uint32_t num_samples, bool is_peek); - -bool simply_msg_menu_get_section(SimplyMsg *self, uint16_t index); -bool simply_msg_menu_get_item(SimplyMsg *self, uint16_t section, uint16_t index); -bool simply_msg_menu_select_click(SimplyMsg *self, uint16_t section, uint16_t index); -bool simply_msg_menu_select_long_click(SimplyMsg *self, uint16_t section, uint16_t index); -bool simply_msg_menu_hide(SimplyMsg *self, uint16_t section, uint16_t index); -bool simply_msg_send_menu_selection(SimplyMsg *self); - -bool simply_msg_animate_element_done(SimplyMsg *self, uint32_t id); +bool simply_msg_send(uint8_t *buffer, size_t length); +bool simply_msg_send_packet(Packet *packet); diff --git a/src/simply/simply_msg_commands.h b/src/simply/simply_msg_commands.h new file mode 100644 index 00000000..75e3e85e --- /dev/null +++ b/src/simply/simply_msg_commands.h @@ -0,0 +1,50 @@ +#pragma once + +typedef enum Command Command; + +enum Command { + CommandSegment = 1, + CommandWindowShow, + CommandWindowHide, + CommandWindowShowEvent, + CommandWindowHideEvent, + CommandWindowProps, + CommandWindowButtonConfig, + CommandWindowActionBar, + CommandClick, + CommandLongClick, + CommandImagePacket, + CommandCardClear, + CommandCardText, + CommandCardImage, + CommandCardStyle, + CommandVibe, + CommandLight, + CommandAccelPeek, + CommandAccelConfig, + CommandAccelData, + CommandAccelTap, + CommandMenuClear, + CommandMenuClearSection, + CommandMenuProps, + CommandMenuSection, + CommandMenuGetSection, + CommandMenuItem, + CommandMenuGetItem, + CommandMenuSelection, + CommandMenuGetSelection, + CommandMenuSelectionEvent, + CommandMenuSelect, + CommandMenuLongSelect, + CommandStageClear, + CommandElementInsert, + CommandElementRemove, + CommandElementCommon, + CommandElementRadius, + CommandElementText, + CommandElementTextStyle, + CommandElementImage, + CommandElementAnimate, + CommandElementAnimateDone, + NumCommands, +}; diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index ada5cb16..f7e0bd60 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -14,6 +14,112 @@ #include +typedef Packet StageClearPacket; + +typedef struct ElementInsertPacket ElementInsertPacket; + +struct __attribute__((__packed__)) ElementInsertPacket { + Packet packet; + uint32_t id; + SimplyElementType type:8; + uint16_t index; +}; + +typedef struct ElementRemovePacket ElementRemovePacket; + +struct __attribute__((__packed__)) ElementRemovePacket { + Packet packet; + uint32_t id; +}; + +typedef struct ElementCommonPacket ElementCommonPacket; + +struct __attribute__((__packed__)) ElementCommonPacket { + Packet packet; + uint32_t id; + GRect frame; + GColor background_color:8; + GColor border_color:8; +}; + +typedef struct ElementRadiusPacket ElementRadiusPacket; + +struct __attribute__((__packed__)) ElementRadiusPacket { + Packet packet; + uint32_t id; + uint16_t radius; +}; + +typedef struct ElementTextPacket ElementTextPacket; + +struct __attribute__((__packed__)) ElementTextPacket { + Packet packet; + uint32_t id; + TimeUnits time_units:8; + char text[]; +}; + +typedef struct ElementTextStylePacket ElementTextStylePacket; + +struct __attribute__((__packed__)) ElementTextStylePacket { + Packet packet; + uint32_t id; + GColor color:8; + GTextOverflowMode overflow_mode:8; + GTextAlignment alignment:8; + uint32_t custom_font; + char system_font[]; +}; + +typedef struct ElementImagePacket ElementImagePacket; + +struct __attribute__((__packed__)) ElementImagePacket { + Packet packet; + uint32_t id; + uint32_t image; + GCompOp compositing:8; +}; + +typedef struct ElementAnimatePacket ElementAnimatePacket; + +struct __attribute__((__packed__)) ElementAnimatePacket { + Packet packet; + uint32_t id; + GRect frame; + uint32_t duration; + AnimationCurve curve:8; +}; + +typedef struct ElementAnimateDonePacket ElementAnimateDonePacket; + +struct __attribute__((__packed__)) ElementAnimateDonePacket { + Packet packet; + uint32_t id; +}; + +static void simply_stage_clear(SimplyStage *self); + +static void simply_stage_update(SimplyStage *self); +static void simply_stage_update_ticker(SimplyStage *self); + +static SimplyElementCommon* simply_stage_auto_element(SimplyStage *self, uint32_t id, SimplyElementType type); +static SimplyElementCommon* simply_stage_insert_element(SimplyStage *self, int index, SimplyElementCommon *element); +static SimplyElementCommon* simply_stage_remove_element(SimplyStage *self, SimplyElementCommon *element); + +static void simply_stage_set_element_frame(SimplyStage *self, SimplyElementCommon *element, GRect frame); + +static SimplyAnimation *simply_stage_animate_element(SimplyStage *self, + SimplyElementCommon *element, SimplyAnimation* animation, GRect to_frame); + +static bool send_animate_element_done(SimplyMsg *self, uint32_t id) { + ElementAnimateDonePacket packet = { + .packet.type = CommandElementAnimateDone, + .packet.length = sizeof(packet), + .id = id, + }; + return simply_msg_send_packet(&packet.packet); +} + static bool id_filter(List1Node *node, void *data) { return (((SimplyElementCommon*) node)->id == (uint32_t)(uintptr_t) data); } @@ -260,7 +366,7 @@ static void animation_stopped(Animation *base_animation, bool finished, void *co } SimplyElementCommon *element = animation->element; destroy_animation(self, animation); - simply_msg_animate_element_done(self->window.simply->msg, element->id); + send_animate_element_done(self->window.simply->msg, element->id); } SimplyAnimation *simply_stage_animate_element(SimplyStage *self, @@ -359,7 +465,7 @@ void simply_stage_update(SimplyStage *self) { } } -void handle_tick(struct tm *tick_time, TimeUnits units_changed) { +static void handle_tick(struct tm *tick_time, TimeUnits units_changed) { window_stack_schedule_top_window_render(); } @@ -381,6 +487,139 @@ void simply_stage_update_ticker(SimplyStage *self) { } } +static void handle_stage_clear_packet(Simply *simply, Packet *data) { + simply_stage_clear(simply->stage); +} + +static void handle_element_insert_packet(Simply *simply, Packet *data) { + ElementInsertPacket *packet = (ElementInsertPacket*) data; + SimplyElementCommon *element = simply_stage_auto_element(simply->stage, packet->id, packet->type); + if (!element) { + return; + } + simply_stage_insert_element(simply->stage, packet->index, element); + simply_stage_update(simply->stage); +} + +static void handle_element_remove_packet(Simply *simply, Packet *data) { + ElementInsertPacket *packet = (ElementInsertPacket*) data; + SimplyElementCommon *element = simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + simply_stage_remove_element(simply->stage, element); + simply_stage_update(simply->stage); +} + +static void handle_element_common_packet(Simply *simply, Packet *data) { + ElementCommonPacket *packet = (ElementCommonPacket*) data; + SimplyElementCommon *element = simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + simply_stage_set_element_frame(simply->stage, element, packet->frame); + element->background_color = packet->background_color; + element->border_color = packet->border_color; + simply_stage_update(simply->stage); +} + +static void handle_element_radius_packet(Simply *simply, Packet *data) { + ElementRadiusPacket *packet = (ElementRadiusPacket*) data; + SimplyElementRect *element = (SimplyElementRect*) simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + element->radius = packet->radius; + simply_stage_update(simply->stage); +}; + +static void handle_element_text_packet(Simply *simply, Packet *data) { + ElementTextPacket *packet = (ElementTextPacket*) data; + SimplyElementText *element = (SimplyElementText*) simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + if (element->time_units != packet->time_units) { + element->time_units = packet->time_units; + simply_stage_update_ticker(simply->stage); + } + strset(&element->text, packet->text); + simply_stage_update(simply->stage); +} + +static void handle_element_text_style_packet(Simply *simply, Packet *data) { + ElementTextStylePacket *packet = (ElementTextStylePacket*) data; + SimplyElementText *element = (SimplyElementText*) simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + element->text_color = packet->color; + element->overflow_mode = packet->overflow_mode; + element->alignment = packet->alignment; + if (packet->custom_font) { + element->font = simply_res_get_font(simply->res, packet->custom_font); + } else if (packet->system_font[0]) { + element->font = fonts_get_system_font(packet->system_font); + } + simply_stage_update(simply->stage); +} + +static void handle_element_image_packet(Simply *simply, Packet *data) { + ElementImagePacket *packet = (ElementImagePacket*) data; + SimplyElementImage *element = (SimplyElementImage*) simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + element->image = packet->image; + element->compositing = packet->compositing; + simply_stage_update(simply->stage); +} + +static void handle_element_animate_packet(Simply *simply, Packet *data) { + ElementAnimatePacket *packet = (ElementAnimatePacket*) data; + SimplyElementCommon *element = simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + SimplyAnimation *animation = malloc0(sizeof(*animation)); + animation->duration = packet->duration; + animation->curve = packet->curve; + simply_stage_animate_element(simply->stage, element, animation, packet->frame); +} + +bool simply_stage_handle_packet(Simply *simply, Packet *packet) { + switch (packet->type) { + case CommandStageClear: + handle_stage_clear_packet(simply, packet); + return true; + case CommandElementInsert: + handle_element_insert_packet(simply, packet); + return true; + case CommandElementRemove: + handle_element_remove_packet(simply, packet); + return true; + case CommandElementCommon: + handle_element_common_packet(simply, packet); + return true; + case CommandElementRadius: + handle_element_radius_packet(simply, packet); + return true; + case CommandElementText: + handle_element_text_packet(simply, packet); + return true; + case CommandElementTextStyle: + handle_element_text_style_packet(simply, packet); + return true; + case CommandElementImage: + handle_element_image_packet(simply, packet); + return true; + case CommandElementAnimate: + handle_element_animate_packet(simply, packet); + return true; + } + return false; +} + SimplyStage *simply_stage_create(Simply *simply) { SimplyStage *self = malloc(sizeof(*self)); *self = (SimplyStage) { .window.simply = simply }; diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index d8956b3d..882a48bc 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -2,6 +2,8 @@ #include "simply_window.h" +#include "simply_msg.h" + #include "simply.h" #include "util/list1.h" @@ -113,16 +115,4 @@ struct SimplyAnimation { SimplyStage *simply_stage_create(Simply *simply); void simply_stage_destroy(SimplyStage *self); -void simply_stage_clear(SimplyStage *self); - -void simply_stage_update(SimplyStage *self); -void simply_stage_update_ticker(SimplyStage *self); - -SimplyElementCommon* simply_stage_auto_element(SimplyStage *self, uint32_t id, SimplyElementType type); -SimplyElementCommon* simply_stage_insert_element(SimplyStage *self, int index, SimplyElementCommon *element); -SimplyElementCommon* simply_stage_remove_element(SimplyStage *self, SimplyElementCommon *element); - -void simply_stage_set_element_frame(SimplyStage *self, SimplyElementCommon *element, GRect frame); - -SimplyAnimation *simply_stage_animate_element(SimplyStage *self, - SimplyElementCommon *element, SimplyAnimation* animation, GRect to_frame); +bool simply_stage_handle_packet(Simply *simply, Packet *packet); diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 042ffb02..fddf7c03 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -7,7 +7,9 @@ #include "simply.h" #include "util/graphics.h" +#include "util/math.h" #include "util/string.h" +#include "util/window.h" #include @@ -42,6 +44,36 @@ static SimplyStyle STYLES[] = { }, }; +typedef struct CardClearPacket CardClearPacket; + +struct __attribute__((__packed__)) CardClearPacket { + Packet packet; + uint8_t flags; +}; + +typedef struct CardTextPacket CardTextPacket; + +struct __attribute__((__packed__)) CardTextPacket { + Packet packet; + uint8_t index; + char text[]; +}; + +typedef struct CardImagePacket CardImagePacket; + +struct __attribute__((__packed__)) CardImagePacket { + Packet packet; + uint32_t image; + uint8_t index; +}; + +typedef struct CardStylePacket CardStylePacket; + +struct __attribute__((__packed__)) CardStylePacket { + Packet packet; + uint8_t style; +}; + void simply_ui_clear(SimplyUi *self, uint32_t clear_mask) { if (clear_mask & (1 << ClearAction)) { simply_window_action_bar_clear(&self->window); @@ -273,6 +305,45 @@ static void window_unload(Window *window) { simply_window_unload(&self->window); } +static void handle_card_clear_packet(Simply *simply, Packet *data) { + CardClearPacket *packet = (CardClearPacket*) data; + simply_ui_clear(simply->ui, packet->flags); +} + +static void handle_card_text_packet(Simply *simply, Packet *data) { + CardTextPacket *packet = (CardTextPacket*) data; + simply_ui_set_text(simply->ui, MIN(NumUiTextfields - 1, packet->index), packet->text); +} + +static void handle_card_image_packet(Simply *simply, Packet *data) { + CardImagePacket *packet = (CardImagePacket*) data; + simply->ui->ui_layer.imagefields[MIN(NumUiImagefields - 1, packet->index)] = packet->image; + window_stack_schedule_top_window_render(); +} + +static void handle_card_style_packet(Simply *simply, Packet *data) { + CardStylePacket *packet = (CardStylePacket*) data; + simply_ui_set_style(simply->ui, packet->style); +} + +bool simply_ui_handle_packet(Simply *simply, Packet *packet) { + switch (packet->type) { + case CommandCardClear: + handle_card_clear_packet(simply, packet); + return true; + case CommandCardText: + handle_card_text_packet(simply, packet); + return true; + case CommandCardImage: + handle_card_image_packet(simply, packet); + return true; + case CommandCardStyle: + handle_card_style_packet(simply, packet); + return true; + } + return false; +} + SimplyUi *simply_ui_create(Simply *simply) { SimplyUi *self = malloc(sizeof(*self)); *self = (SimplyUi) { .window.layer = NULL }; diff --git a/src/simply/simply_ui.h b/src/simply/simply_ui.h index 3fc44c7c..86c0cf6f 100644 --- a/src/simply/simply_ui.h +++ b/src/simply/simply_ui.h @@ -51,3 +51,4 @@ void simply_ui_clear(SimplyUi *self, uint32_t clear_mask); void simply_ui_set_style(SimplyUi *self, int style_index); void simply_ui_set_text(SimplyUi *self, SimplyUiTextfield textfield, const char *str); +bool simply_ui_handle_packet(Simply *simply, Packet *packet); diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 90ecaf0f..e3943278 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -13,8 +13,60 @@ #include +typedef struct WindowPropsPacket WindowPropsPacket; + +struct __attribute__((__packed__)) WindowPropsPacket { + Packet packet; + uint32_t id; + GColor background_color:8; + bool fullscreen; + bool scrollable; +}; + +typedef struct WindowButtonConfigPacket WindowButtonConfigPacket; + +struct __attribute__((__packed__)) WindowButtonConfigPacket { + Packet packet; + uint8_t button_mask; +}; + +typedef struct WindowActionBarPacket WindowActionBarPacket; + +struct __attribute__((__packed__)) WindowActionBarPacket { + Packet packet; + uint32_t image[3]; + bool action; + GColor background_color:8; +}; + +typedef struct ClickPacket ClickPacket; + +struct __attribute__((__packed__)) ClickPacket { + Packet packet; + ButtonId button:8; +}; + +typedef ClickPacket LongClickPacket; + static void click_config_provider(void *data); +static bool send_click(SimplyMsg *self, Command type, ButtonId button) { + ClickPacket packet = { + .packet.type = type, + .packet.length = sizeof(packet), + .button = button, + }; + return simply_msg_send_packet(&packet.packet); +} + +static bool send_single_click(SimplyMsg *self, ButtonId button) { + return send_click(self, CommandClick, button); +} + +static bool send_long_click(SimplyMsg *self, ButtonId button) { + return send_click(self, CommandLongClick, button); +} + static void set_scroll_layer_click_config(SimplyWindow *self) { if (!self->scroll_layer) { return; @@ -154,7 +206,7 @@ void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *con } } if (is_enabled) { - simply_msg_single_click(self->simply->msg, button); + send_single_click(self->simply->msg, button); } } @@ -163,7 +215,7 @@ static void long_click_handler(ClickRecognizerRef recognizer, void *context) { ButtonId button = click_recognizer_get_button_id(recognizer); bool is_enabled = (self->button_mask & (1 << button)); if (is_enabled) { - simply_msg_long_click(self->simply->msg, button); + send_long_click(self->simply->msg, button); } } @@ -202,6 +254,55 @@ void simply_window_unload(SimplyWindow *self) { self->scroll_layer = NULL; } +static void handle_window_props_packet(Simply *simply, Packet *data) { + WindowPropsPacket *packet = (WindowPropsPacket*) data; + SimplyWindow *window = simply_window_stack_get_top_window(simply); + if (!window) { + return; + } + window->id = packet->id; + simply_window_set_background_color(window, packet->background_color); + simply_window_set_fullscreen(window, packet->fullscreen); + simply_window_set_scrollable(window, packet->scrollable); +} + +static void handle_window_button_config_packet(Simply *simply, Packet *data) { + WindowButtonConfigPacket *packet = (WindowButtonConfigPacket*) data; + SimplyWindow *window = simply_window_stack_get_top_window(simply); + if (!window) { + return; + } + window->button_mask = packet->button_mask; +} + +static void handle_window_action_bar_packet(Simply *simply, Packet *data) { + WindowActionBarPacket *packet = (WindowActionBarPacket*) data; + SimplyWindow *window = simply_window_stack_get_top_window(simply); + if (!window) { + return; + } + simply_window_set_action_bar_background_color(window, packet->background_color); + for (unsigned int i = 0; i < ARRAY_LENGTH(packet->image); ++i) { + simply_window_set_action_bar_icon(window, i + 1, packet->image[i]); + } + simply_window_set_action_bar(window, packet->action); +} + +bool simply_window_handle_packet(Simply *simply, Packet *packet) { + switch (packet->type) { + case CommandWindowProps: + handle_window_props_packet(simply, packet); + return true; + case CommandWindowButtonConfig: + handle_window_button_config_packet(simply, packet); + return true; + case CommandWindowActionBar: + handle_window_action_bar_packet(simply, packet); + return true; + } + return false; +} + SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { self->simply = simply; diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index 6f54f644..b0d026e6 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -1,5 +1,7 @@ #pragma once +#include "simply_msg.h" + #include "simply.h" #include @@ -40,3 +42,5 @@ void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar); void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint32_t id); void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor background_color); void simply_window_action_bar_clear(SimplyWindow *self); + +bool simply_window_handle_packet(Simply *simply, Packet *packet); diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index 9d20bf9c..da512294 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -5,8 +5,82 @@ #include "simply.h" +#include "util/math.h" + #include +typedef enum WindowType WindowType; + +enum WindowType { + WindowTypeWindow = 0, + WindowTypeMenu, + WindowTypeCard, + WindowTypeLast, +}; + +typedef struct WindowShowPacket WindowShowPacket; + +struct __attribute__((__packed__)) WindowShowPacket { + Packet packet; + WindowType type:8; + bool pushing; +}; + +typedef struct WindowSignalPacket WindowSignalPacket; + +struct __attribute__((__packed__)) WindowSignalPacket { + Packet packet; + uint32_t id; +}; + +typedef WindowSignalPacket WindowHidePacket; + +typedef WindowHidePacket WindowEventPacket; + +typedef WindowEventPacket WindowShowEventPacket; + +typedef WindowEventPacket WindowHideEventPacket; + +static bool s_broadcast_window = true; + +static bool send_window(SimplyMsg *self, Command type, uint32_t id) { + if (!s_broadcast_window) { + return false; + } + WindowEventPacket packet = { + .packet.type = type, + .packet.length = sizeof(packet), + .id = id, + }; + return simply_msg_send_packet(&packet.packet); +} + +static bool send_window_show(SimplyMsg *self, uint32_t id) { + return send_window(self, CommandWindowShowEvent, id); +} + +static bool send_window_hide(SimplyMsg *self, uint32_t id) { + return send_window(self, CommandWindowHideEvent, id); +} + +bool simply_window_stack_set_broadcast(bool broadcast) { + bool was_broadcast = s_broadcast_window; + s_broadcast_window = broadcast; + return was_broadcast; +} + +SimplyWindow *simply_window_stack_get_top_window(Simply *simply) { + Window *base_window = window_stack_get_top_window(); + if (!base_window) { + return NULL; + } + SimplyWindow *window = window_get_user_data(base_window); + if (!window || (void*) window == simply->splash) { + return NULL; + } + return window; +} + void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, bool is_push) { bool animated = (self->simply->splash == NULL); @@ -44,7 +118,7 @@ void simply_window_stack_send_show(SimplyWindowStack *self, SimplyWindow *window if (self->is_showing) { return; } - simply_msg_window_show(self->simply->msg, window->id); + send_window_show(self->simply->msg, window->id); } void simply_window_stack_send_hide(SimplyWindowStack *self, SimplyWindow *window) { @@ -54,12 +128,41 @@ void simply_window_stack_send_hide(SimplyWindowStack *self, SimplyWindow *window if (self->is_showing) { return; } - simply_msg_window_hide(self->simply->msg, window->id); + send_window_hide(self->simply->msg, window->id); if (!self->is_hiding) { window_stack_push(self->pusher, false); } } +static void handle_window_show_packet(Simply *simply, Packet *data) { + WindowShowPacket *packet = (WindowShowPacket*) data; + SimplyWindow *window = simply->windows[MIN(WindowTypeLast - 1, packet->type)]; + simply_window_stack_show(simply->window_stack, window, packet->pushing); +} + +static void handle_window_hide_packet(Simply *simply, Packet *data) { + WindowHidePacket *packet = (WindowHidePacket*) data; + SimplyWindow *window = simply_window_stack_get_top_window(simply); + if (!window) { + return; + } + if (window->id == packet->id) { + simply_window_stack_pop(simply->window_stack, window); + } +} + +bool simply_window_stack_handle_packet(Simply *simply, Packet *packet) { + switch (packet->type) { + case CommandWindowShow: + handle_window_show_packet(simply, packet); + return true; + case CommandWindowHide: + handle_window_hide_packet(simply, packet); + return true; + } + return false; +} + SimplyWindowStack *simply_window_stack_create(Simply *simply) { SimplyWindowStack *self = malloc(sizeof(*self)); *self = (SimplyWindowStack) { .simply = simply }; diff --git a/src/simply/simply_window_stack.h b/src/simply/simply_window_stack.h index fe7618d9..c55144c1 100644 --- a/src/simply/simply_window_stack.h +++ b/src/simply/simply_window_stack.h @@ -2,6 +2,8 @@ #include "simply_window.h" +#include "simply_msg.h" + #include "simply.h" #include @@ -18,9 +20,15 @@ struct SimplyWindowStack { SimplyWindowStack *simply_window_stack_create(Simply *simply); void simply_window_stack_destroy(SimplyWindowStack *self); +bool simply_window_stack_set_broadcast(bool broadcast); + +SimplyWindow *simply_window_stack_get_top_window(Simply *simply); + void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, bool is_push); void simply_window_stack_pop(SimplyWindowStack *self, SimplyWindow *window); void simply_window_stack_back(SimplyWindowStack *self, SimplyWindow *window); void simply_window_stack_send_show(SimplyWindowStack *self, SimplyWindow *window); void simply_window_stack_send_hide(SimplyWindowStack *self, SimplyWindow *window); + +bool simply_window_stack_handle_packet(Simply *simply, Packet *packet); From 340b202f3a0758ed93b44c675fd8954ea78ba395 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 01:19:18 -0800 Subject: [PATCH 445/791] Add light module to ui index --- src/js/ui/index.js | 1 + src/js/ui/light.js | 7 ++++--- src/js/ui/vibe.js | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/js/ui/index.js b/src/js/ui/index.js index ab46b1f5..fc76ae54 100644 --- a/src/js/ui/index.js +++ b/src/js/ui/index.js @@ -11,5 +11,6 @@ UI.TimeText = require('ui/timetext'); UI.Image = require('ui/image'); UI.Inverter = require('ui/inverter'); UI.Vibe = require('ui/vibe'); +UI.Light = require('ui/light'); module.exports = UI; diff --git a/src/js/ui/light.js b/src/js/ui/light.js index 2a513217..804e856d 100644 --- a/src/js/ui/light.js +++ b/src/js/ui/light.js @@ -1,14 +1,15 @@ -var Light = module.exports; var simply = require('ui/simply'); +var Light = module.exports; + Light.on = function() { simply.impl.light('on'); }; Light.auto = function() { simply.impl.light('auto'); -} +}; Light.trigger = function() { simply.impl.light('trigger'); -} +}; diff --git a/src/js/ui/vibe.js b/src/js/ui/vibe.js index 9887f8e3..3e23fa21 100644 --- a/src/js/ui/vibe.js +++ b/src/js/ui/vibe.js @@ -1,7 +1,7 @@ -var Vibe = module.exports; var simply = require('ui/simply'); +var Vibe = module.exports; + Vibe.vibrate = function(type) { simply.impl.vibe(type); }; - From af55dc3980b89a6c038d301942a361b6e3e0e4a3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 01:20:11 -0800 Subject: [PATCH 446/791] Move Settings JSON parsing and prefix load save methods Some JIT compilers can't optimize a function with try catch. Settings loadData and saveData are private methods for internal use. --- src/js/settings/settings.js | 54 ++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index 77751043..37f9b866 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -5,6 +5,14 @@ var appinfo = require('appinfo'); var Settings = module.exports; +var parseJson = function(data) { + try { + return JSON.parse(data); + } catch (e) { + return data; + } +}; + var state; Settings.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; @@ -12,8 +20,8 @@ Settings.settingsUrl = 'http://meiguro.com/simplyjs/settings.html'; Settings.init = function() { Settings.reset(); - Settings.loadOptions(); - Settings.loadData(); + Settings._loadOptions(); + Settings._loadData(); // Register listeners for the Settings Pebble.addEventListener('showConfiguration', Settings.onOpenConfig); @@ -52,35 +60,39 @@ Settings.getBaseOptions = function() { }; }; -var getDataKey = function(path, field) { +Settings._getDataKey = function(path, field) { path = path || appinfo.uuid; return field + ':' + path; }; -Settings.saveData = function(path, field) { +Settings._saveData = function(path, field, data) { field = field || 'data'; - var data = data || state[field]; - localStorage.setItem(getDataKey(path, field), JSON.stringify(data)); + if (data) { + state[field] = data; + } else { + data = state[field]; + } + var key = Settings._getDataKey(path, field); + localStorage.setItem(key, JSON.stringify(data)); }; -Settings.loadData = function(path, field) { +Settings._loadData = function(path, field, nocache) { field = field || 'data'; state[field] = {}; - var data = localStorage.getItem(getDataKey(path, field)); - try { - data = JSON.parse(data); - } catch (e) {} - if (typeof data === 'object' && data !== null) { + var key = Settings._getDataKey(path, field); + var data = parseJson(localStorage.getItem(key)); + if (!nocache && typeof data === 'object' && data !== null) { state[field] = data; } + return data; }; -Settings.saveOptions = function(path) { - Settings.saveData(path, 'options'); +Settings._saveOptions = function(path) { + Settings._saveData(path, 'options'); }; -Settings.loadOptions = function(path) { - Settings.loadData(path, 'options'); +Settings._loadOptions = function(path) { + Settings._loadData(path, 'options'); }; var makeDataAccessor = function(type, path) { @@ -98,7 +110,7 @@ var makeDataAccessor = function(type, path) { } var def = myutil.toObject(field, value); util2.copy(def, data); - Settings.saveData(path, type); + Settings._saveData(path, type); return value; }; }; @@ -165,10 +177,10 @@ Settings.onCloseConfig = function(e) { var options = {}; var format; if (e.response) { - try { - options = JSON.parse(decodeURIComponent(e.response)); + options = parseJson(decodeURIComponent(e.response)); + if (typeof options === 'object' && options !== null) { format = 'json'; - } catch (err) {} + } if (!format && e.response.match(/(&|=)/)) { options = ajax.deformify(e.response); if (util2.count(options) > 0) { @@ -189,7 +201,7 @@ Settings.onCloseConfig = function(e) { if (format && listener.params.autoSave !== false) { e.originalOptions = util2.copy(state.options); util2.copy(options, state.options); - Settings.saveOptions(); + Settings._saveOptions(); } if (listener.close) { return listener.close(e); From 227c52736c3c2a169f8b33f455739c81dba06a63 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 01:27:04 -0800 Subject: [PATCH 447/791] Add simply wakeup c backend --- src/simply/simply_wakeup.c | 130 +++++++++++++++++++++++++++++++++++++ src/simply/simply_wakeup.h | 11 ++++ 2 files changed, 141 insertions(+) create mode 100644 src/simply/simply_wakeup.c create mode 100644 src/simply/simply_wakeup.h diff --git a/src/simply/simply_wakeup.c b/src/simply/simply_wakeup.c new file mode 100644 index 00000000..b52be3a0 --- /dev/null +++ b/src/simply/simply_wakeup.c @@ -0,0 +1,130 @@ +#include "simply_wakeup.h" + +#include "simply_msg.h" + +#include "simply.h" + +#include + +typedef struct LaunchReasonPacket LaunchReasonPacket; + +struct __attribute__((__packed__)) LaunchReasonPacket { + Packet packet; + uint32_t reason; + uint32_t time; + uint8_t is_timezone:8; +}; + +typedef struct WakeupSetPacket WakeupSetPacket; + +struct __attribute__((__packed__)) WakeupSetPacket { + Packet packet; + time_t timestamp; + int32_t cookie; + uint8_t notify_if_missed; +}; + +typedef struct WakeupSignalPacket WakeupSignalPacket; + +struct __attribute__((__packed__)) WakeupSignalPacket { + Packet packet; + int32_t id; + int32_t cookie; +}; + +typedef struct WakeupCancelPacket WakeupCancelPacket; + +struct __attribute__((__packed__)) WakeupCancelPacket { + Packet packet; + int32_t id; +}; + +typedef struct WakeupSetContext WakeupSetContext; + +struct WakeupSetContext { + WakeupId id; + int32_t cookie; +}; + +static bool send_launch_reason(AppLaunchReason reason) { + LaunchReasonPacket packet = { + .packet.type = CommandLaunchReason, + .packet.length = sizeof(packet), + .reason = reason, + .time = time(NULL), + .is_timezone = clock_is_timezone_set(), + }; + return simply_msg_send_packet(&packet.packet); +} + +static bool send_wakeup_signal(Command type, WakeupId id, int32_t cookie) { + WakeupSignalPacket packet = { + .packet.type = type, + .packet.length = sizeof(packet), + .id = id, + .cookie = cookie, + }; + return simply_msg_send_packet(&packet.packet); +} + +static void wakeup_handler(WakeupId wakeup_id, int32_t cookie) { + send_wakeup_signal(CommandWakeupEvent, wakeup_id, cookie); +} + +static void wakeup_set_timer_callback(void *data) { + WakeupSetContext *context = data; + send_wakeup_signal(CommandWakeupSetResult, context->id, context->cookie); +} + +static void process_launch_reason() { + AppLaunchReason reason = launch_reason(); + + send_launch_reason(reason); + + WakeupId wakeup_id; + int32_t cookie; + if (reason == APP_LAUNCH_WAKEUP && wakeup_get_launch_event(&wakeup_id, &cookie)) { + wakeup_handler(wakeup_id, cookie); + } +} + +static void handle_wakeup_set(Simply *simply, Packet *data) { + WakeupSetPacket *packet = (WakeupSetPacket*) data; + WakeupId id = wakeup_schedule(packet->timestamp, packet->cookie, packet->notify_if_missed); + + WakeupSetContext *context = malloc(sizeof(*context)); + if (!context) { + return; + } + context->id = id; + context->cookie = packet->cookie; + app_timer_register(10, wakeup_set_timer_callback, context); +} + +static void handle_wakeup_cancel(Simply *simply, Packet *data) { + WakeupCancelPacket *packet = (WakeupCancelPacket*) data; + if (packet->id == -1) { + wakeup_cancel_all(); + } else { + wakeup_cancel(packet->id); + } +} + +bool simply_wakeup_handle_packet(Simply *simply, Packet *packet) { + switch (packet->type) { + case CommandReady: + process_launch_reason(); + return false; + case CommandWakeupSet: + handle_wakeup_set(simply, packet); + return true; + case CommandWakeupCancel: + handle_wakeup_cancel(simply, packet); + return true; + } + return false; +} + +void simply_wakeup_init(Simply *simply) { + wakeup_service_subscribe(wakeup_handler); +} diff --git a/src/simply/simply_wakeup.h b/src/simply/simply_wakeup.h new file mode 100644 index 00000000..0dbab297 --- /dev/null +++ b/src/simply/simply_wakeup.h @@ -0,0 +1,11 @@ +#pragma once + +#include "simply_msg.h" + +#include "simply.h" + +#include + +void simply_wakeup_init(Simply *simply); + +bool simply_wakeup_handle_packet(Simply *simply, Packet *packet); From 7d86319197b077fa5165ac4f5db6b4babcc0b6d2 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 01:33:54 -0800 Subject: [PATCH 448/791] Add wakeup packets to communication tables --- src/js/ui/simply-pebble.js | 41 ++++++++++++++++++++++++++++++++ src/simply/simply.c | 3 +++ src/simply/simply_msg.c | 2 ++ src/simply/simply_msg_commands.h | 6 +++++ 4 files changed, 52 insertions(+) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index dfadedf2..250136b5 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -243,6 +243,41 @@ var SegmentPacket = new struct([ ['data', 'buffer'], ]); +var ReadyPacket = new struct([ + [Packet, 'packet'], +]); + +var LaunchReasonPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'reason'], + ['uint32', 'time'], + ['bool', 'isTimezone'], +]); + +var WakeupSetPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'timestamp'], + ['int32', 'cookie'], + ['uint8', 'notifyIfMissed'], +]); + +var WakeupSetResultPacket = new struct([ + [Packet, 'packet'], + ['int32', 'id'], + ['int32', 'cookie'], +]); + +var WakeupCancelPacket = new struct([ + [Packet, 'packet'], + ['int32', 'id'], +]); + +var WakeupEventPacket = new struct([ + [Packet, 'packet'], + ['int32', 'id'], + ['int32', 'cookie'], +]); + var WindowShowPacket = new struct([ [Packet, 'packet'], ['uint8', 'type', WindowType], @@ -528,6 +563,12 @@ var ElementAnimateDonePacket = new struct([ var CommandPackets = [ Packet, SegmentPacket, + ReadyPacket, + LaunchReasonPacket, + WakeupSetPacket, + WakeupSetResultPacket, + WakeupCancelPacket, + WakeupEventPacket, WindowShowPacket, WindowHidePacket, WindowShowEventPacket, diff --git a/src/simply/simply.c b/src/simply/simply.c index dfb10090..4b27f9b0 100644 --- a/src/simply/simply.c +++ b/src/simply/simply.c @@ -8,6 +8,7 @@ #include "simply_msg.h" #include "simply_ui.h" #include "simply_window_stack.h" +#include "simply_wakeup.h" #include @@ -22,6 +23,8 @@ Simply *simply_init(void) { simply->ui = simply_ui_create(simply); simply->window_stack = simply_window_stack_create(simply); + simply_wakeup_init(simply); + bool animated = false; window_stack_push(simply->splash->window, animated); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index ec62f8ae..8cd1d23e 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -6,6 +6,7 @@ #include "simply_menu.h" #include "simply_ui.h" #include "simply_window_stack.h" +#include "simply_wakeup.h" #include "simply.h" @@ -193,6 +194,7 @@ static bool simply_base_handle_packet(Simply *simply, Packet *packet) { static void handle_packet(Simply *simply, Packet *packet) { if (simply_base_handle_packet(simply, packet)) { return; } + if (simply_wakeup_handle_packet(simply, packet)) { return; } if (simply_window_stack_handle_packet(simply, packet)) { return; } if (simply_window_handle_packet(simply, packet)) { return; } if (simply_ui_handle_packet(simply, packet)) { return; } diff --git a/src/simply/simply_msg_commands.h b/src/simply/simply_msg_commands.h index 75e3e85e..e5232eba 100644 --- a/src/simply/simply_msg_commands.h +++ b/src/simply/simply_msg_commands.h @@ -4,6 +4,12 @@ typedef enum Command Command; enum Command { CommandSegment = 1, + CommandReady, + CommandLaunchReason, + CommandWakeupSet, + CommandWakeupSetResult, + CommandWakeupCancel, + CommandWakeupEvent, CommandWindowShow, CommandWindowHide, CommandWindowShowEvent, From fa5bf60a3879b884cff48cefef167361fe31a8b0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 01:35:04 -0800 Subject: [PATCH 449/791] Add Wakeup js module --- src/js/wakeup/index.js | 3 ++ src/js/wakeup/wakeup.js | 117 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/js/wakeup/index.js create mode 100644 src/js/wakeup/wakeup.js diff --git a/src/js/wakeup/index.js b/src/js/wakeup/index.js new file mode 100644 index 00000000..cc54e4fb --- /dev/null +++ b/src/js/wakeup/index.js @@ -0,0 +1,3 @@ +var Wakeup = require('./wakeup'); + +module.exports = Wakeup; diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js new file mode 100644 index 00000000..5d0cb52d --- /dev/null +++ b/src/js/wakeup/wakeup.js @@ -0,0 +1,117 @@ +var util2 = require('util2'); +var Emitter = require('emitter'); +var Settings = require('settings'); +var simply = require('ui/simply'); + +var Wakeup = function() { + this.init(); +}; + +util2.copy(Emitter.prototype, Wakeup.prototype); + +Wakeup.prototype.init = function() { + this.off(); + this._setRequests = []; + this._loadData(); +}; + +Wakeup.prototype._loadData = function() { + this.state = Settings._loadData(null, 'wakeup', true) || {}; + this.state.wakeups = this.state.wakeups || {}; +}; + +Wakeup.prototype._saveData = function() { + Settings._saveData(null, 'wakeup', this.state); +}; + +Wakeup.prototype.get = function(id) { + var wakeup = this.state.wakeups[id]; + if (wakeup) { + return { + id: id, + cookie: wakeup.cookie, + data: wakeup.data, + time: wakeup.params.time, + notifyIfMissed: wakeup.params.notifyIfMissed, + }; + } +}; + +Wakeup.prototype.each = function(callback) { + for (var id in this.state.wakeups) { + callback(this.get(id)); + } +}; + +Wakeup.prototype.schedule = function(opt, callback) { + if (typeof opt === 'number') { + opt = { time: opt }; + } else if (opt instanceof Date) { + opt = { time: opt.getTime() / 1000 }; + } + var cookie = opt.cookie || 0; + this._setRequests.push({ + params: opt, + data: opt.data, + callback: callback, + }); + simply.impl.wakeupSet(opt.time, opt.cookie, opt.notifyIfMissed); +}; + +Wakeup.prototype.cancel = function(id) { + if (id === 'all') { + this.state.wakeups = {}; + } else { + delete this.state.wakeups[id]; + } + simply.impl.wakeupCancel(id); +}; + +Wakeup.prototype._makeWakeupEvent = function(id, cookie, remove) { + var wakeup = this.state.wakeups[id]; + if (remove) { + delete this.state.wakeups[id]; + } + var e = { + id: id, + cookie: cookie, + }; + if (wakeup) { + e.data = wakeup.data; + } + return e; +}; + +Wakeup.prototype.emitSetResult = function(id, cookie) { + var req = this._setRequests.splice(0, 1)[0]; + if (!req) { + return; + } + var e; + if (typeof id === 'number') { + this.state.wakeups[id] = { + id: id, + cookie: cookie, + data: req.data, + params: req.params, + }; + this._saveData(); + e = this._makeWakeupEvent(id, cookie); + e.failed = false; + } else { + e = { + error: id, + failed: true, + cookie: cookie, + data: req.data, + }; + } + req.callback(e); +}; + +Wakeup.prototype.emitWakeup = function(id, cookie) { + var e = this._makeWakeupEvent(id, cookie, true); + this.emit('wakeup', e); +}; + +module.exports = new Wakeup(); From a134b3101efee4394cbc3c409c4a0a8e3560a828 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 01:35:41 -0800 Subject: [PATCH 450/791] Add SimplyPebble wakeup support. Implements #7 --- src/js/ui/simply-pebble.js | 59 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 250136b5..4a79462e 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1,6 +1,7 @@ var struct = require('struct'); var util2 = require('util2'); var myutil = require('myutil'); +var Wakeup = require('wakeup'); var Resource = require('ui/resource'); var Accel = require('ui/accel'); var ImageService = require('ui/imageservice'); @@ -21,6 +22,8 @@ var simply = require('ui/simply'); * First part of this file is defining the commands and types that we will use later. */ +var state; + var BoolType = function(x) { return x ? 1 : 0; }; @@ -42,6 +45,13 @@ var EnumerableType = function(x) { return x ? Number(x) : 0; }; +var TimeType = function(x) { + if (x instanceof Date) { + x = x.getTime() / 1000; + } + return (x ? Number(x) : 0) + state.timeOffset; +}; + var ImageType = function(x) { if (x && typeof x !== 'number') { return ImageService.resolve(x); @@ -256,7 +266,7 @@ var LaunchReasonPacket = new struct([ var WakeupSetPacket = new struct([ [Packet, 'packet'], - ['uint32', 'timestamp'], + ['uint32', 'timestamp', TimeType], ['int32', 'cookie'], ['uint8', 'notifyIfMissed'], ]); @@ -631,8 +641,6 @@ var clearFlagMap = { * It's an implementation of an abstract interface used by all the other classes. */ -var state; - var SimplyPebble = {}; SimplyPebble.init = function() { @@ -644,11 +652,16 @@ SimplyPebble.init = function() { state = SimplyPebble.state = {}; + state.timeOffset = new Date().getTimezoneOffset() * -60; + // Initialize the app message queue state.messageQueue = new MessageQueue(); // Initialize the packet queue state.packetQueue = new PacketQueue(); + + // Signal the Pebble that the Phone's app message is ready + SimplyPebble.ready(); }; /** @@ -759,6 +772,22 @@ SimplyPebble.sendPacket = function(packet) { } }; +SimplyPebble.ready = function() { + SimplyPebble.sendPacket(ReadyPacket); +}; + +SimplyPebble.wakeupSet = function(timestamp, cookie, notifyIfMissed) { + WakeupSetPacket + .timestamp(timestamp) + .cookie(cookie) + .notifyIfMissed(notifyIfMissed); + SimplyPebble.sendPacket(WakeupSetPacket); +}; + +SimplyPebble.wakeupCancel = function(id) { + SimplyPebble.sendPacket(WakeupCancelPacket.id(id === 'all' ? -1 : id)); +}; + SimplyPebble.windowShow = function(def) { SimplyPebble.sendPacket(WindowShowPacket.prop(def)); }; @@ -1046,6 +1075,30 @@ SimplyPebble.onPacket = function(buffer, offset) { packet._view = Packet._view; packet._offset = offset; switch (packet) { + case LaunchReasonPacket: + var remoteTime = LaunchReasonPacket.time(); + var isTimezone = LaunchReasonPacket.isTimezone(); + if (isTimezone) { + state.timeOffset = 0; + } else { + var time = new Date().getTime() / 1000; + var resolution = 60 * 30; + state.timeOffset = Math.round((remoteTime - time) / resolution) * resolution; + } + break; + case WakeupSetResultPacket: + var id = packet.id(); + switch (id) { + case -8: id = 'range'; break; + case -4: id = 'invalid-argument'; break; + case -7: id = 'out-of-resources'; break; + case -3: id = 'internal'; break; + } + Wakeup.emitSetResult(id, packet.cookie()); + break; + case WakeupEventPacket: + Wakeup.emitWakeup(packet.id(), packet.cookie()); + break; case WindowHideEventPacket: ImageService.markAllUnloaded(); WindowStack.emitHide(packet.id()); From c84a499e3d86a244365927e677fd54d729e0696c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 01:38:33 -0800 Subject: [PATCH 451/791] Change simply msg to obtain the packet type and length from the packet --- src/simply/simply_msg.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 8cd1d23e..4b9ca86b 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -364,18 +364,14 @@ static void send_msg_retry(void *data) { self->send_timer = app_timer_register(self->send_delay_ms, send_msg_retry, self); } -static SimplyPacket *add_packet(SimplyMsg *self, Packet *buffer, Command type, size_t length) { - SimplyPacket *packet = malloc0(sizeof(*packet)); +static SimplyPacket *add_packet(SimplyMsg *self, Packet *buffer) { + SimplyPacket *packet = malloc(sizeof(*packet)); if (!packet) { free(buffer); return NULL; } - *buffer = (Packet) { - .type = type, - .length = length, - }; *packet = (SimplyPacket) { - .length = length, + .length = buffer->length, .buffer = buffer, }; list1_append(&self->send_queue, &packet->node); @@ -394,5 +390,5 @@ bool simply_msg_send_packet(Packet *packet) { return false; } memcpy(copy, packet, packet->length); - return add_packet(s_msg, copy, packet->type, packet->length); + return add_packet(s_msg, copy); } From 271e9e68f16017acb7ab8c70e00b9ac60edbc387 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 01:44:11 -0800 Subject: [PATCH 452/791] Remove saved wakeups after they trigger --- src/js/wakeup/wakeup.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js index 5d0cb52d..9634dae8 100644 --- a/src/js/wakeup/wakeup.js +++ b/src/js/wakeup/wakeup.js @@ -67,11 +67,8 @@ Wakeup.prototype.cancel = function(id) { simply.impl.wakeupCancel(id); }; -Wakeup.prototype._makeWakeupEvent = function(id, cookie, remove) { +Wakeup.prototype._makeWakeupEvent = function(id, cookie) { var wakeup = this.state.wakeups[id]; - if (remove) { - delete this.state.wakeups[id]; - } var e = { id: id, cookie: cookie, @@ -110,7 +107,9 @@ Wakeup.prototype.emitSetResult = function(id, cookie) { }; Wakeup.prototype.emitWakeup = function(id, cookie) { - var e = this._makeWakeupEvent(id, cookie, true); + var e = this._makeWakeupEvent(id, cookie); + delete this.state.wakeups[id]; + this._saveData(); this.emit('wakeup', e); }; From ab30200e9c34c3f7cad6cb4281055e0e0512ca54 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 02:02:53 -0800 Subject: [PATCH 453/791] Add Wakeup launch method --- src/js/wakeup/wakeup.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js index 9634dae8..cba9d880 100644 --- a/src/js/wakeup/wakeup.js +++ b/src/js/wakeup/wakeup.js @@ -12,6 +12,7 @@ util2.copy(Emitter.prototype, Wakeup.prototype); Wakeup.prototype.init = function() { this.off(); this._setRequests = []; + this._launchCallbacks = []; this._loadData(); }; @@ -67,6 +68,14 @@ Wakeup.prototype.cancel = function(id) { simply.impl.wakeupCancel(id); }; +Wakeup.prototype.launch = function(callback) { + if (this._launchEvent) { + callback(this._launchEvent); + } else { + this._launchCallbacks.push(callback); + } +}; + Wakeup.prototype._makeWakeupEvent = function(id, cookie) { var wakeup = this.state.wakeups[id]; var e = { @@ -103,14 +112,25 @@ Wakeup.prototype.emitSetResult = function(id, cookie) { data: req.data, }; } - req.callback(e); + return req.callback(e); }; Wakeup.prototype.emitWakeup = function(id, cookie) { var e = this._makeWakeupEvent(id, cookie); + delete this.state.wakeups[id]; this._saveData(); - this.emit('wakeup', e); + this._launchEvent = e; + + var callbacks = this._launchCallbacks; + this._launchCallbacks = []; + for (var i = 0, ii = callbacks.length; i < ii; ++i) { + if (callbacks[i](e) === false) { + return false; + } + } + + return this.emit('wakeup', e); }; module.exports = new Wakeup(); From c8dbd2082db5006efbc8f1a2949747cba0a94392 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 06:28:14 -0800 Subject: [PATCH 454/791] Remove Wakeup Emitter dependency --- src/js/wakeup/index.js | 2 ++ src/js/wakeup/wakeup.js | 56 +++++++++++++++++++++++++---------------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/js/wakeup/index.js b/src/js/wakeup/index.js index cc54e4fb..158f7335 100644 --- a/src/js/wakeup/index.js +++ b/src/js/wakeup/index.js @@ -1,3 +1,5 @@ var Wakeup = require('./wakeup'); +Wakeup.init(); + module.exports = Wakeup; diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js index cba9d880..5c918138 100644 --- a/src/js/wakeup/wakeup.js +++ b/src/js/wakeup/wakeup.js @@ -1,31 +1,49 @@ var util2 = require('util2'); -var Emitter = require('emitter'); var Settings = require('settings'); var simply = require('ui/simply'); -var Wakeup = function() { - this.init(); -}; +var Wakeup = module.exports; -util2.copy(Emitter.prototype, Wakeup.prototype); +var cleanupGracePeriod = 60 * 5; -Wakeup.prototype.init = function() { - this.off(); +Wakeup.init = function() { this._setRequests = []; this._launchCallbacks = []; this._loadData(); + this._cleanup(); }; -Wakeup.prototype._loadData = function() { +Wakeup._loadData = function() { this.state = Settings._loadData(null, 'wakeup', true) || {}; this.state.wakeups = this.state.wakeups || {}; }; -Wakeup.prototype._saveData = function() { +Wakeup._saveData = function() { Settings._saveData(null, 'wakeup', this.state); }; -Wakeup.prototype.get = function(id) { +Wakeup._cleanup = function() { + var id; + var ids = []; + for (id in this.state.wakeups) { + ids.push(id); + } + var cleanupTime = new Date().getTime() / 1000 - cleanupGracePeriod; + var deleted = false; + for (var i = 0, ii = ids.length; i < ii; ++i) { + id = ids[i]; + var wakeup = this.state.wakeups[id]; + if (wakeup.params.time < cleanupTime) { + deleted = true; + delete this.state.wakeups[id]; + } + } + if (deleted) { + this._saveData(); + } +}; + +Wakeup.get = function(id) { var wakeup = this.state.wakeups[id]; if (wakeup) { return { @@ -38,13 +56,13 @@ Wakeup.prototype.get = function(id) { } }; -Wakeup.prototype.each = function(callback) { +Wakeup.each = function(callback) { for (var id in this.state.wakeups) { callback(this.get(id)); } }; -Wakeup.prototype.schedule = function(opt, callback) { +Wakeup.schedule = function(opt, callback) { if (typeof opt === 'number') { opt = { time: opt }; } else if (opt instanceof Date) { @@ -59,7 +77,7 @@ Wakeup.prototype.schedule = function(opt, callback) { simply.impl.wakeupSet(opt.time, opt.cookie, opt.notifyIfMissed); }; -Wakeup.prototype.cancel = function(id) { +Wakeup.cancel = function(id) { if (id === 'all') { this.state.wakeups = {}; } else { @@ -68,7 +86,7 @@ Wakeup.prototype.cancel = function(id) { simply.impl.wakeupCancel(id); }; -Wakeup.prototype.launch = function(callback) { +Wakeup.launch = function(callback) { if (this._launchEvent) { callback(this._launchEvent); } else { @@ -76,7 +94,7 @@ Wakeup.prototype.launch = function(callback) { } }; -Wakeup.prototype._makeWakeupEvent = function(id, cookie) { +Wakeup._makeWakeupEvent = function(id, cookie) { var wakeup = this.state.wakeups[id]; var e = { id: id, @@ -88,7 +106,7 @@ Wakeup.prototype._makeWakeupEvent = function(id, cookie) { return e; }; -Wakeup.prototype.emitSetResult = function(id, cookie) { +Wakeup.emitSetResult = function(id, cookie) { var req = this._setRequests.splice(0, 1)[0]; if (!req) { return; @@ -115,7 +133,7 @@ Wakeup.prototype.emitSetResult = function(id, cookie) { return req.callback(e); }; -Wakeup.prototype.emitWakeup = function(id, cookie) { +Wakeup.emitWakeup = function(id, cookie) { var e = this._makeWakeupEvent(id, cookie); delete this.state.wakeups[id]; @@ -129,8 +147,4 @@ Wakeup.prototype.emitWakeup = function(id, cookie) { return false; } } - - return this.emit('wakeup', e); }; - -module.exports = new Wakeup(); From 2e8ac662e21500c5e2f1c8c47970e7d5c9b778e7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 07:11:09 -0800 Subject: [PATCH 455/791] Change Wakeup launch to also fire on no wakeup for user branching --- src/js/ui/simply-pebble.js | 97 ++++++++++++++++++++++++-------------- src/js/wakeup/wakeup.js | 13 ++++- 2 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 4a79462e..de942b18 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -183,6 +183,17 @@ var makeFlagsType = function(types) { }; }; +var LaunchReasonTypes = [ + 'system', + 'user', + 'phone', + 'wakeup', + 'worker', + 'quickLaunch', +]; + +var LaunchReasonType = makeArrayType(LaunchReasonTypes); + var WindowTypes = [ 'window', 'menu', @@ -259,7 +270,7 @@ var ReadyPacket = new struct([ var LaunchReasonPacket = new struct([ [Packet, 'packet'], - ['uint32', 'reason'], + ['uint32', 'reason', LaunchReasonType], ['uint32', 'time'], ['bool', 'isTimezone'], ]); @@ -1062,6 +1073,53 @@ var toArrayBuffer = function(array, length) { return copy; }; +SimplyPebble.onLaunchReason = function(packet) { + var reason = LaunchReasonTypes[packet.reason()]; + var remoteTime = packet.time(); + var isTimezone = packet.isTimezone(); + if (isTimezone) { + state.timeOffset = 0; + } else { + var time = new Date().getTime() / 1000; + var resolution = 60 * 30; + state.timeOffset = Math.round((remoteTime - time) / resolution) * resolution; + } + if (reason !== 'wakeup') { + Wakeup.emitWakeup('noWakeup', 0); + } +}; + +SimplyPebble.onWakeupSetResult = function(packet) { + var id = packet.id(); + switch (id) { + case -8: id = 'range'; break; + case -4: id = 'invalidArgument'; break; + case -7: id = 'outOfResources'; break; + case -3: id = 'internal'; break; + } + Wakeup.emitSetResult(id, packet.cookie()); +}; + +SimplyPebble.onAccelData = function(packet) { + var samples = packet.samples(); + var accels = []; + AccelData._view = packet._view; + AccelData._offset = packet._size; + for (var i = 0; i < samples; ++i) { + accels.push(AccelData.prop()); + AccelData._offset += AccelData._size; + } + if (!packet.peek()) { + Accel.emitAccelData(accels); + } else { + var handlers = accelListeners; + accelListeners = []; + for (var j = 0, jj = handlers.length; j < jj; ++j) { + Accel.emitAccelData(accels, handlers[j]); + } + } +}; + SimplyPebble.onPacket = function(buffer, offset) { Packet._view = buffer; Packet._offset = offset; @@ -1076,25 +1134,10 @@ SimplyPebble.onPacket = function(buffer, offset) { packet._offset = offset; switch (packet) { case LaunchReasonPacket: - var remoteTime = LaunchReasonPacket.time(); - var isTimezone = LaunchReasonPacket.isTimezone(); - if (isTimezone) { - state.timeOffset = 0; - } else { - var time = new Date().getTime() / 1000; - var resolution = 60 * 30; - state.timeOffset = Math.round((remoteTime - time) / resolution) * resolution; - } + SimplyPebble.onLaunchReason(packet); break; case WakeupSetResultPacket: - var id = packet.id(); - switch (id) { - case -8: id = 'range'; break; - case -4: id = 'invalid-argument'; break; - case -7: id = 'out-of-resources'; break; - case -3: id = 'internal'; break; - } - Wakeup.emitSetResult(id, packet.cookie()); + SimplyPebble.onWakeupSetResult(packet); break; case WakeupEventPacket: Wakeup.emitWakeup(packet.id(), packet.cookie()); @@ -1110,23 +1153,7 @@ SimplyPebble.onPacket = function(buffer, offset) { Window.emitClick('longClick', ButtonTypes[packet.button()]); break; case AccelDataPacket: - var samples = packet.samples(); - var accels = []; - AccelData._view = packet._view; - AccelData._offset = packet._size; - for (var i = 0; i < samples; ++i) { - accels.push(AccelData.prop()); - AccelData._offset += AccelData._size; - } - if (!packet.peek()) { - Accel.emitAccelData(accels); - } else { - var handlers = accelListeners; - accelListeners = []; - for (var j = 0, jj = handlers.length; j < jj; ++j) { - Accel.emitAccelData(accels, handlers[j]); - } - } + SimplyPebble.onAccelData(packet); break; case AccelTapPacket: Accel.emitAccelTap(accelAxes[packet.axis()], packet.direction()); diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js index 5c918138..7d08dba3 100644 --- a/src/js/wakeup/wakeup.js +++ b/src/js/wakeup/wakeup.js @@ -57,8 +57,9 @@ Wakeup.get = function(id) { }; Wakeup.each = function(callback) { + var i = 0; for (var id in this.state.wakeups) { - callback(this.get(id)); + callback(this.get(id), i++); } }; @@ -134,7 +135,15 @@ Wakeup.emitSetResult = function(id, cookie) { }; Wakeup.emitWakeup = function(id, cookie) { - var e = this._makeWakeupEvent(id, cookie); + var e; + if (typeof id === 'number') { + e = this._makeWakeupEvent(id, cookie); + e.wakeup = true; + } else { + e = { + wakeup: false, + }; + } delete this.state.wakeups[id]; this._saveData(); From 83d51c579fcb6f597b4ae9827e4ab9a976447bc4 Mon Sep 17 00:00:00 2001 From: Alessio Bogon Date: Mon, 23 Feb 2015 20:26:53 +0100 Subject: [PATCH 456/791] Readme: fix ajax documentation --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 18c562bf..304eda0f 100644 --- a/README.md +++ b/README.md @@ -1014,10 +1014,10 @@ ajax( url: 'http://api.theysaidso.com/qod.json', type: 'json' }, - function(data) { + function(data, status, request) { console.log('Quote of the day is: ' + data.contents.quote); }, - function(error) { + function(error, status, request) { console.log('The ajax request failed: ' + error); } ); @@ -1037,9 +1037,9 @@ The supported options are: | `async` | boolean | (optional) | true | Whether the request will be asynchronous. Specify `false` for a blocking, synchronous request. | `cache` | boolean | (optional) | true | Whether the result may be cached. Specify `false` to use the internal cache buster which appends the URL with the query parameter `_set` to the current time in milliseconds. | -The `success` callback will be called if the HTTP request is successful (When the status code is 200). The only parameter is the data received from the server. If the option `type: 'json'` was set, the response will automatically be converted to an object; otherwise `data` is a string. +The `success` callback will be called if the HTTP request is successful (when status code inside [200, 300) or 304). The parameters are the data received from the server, the status code and the request object. If the option `type: 'json'` was set, the response will automatically be converted to an object; otherwise `data` is a string. -The `failure` callback is called when an error occurred. The only parameter is a description of the error. +The `failure` callback is called when an error occurred. The parameters are the same of `success`. ### Vector2 From 9c2aff9682d02b3edad11add4c34defd95d79bb6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 Feb 2015 14:32:27 -0800 Subject: [PATCH 457/791] Fix Wakeup schedule cookie usage and parameter handling Thanks youtux for catching the cookie issue! SimplyPebble can use both Date and seconds, so it is passed unmodified. notifyIfMissed is guaranteed to exist on retrospection through events, get, or each for comparison. --- src/js/ui/simply-pebble.js | 2 +- src/js/wakeup/wakeup.js | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index de942b18..b8f345fd 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -279,7 +279,7 @@ var WakeupSetPacket = new struct([ [Packet, 'packet'], ['uint32', 'timestamp', TimeType], ['int32', 'cookie'], - ['uint8', 'notifyIfMissed'], + ['uint8', 'notifyIfMissed', BoolType], ]); var WakeupSetResultPacket = new struct([ diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js index 7d08dba3..1e4defc0 100644 --- a/src/js/wakeup/wakeup.js +++ b/src/js/wakeup/wakeup.js @@ -51,7 +51,7 @@ Wakeup.get = function(id) { cookie: wakeup.cookie, data: wakeup.data, time: wakeup.params.time, - notifyIfMissed: wakeup.params.notifyIfMissed, + notifyIfMissed: !!wakeup.params.notifyIfMissed, }; } }; @@ -64,10 +64,8 @@ Wakeup.each = function(callback) { }; Wakeup.schedule = function(opt, callback) { - if (typeof opt === 'number') { + if (typeof opt !== 'object' || opt instanceof Date) { opt = { time: opt }; - } else if (opt instanceof Date) { - opt = { time: opt.getTime() / 1000 }; } var cookie = opt.cookie || 0; this._setRequests.push({ @@ -75,7 +73,7 @@ Wakeup.schedule = function(opt, callback) { data: opt.data, callback: callback, }); - simply.impl.wakeupSet(opt.time, opt.cookie, opt.notifyIfMissed); + simply.impl.wakeupSet(opt.time, cookie, opt.notifyIfMissed); }; Wakeup.cancel = function(id) { From 89bc0d5bf551602a7e23f34575040a22dcbfb90a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 27 Feb 2015 06:00:49 -0800 Subject: [PATCH 458/791] Update moment.js to v2.9.0 --- src/js/vendor/moment.js | 1510 ++++++++++++++++++++++++++------------- 1 file changed, 1032 insertions(+), 478 deletions(-) diff --git a/src/js/vendor/moment.js b/src/js/vendor/moment.js index 257ee7ec..c635ec0b 100644 --- a/src/js/vendor/moment.js +++ b/src/js/vendor/moment.js @@ -1,21 +1,21 @@ //! moment.js -//! version : 2.6.0 +//! version : 2.9.0 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com (function (undefined) { - /************************************ Constants ************************************/ var moment, - VERSION = "2.6.0", + VERSION = '2.9.0', // the global-scope this is NOT the global object in Node.js - globalScope = typeof global !== 'undefined' ? global : this, + globalScope = (typeof global !== 'undefined' && (typeof window === 'undefined' || window === global.window)) ? global : this, oldGlobalMoment, round = Math.round, + hasOwnProperty = Object.prototype.hasOwnProperty, i, YEAR = 0, @@ -26,24 +26,14 @@ SECOND = 5, MILLISECOND = 6, - // internal storage for language config files - languages = {}, + // internal storage for locale config files + locales = {}, - // moment internal properties - momentProperties = { - _isAMomentObject: null, - _i : null, - _f : null, - _l : null, - _strict : null, - _isUTC : null, - _offset : null, // optional. Combine with _isUTC - _pf : null, - _lang : null // optional - }, + // extra moment internal properties (plugins register props here) + momentProperties = [], // check for nodeJS - hasModule = (typeof module !== 'undefined' && module.exports), + hasModule = (typeof module !== 'undefined' && module && module.exports), // ASP.NET json date format regex aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, @@ -54,8 +44,8 @@ isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, - localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g, + localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, // parsing token regexes parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 @@ -66,8 +56,8 @@ parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z parseTokenT = /T/i, // T (ISO separator) + parseTokenOffsetMs = /[\+\-]?\d+/, // 1234567890123 parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 - parseTokenOrdinal = /\d{1,2}/, //strict parsing regexes parseTokenOneDigit = /\d/, // 0 - 9 @@ -99,7 +89,7 @@ ['HH', /(T| )\d\d/] ], - // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] + // timezone chunker '+10:00' > ['10', '00'] or '-1530' > ['-', '15', '30'] parseTimezoneChunker = /([\+\-]|\d\d)/gi, // getter and setter names @@ -144,6 +134,15 @@ // format function strings formatFunctions = {}, + // default relative time thresholds + relativeTimeThresholds = { + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month + M: 11 // months to year + }, + // tokens to ordinalize and pad ordinalizeTokens = 'DDD w W M D d'.split(' '), paddedTokens = 'M D H h m s w W'.split(' '), @@ -153,10 +152,10 @@ return this.month() + 1; }, MMM : function (format) { - return this.lang().monthsShort(this, format); + return this.localeData().monthsShort(this, format); }, MMMM : function (format) { - return this.lang().months(this, format); + return this.localeData().months(this, format); }, D : function () { return this.date(); @@ -168,13 +167,13 @@ return this.day(); }, dd : function (format) { - return this.lang().weekdaysMin(this, format); + return this.localeData().weekdaysMin(this, format); }, ddd : function (format) { - return this.lang().weekdaysShort(this, format); + return this.localeData().weekdaysShort(this, format); }, dddd : function (format) { - return this.lang().weekdays(this, format); + return this.localeData().weekdays(this, format); }, w : function () { return this.week(); @@ -220,10 +219,10 @@ return this.isoWeekday(); }, a : function () { - return this.lang().meridiem(this.hours(), this.minutes(), true); + return this.localeData().meridiem(this.hours(), this.minutes(), true); }, A : function () { - return this.lang().meridiem(this.hours(), this.minutes(), false); + return this.localeData().meridiem(this.hours(), this.minutes(), false); }, H : function () { return this.hours(); @@ -250,20 +249,20 @@ return leftZeroFill(this.milliseconds(), 3); }, Z : function () { - var a = -this.zone(), - b = "+"; + var a = this.utcOffset(), + b = '+'; if (a < 0) { a = -a; - b = "-"; + b = '-'; } - return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2); + return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2); }, ZZ : function () { - var a = -this.zone(), - b = "+"; + var a = this.utcOffset(), + b = '+'; if (a < 0) { a = -a; - b = "-"; + b = '-'; } return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); }, @@ -273,6 +272,9 @@ zz : function () { return this.zoneName(); }, + x : function () { + return this.valueOf(); + }, X : function () { return this.unix(); }, @@ -281,7 +283,25 @@ } }, - lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; + deprecations = {}, + + lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'], + + updateInProgress = false; + + // Pick the first defined of two or three arguments. dfl comes from + // default. + function dfl(a, b, c) { + switch (arguments.length) { + case 2: return a != null ? a : b; + case 3: return a != null ? a : b != null ? b : c; + default: throw new Error('Implement me'); + } + } + + function hasOwnProp(a, b) { + return hasOwnProperty.call(a, b); + } function defaultParsingFlags() { // We need to deep clone this object, and es5 standard is not very @@ -300,23 +320,31 @@ }; } + function printMsg(msg) { + if (moment.suppressDeprecationWarnings === false && + typeof console !== 'undefined' && console.warn) { + console.warn('Deprecation warning: ' + msg); + } + } + function deprecate(msg, fn) { var firstTime = true; - function printMsg() { - if (moment.suppressDeprecationWarnings === false && - typeof console !== 'undefined' && console.warn) { - console.warn("Deprecation warning: " + msg); - } - } return extend(function () { if (firstTime) { - printMsg(); + printMsg(msg); firstTime = false; } return fn.apply(this, arguments); }, fn); } + function deprecateSimple(name, msg) { + if (!deprecations[name]) { + printMsg(msg); + deprecations[name] = true; + } + } + function padToken(func, count) { return function (a) { return leftZeroFill(func.call(this, a), count); @@ -324,10 +352,30 @@ } function ordinalizeToken(func, period) { return function (a) { - return this.lang().ordinal(func.call(this, a), period); + return this.localeData().ordinal(func.call(this, a), period); }; } + function monthDiff(a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + + return -(wholeMonthDiff + adjust); + } + while (ordinalizeTokens.length) { i = ordinalizeTokens.pop(); formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); @@ -339,18 +387,52 @@ formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); + function meridiemFixWrap(locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // thie is not supposed to happen + return hour; + } + } + /************************************ Constructors ************************************/ - function Language() { - + function Locale() { } // Moment prototype object - function Moment(config) { - checkOverflow(config); - extend(this, config); + function Moment(config, skipOverflow) { + if (skipOverflow !== false) { + checkOverflow(config); + } + copyConfig(this, config); + this._d = new Date(+config._d); + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + moment.updateOffset(this); + updateInProgress = false; + } } // Duration Constructor @@ -384,6 +466,8 @@ this._data = {}; + this._locale = moment.localeData(); + this._bubble(); } @@ -394,31 +478,67 @@ function extend(a, b) { for (var i in b) { - if (b.hasOwnProperty(i)) { + if (hasOwnProp(b, i)) { a[i] = b[i]; } } - if (b.hasOwnProperty("toString")) { + if (hasOwnProp(b, 'toString')) { a.toString = b.toString; } - if (b.hasOwnProperty("valueOf")) { + if (hasOwnProp(b, 'valueOf')) { a.valueOf = b.valueOf; } return a; } - function cloneMoment(m) { - var result = {}, i; - for (i in m) { - if (m.hasOwnProperty(i) && momentProperties.hasOwnProperty(i)) { - result[i] = m[i]; + function copyConfig(to, from) { + var i, prop, val; + + if (typeof from._isAMomentObject !== 'undefined') { + to._isAMomentObject = from._isAMomentObject; + } + if (typeof from._i !== 'undefined') { + to._i = from._i; + } + if (typeof from._f !== 'undefined') { + to._f = from._f; + } + if (typeof from._l !== 'undefined') { + to._l = from._l; + } + if (typeof from._strict !== 'undefined') { + to._strict = from._strict; + } + if (typeof from._tzm !== 'undefined') { + to._tzm = from._tzm; + } + if (typeof from._isUTC !== 'undefined') { + to._isUTC = from._isUTC; + } + if (typeof from._offset !== 'undefined') { + to._offset = from._offset; + } + if (typeof from._pf !== 'undefined') { + to._pf = from._pf; + } + if (typeof from._locale !== 'undefined') { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i in momentProperties) { + prop = momentProperties[i]; + val = from[prop]; + if (typeof val !== 'undefined') { + to[prop] = val; + } } } - return result; + return to; } function absRound(number) { @@ -441,7 +561,51 @@ return (sign ? (forceSign ? '+' : '') : '-') + output; } - // helper function for _.addTime and _.subtractTime + function positiveMomentsDifference(base, other) { + var res = {milliseconds: 0, months: 0}; + + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); + + return res; + } + + function momentsDifference(base, other) { + var res; + other = makeAs(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; + } + + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).'); + tmp = val; val = period; period = tmp; + } + + val = typeof val === 'string' ? +val : val; + dur = moment.duration(val, period); + addOrSubtractDurationFromMoment(this, dur, direction); + return this; + }; + } + function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) { var milliseconds = duration._milliseconds, days = duration._days, @@ -468,8 +632,8 @@ } function isDate(input) { - return Object.prototype.toString.call(input) === '[object Date]' || - input instanceof Date; + return Object.prototype.toString.call(input) === '[object Date]' || + input instanceof Date; } // compare two arrays, return the number of differences @@ -501,7 +665,7 @@ prop; for (prop in inputObject) { - if (inputObject.hasOwnProperty(prop)) { + if (hasOwnProp(inputObject, prop)) { normalizedProp = normalizeUnits(prop); if (normalizedProp) { normalizedInput[normalizedProp] = inputObject[prop]; @@ -529,7 +693,7 @@ moment[field] = function (format, index) { var i, getter, - method = moment.fn._lang[field], + method = moment._locale[field], results = []; if (typeof format === 'number') { @@ -539,7 +703,7 @@ getter = function (i) { var m = moment().utc().set(setter, i); - return method.call(moment.fn._lang, m, format || ''); + return method.call(moment._locale, m, format || ''); }; if (index != null) { @@ -591,7 +755,10 @@ overflow = m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : - m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR : + m._a[HOUR] < 0 || m._a[HOUR] > 24 || + (m._a[HOUR] === 24 && (m._a[MINUTE] !== 0 || + m._a[SECOND] !== 0 || + m._a[MILLISECOND] !== 0)) ? HOUR : m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : @@ -618,28 +785,80 @@ if (m._strict) { m._isValid = m._isValid && m._pf.charsLeftOver === 0 && - m._pf.unusedTokens.length === 0; + m._pf.unusedTokens.length === 0 && + m._pf.bigHour === undefined; } } return m._isValid; } - function normalizeLanguage(key) { + function normalizeLocale(key) { return key ? key.toLowerCase().replace('_', '-') : key; } - // Return a moment from input, that is local/utc/zone equivalent to model. + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, j, next, locale, split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return null; + } + + function loadLocale(name) { + var oldLocale = null; + if (!locales[name] && hasModule) { + try { + oldLocale = moment.locale(); + require('./locale/' + name); + // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales + moment.locale(oldLocale); + } catch (e) { } + } + return locales[name]; + } + + // Return a moment from input, that is local/utc/utcOffset equivalent to + // model. function makeAs(input, model) { - return model._isUTC ? moment(input).zone(model._offset || 0) : - moment(input).local(); + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (moment.isMoment(input) || isDate(input) ? + +input : +moment(input)) - (+res); + // Use low-level api, because this fn is low-level api. + res._d.setTime(+res._d + diff); + moment.updateOffset(res, false); + return res; + } else { + return moment(input).local(); + } } /************************************ - Languages + Locale ************************************/ - extend(Language.prototype, { + extend(Locale.prototype, { set : function (config) { var prop, i; @@ -651,50 +870,63 @@ this['_' + i] = prop; } } + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _ordinalParseLenient. + this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + /\d{1,2}/.source); }, - _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), + _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), months : function (m) { return this._months[m.month()]; }, - _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), + _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), monthsShort : function (m) { return this._monthsShort[m.month()]; }, - monthsParse : function (monthName) { + monthsParse : function (monthName, format, strict) { var i, mom, regex; if (!this._monthsParse) { this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; } for (i = 0; i < 12; i++) { // make the regex if we don't have it already - if (!this._monthsParse[i]) { - mom = moment.utc([2000, i]); + mom = moment.utc([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); + } + if (!strict && !this._monthsParse[i]) { regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); } // test the regex - if (this._monthsParse[i].test(monthName)) { + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { return i; } } }, - _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), + _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), weekdays : function (m) { return this._weekdays[m.day()]; }, - _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), weekdaysShort : function (m) { return this._weekdaysShort[m.day()]; }, - _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), + _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), weekdaysMin : function (m) { return this._weekdaysMin[m.day()]; }, @@ -721,11 +953,12 @@ }, _longDateFormat : { - LT : "h:mm A", - L : "MM/DD/YYYY", - LL : "MMMM D YYYY", - LLL : "MMMM D YYYY LT", - LLLL : "dddd, MMMM D YYYY LT" + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY LT', + LLLL : 'dddd, MMMM D, YYYY LT' }, longDateFormat : function (key) { var output = this._longDateFormat[key]; @@ -753,6 +986,7 @@ } }, + _calendar : { sameDay : '[Today at] LT', nextDay : '[Tomorrow at] LT', @@ -761,41 +995,44 @@ lastWeek : '[Last] dddd [at] LT', sameElse : 'L' }, - calendar : function (key, mom) { + calendar : function (key, mom, now) { var output = this._calendar[key]; - return typeof output === 'function' ? output.apply(mom) : output; + return typeof output === 'function' ? output.apply(mom, [now]) : output; }, _relativeTime : { - future : "in %s", - past : "%s ago", - s : "a few seconds", - m : "a minute", - mm : "%d minutes", - h : "an hour", - hh : "%d hours", - d : "a day", - dd : "%d days", - M : "a month", - MM : "%d months", - y : "a year", - yy : "%d years" + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' }, + relativeTime : function (number, withoutSuffix, string, isFuture) { var output = this._relativeTime[string]; return (typeof output === 'function') ? output(number, withoutSuffix, string, isFuture) : output.replace(/%d/i, number); }, + pastFuture : function (diff, output) { var format = this._relativeTime[diff > 0 ? 'future' : 'past']; return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); }, ordinal : function (number) { - return this._ordinal.replace("%d", number); + return this._ordinal.replace('%d', number); }, - _ordinal : "%d", + _ordinal : '%d', + _ordinalParse : /\d{1,2}/, preparse : function (string) { return string; @@ -814,84 +1051,20 @@ doy : 6 // The week that contains Jan 1st is the first week of the year. }, + firstDayOfWeek : function () { + return this._week.dow; + }, + + firstDayOfYear : function () { + return this._week.doy; + }, + _invalidDate: 'Invalid date', invalidDate: function () { return this._invalidDate; } }); - // Loads a language definition into the `languages` cache. The function - // takes a key and optionally values. If not in the browser and no values - // are provided, it will load the language file module. As a convenience, - // this function also returns the language values. - function loadLang(key, values) { - values.abbr = key; - if (!languages[key]) { - languages[key] = new Language(); - } - languages[key].set(values); - return languages[key]; - } - - // Remove a language from the `languages` cache. Mostly useful in tests. - function unloadLang(key) { - delete languages[key]; - } - - // Determines which language definition to use and returns it. - // - // With no parameters, it will return the global language. If you - // pass in a language key, such as 'en', it will return the - // definition for 'en', so long as 'en' has already been loaded using - // moment.lang. - function getLangDefinition(key) { - var i = 0, j, lang, next, split, - get = function (k) { - if (!languages[k] && hasModule) { - try { - require('./lang/' + k); - } catch (e) { } - } - return languages[k]; - }; - - if (!key) { - return moment.fn._lang; - } - - if (!isArray(key)) { - //short-circuit everything else - lang = get(key); - if (lang) { - return lang; - } - key = [key]; - } - - //pick the language from the array - //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - while (i < key.length) { - split = normalizeLanguage(key[i]).split('-'); - j = split.length; - next = normalizeLanguage(key[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - lang = get(split.slice(0, j).join('-')); - if (lang) { - return lang; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; - } - return moment.fn._lang; - } - /************************************ Formatting ************************************/ @@ -899,9 +1072,9 @@ function removeFormattingTokens(input) { if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ""); + return input.replace(/^\[|\]$/g, ''); } - return input.replace(/\\/g, ""); + return input.replace(/\\/g, ''); } function makeFormatFunction(format) { @@ -916,7 +1089,7 @@ } return function (mom) { - var output = ""; + var output = ''; for (i = 0; i < length; i++) { output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; } @@ -926,12 +1099,11 @@ // format date using native date object function formatMoment(m, format) { - if (!m.isValid()) { - return m.lang().invalidDate(); + return m.localeData().invalidDate(); } - format = expandFormat(format, m.lang()); + format = expandFormat(format, m.localeData()); if (!formatFunctions[format]) { formatFunctions[format] = makeFormatFunction(format); @@ -940,11 +1112,11 @@ return formatFunctions[format](m); } - function expandFormat(format, lang) { + function expandFormat(format, locale) { var i = 5; function replaceLongDateFormatTokens(input) { - return lang.longDateFormat(input) || input; + return locale.longDateFormat(input) || input; } localFormattingTokens.lastIndex = 0; @@ -985,13 +1157,19 @@ case 'ggggg': return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; case 'S': - if (strict) { return parseTokenOneDigit; } + if (strict) { + return parseTokenOneDigit; + } /* falls through */ case 'SS': - if (strict) { return parseTokenTwoDigits; } + if (strict) { + return parseTokenTwoDigits; + } /* falls through */ case 'SSS': - if (strict) { return parseTokenThreeDigits; } + if (strict) { + return parseTokenThreeDigits; + } /* falls through */ case 'DDD': return parseTokenOneToThreeDigits; @@ -1003,7 +1181,9 @@ return parseTokenWord; case 'a': case 'A': - return getLangDefinition(config._l)._meridiemParse; + return config._locale._meridiemParse; + case 'x': + return parseTokenOffsetMs; case 'X': return parseTokenTimestampMs; case 'Z': @@ -1038,21 +1218,21 @@ case 'E': return parseTokenOneOrTwoDigits; case 'Do': - return parseTokenOrdinal; + return strict ? config._locale._ordinalParse : config._locale._ordinalParseLenient; default : - a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i")); + a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i')); return a; } } - function timezoneMinutesFromString(string) { - string = string || ""; + function utcOffsetFromString(string) { + string = string || ''; var possibleTzMatches = (string.match(parseTokenTimezone) || []), tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], minutes = +(parts[1] * 60) + toInt(parts[2]); - return parts[0] === '+' ? -minutes : minutes; + return parts[0] === '+' ? minutes : -minutes; } // function to convert string input to date @@ -1075,7 +1255,7 @@ break; case 'MMM' : // fall through to MMMM case 'MMMM' : - a = getLangDefinition(config._l).monthsParse(input); + a = config._locale.monthsParse(input, token, config._strict); // if we didn't find a month name, mark the date as invalid. if (a != null) { datePartArray[MONTH] = a; @@ -1092,7 +1272,8 @@ break; case 'Do' : if (input != null) { - datePartArray[DATE] = toInt(parseInt(input, 10)); + datePartArray[DATE] = toInt(parseInt( + input.match(/\d{1,2}/)[0], 10)); } break; // DAY OF YEAR @@ -1115,13 +1296,16 @@ // AM / PM case 'a' : // fall through to A case 'A' : - config._isPm = getLangDefinition(config._l).isPM(input); + config._meridiem = input; + // config._isPm = config._locale.isPM(input); break; - // 24 HOUR - case 'H' : // fall through to hh - case 'HH' : // fall through to hh + // HOUR case 'h' : // fall through to hh case 'hh' : + config._pf.bigHour = true; + /* falls through */ + case 'H' : // fall through to HH + case 'HH' : datePartArray[HOUR] = toInt(input); break; // MINUTE @@ -1141,6 +1325,10 @@ case 'SSSS' : datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); break; + // UNIX OFFSET (MILLISECONDS) + case 'x': + config._d = new Date(toInt(input)); + break; // UNIX TIMESTAMP WITH MS case 'X': config._d = new Date(parseFloat(input) * 1000); @@ -1149,32 +1337,87 @@ case 'Z' : // fall through to ZZ case 'ZZ' : config._useUTC = true; - config._tzm = timezoneMinutesFromString(input); + config._tzm = utcOffsetFromString(input); + break; + // WEEKDAY - human + case 'dd': + case 'ddd': + case 'dddd': + a = config._locale.weekdaysParse(input); + // if we didn't get a weekday name, mark the date as invalid + if (a != null) { + config._w = config._w || {}; + config._w['d'] = a; + } else { + config._pf.invalidWeekday = input; + } break; + // WEEK, WEEK DAY - numeric case 'w': case 'ww': case 'W': case 'WW': case 'd': - case 'dd': - case 'ddd': - case 'dddd': case 'e': case 'E': token = token.substr(0, 1); /* falls through */ - case 'gg': case 'gggg': - case 'GG': case 'GGGG': case 'GGGGG': token = token.substr(0, 2); if (input) { config._w = config._w || {}; - config._w[token] = input; + config._w[token] = toInt(input); } break; + case 'gg': + case 'GG': + config._w = config._w || {}; + config._w[token] = moment.parseTwoDigitYear(input); + } + } + + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year); + week = dfl(w.W, 1); + weekday = dfl(w.E, 1); + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year); + week = dfl(w.w, 1); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < dow) { + ++week; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + } else { + // default to begining of week + weekday = dow; + } } + temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); + + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; } // convert an array to a date. @@ -1182,8 +1425,7 @@ // note: all values past the year are optional and will default to the lowest possible value. // [year, month, day , hour, minute, second, millisecond] function dateFromConfig(config) { - var i, date, input = [], currentDate, - yearToUse, fixYear, w, temp, lang, weekday, week; + var i, date, input = [], currentDate, yearToUse; if (config._d) { return; @@ -1193,39 +1435,12 @@ //compute day of the year from weeks and weekdays if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - fixYear = function (val) { - var intVal = parseInt(val, 10); - return val ? - (val.length < 3 ? (intVal > 68 ? 1900 + intVal : 2000 + intVal) : intVal) : - (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]); - }; - - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1); - } - else { - lang = getLangDefinition(config._l); - weekday = w.d != null ? parseWeekday(w.d, lang) : - (w.e != null ? parseInt(w.e, 10) + lang._week.dow : 0); - - week = parseInt(w.w, 10) || 1; - - //if we're parsing 'd', then the low day numbers may be next week - if (w.d != null && weekday < lang._week.dow) { - week++; - } - - temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow); - } - - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; + dayOfYearFromWeekInfo(config); } //if the day of the year is set, figure out what it is if (config._dayOfYear) { - yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR]; + yearToUse = dfl(config._a[YEAR], currentDate[YEAR]); if (config._dayOfYear > daysInYear(yearToUse)) { config._pf._overflowDayOfYear = true; @@ -1250,11 +1465,25 @@ config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; } - // add the offsets to the time to be parsed so that we can have a clean array for checking isValid - input[HOUR] += toInt((config._tzm || 0) / 60); - input[MINUTE] += toInt((config._tzm || 0) % 60); + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } + + if (config._nextDay) { + config._a[HOUR] = 24; + } } function dateFromObject(config) { @@ -1268,7 +1497,7 @@ config._a = [ normalizedInput.year, normalizedInput.month, - normalizedInput.day, + normalizedInput.day || normalizedInput.date, normalizedInput.hour, normalizedInput.minute, normalizedInput.second, @@ -1293,18 +1522,21 @@ // date from string and format string function makeDateFromStringAndFormat(config) { + if (config._f === moment.ISO_8601) { + parseISO(config); + return; + } config._a = []; config._pf.empty = true; // This array is used to make a Date, either with `new Date` or `Date.UTC` - var lang = getLangDefinition(config._l), - string = '' + config._i, + var string = '' + config._i, i, parsedInput, tokens, token, skipped, stringLength = string.length, totalParsedInputLength = 0; - tokens = expandFormat(config._f, lang).match(formattingTokens) || []; + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; for (i = 0; i < tokens.length; i++) { token = tokens[i]; @@ -1338,15 +1570,13 @@ config._pf.unusedInput.push(string); } - // handle am pm - if (config._isPm && config._a[HOUR] < 12) { - config._a[HOUR] += 12; - } - // if is 12 am, change hours to 0 - if (config._isPm === false && config._a[HOUR] === 12) { - config._a[HOUR] = 0; + // clear _12h flag if hour is <= 12 + if (config._pf.bigHour === true && config._a[HOUR] <= 12) { + config._pf.bigHour = undefined; } - + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], + config._meridiem); dateFromConfig(config); checkOverflow(config); } @@ -1379,7 +1609,10 @@ for (i = 0; i < config._f.length; i++) { currentScore = 0; - tempConfig = extend({}, config); + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } tempConfig._pf = defaultParsingFlags(); tempConfig._f = config._f[i]; makeDateFromStringAndFormat(tempConfig); @@ -1406,7 +1639,7 @@ } // date from iso format - function makeDateFromString(config) { + function parseISO(config) { var i, l, string = config._i, match = isoRegex.exec(string); @@ -1415,8 +1648,8 @@ config._pf.iso = true; for (i = 0, l = isoDates.length; i < l; i++) { if (isoDates[i][1].exec(string)) { - // match[5] should be "T" or undefined - config._f = isoDates[i][0] + (match[6] || " "); + // match[5] should be 'T' or undefined + config._f = isoDates[i][0] + (match[6] || ' '); break; } } @@ -1427,30 +1660,46 @@ } } if (string.match(parseTokenTimezone)) { - config._f += "Z"; + config._f += 'Z'; } makeDateFromStringAndFormat(config); + } else { + config._isValid = false; } - else { + } + + // date from iso format or fallback + function makeDateFromString(config) { + parseISO(config); + if (config._isValid === false) { + delete config._isValid; moment.createFromInputFallback(config); } } - function makeDateFromInput(config) { - var input = config._i, - matched = aspNetJsonRegex.exec(input); + function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; + } + function makeDateFromInput(config) { + var input = config._i, matched; if (input === undefined) { config._d = new Date(); - } else if (matched) { + } else if (isDate(input)) { + config._d = new Date(+input); + } else if ((matched = aspNetJsonRegex.exec(input)) !== null) { config._d = new Date(+matched[1]); } else if (typeof input === 'string') { makeDateFromString(config); } else if (isArray(input)) { - config._a = input.slice(0); + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); dateFromConfig(config); - } else if (isDate(input)) { - config._d = new Date(+input); } else if (typeof(input) === 'object') { dateFromObject(config); } else if (typeof(input) === 'number') { @@ -1481,13 +1730,13 @@ return date; } - function parseWeekday(input, language) { + function parseWeekday(input, locale) { if (typeof input === 'string') { if (!isNaN(input)) { input = parseInt(input, 10); } else { - input = language.weekdaysParse(input); + input = locale.weekdaysParse(input); if (typeof input !== 'number') { return null; } @@ -1502,29 +1751,33 @@ // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { - return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); } - function relativeTime(milliseconds, withoutSuffix, lang) { - var seconds = round(Math.abs(milliseconds) / 1000), - minutes = round(seconds / 60), - hours = round(minutes / 60), - days = round(hours / 24), - years = round(days / 365), - args = seconds < 45 && ['s', seconds] || + function relativeTime(posNegDuration, withoutSuffix, locale) { + var duration = moment.duration(posNegDuration).abs(), + seconds = round(duration.as('s')), + minutes = round(duration.as('m')), + hours = round(duration.as('h')), + days = round(duration.as('d')), + months = round(duration.as('M')), + years = round(duration.as('y')), + + args = seconds < relativeTimeThresholds.s && ['s', seconds] || minutes === 1 && ['m'] || - minutes < 45 && ['mm', minutes] || + minutes < relativeTimeThresholds.m && ['mm', minutes] || hours === 1 && ['h'] || - hours < 22 && ['hh', hours] || + hours < relativeTimeThresholds.h && ['hh', hours] || days === 1 && ['d'] || - days <= 25 && ['dd', days] || - days <= 45 && ['M'] || - days < 345 && ['MM', round(days / 30)] || + days < relativeTimeThresholds.d && ['dd', days] || + months === 1 && ['M'] || + months < relativeTimeThresholds.M && ['MM', months] || years === 1 && ['y'] || ['yy', years]; + args[2] = withoutSuffix; - args[3] = milliseconds > 0; - args[4] = lang; + args[3] = +posNegDuration > 0; + args[4] = locale; return substituteTimeAgo.apply({}, args); } @@ -1555,7 +1808,7 @@ daysToDayOfWeek += 7; } - adjustedMoment = moment(mom).add('d', daysToDayOfWeek); + adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd'); return { week: Math.ceil(adjustedMoment.dayOfYear() / 7), year: adjustedMoment.year() @@ -1566,6 +1819,7 @@ function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; + d = d === 0 ? 7 : d; weekday = weekday != null ? weekday : firstDayOfWeek; daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; @@ -1582,20 +1836,21 @@ function makeMoment(config) { var input = config._i, - format = config._f; + format = config._f, + res; + + config._locale = config._locale || moment.localeData(config._l); if (input === null || (format === undefined && input === '')) { return moment.invalid({nullInput: true}); } if (typeof input === 'string') { - config._i = input = getLangDefinition().preparse(input); + config._i = input = config._locale.preparse(input); } if (moment.isMoment(input)) { - config = cloneMoment(input); - - config._d = new Date(+input._d); + return new Moment(input, true); } else if (format) { if (isArray(format)) { makeDateFromStringAndArray(config); @@ -1606,15 +1861,22 @@ makeDateFromInput(config); } - return new Moment(config); + res = new Moment(config); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; } - moment = function (input, format, lang, strict) { + moment = function (input, format, locale, strict) { var c; - if (typeof(lang) === "boolean") { - strict = lang; - lang = undefined; + if (typeof(locale) === 'boolean') { + strict = locale; + locale = undefined; } // object construction must be done this way. // https://github.com/moment/moment/issues/1423 @@ -1622,7 +1884,7 @@ c._isAMomentObject = true; c._i = input; c._f = format; - c._l = lang; + c._l = locale; c._strict = strict; c._isUTC = false; c._pf = defaultParsingFlags(); @@ -1633,21 +1895,56 @@ moment.suppressDeprecationWarnings = false; moment.createFromInputFallback = deprecate( - "moment construction falls back to js Date. This is " + - "discouraged and will be removed in upcoming major " + - "release. Please refer to " + - "https://github.com/moment/moment/issues/1407 for more info.", - function (config) { - config._d = new Date(config._i); - }); + 'moment construction falls back to js Date. This is ' + + 'discouraged and will be removed in upcoming major ' + + 'release. Please refer to ' + + 'https://github.com/moment/moment/issues/1407 for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } + ); + + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return moment(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + + moment.min = function () { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); + }; + + moment.max = function () { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); + }; // creating with utc - moment.utc = function (input, format, lang, strict) { + moment.utc = function (input, format, locale, strict) { var c; - if (typeof(lang) === "boolean") { - strict = lang; - lang = undefined; + if (typeof(locale) === 'boolean') { + strict = locale; + locale = undefined; } // object construction must be done this way. // https://github.com/moment/moment/issues/1423 @@ -1655,7 +1952,7 @@ c._isAMomentObject = true; c._useUTC = true; c._isUTC = true; - c._l = lang; + c._l = locale; c._i = input; c._f = format; c._strict = strict; @@ -1676,7 +1973,8 @@ match = null, sign, ret, - parseIso; + parseIso, + diffRes; if (moment.isDuration(input)) { duration = { @@ -1692,7 +1990,7 @@ duration.milliseconds = input; } } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { - sign = (match[1] === "-") ? -1 : 1; + sign = (match[1] === '-') ? -1 : 1; duration = { y: 0, d: toInt(match[DATE]) * sign, @@ -1702,7 +2000,7 @@ ms: toInt(match[MILLISECOND]) * sign }; } else if (!!(match = isoDurationRegex.exec(input))) { - sign = (match[1] === "-") ? -1 : 1; + sign = (match[1] === '-') ? -1 : 1; parseIso = function (inp) { // We'd normally use ~~inp for this, but unfortunately it also // converts floats to ints. @@ -1720,12 +2018,21 @@ s: parseIso(match[7]), w: parseIso(match[8]) }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && + ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(moment(duration.from), moment(duration.to)); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; } ret = new Duration(duration); - if (moment.isDuration(input) && input.hasOwnProperty('_lang')) { - ret._lang = input._lang; + if (moment.isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; } return ret; @@ -1737,6 +2044,9 @@ // default format moment.defaultFormat = isoFormat; + // constant that refers to the ISO standard + moment.ISO_8601 = function () {}; + // Plugins that add properties should also add the key here (null value), // so we can properly clone ourselves. moment.momentProperties = momentProperties; @@ -1745,38 +2055,100 @@ // It is intended to keep the offset in sync with the timezone. moment.updateOffset = function () {}; - // This function will load languages and then set the global language. If + // This function allows you to set a threshold for relative time strings + moment.relativeTimeThreshold = function (threshold, limit) { + if (relativeTimeThresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return relativeTimeThresholds[threshold]; + } + relativeTimeThresholds[threshold] = limit; + return true; + }; + + moment.lang = deprecate( + 'moment.lang is deprecated. Use moment.locale instead.', + function (key, value) { + return moment.locale(key, value); + } + ); + + // This function will load locale and then set the global locale. If // no arguments are passed in, it will simply return the current global - // language key. - moment.lang = function (key, values) { - var r; - if (!key) { - return moment.fn._lang._abbr; - } - if (values) { - loadLang(normalizeLanguage(key), values); - } else if (values === null) { - unloadLang(key); - key = 'en'; - } else if (!languages[key]) { - getLangDefinition(key); - } - r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); - return r._abbr; + // locale key. + moment.locale = function (key, values) { + var data; + if (key) { + if (typeof(values) !== 'undefined') { + data = moment.defineLocale(key, values); + } + else { + data = moment.localeData(key); + } + + if (data) { + moment.duration._locale = moment._locale = data; + } + } + + return moment._locale._abbr; + }; + + moment.defineLocale = function (name, values) { + if (values !== null) { + values.abbr = name; + if (!locales[name]) { + locales[name] = new Locale(); + } + locales[name].set(values); + + // backwards compat for now: also set the locale + moment.locale(name); + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } }; - // returns language data - moment.langData = function (key) { - if (key && key._lang && key._lang._abbr) { - key = key._lang._abbr; + moment.langData = deprecate( + 'moment.langData is deprecated. Use moment.localeData instead.', + function (key) { + return moment.localeData(key); + } + ); + + // returns locale data + moment.localeData = function (key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return moment._locale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; } - return getLangDefinition(key); + + return chooseLocale(key); }; // compare moment object moment.isMoment = function (obj) { return obj instanceof Moment || - (obj != null && obj.hasOwnProperty('_isAMomentObject')); + (obj != null && hasOwnProp(obj, '_isAMomentObject')); }; // for typechecking Duration objects @@ -1812,6 +2184,8 @@ return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); }; + moment.isDate = isDate; + /************************************ Moment Prototype ************************************/ @@ -1824,7 +2198,7 @@ }, valueOf : function () { - return +this._d + ((this._offset || 0) * 60000); + return +this._d - ((this._offset || 0) * 60000); }, unix : function () { @@ -1832,7 +2206,7 @@ }, toString : function () { - return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); }, toDate : function () { @@ -1842,7 +2216,12 @@ toISOString : function () { var m = moment(this).utc(); if (0 < m.year() && m.year() <= 9999) { - return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + if ('function' === typeof Date.prototype.toISOString) { + // native implementation is ~50x faster, use it when we can + return this.toDate().toISOString(); + } else { + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } } else { return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); } @@ -1866,7 +2245,6 @@ }, isDSTShifted : function () { - if (this._a) { return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; } @@ -1882,69 +2260,47 @@ return this._pf.overflow; }, - utc : function () { - return this.zone(0); + utc : function (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); }, - local : function () { - this.zone(0); - this._isUTC = false; + local : function (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(this._dateUtcOffset(), 'm'); + } + } return this; }, format : function (inputString) { var output = formatMoment(this, inputString || moment.defaultFormat); - return this.lang().postformat(output); + return this.localeData().postformat(output); }, - add : function (input, val) { - var dur; - // switch args to support add('s', 1) and add(1, 's') - if (typeof input === 'string') { - dur = moment.duration(+val, input); - } else { - dur = moment.duration(input, val); - } - addOrSubtractDurationFromMoment(this, dur, 1); - return this; - }, + add : createAdder(1, 'add'), - subtract : function (input, val) { - var dur; - // switch args to support subtract('s', 1) and subtract(1, 's') - if (typeof input === 'string') { - dur = moment.duration(+val, input); - } else { - dur = moment.duration(input, val); - } - addOrSubtractDurationFromMoment(this, dur, -1); - return this; - }, + subtract : createAdder(-1, 'subtract'), diff : function (input, units, asFloat) { var that = makeAs(input, this), - zoneDiff = (this.zone() - that.zone()) * 6e4, - diff, output; + zoneDiff = (that.utcOffset() - this.utcOffset()) * 6e4, + anchor, diff, output, daysAdjust; units = normalizeUnits(units); - if (units === 'year' || units === 'month') { - // average number of days in the months in the given dates - diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 - // difference in months - output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); - // adjust by taking difference in days, average number of days - // and dst in the given months. - output += ((this - moment(this).startOf('month')) - - (that - moment(that).startOf('month'))) / diff; - // same as above but with zones, to negate all dst - output -= ((this.zone() - moment(this).startOf('month').zone()) - - (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff; - if (units === 'year') { + if (units === 'year' || units === 'month' || units === 'quarter') { + output = monthDiff(this, that); + if (units === 'quarter') { + output = output / 3; + } else if (units === 'year') { output = output / 12; } } else { - diff = (this - that); + diff = this - that; output = units === 'second' ? diff / 1e3 : // 1000 units === 'minute' ? diff / 6e4 : // 1000 * 60 units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 @@ -1956,17 +2312,19 @@ }, from : function (time, withoutSuffix) { - return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); + return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); }, fromNow : function (withoutSuffix) { return this.from(moment(), withoutSuffix); }, - calendar : function () { + calendar : function (time) { // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're zone'd or not. - var sod = makeAs(moment(), this).startOf('day'), + // Getting start-of-today depends on whether we're locat/utc/offset + // or not. + var now = time || moment(), + sod = makeAs(now, this).startOf('day'), diff = this.diff(sod, 'days', true), format = diff < -6 ? 'sameElse' : diff < -1 ? 'lastWeek' : @@ -1974,7 +2332,7 @@ diff < 1 ? 'sameDay' : diff < 2 ? 'nextDay' : diff < 7 ? 'nextWeek' : 'sameElse'; - return this.format(this.lang().calendar(format, this)); + return this.format(this.localeData().calendar(format, this, moment(now))); }, isLeapYear : function () { @@ -1982,15 +2340,15 @@ }, isDST : function () { - return (this.zone() < this.clone().month(0).zone() || - this.zone() < this.clone().month(5).zone()); + return (this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset()); }, day : function (input) { var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); if (input != null) { - input = parseWeekday(input, this.lang()); - return this.add({ d : input - day }); + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); } else { return day; } @@ -1998,7 +2356,7 @@ month : makeAccessor('Month', true), - startOf: function (units) { + startOf : function (units) { units = normalizeUnits(units); // the following switch intentionally omits break keywords // to utilize falling through the cases. @@ -2043,84 +2401,156 @@ endOf: function (units) { units = normalizeUnits(units); - return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1); + if (units === undefined || units === 'millisecond') { + return this; + } + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); }, isAfter: function (input, units) { - units = typeof units !== 'undefined' ? units : 'millisecond'; - return +this.clone().startOf(units) > +moment(input).startOf(units); + var inputMs; + units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); + if (units === 'millisecond') { + input = moment.isMoment(input) ? input : moment(input); + return +this > +input; + } else { + inputMs = moment.isMoment(input) ? +input : +moment(input); + return inputMs < +this.clone().startOf(units); + } }, isBefore: function (input, units) { - units = typeof units !== 'undefined' ? units : 'millisecond'; - return +this.clone().startOf(units) < +moment(input).startOf(units); + var inputMs; + units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); + if (units === 'millisecond') { + input = moment.isMoment(input) ? input : moment(input); + return +this < +input; + } else { + inputMs = moment.isMoment(input) ? +input : +moment(input); + return +this.clone().endOf(units) < inputMs; + } }, - isSame: function (input, units) { - units = units || 'ms'; - return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); + isBetween: function (from, to, units) { + return this.isAfter(from, units) && this.isBefore(to, units); }, - min: function (other) { - other = moment.apply(null, arguments); - return other < this ? this : other; + isSame: function (input, units) { + var inputMs; + units = normalizeUnits(units || 'millisecond'); + if (units === 'millisecond') { + input = moment.isMoment(input) ? input : moment(input); + return +this === +input; + } else { + inputMs = +moment(input); + return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units)); + } }, - max: function (other) { - other = moment.apply(null, arguments); - return other > this ? this : other; - }, + min: deprecate( + 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', + function (other) { + other = moment.apply(null, arguments); + return other < this ? this : other; + } + ), - // keepTime = true means only change the timezone, without affecting - // the local hour. So 5:31:26 +0300 --[zone(2, true)]--> 5:31:26 +0200 - // It is possible that 5:31:26 doesn't exist int zone +0200, so we - // adjust the time as needed, to be valid. + max: deprecate( + 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', + function (other) { + other = moment.apply(null, arguments); + return other > this ? this : other; + } + ), + + zone : deprecate( + 'moment().zone is deprecated, use moment().utcOffset instead. ' + + 'https://github.com/moment/moment/issues/1779', + function (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } + } + ), + + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. // // Keeping the time actually adds/subtracts (one hour) // from the actual represented time. That is why we call updateOffset // a second time. In case it wants us to change the offset again // _changeInProgress == true case, then we have to adjust, because // there is no such time in the given timezone. - zone : function (input, keepTime) { - var offset = this._offset || 0; + utcOffset : function (input, keepLocalTime) { + var offset = this._offset || 0, + localAdjust; if (input != null) { - if (typeof input === "string") { - input = timezoneMinutesFromString(input); + if (typeof input === 'string') { + input = utcOffsetFromString(input); } if (Math.abs(input) < 16) { input = input * 60; } + if (!this._isUTC && keepLocalTime) { + localAdjust = this._dateUtcOffset(); + } this._offset = input; this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } if (offset !== input) { - if (!keepTime || this._changeInProgress) { + if (!keepLocalTime || this._changeInProgress) { addOrSubtractDurationFromMoment(this, - moment.duration(offset - input, 'm'), 1, false); + moment.duration(input - offset, 'm'), 1, false); } else if (!this._changeInProgress) { this._changeInProgress = true; moment.updateOffset(this, true); this._changeInProgress = null; } } + + return this; } else { - return this._isUTC ? offset : this._d.getTimezoneOffset(); + return this._isUTC ? offset : this._dateUtcOffset(); } - return this; + }, + + isLocal : function () { + return !this._isUTC; + }, + + isUtcOffset : function () { + return this._isUTC; + }, + + isUtc : function () { + return this._isUTC && this._offset === 0; }, zoneAbbr : function () { - return this._isUTC ? "UTC" : ""; + return this._isUTC ? 'UTC' : ''; }, zoneName : function () { - return this._isUTC ? "Coordinated Universal Time" : ""; + return this._isUTC ? 'Coordinated Universal Time' : ''; }, parseZone : function () { if (this._tzm) { - this.zone(this._tzm); + this.utcOffset(this._tzm); } else if (typeof this._i === 'string') { - this.zone(this._i); + this.utcOffset(utcOffsetFromString(this._i)); } return this; }, @@ -2130,10 +2560,10 @@ input = 0; } else { - input = moment(input).zone(); + input = moment(input).utcOffset(); } - return (this.zone() - input) % 60 === 0; + return (this.utcOffset() - input) % 60 === 0; }, daysInMonth : function () { @@ -2142,7 +2572,7 @@ dayOfYear : function (input) { var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); }, quarter : function (input) { @@ -2150,28 +2580,28 @@ }, weekYear : function (input) { - var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year; - return input == null ? year : this.add("y", (input - year)); + var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year; + return input == null ? year : this.add((input - year), 'y'); }, isoWeekYear : function (input) { var year = weekOfYear(this, 1, 4).year; - return input == null ? year : this.add("y", (input - year)); + return input == null ? year : this.add((input - year), 'y'); }, week : function (input) { - var week = this.lang().week(this); - return input == null ? week : this.add("d", (input - week) * 7); + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); }, isoWeek : function (input) { var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add("d", (input - week) * 7); + return input == null ? week : this.add((input - week) * 7, 'd'); }, weekday : function (input) { - var weekday = (this.day() + 7 - this.lang()._week.dow) % 7; - return input == null ? weekday : this.add("d", input - weekday); + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); }, isoWeekday : function (input) { @@ -2186,7 +2616,7 @@ }, weeksInYear : function () { - var weekInfo = this._lang._week; + var weekInfo = this.localeData()._week; return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); }, @@ -2196,24 +2626,59 @@ }, set : function (units, value) { - units = normalizeUnits(units); - if (typeof this[units] === 'function') { - this[units](value); + var unit; + if (typeof units === 'object') { + for (unit in units) { + this.set(unit, units[unit]); + } + } + else { + units = normalizeUnits(units); + if (typeof this[units] === 'function') { + this[units](value); + } } return this; }, - // If passed a language key, it will set the language for this - // instance. Otherwise, it will return the language configuration + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration // variables for this instance. - lang : function (key) { + locale : function (key) { + var newLocaleData; + if (key === undefined) { - return this._lang; + return this._locale._abbr; } else { - this._lang = getLangDefinition(key); + newLocaleData = moment.localeData(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } return this; } + }, + + lang : deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ), + + localeData : function () { + return this._locale; + }, + + _dateUtcOffset : function () { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(this._d.getTimezoneOffset() / 15) * 15; } + }); function rawMonthSetter(mom, value) { @@ -2221,7 +2686,7 @@ // TODO: Move this out of here! if (typeof value === 'string') { - value = mom.lang().monthsParse(value); + value = mom.localeData().monthsParse(value); // TODO: Another silent failure? if (typeof value !== 'number') { return mom; @@ -2268,9 +2733,9 @@ moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true); // moment.fn.month is defined separately moment.fn.date = makeAccessor('Date', true); - moment.fn.dates = deprecate("dates accessor is deprecated. Use date instead.", makeAccessor('Date', true)); + moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true)); moment.fn.year = makeAccessor('FullYear', true); - moment.fn.years = deprecate("years accessor is deprecated. Use year instead.", makeAccessor('FullYear', true)); + moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true)); // add plural methods moment.fn.days = moment.fn.day; @@ -2282,11 +2747,25 @@ // add aliased format methods moment.fn.toJSON = moment.fn.toISOString; + // alias isUtc for dev-friendliness + moment.fn.isUTC = moment.fn.isUtc; + /************************************ Duration Prototype ************************************/ + function daysToYears (days) { + // 400 years have 146097 days (taking into account leap year rules) + return days * 400 / 146097; + } + + function yearsToDays (years) { + // years * 365 + absRound(years / 4) - + // absRound(years / 100) + absRound(years / 400); + return years * 146097 / 400; + } + extend(moment.duration.fn = Duration.prototype, { _bubble : function () { @@ -2294,7 +2773,7 @@ days = this._days, months = this._months, data = this._data, - seconds, minutes, hours, years; + seconds, minutes, hours, years = 0; // The following code bubbles up values, see the tests for // examples of what that means. @@ -2310,15 +2789,40 @@ data.hours = hours % 24; days += absRound(hours / 24); - data.days = days % 30; + // Accurately convert days to years, assume start from year 0. + years = absRound(daysToYears(days)); + days -= absRound(yearsToDays(years)); + + // 30 days to a month + // TODO (iskren): Use anchor date (like 1st Jan) to compute this. months += absRound(days / 30); - data.months = months % 12; + days %= 30; + + // 12 months -> 1 year + years += absRound(months / 12); + months %= 12; - years = absRound(months / 12); + data.days = days; + data.months = months; data.years = years; }, + abs : function () { + this._milliseconds = Math.abs(this._milliseconds); + this._days = Math.abs(this._days); + this._months = Math.abs(this._months); + + this._data.milliseconds = Math.abs(this._data.milliseconds); + this._data.seconds = Math.abs(this._data.seconds); + this._data.minutes = Math.abs(this._data.minutes); + this._data.hours = Math.abs(this._data.hours); + this._data.months = Math.abs(this._data.months); + this._data.years = Math.abs(this._data.years); + + return this; + }, + weeks : function () { return absRound(this.days() / 7); }, @@ -2331,14 +2835,13 @@ }, humanize : function (withSuffix) { - var difference = +this, - output = relativeTime(difference, !withSuffix, this.lang()); + var output = relativeTime(this, !withSuffix, this.localeData()); if (withSuffix) { - output = this.lang().pastFuture(difference, output); + output = this.localeData().pastFuture(+this, output); } - return this.lang().postformat(output); + return this.localeData().postformat(output); }, add : function (input, val) { @@ -2372,13 +2875,41 @@ }, as : function (units) { + var days, months; units = normalizeUnits(units); - return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's'](); + + if (units === 'month' || units === 'year') { + days = this._days + this._milliseconds / 864e5; + months = this._months + daysToYears(days) * 12; + return units === 'month' ? months : months / 12; + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(yearsToDays(this._months / 12)); + switch (units) { + case 'week': return days / 7 + this._milliseconds / 6048e5; + case 'day': return days + this._milliseconds / 864e5; + case 'hour': return days * 24 + this._milliseconds / 36e5; + case 'minute': return days * 24 * 60 + this._milliseconds / 6e4; + case 'second': return days * 24 * 60 * 60 + this._milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + this._milliseconds; + default: throw new Error('Unknown unit ' + units); + } + } }, lang : moment.fn.lang, + locale : moment.fn.locale, - toIsoString : function () { + toIsoString : deprecate( + 'toIsoString() is deprecated. Please use toISOString() instead ' + + '(notice the capitals)', + function () { + return this.toISOString(); + } + ), + + toISOString : function () { // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js var years = Math.abs(this.years()), months = Math.abs(this.months()), @@ -2402,41 +2933,64 @@ (hours ? hours + 'H' : '') + (minutes ? minutes + 'M' : '') + (seconds ? seconds + 'S' : ''); + }, + + localeData : function () { + return this._locale; + }, + + toJSON : function () { + return this.toISOString(); } }); + moment.duration.fn.toString = moment.duration.fn.toISOString; + function makeDurationGetter(name) { moment.duration.fn[name] = function () { return this._data[name]; }; } - function makeDurationAsGetter(name, factor) { - moment.duration.fn['as' + name] = function () { - return +this / factor; - }; - } - for (i in unitMillisecondFactors) { - if (unitMillisecondFactors.hasOwnProperty(i)) { - makeDurationAsGetter(i, unitMillisecondFactors[i]); + if (hasOwnProp(unitMillisecondFactors, i)) { makeDurationGetter(i.toLowerCase()); } } - makeDurationAsGetter('Weeks', 6048e5); + moment.duration.fn.asMilliseconds = function () { + return this.as('ms'); + }; + moment.duration.fn.asSeconds = function () { + return this.as('s'); + }; + moment.duration.fn.asMinutes = function () { + return this.as('m'); + }; + moment.duration.fn.asHours = function () { + return this.as('h'); + }; + moment.duration.fn.asDays = function () { + return this.as('d'); + }; + moment.duration.fn.asWeeks = function () { + return this.as('weeks'); + }; moment.duration.fn.asMonths = function () { - return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12; + return this.as('M'); + }; + moment.duration.fn.asYears = function () { + return this.as('y'); }; - /************************************ - Default Lang + Default Locale ************************************/ - // Set default language, other languages will inherit from English. - moment.lang('en', { + // Set default locale, other locale will inherit from English. + moment.locale('en', { + ordinalParse: /\d{1,2}(th|st|nd|rd)/, ordinal : function (number) { var b = number % 10, output = (toInt(number % 100 / 10) === 1) ? 'th' : @@ -2447,7 +3001,7 @@ } }); - /* EMBED_LANGUAGES */ + /* EMBED_LOCALES */ /************************************ Exposing Moment @@ -2461,9 +3015,9 @@ oldGlobalMoment = globalScope.moment; if (shouldDeprecate) { globalScope.moment = deprecate( - "Accessing Moment through the global scope is " + - "deprecated, and will be removed in an upcoming " + - "release.", + 'Accessing Moment through the global scope is ' + + 'deprecated, and will be removed in an upcoming ' + + 'release.', moment); } else { globalScope.moment = moment; @@ -2473,8 +3027,8 @@ // CommonJS module is defined if (hasModule) { module.exports = moment; - } else if (typeof define === "function" && define.amd) { - define("moment", function (require, exports, module) { + } else if (typeof define === 'function' && define.amd) { + define(function (require, exports, module) { if (module.config && module.config() && module.config().noGlobal === true) { // release the global variable globalScope.moment = oldGlobalMoment; From 8843cc23a0bf01da6b8f14f69377ac87fd53d6dd Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 27 Feb 2015 06:01:23 -0800 Subject: [PATCH 459/791] Add Clock module --- src/js/clock/clock.js | 5 +++++ src/js/clock/index.js | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 src/js/clock/clock.js create mode 100644 src/js/clock/index.js diff --git a/src/js/clock/clock.js b/src/js/clock/clock.js new file mode 100644 index 00000000..19045379 --- /dev/null +++ b/src/js/clock/clock.js @@ -0,0 +1,5 @@ +var Clock = module.exports; + +Clock.weekday = function(weekday, hour, minute, seconds) { + return moment({ hour: hour, minute: minute, seconds: seconds }).day(weekday).unix(); +}; diff --git a/src/js/clock/index.js b/src/js/clock/index.js new file mode 100644 index 00000000..9b4e7453 --- /dev/null +++ b/src/js/clock/index.js @@ -0,0 +1,3 @@ +var Clock = require('./clock'); + +module.exports = Clock; From 30d6d17d93dcd643c26d3905ff5ccbe81e4bb1e8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 27 Feb 2015 06:01:54 -0800 Subject: [PATCH 460/791] Use Date.now instead of Date.getTime --- src/js/lib/ajax.js | 2 +- src/js/ui/simply-pebble.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/lib/ajax.js b/src/js/lib/ajax.js index 5d74f036..5aa5c7b0 100644 --- a/src/js/lib/ajax.js +++ b/src/js/lib/ajax.js @@ -69,7 +69,7 @@ var ajax = function(opt, success, failure) { if (opt.cache === false) { var appendSymbol = url.indexOf('?') === -1 ? '?' : '&'; - url += appendSymbol + '_=' + new Date().getTime(); + url += appendSymbol + '_=' + Date.now(); } var req = new XMLHttpRequest(); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index b8f345fd..fb1b5626 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1080,7 +1080,7 @@ SimplyPebble.onLaunchReason = function(packet) { if (isTimezone) { state.timeOffset = 0; } else { - var time = new Date().getTime() / 1000; + var time = Date.now() / 1000; var resolution = 60 * 30; state.timeOffset = Math.round((remoteTime - time) / resolution) * resolution; } From b3cf87f83a968cb147742568126791a726de53f3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 27 Feb 2015 11:14:37 -0800 Subject: [PATCH 461/791] Add Circle radius doc. Addresses #32 --- README.md | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ac8dcaa0..551726b9 100644 --- a/README.md +++ b/README.md @@ -752,12 +752,12 @@ There are four types of [Element] that can be instantiated at the moment: [Circl They all share some common properties: -| Name | Type | Default | Description | -| ------------ | :-------: | --------- | ------------- | -| `position` | Vector2 | | Position of this element in the window. | -| `size` | Vector2 | | Size of this element in this window. | -| `borderColor` | string | '' | Color of the border of this element ('clear', 'black',or 'white'). | -| `backgroundColor` | string | '' | Background color of this element ('clear', 'black' or 'white'). | +| Name | Type | Default | Description | +| ------------ | :-------: | --------- | ------------- | +| `position` | Vector2 | | Position of this element in the window. | +| `size` | Vector2 | | Size of this element in this window. Note that [Circle] uses `radius` instead. | +| `borderColor` | string | '' | Color of the border of this element ('clear', 'black',or 'white'). | +| `backgroundColor` | string | '' | Background color of this element ('clear', 'black' or 'white'). | All properties can be initialized by passing an object when creating the Element, and changed with accessors functions who have the name of the properties. Calling an accessor without a parameter will return the current value. @@ -857,6 +857,25 @@ Default properties value: * `backgroundColor`: 'white' * `borderColor`: 'clear' +[Circle] also has the additional property `radius` which it uses for size rather than `size`. [Circle] is also different in that it positions its origin at the position, rather than anchoring by its top left. These differences are to keep the graphics operation characteristics that it is built upon. + +````js +var wind = new UI.Window(); + +var circle = new UI.Circle({ + position: new Vector2(72, 84), + radius: 25, + backgroundColor: 'white', +}); + +wind.add(circle); +wind.show(circle); +```` + +#### Circle.radius(radius) + +Accessor to the `radius` property. See [Circle] + ### Rect An [Element] that displays a rectangle on the screen. From 9c57dc1810fb7f7e300ba21b5b5b9b8e37e860b0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Mar 2015 00:15:36 -0800 Subject: [PATCH 462/791] Add util color for aplite compatibility --- src/util/color.h | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/util/color.h diff --git a/src/util/color.h b/src/util/color.h new file mode 100644 index 00000000..bd98afd1 --- /dev/null +++ b/src/util/color.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +#define GColor8White (GColor8){.argb=GColorWhiteARGB8} +#define GColor8Black (GColor8){.argb=GColorBlackARGB8} +#define GColor8Clear (GColor8){.argb=GColorClearARGB8} + +#ifdef PBL_BW + +#define GColorWhiteARGB8 ((uint8_t)0b11111111) +#define GColorBlackARGB8 ((uint8_t)0b11000000) +#define GColorClearARGB8 ((uint8_t)0b00000000) + +typedef union GColor8 { + uint8_t argb; + struct { + uint8_t b:2; //!< Blue + uint8_t g:2; //!< Green + uint8_t r:2; //!< Red + uint8_t a:2; //!< Alpha. 3 = 100% opaque, 2 = 66% opaque, 1 = 33% opaque, 0 = transparent. + }; +} GColor8; + +static inline GColor GColor8Get(GColor8 color) { + switch (color.argb) { + case GColorWhiteARGB8: return GColorWhite; + case GColorBlackARGB8: return GColorBlack; + default: return GColorClear; + } +} + +static inline GColor8 GColorGet8(GColor color) { + switch (color) { + case GColorWhite: return GColor8White; + case GColorBlack: return GColor8Black; + default: return GColor8Clear; + } +} + +static inline bool GColorEq(GColor color, GColor other) { + return (color == other); +} + +static inline bool GColor8Eq(GColor8 color, GColor other) { + return (color.argb == GColorGet8(other).argb); +} + +#else + +static inline bool GColor8Eq(GColor8 color, GColor other) { + return (color.argb == other.argb); +} + +static inline GColor GColor8Get(GColor8 color) { + return color; +} + +#endif + From 18f2bd50e62c5791b32caee615b77018c885b43d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Mar 2015 00:16:18 -0800 Subject: [PATCH 463/791] Add util animation for property animation set grect aplite compatibility --- src/util/animation.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/util/animation.h diff --git a/src/util/animation.h b/src/util/animation.h new file mode 100644 index 00000000..b242c2fe --- /dev/null +++ b/src/util/animation.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#ifdef PBL_BW + +static inline void property_animation_set_from_grect(PropertyAnimation *property_animation, GRect *from) { + property_animation->values.from.grect = *from; +} + +static inline void property_animation_set_to_grect(PropertyAnimation *property_animation, GRect *to) { + property_animation->values.to.grect = *to; +} + +#endif From 6a28d93e78dea4d589479519225144f9e994c125 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Mar 2015 00:16:43 -0800 Subject: [PATCH 464/791] Add util graphics gbitmap aplite compatibility functions --- src/util/graphics.h | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/util/graphics.h b/src/util/graphics.h index 86c1f09c..3475e520 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -2,6 +2,39 @@ #include +#ifdef PBL_BW + +typedef enum GBitmapFormat { + GBitmapFormat1Bit = 0, //bounds; +} + +static inline uint8_t *gbitmap_get_data(GBitmap *bitmap) { + return bitmap->addr; +} + +static inline void gbitmap_set_data(GBitmap *bitmap, uint8_t *data, GBitmapFormat format, + uint16_t row_size_bytes, bool free_on_destroy) { + bitmap->is_heap_allocated = free_on_destroy; + bitmap->row_size_bytes = row_size_bytes; + bitmap->addr = data; +} + +static inline uint16_t gbitmap_get_bytes_per_row(GBitmap *bitmap) { + return bitmap->row_size_bytes; +} + +#endif + static inline GRect grect_center_rect(const GRect *rect_a, const GRect *rect_b) { return (GRect) { .origin = { @@ -13,5 +46,6 @@ static inline GRect grect_center_rect(const GRect *rect_a, const GRect *rect_b) } static inline void graphics_draw_bitmap_centered(GContext *ctx, GBitmap *bitmap, const GRect frame) { - graphics_draw_bitmap_in_rect(ctx, bitmap, grect_center_rect(&frame, &bitmap->bounds)); + GRect bounds = gbitmap_get_bounds(bitmap); + graphics_draw_bitmap_in_rect(ctx, bitmap, grect_center_rect(&frame, &bounds)); } From 2b5c6f5e83926a873079256d68aa90bcb323b1f9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Mar 2015 00:22:07 -0800 Subject: [PATCH 465/791] Change simply to explicitly use GColor8 --- src/js/ui/simply-pebble.js | 13 +++++++------ src/simply/simply_stage.c | 31 ++++++++++++++++--------------- src/simply/simply_stage.h | 7 ++++--- src/simply/simply_ui.c | 4 ++-- src/simply/simply_window.c | 10 +++++----- src/simply/simply_window.h | 8 +++++--- 6 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index fb1b5626..a3fb82f0 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -71,9 +71,9 @@ var SizeType = function(x) { var Color = function(x) { switch (x) { - case 'clear': return ~0; - case 'black': return 0; - case 'white': return 1; + case 'clear': return 0x00; + case 'black': return 0xC0; + case 'white': return 0xFF; } return Number(x); }; @@ -808,9 +808,10 @@ SimplyPebble.windowHide = function(id) { }; SimplyPebble.windowProps = function(def, backgroundColor) { - WindowPropsPacket - .prop(def) - .backgroundColor(backgroundColor); + WindowPropsPacket.prop(def); + if (backgroundColor) { + WindowPropsPacket.backgroundColor(backgroundColor); + } SimplyPebble.sendPacket(WindowPropsPacket); }; diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index f7e0bd60..24ece944 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -7,6 +7,7 @@ #include "simply.h" +#include "util/color.h" #include "util/graphics.h" #include "util/memory.h" #include "util/string.h" @@ -38,8 +39,8 @@ struct __attribute__((__packed__)) ElementCommonPacket { Packet packet; uint32_t id; GRect frame; - GColor background_color:8; - GColor border_color:8; + GColor8 background_color; + GColor8 border_color; }; typedef struct ElementRadiusPacket ElementRadiusPacket; @@ -64,7 +65,7 @@ typedef struct ElementTextStylePacket ElementTextStylePacket; struct __attribute__((__packed__)) ElementTextStylePacket { Packet packet; uint32_t id; - GColor color:8; + GColor8 color; GTextOverflowMode overflow_mode:8; GTextAlignment alignment:8; uint32_t custom_font; @@ -168,15 +169,15 @@ void simply_stage_clear(SimplyStage *self) { } static void rect_element_draw_background(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { - if (element->background_color != GColorClear) { - graphics_context_set_fill_color(ctx, element->background_color); + if (element->background_color.a) { + graphics_context_set_fill_color(ctx, GColor8Get(element->background_color)); graphics_fill_rect(ctx, element->frame, element->radius, GCornersAll); } } static void rect_element_draw_border(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { - if (element->border_color != GColorClear) { - graphics_context_set_stroke_color(ctx, element->border_color); + if (element->border_color.a) { + graphics_context_set_stroke_color(ctx, GColor8Get(element->border_color)); graphics_draw_round_rect(ctx, element->frame, element->radius); } } @@ -187,12 +188,12 @@ static void rect_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRec } static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementCircle *element) { - if (element->background_color != GColorClear) { - graphics_context_set_fill_color(ctx, element->background_color); + if (element->background_color.a) { + graphics_context_set_fill_color(ctx, GColor8Get(element->background_color)); graphics_fill_circle(ctx, element->frame.origin, element->radius); } - if (element->border_color != GColorClear) { - graphics_context_set_stroke_color(ctx, element->border_color); + if (element->border_color.a) { + graphics_context_set_stroke_color(ctx, GColor8Get(element->border_color)); graphics_draw_circle(ctx, element->frame.origin, element->radius); } } @@ -208,12 +209,12 @@ static char *format_time(char *format) { static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementText *element) { rect_element_draw(ctx, self, (SimplyElementRect*) element); char *text = element->text; - if (element->text_color != GColorClear && is_string(text)) { + if (element->text_color.a && is_string(text)) { if (element->time_units) { text = format_time(text); } GFont font = element->font ? element->font : fonts_get_system_font(FONT_KEY_GOTHIC_14); - graphics_context_set_text_color(ctx, element->text_color); + graphics_context_set_text_color(ctx, GColor8Get(element->text_color)); graphics_draw_text(ctx, text, font, element->frame, element->overflow_mode, element->alignment, NULL); } } @@ -241,7 +242,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { frame.origin.x = -frame.origin.x; frame.origin.y = -frame.origin.y; - graphics_context_set_fill_color(ctx, self->window.background_color); + graphics_context_set_fill_color(ctx, GColor8Get(self->window.background_color)); graphics_fill_rect(ctx, frame, 0, GCornerNone); SimplyElementCommon *element = (SimplyElementCommon*) self->stage_layer.elements; @@ -625,7 +626,7 @@ SimplyStage *simply_stage_create(Simply *simply) { *self = (SimplyStage) { .window.simply = simply }; simply_window_init(&self->window, simply); - simply_window_set_background_color(&self->window, GColorBlack); + simply_window_set_background_color(&self->window, GColor8Black); window_set_user_data(self->window.window, self); window_set_window_handlers(self->window.window, (WindowHandlers) { diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index 882a48bc..9f4368c3 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -7,6 +7,7 @@ #include "simply.h" #include "util/list1.h" +#include "util/color.h" #include @@ -47,8 +48,8 @@ typedef struct SimplyElementCommon SimplyElementCommon; uint32_t id; \ SimplyElementType type; \ GRect frame; \ - GColor background_color:2; \ - GColor border_color:2; \ + GColor8 background_color; \ + GColor8 border_color; \ } struct SimplyElementCommon SimplyElementCommonDef; @@ -78,7 +79,7 @@ struct SimplyElementText { char *text; GFont font; TimeUnits time_units:8; - GColor text_color:2; + GColor8 text_color; GTextOverflowMode overflow_mode:2; GTextAlignment alignment:2; }; diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index fddf7c03..7bfd04b5 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -213,7 +213,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, GColorBlack); graphics_fill_rect(ctx, frame, 0, GCornerNone); - if (self->window.background_color == GColorWhite) { + if (GColor8Eq(self->window.background_color, GColorWhite)) { graphics_context_set_fill_color(ctx, GColorWhite); graphics_fill_rect(ctx, frame, 4, GCornersAll); } @@ -349,7 +349,7 @@ SimplyUi *simply_ui_create(Simply *simply) { *self = (SimplyUi) { .window.layer = NULL }; simply_window_init(&self->window, simply); - simply_window_set_background_color(&self->window, GColorWhite); + simply_window_set_background_color(&self->window, GColor8White); window_set_user_data(self->window.window, self); window_set_window_handlers(self->window.window, (WindowHandlers) { diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index e3943278..da8a05da 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -18,7 +18,7 @@ typedef struct WindowPropsPacket WindowPropsPacket; struct __attribute__((__packed__)) WindowPropsPacket { Packet packet; uint32_t id; - GColor background_color:8; + GColor8 background_color; bool fullscreen; bool scrollable; }; @@ -36,7 +36,7 @@ struct __attribute__((__packed__)) WindowActionBarPacket { Packet packet; uint32_t image[3]; bool action; - GColor background_color:8; + GColor8 background_color; }; typedef struct ClickPacket ClickPacket; @@ -130,7 +130,7 @@ void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { self->id = id; } -void simply_window_set_background_color(SimplyWindow *self, GColor background_color) { +void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color) { self->background_color = background_color; } @@ -168,12 +168,12 @@ void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint } } -void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor background_color) { +void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor8 background_color) { if (!self->action_bar_layer) { return; } - action_bar_layer_set_background_color(self->action_bar_layer, background_color); + action_bar_layer_set_background_color(self->action_bar_layer, GColor8Get(background_color)); simply_window_set_action_bar(self, true); } diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index b0d026e6..0a4300ba 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -4,6 +4,8 @@ #include "simply.h" +#include "util/color.h" + #include typedef struct SimplyWindow SimplyWindow; @@ -16,7 +18,7 @@ struct SimplyWindow { ActionBarLayer *action_bar_layer; uint32_t id; ButtonId button_mask:4; - GColor background_color:2; + GColor8 background_color; bool is_fullscreen:1; bool is_scrollable:1; bool is_action_bar:1; @@ -34,13 +36,13 @@ void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *con void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable); void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen); -void simply_window_set_background_color(SimplyWindow *self, GColor background_color); +void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color); void simply_window_set_button(SimplyWindow *self, ButtonId button, bool enable); void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar); void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint32_t id); -void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor background_color); +void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor8 background_color); void simply_window_action_bar_clear(SimplyWindow *self); bool simply_window_handle_packet(Simply *simply, Packet *packet); From f8512987e6c62d93773c064fda41fb686e8eb709 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Mar 2015 00:25:47 -0800 Subject: [PATCH 466/791] Update bitmap usage to sdk 3 --- src/simply/simply_msg.c | 2 +- src/simply/simply_res.c | 37 ++++++++++++++++++------------------- src/simply/simply_res.h | 5 +++-- src/simply/simply_stage.c | 2 +- src/simply/simply_ui.c | 32 ++++++++++++++++++++++---------- 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 4b9ca86b..efd78de1 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -55,7 +55,7 @@ struct __attribute__((__packed__)) ImagePacket { uint32_t id; int16_t width; int16_t height; - uint32_t pixels[]; + uint8_t pixels[]; }; typedef struct VibePacket VibePacket; diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 13345154..1f4cf192 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -1,5 +1,6 @@ #include "simply_res.h" +#include "util/graphics.h" #include "util/window.h" #include @@ -10,8 +11,7 @@ static bool id_filter(List1Node *node, void *data) { static void destroy_image(SimplyRes *self, SimplyImage *image) { list1_remove(&self->images, &image->node); - free(image->bitmap.addr); - free(image); + gbitmap_destroy(image->bitmap); } static void destroy_font(SimplyRes *self, SimplyFont *font) { @@ -34,21 +34,24 @@ GBitmap *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { *image = (SimplyImage) { .id = id, - .bitmap = *bitmap, + .bitmap = bitmap, }; list1_prepend(&self->images, &image->node); window_stack_schedule_top_window_render(); - return &image->bitmap; + return image->bitmap; } -GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint32_t *pixels) { +GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint8_t *pixels) { SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); + if (image) { - free(image->bitmap.addr); - image->bitmap.addr = NULL; + free(gbitmap_get_data(image->bitmap)); + uint16_t row_size_bytes = gbitmap_get_bytes_per_row(image->bitmap); + gbitmap_set_data(image->bitmap, pixels, GBitmapFormat1Bit, row_size_bytes, true); + image->bitmap_data = pixels; } else { image = malloc(sizeof(*image)); if (!image) { @@ -56,21 +59,17 @@ GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16 } *image = (SimplyImage) { .id = id }; list1_prepend(&self->images, &image->node); - } - uint16_t row_size_bytes = (1 + (width - 1) / 32) * 4; - size_t pixels_size = height * row_size_bytes; - image->bitmap = (GBitmap) { - .row_size_bytes = row_size_bytes, - .bounds.size = { width, height }, - }; - - image->bitmap.addr = malloc(pixels_size); - memcpy(image->bitmap.addr, pixels, pixels_size); + image->bitmap = gbitmap_create_blank(GSize(width, height), GBitmapFormat1Bit); + image->bitmap_data = gbitmap_get_data(image->bitmap); + uint16_t row_size_bytes = gbitmap_get_bytes_per_row(image->bitmap); + size_t pixels_size = height * row_size_bytes; + memcpy(image->bitmap_data, pixels, pixels_size); + } window_stack_schedule_top_window_render(); - return &image->bitmap; + return image->bitmap; } void simply_res_remove_image(SimplyRes *self, uint32_t id) { @@ -86,7 +85,7 @@ GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder } SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); if (image) { - return &image->bitmap; + return image->bitmap; } if (id <= self->num_bundled_res) { return simply_res_add_bundled_image(self, id); diff --git a/src/simply/simply_res.h b/src/simply/simply_res.h index 634038e7..c849b5d4 100644 --- a/src/simply/simply_res.h +++ b/src/simply/simply_res.h @@ -35,7 +35,8 @@ typedef struct SimplyImage SimplyImage; struct SimplyImage { SimplyResItemCommonMember; - GBitmap bitmap; + uint8_t *bitmap_data; + GBitmap *bitmap; }; typedef struct SimplyFont SimplyFont; @@ -50,7 +51,7 @@ void simply_res_destroy(SimplyRes *self); void simply_res_clear(SimplyRes *self); GBitmap *simply_res_add_bundled_image(SimplyRes *self, uint32_t id); -GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint32_t *pixels); +GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint8_t *pixels); GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder); GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id); diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 24ece944..7e181342 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -226,7 +226,7 @@ static void image_element_draw(GContext *ctx, SimplyStage *self, SimplyElementIm if (bitmap) { GRect frame = element->frame; if (frame.size.w == 0 && frame.size.h == 0) { - frame = bitmap->bounds; + frame = gbitmap_get_bounds(bitmap); } graphics_draw_bitmap_centered(ctx, bitmap, frame); } diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 7bfd04b5..cd02c13c 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -155,18 +155,29 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { GBitmap *body_image = simply_res_get_image( self->window.simply->res, self->ui_layer.imagefields[UiBodyImage]); + GRect title_icon_bounds; + GRect subtitle_icon_bounds; + GRect body_image_bounds; + + if (title_icon) { + title_icon_bounds = gbitmap_get_bounds(title_icon); + } + if (subtitle_icon) { + subtitle_icon_bounds = gbitmap_get_bounds(title_icon); + } + if (has_title) { GRect title_frame = text_frame; if (title_icon) { - title_frame.origin.x += title_icon->bounds.size.w; - title_frame.size.w -= title_icon->bounds.size.w; + title_frame.origin.x += title_icon_bounds.size.w; + title_frame.size.w -= title_icon_bounds.size.w; } title_size = graphics_text_layout_get_content_size(title_text, title_font, title_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); title_size.w = title_frame.size.w; title_pos = cursor; if (title_icon) { - title_pos.x += title_icon->bounds.size.w; + title_pos.x += title_icon_bounds.size.w; } cursor.y += title_size.h; } @@ -174,22 +185,23 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { if (has_subtitle) { GRect subtitle_frame = text_frame; if (subtitle_icon) { - subtitle_frame.origin.x += subtitle_icon->bounds.size.w; - subtitle_frame.size.w -= subtitle_icon->bounds.size.w; + subtitle_frame.origin.x += subtitle_icon_bounds.size.w; + subtitle_frame.size.w -= subtitle_icon_bounds.size.w; } subtitle_size = graphics_text_layout_get_content_size(subtitle_text, title_font, subtitle_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); subtitle_size.w = subtitle_frame.size.w; subtitle_pos = cursor; if (subtitle_icon) { - subtitle_pos.x += subtitle_icon->bounds.size.w; + subtitle_pos.x += subtitle_icon_bounds.size.w; } cursor.y += subtitle_size.h; } if (body_image) { + body_image_bounds = gbitmap_get_bounds(body_image); image_pos = cursor; - cursor.y += body_image->bounds.size.h; + cursor.y += body_image_bounds.size.h; } if (has_body) { @@ -221,7 +233,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { if (title_icon) { GRect icon_frame = (GRect) { .origin = { margin_x, title_pos.y + image_offset_y }, - .size = { title_icon->bounds.size.w, title_size.h } + .size = { title_icon_bounds.size.w, title_size.h } }; graphics_draw_bitmap_centered(ctx, title_icon, icon_frame); } @@ -234,7 +246,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { if (subtitle_icon) { GRect subicon_frame = (GRect) { .origin = { margin_x, subtitle_pos.y + image_offset_y }, - .size = { subtitle_icon->bounds.size.w, subtitle_size.h } + .size = { subtitle_icon_bounds.size.w, subtitle_size.h } }; graphics_draw_bitmap_centered(ctx, subtitle_icon, subicon_frame); } @@ -247,7 +259,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { if (body_image) { GRect image_frame = (GRect) { .origin = { 0, image_pos.y + image_offset_y }, - .size = { window_frame.size.w, body_image->bounds.size.h } + .size = { window_frame.size.w, body_image_bounds.size.h } }; graphics_draw_bitmap_centered(ctx, body_image, image_frame); } From 1be966086f9255a12a3970f19893b363e23e114f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Mar 2015 00:26:09 -0800 Subject: [PATCH 467/791] Update property animation set grect to sdk 3 --- src/simply/simply_stage.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 7e181342..650f89af 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -7,6 +7,7 @@ #include "simply.h" +#include "util/animation.h" #include "util/color.h" #include "util/graphics.h" #include "util/memory.h" @@ -401,8 +402,8 @@ SimplyAnimation *simply_stage_animate_element(SimplyStage *self, return NULL; } - property_animation->values.from.grect = element->frame; - property_animation->values.to.grect = to_frame; + property_animation_set_from_grect(property_animation, &element->frame); + property_animation_set_to_grect(property_animation, &to_frame); animation->animation = property_animation; list1_append(&self->stage_layer.animations, &animation->node); From 53edc691780a7b6cd1d95567ea3cd63e883dc7a3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Mar 2015 00:44:13 -0800 Subject: [PATCH 468/791] Add subtle pattern to tile splash The png to png converter in the sdk cannot handle png's of a single color. This works around this issue by adding a subtle pattern involving at least two colors to tile splash. --- resources/images/tile_splash.png | Bin 18120 -> 15406 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/images/tile_splash.png b/resources/images/tile_splash.png index 547b659659f7d9fddd166571d1f3d8902ea05bd2..394cfe722b03ced497d47f4207a894be8d16ed7a 100644 GIT binary patch delta 644 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 From a9fffd606ebc7c35293259cac99be7973c469aa9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 1 Mar 2015 00:18:46 -0800 Subject: [PATCH 469/791] Update wscript and appinfo to sdk 3. Fixes #31 --- appinfo.json | 20 +++++++++++++++----- wscript | 29 +++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/appinfo.json b/appinfo.json index 9cbc1e2a..4c5f4d1e 100644 --- a/appinfo.json +++ b/appinfo.json @@ -5,7 +5,9 @@ "companyName": "Meiguro", "versionCode": 1, "versionLabel": "0.4", - "capabilities": [ "configurable" ], + "capabilities": [ + "configurable" + ], "watchapp": { "watchface": false }, @@ -17,19 +19,27 @@ "type": "png", "name": "IMAGE_MENU_ICON", "file": "images/menu_icon.png" - }, { + }, + { "type": "png", "name": "IMAGE_LOGO_SPLASH", "file": "images/logo_splash.png" - }, { + }, + { "type": "png", "name": "IMAGE_TILE_SPLASH", "file": "images/tile_splash.png" - }, { + }, + { "type": "font", "name": "MONO_FONT_14", "file": "fonts/UbuntuMono-Regular.ttf" } ] - } + }, + "targetPlatforms": [ + "aplite", + "basalt" + ], + "sdkVersion": "3" } diff --git a/wscript b/wscript index f03132d2..737634ca 100644 --- a/wscript +++ b/wscript @@ -16,15 +16,32 @@ def configure(ctx): def build(ctx): ctx.load('pebble_sdk') - ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), - cflags=['-Wno-address', - '-Wno-type-limits', - '-Wno-missing-field-initializers'], - target='pebble-app.elf') + cflags = ['-Wno-address', + '-Wno-type-limits', + '-Wno-missing-field-initializers'] + + build_worker = os.path.exists('worker_src') + binaries = [] + + for p in ctx.env.TARGET_PLATFORMS: + ctx.set_env(ctx.all_envs[p]) + app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) + ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), + cflags=cflags, + target=app_elf) + + if build_worker: + worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) + binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) + ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), + cflags=cflags, + target=worker_elf) + else: + binaries.append({'platform': p, 'app_elf': app_elf}) js_target = ctx.concat_javascript(js_path='src/js') - ctx.pbl_bundle(elf='pebble-app.elf', + ctx.pbl_bundle(binaries=binaries, js=js_target) @conf From c4fc71fe02190520253f92f88a5cb3e7ee8134ac Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 5 Mar 2015 16:34:20 -0800 Subject: [PATCH 470/791] Change compatibility layer to detect against PBL_COLOR --- src/util/animation.h | 2 +- src/util/color.h | 2 +- src/util/graphics.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/animation.h b/src/util/animation.h index b242c2fe..809f92ee 100644 --- a/src/util/animation.h +++ b/src/util/animation.h @@ -2,7 +2,7 @@ #include -#ifdef PBL_BW +#ifndef PBL_COLOR static inline void property_animation_set_from_grect(PropertyAnimation *property_animation, GRect *from) { property_animation->values.from.grect = *from; diff --git a/src/util/color.h b/src/util/color.h index bd98afd1..5fb14358 100644 --- a/src/util/color.h +++ b/src/util/color.h @@ -6,7 +6,7 @@ #define GColor8Black (GColor8){.argb=GColorBlackARGB8} #define GColor8Clear (GColor8){.argb=GColorClearARGB8} -#ifdef PBL_BW +#ifndef PBL_COLOR #define GColorWhiteARGB8 ((uint8_t)0b11111111) #define GColorBlackARGB8 ((uint8_t)0b11000000) diff --git a/src/util/graphics.h b/src/util/graphics.h index 3475e520..fb0b3c48 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -2,7 +2,7 @@ #include -#ifdef PBL_BW +#ifndef PBL_COLOR typedef enum GBitmapFormat { GBitmapFormat1Bit = 0, // Date: Thu, 5 Mar 2015 16:34:43 -0800 Subject: [PATCH 471/791] Update wscript to be compatible with both SDK 2.9 and SDK 3 --- wscript | 69 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/wscript b/wscript index 737634ca..fe5fa5bf 100644 --- a/wscript +++ b/wscript @@ -16,40 +16,55 @@ def configure(ctx): def build(ctx): ctx.load('pebble_sdk') + binaries = [] + js_target = ctx.concat_javascript(js_path='src/js') + + if ctx.env.TARGET_PLATFORMS: + for platform in ctx.env.TARGET_PLATFORMS: + ctx.build_platform(platform, binaries=binaries) + + ctx.pbl_bundle(binaries=binaries, + js=js_target) + else: + ctx.env.BUILD_DIR = 'aplite' + ctx.build_platform(binaries=binaries) + + elfs = binaries[0] + ctx.pbl_bundle(elf=elfs['app_elf'], + worker_elf=elfs['worker_elf'] if 'worker_elf' in elfs else None, + js=js_target) + +@conf +def build_platform(ctx, platform=None, binaries=None): + if platform is not None: + ctx.set_env(ctx.all_envs[platform]) + cflags = ['-Wno-address', '-Wno-type-limits', '-Wno-missing-field-initializers'] build_worker = os.path.exists('worker_src') - binaries = [] - for p in ctx.env.TARGET_PLATFORMS: - ctx.set_env(ctx.all_envs[p]) - app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) - ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), - cflags=cflags, - target=app_elf) - - if build_worker: - worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) - binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) - ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), - cflags=cflags, - target=worker_elf) - else: - binaries.append({'platform': p, 'app_elf': app_elf}) + app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) + ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), + cflags=cflags, + target=app_elf) - js_target = ctx.concat_javascript(js_path='src/js') + if build_worker: + worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) + binaries.append({'platform': platform, 'app_elf': app_elf, 'worker_elf': worker_elf}) + ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), + cflags=cflags, + target=worker_elf) + else: + binaries.append({'platform': platform, 'app_elf': app_elf}) - ctx.pbl_bundle(binaries=binaries, - js=js_target) @conf -def concat_javascript(self, *k, **kw): - js_path = kw['js_path'] - js_nodes = (self.path.ant_glob(js_path + '/**/*.js') + - self.path.ant_glob(js_path + '/**/*.json') + - self.path.ant_glob(js_path + '/**/*.coffee')) +def concat_javascript(ctx, js_path=None): + js_nodes = (ctx.path.ant_glob(js_path + '/**/*.js') + + ctx.path.ant_glob(js_path + '/**/*.json') + + ctx.path.ant_glob(js_path + '/**/*.coffee')) if not js_nodes: return [] @@ -71,7 +86,7 @@ def concat_javascript(self, *k, **kw): try: import coffeescript except ImportError: - self.fatal(""" + ctx.fatal(""" Coffeescript file '%s' found but coffeescript module isn't installed. You may try `pip install coffeescript` or `easy_install coffeescript`. """ % (relpath)) @@ -113,9 +128,9 @@ def concat_javascript(self, *k, **kw): f.write(out + '\n') lineno += out.count('\n') + 1 - js_target = self.path.make_node('build/src/js/pebble-js-app.js') + js_target = ctx.path.make_node('build/src/js/pebble-js-app.js') - self(rule=concat_javascript_task, + ctx(rule=concat_javascript_task, source=js_nodes, target=js_target) From 31cefe6a52c615814563419ed964bdf4f6812526 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 5 Mar 2015 20:03:34 -0800 Subject: [PATCH 472/791] Add macro undefs for compatibility with SDK 3 dp2 The macros are undef'd so that compatibility with SDK 2.9 is retained. In the event that the static inline versions are different, the behavior should also remain similar. gbitmap_get_data in particular is differing. The row_size_bytes and is_heap_allocated must both be settable. --- src/util/animation.h | 2 ++ src/util/color.h | 1 + src/util/graphics.h | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/src/util/animation.h b/src/util/animation.h index 809f92ee..18c07829 100644 --- a/src/util/animation.h +++ b/src/util/animation.h @@ -4,10 +4,12 @@ #ifndef PBL_COLOR +#undef property_animation_set_from_grect static inline void property_animation_set_from_grect(PropertyAnimation *property_animation, GRect *from) { property_animation->values.from.grect = *from; } +#undef property_animation_set_to_grect static inline void property_animation_set_to_grect(PropertyAnimation *property_animation, GRect *to) { property_animation->values.to.grect = *to; } diff --git a/src/util/color.h b/src/util/color.h index 5fb14358..8ccac93c 100644 --- a/src/util/color.h +++ b/src/util/color.h @@ -38,6 +38,7 @@ static inline GColor8 GColorGet8(GColor color) { } } +#undef GColorEq static inline bool GColorEq(GColor color, GColor other) { return (color == other); } diff --git a/src/util/graphics.h b/src/util/graphics.h index fb0b3c48..f4b885c5 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -14,14 +14,17 @@ static inline GBitmap *gbitmap_create_blank_with_format(GSize size, GBitmapForma #define gbitmap_create_blank gbitmap_create_blank_with_format +#undef gbitmap_get_bounds static inline GRect gbitmap_get_bounds(GBitmap *bitmap) { return bitmap->bounds; } +#undef gbitmap_get_data static inline uint8_t *gbitmap_get_data(GBitmap *bitmap) { return bitmap->addr; } +#undef gbitmap_set_data static inline void gbitmap_set_data(GBitmap *bitmap, uint8_t *data, GBitmapFormat format, uint16_t row_size_bytes, bool free_on_destroy) { bitmap->is_heap_allocated = free_on_destroy; @@ -29,6 +32,7 @@ static inline void gbitmap_set_data(GBitmap *bitmap, uint8_t *data, GBitmapForma bitmap->addr = data; } +#undef gbitmap_get_bytes_per_row static inline uint16_t gbitmap_get_bytes_per_row(GBitmap *bitmap) { return bitmap->row_size_bytes; } From 3bf4aac1bdb5e6e0a71bdaa9d20664704b631cb0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 8 Mar 2015 16:29:43 -0700 Subject: [PATCH 473/791] Change tile splash back to a white image of size 32x32 --- resources/images/tile_splash.png | Bin 15406 -> 15403 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/images/tile_splash.png b/resources/images/tile_splash.png index 394cfe722b03ced497d47f4207a894be8d16ed7a..87f2e401ac973b6927994c407fef8891d43dfd19 100644 GIT binary patch delta 266 zcmZ2ivAROBGr-TCmrII^fq{Y7)59eQNGpIaBamQ-W^|97s3^fWZKG8Uvw*R#fklX+ znU#@+l_`*IKKU548Kxi)%Pi$YQv;La#IzLMBoh;3U6Vv(Bi*DVLla#iOACV}a|^>% z3-iq2IZo+Eej4Xz+YCS#+?+C1h!YI8DA zzCKI5;#a=O*R1tb_&r@5LpZJ{|KMlgX`ar&aO-7#zbNars$d&rY7rJm>5~=nx~kjnx~o? zTbh|{KFM;EMTH23hLia@OodH#4bZLM%*Q3e#Aq>DhsT`JVsk1_zCKG&CsXR=8`k=o tBAzaeAr-fhew_a>|DpY1Yc>sD2?l#L_UkquFFApH=IQF^vd$@?2>_^cL$Lq= From 0503d65650421f2d2a6f60d9e98c9932f6df744d Mon Sep 17 00:00:00 2001 From: Alessio Bogon Date: Mon, 9 Mar 2015 21:56:38 +0100 Subject: [PATCH 474/791] Save compiled coffeescript files Coffeescript files should be compiled and saved, to make debugging more easy (you can see the actual javascript line that fails) and to make javascript files able to import compiled coffeescripts. --- wscript | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/wscript b/wscript index fe5fa5bf..c6a15fcc 100644 --- a/wscript +++ b/wscript @@ -92,18 +92,23 @@ def concat_javascript(ctx, js_path=None): """ % (relpath)) body = coffeescript.compile(body) # change ".coffee" or ".js.coffee" extensions to ".js" - relpath = re.sub('(\.js)?\.coffee$', '.js', relpath) + relpath = re.sub('(\.js)?\.coffee$', '.build.js', relpath) return relpath, body sources = [] for node in task.inputs: relpath = os.path.relpath(node.abspath(), js_path) with open(node.abspath(), 'r') as f: + if relpath.endswith('.build.js'): + continue body = f.read() if relpath.endswith('.json'): body = JSON_TEMPLATE.format(body=body) elif relpath.endswith('.coffee'): relpath, body = coffeescript_compile(relpath, body) + compiled_js_path = os.path.join(js_path, relpath) + with open(compiled_js_path, 'w') as out: + out.write(body) if relpath == LOADER_PATH: sources.insert(0, body) From 164d27f44ea1ca71635c986e13624b5ae5c1dc3e Mon Sep 17 00:00:00 2001 From: Alessio Bogon Date: Tue, 10 Mar 2015 00:01:57 +0100 Subject: [PATCH 475/791] Move coffeescript compiled files into `build/` --- wscript | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/wscript b/wscript index c6a15fcc..4137f460 100644 --- a/wscript +++ b/wscript @@ -92,23 +92,25 @@ def concat_javascript(ctx, js_path=None): """ % (relpath)) body = coffeescript.compile(body) # change ".coffee" or ".js.coffee" extensions to ".js" - relpath = re.sub('(\.js)?\.coffee$', '.build.js', relpath) + relpath = re.sub('(\.js)?\.coffee$', '.js', relpath) return relpath, body sources = [] for node in task.inputs: relpath = os.path.relpath(node.abspath(), js_path) with open(node.abspath(), 'r') as f: - if relpath.endswith('.build.js'): - continue body = f.read() if relpath.endswith('.json'): body = JSON_TEMPLATE.format(body=body) elif relpath.endswith('.coffee'): relpath, body = coffeescript_compile(relpath, body) - compiled_js_path = os.path.join(js_path, relpath) - with open(compiled_js_path, 'w') as out: - out.write(body) + + compiled_js_path = os.path.join(out, js_path, relpath) + compiled_js_dir = os.path.dirname(compiled_js_path) + if not os.path.exists(compiled_js_dir): + os.makedirs(compiled_js_dir) + with open(compiled_js_path, 'w') as f: + f.write(body) if relpath == LOADER_PATH: sources.insert(0, body) @@ -127,11 +129,11 @@ def concat_javascript(ctx, js_path=None): lineno = 1 for source in sources: if type(source) is dict: - out = loader_translate(source, lineno) + body = loader_translate(source, lineno) else: - out = source - f.write(out + '\n') - lineno += out.count('\n') + 1 + body = source + f.write(body + '\n') + lineno += body.count('\n') + 1 js_target = ctx.path.make_node('build/src/js/pebble-js-app.js') From 741325649a7bac45d37c50c58e50842a9ea2ee79 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 9 Mar 2015 19:08:17 -0700 Subject: [PATCH 476/791] Add aplite and SDK 2.9 compatibility header Rather than undefining the compatibility headers provided by SDK 3.0 and redefining them, copy all missing definitions and fallback on those defined in the header only if they are missing. The header is also an aplite compatibility header, giving aplite a few definitions that are only available on basalt such as GColor8 and GBitmapFormat. --- src/simply/simply_stage.c | 2 +- src/util/animation.h | 17 -------- src/util/color.h | 21 +--------- src/util/compat.h | 86 +++++++++++++++++++++++++++++++++++++++ src/util/graphics.h | 29 +------------ 5 files changed, 91 insertions(+), 64 deletions(-) delete mode 100644 src/util/animation.h create mode 100644 src/util/compat.h diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 650f89af..cbbf8442 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -7,8 +7,8 @@ #include "simply.h" -#include "util/animation.h" #include "util/color.h" +#include "util/compat.h" #include "util/graphics.h" #include "util/memory.h" #include "util/string.h" diff --git a/src/util/animation.h b/src/util/animation.h deleted file mode 100644 index 18c07829..00000000 --- a/src/util/animation.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -#ifndef PBL_COLOR - -#undef property_animation_set_from_grect -static inline void property_animation_set_from_grect(PropertyAnimation *property_animation, GRect *from) { - property_animation->values.from.grect = *from; -} - -#undef property_animation_set_to_grect -static inline void property_animation_set_to_grect(PropertyAnimation *property_animation, GRect *to) { - property_animation->values.to.grect = *to; -} - -#endif diff --git a/src/util/color.h b/src/util/color.h index 8ccac93c..6ff1e72b 100644 --- a/src/util/color.h +++ b/src/util/color.h @@ -1,5 +1,7 @@ #pragma once +#include "util/compat.h" + #include #define GColor8White (GColor8){.argb=GColorWhiteARGB8} @@ -8,20 +10,6 @@ #ifndef PBL_COLOR -#define GColorWhiteARGB8 ((uint8_t)0b11111111) -#define GColorBlackARGB8 ((uint8_t)0b11000000) -#define GColorClearARGB8 ((uint8_t)0b00000000) - -typedef union GColor8 { - uint8_t argb; - struct { - uint8_t b:2; //!< Blue - uint8_t g:2; //!< Green - uint8_t r:2; //!< Red - uint8_t a:2; //!< Alpha. 3 = 100% opaque, 2 = 66% opaque, 1 = 33% opaque, 0 = transparent. - }; -} GColor8; - static inline GColor GColor8Get(GColor8 color) { switch (color.argb) { case GColorWhiteARGB8: return GColorWhite; @@ -38,11 +26,6 @@ static inline GColor8 GColorGet8(GColor color) { } } -#undef GColorEq -static inline bool GColorEq(GColor color, GColor other) { - return (color == other); -} - static inline bool GColor8Eq(GColor8 color, GColor other) { return (color.argb == GColorGet8(other).argb); } diff --git a/src/util/compat.h b/src/util/compat.h new file mode 100644 index 00000000..54b9b054 --- /dev/null +++ b/src/util/compat.h @@ -0,0 +1,86 @@ +#pragma once + +/** + * aplite and SDK 2.9 compatibility utilities + * These are a collection of types and compatibility macros taken from SDK 3.0. + * When possible, they are copied directly without modification. + */ + +#ifndef PBL_COLOR + +#define GColorWhiteARGB8 ((uint8_t)0b11111111) +#define GColorBlackARGB8 ((uint8_t)0b11000000) +#define GColorClearARGB8 ((uint8_t)0b00000000) + +typedef union GColor8 { + uint8_t argb; + struct { + uint8_t b:2; //!< Blue + uint8_t g:2; //!< Green + uint8_t r:2; //!< Red + uint8_t a:2; //!< Alpha. 3 = 100% opaque, 2 = 66% opaque, 1 = 33% opaque, 0 = transparent. + }; +} GColor8; + +//! Convenience macro to enable use of SDK 3.0 function to compare equality of two colors. +#define GColorEq(a, b) ((a) == (b)) + + +//! The format of a GBitmap can either be 1-bit or 8-bit. +typedef enum GBitmapFormat { + GBitmapFormat1Bit = 0, //row_size_bytes) +#endif + +//! Convenience function to use SDK 3.0 function to get a `GBitmap`'s `bounds` field. +#ifndef gbitmap_get_bounds +#define gbitmap_get_bounds(bmp) ((bmp)->bounds) +#endif + +//! Convenience function to use SDK 3.0 function to get a `GBitmap`'s `addr` field. +#ifndef gbitmap_get_data +#define gbitmap_get_data(bmp) ((bmp)->addr) +#endif + +//! Convenience function to use SDK 3.0 function to set a `GBitmap`'s `bounds` field. +#ifndef gbitmap_set_bounds +#define gbitmap_set_bounds(bmp, new_bounds) ((bmp)->bounds = (new_bounds)) +#endif + +//! Convenience function to use SDK 3.0 function to set a `GBitmap`'s `addr` field. +//! Modified to support row_size_bytes and free_on_destroy. +//! Change to ifndef when SDK 3.0 supports those additional parameters. +#undef gbitmap_set_data +#define gbitmap_set_data(bmp, data, fmt, rsb, fod) ({ \ + __typeof__(bmp) __gbitmap_tmp_bmp = (bmp); \ + __gbitmap_tmp_bmp->addr = (data); \ + __gbitmap_tmp_bmp->is_heap_allocated = (fod); \ + __gbitmap_tmp_bmp->row_size_bytes = (rsb); \ +}) + + +//! Convenience macro to use SDK 3.0 function to set a `PropertyAnimation`'s +//! `values.from.grect` field. +#ifndef property_animation_set_from_grect +#define property_animation_set_from_grect(prop_anim, value_ptr) \ + ((prop_anim)->values.from.grect = *(value_ptr)) +#endif + +//! Convenience macro to use SDK 3.0 function to set a `PropertyAnimation`'s +//! `values.to.grect` field. +#ifndef property_animation_set_to_grect +#define property_animation_set_to_grect(prop_anim, value_ptr) \ + ((prop_anim)->values.to.grect = *(value_ptr)) +#endif + +#endif diff --git a/src/util/graphics.h b/src/util/graphics.h index f4b885c5..6fd801ba 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -1,42 +1,17 @@ #pragma once +#include "util/compat.h" + #include #ifndef PBL_COLOR -typedef enum GBitmapFormat { - GBitmapFormat1Bit = 0, //bounds; -} - -#undef gbitmap_get_data -static inline uint8_t *gbitmap_get_data(GBitmap *bitmap) { - return bitmap->addr; -} - -#undef gbitmap_set_data -static inline void gbitmap_set_data(GBitmap *bitmap, uint8_t *data, GBitmapFormat format, - uint16_t row_size_bytes, bool free_on_destroy) { - bitmap->is_heap_allocated = free_on_destroy; - bitmap->row_size_bytes = row_size_bytes; - bitmap->addr = data; -} - -#undef gbitmap_get_bytes_per_row -static inline uint16_t gbitmap_get_bytes_per_row(GBitmap *bitmap) { - return bitmap->row_size_bytes; -} - #endif static inline GRect grect_center_rect(const GRect *rect_a, const GRect *rect_b) { From 57e452edfd322246f139486307c91be44034237a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 21 Mar 2015 02:43:23 -0700 Subject: [PATCH 477/791] Move method anchor associations to their respective definition sites --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 551726b9..26314ec1 100644 --- a/README.md +++ b/README.md @@ -473,6 +473,7 @@ Pebble.js provides three types of Windows: #### Window actionDef +[Window actionDef]: #window-actiondef A `Window` action bar can be displayed by setting its Window `action` property to an `actionDef`: @@ -723,7 +724,9 @@ menu.item(0, 0, { title: 'A new item', subtitle: 'replacing the previous one' }) When called with no `item`, returns the item at the given `sectionIndex` and `itemIndex`. + #### Menu.on('select', callback) +[Menu.on('select', callback)]: #menu-on-select-callback Registers a callback called when an item in the menu is selected. The callback function will be passed an event with the following fields: @@ -744,7 +747,7 @@ menu.on('select', function(e) { #### Menu.on('longSelect', callback) -See `Menu.on('select, callback)` +Similar to the select callback, except for long select presses. See [Menu.on('select', callback)]. ### Element @@ -811,6 +814,7 @@ element.animate('position', pos, 1000); #### Element.queue(callback(next)) +[Element.queue(callback(next))]: #element-queue-callback-next `Element.queue` can be used to perform tasks that are dependent upon an animation completing, such as preparing the element for a different animation. `Element.queue` can also be used to coordinate animations across different elements. It is recommended to use `Element.queue` instead of a timeout if the same element will be animated after the custom task. @@ -988,6 +992,7 @@ Sets the image property. See [Image]. #### Image.compositing(compop) +[Image.compositing(compop)]: #image-compositing Sets the compositing operation to be used when rendering. Specify the compositing operation as a string such as `"invert"`. The following is a list of compositing operations available. @@ -1044,7 +1049,7 @@ Pebble.js includes several libraries to help you write applications. This module gives you a very simple and easy way to make HTTP requests. -```` +````js var ajax = require('ajax'); ajax( @@ -1105,10 +1110,4 @@ For more information, see [Vector2 in the three.js reference documentation][thre [Rect]: #rect [Text]: #text [TimeText]: #timetext -[Window actionDef]: #window-actiondef -[Window.show()]: #window-show -[Window.hide()]: #window-hide -[Element.queue(callback(next))]: #element-queue-callback-next -[Image.compositing(compop)]: #image-compositing -[Menu.on('select, callback)]: #menu-on-select-callback [three.js Vector2]: http://threejs.org/docs/#Reference/Math/Vector2 From d6e429fa30f9ef74c9198e33148ca75d3ff7f408 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 21 Mar 2015 02:44:16 -0700 Subject: [PATCH 478/791] Add Wakeup docs --- README.md | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/README.md b/README.md index 26314ec1..63f25c98 100644 --- a/README.md +++ b/README.md @@ -1041,6 +1041,144 @@ Restore the normal behaviour. #### Light.trigger() Trigger the backlight to turn on momentarily, just like if the user shook their wrist. +## Wakeup + +The Wakeup module allows you to schedule your app to wakeup at a specified time using Pebble's wakeup functionality. Whether the user is in a watchface or in a different app, your app while launch by the specified time. This allows you to write a custom alarm app, for example. With the Wakeup module, you can save data to be read on launch and configure your app to behave differently based on launch data. The Wakeup module, like the Settings module, is backed by localStorage. + +### Wakeup + +`Wakeup` provides a single module of the same name `Wakeup`. + +````js +var Wakeup = require('wakeup'); +```` + + +#### Wakeup.schedule(options, callback(event)) +[Wakeup.schedule]: #wakeup.schedule + +Schedules a wakeup event that will wake up the app at the specified time. `callback` will be immediately called asynchronously with whether the wakeup event was successfully set or not. Wakeup events cannot be scheduled within one minute of each other regardless of what app scheduled them. Each app may only schedule up to 8 wakeup events. + +````js +Wakeup.schedule( + { + // Set the wakeup event for one minute from now + time: Date.now() / 1000 + 60, + // Pass data for the app on launch + data: { hello: 'world' } + }, + function(e) { + if (e.failed) { + // Log the error reason + console.log('Wakeup failed: ' + e.error + '!'); + } else { + console.log('Wakeup set! Event ID: ' + e.id); + } + } +); +```` + +The supported `Wakeup.schedule` options are: + +| Name | Type | Argument | Default | Description | +| ---- | :----: | :--------: | --------- | ------------- | +| `time` | number | required | | The time for the app to launch in seconds since the epoch as a number. Time can be specified as a Date object, but is not recommended due to timezone confusion. If using a Date object, no timezone adjustments are necessary if the phone's timezone is properly set. | +| `data` | * | optional | | The data to be saved for the app to read on launch. This is optional. See [Wakeup.launch]. Note that `data` is backed by localStorage and is thus saved on the phone. Data must be JSON serializable as it uses `JSON.stringify` to save the data. | +| `cookie` | number | optional | 0 | A 32-bit unsigned integer to be saved for the app to read on launch. This is an optional alternative to `data` can also be used in combination. The integer is saved on the watch rather than the phone. | +| `notifyIfMissed` | boolean | optional | false | The user can miss a wakeup event if their watch is powered off. Specify `true` if you would like Pebble OS to notify them if they missed the event. | + +Scheduling a wakeup event can result in errors. By providing a `callback`, you can inspect whether or not you have successfully set the wakeup event. The `callback` will be called with a wakeup set result event which has the following properties: + +| Name | Type | Description | +| ---- | :----: | ------------- | +| `id` | number | If successfully set, the wakeup event id. | +| `error` | string | On set failure, the type of error. | +| `failed` | boolean | `true` if the event could not be set, otherwise `false`. | +| `data` | number | The custom `data` that was associated with the wakeup event. | +| `cookie` | number | The custom 32-bit unsigned integer `cookie` that was associated with the wakeup event. | + +Finally, there are multiple reasons why setting a wakeup event can fail. When a wakeup event fails to be set, `error` can be one of the following strings: + +| Error | Description | +| ----- | ------------- | +| `'range'` | Another wakeup event is already scheduled close to the requested time. | +| `'invalidArgument'` | The wakeup event was requested to be set in the past. | +| `'outOfResources'` | The app already has the maximum of 8 wakeup events scheduled. | +| `'internal'` | There was a Pebble OS error in scheduling the event. | + + +#### Wakeup.launch(callback(event)) +[Wakeup.launch]: #wakeup-launch + +If you wish to change the behavior of your app depending on whether it was launched by a wakeup event, and further configure the behavior based on the data associated with the wakeup event, use `Wakeup.launch` on startup. `Wakeup.launch` will immediately called your launch callback asynchronously with a launch event detailing whether or not your app was launched by a wakeup event. + +````js +// Query whether we launched by a wakeup event +Wakeup.launch(function(e) { + if (e.wakeup) { + console.log('Woke up to ' + e.id + '! data: ' + JSON.stringify(e.data)); + } else { + console.log('Regular launch not by a wakeup event.'); + } +}); +```` + +The `callback` will be called with a wakeup launch event. The event has the following properties: + +| Name | Type | Description | +| ---- | :----: | ------------- | +| `id` | number | If woken by a wakeup event, the wakeup event id. | +| `wakeup` | boolean | `true` if the app woke up by a wakeup event, otherwise `false`. | +| `data` | number | If woken by a wakeup event, the custom `data` that was associated with the wakeup event. | +| `cookie` | number | If woken by a wakeup event, the custom 32-bit unsigned integer `cookie` that was associated with the wakeup event. | + +Note that this means you may have to move portions of your startup logic into the `Wakeup.launch` callback or a function called by the callback. This can also add a very small delay to startup behavior because the underlying implementation must query the watch for the launch information. + + +#### Wakeup.get(id) +[Wakeup.get]: #wakeup-get + +Get the wakeup state information by the wakeup id. A wakeup state has the following properties: + +| Name | Type | Description | +| ---- | :----: | ------------- | +| `id` | number | The wakeup event id. | +| `time` | number | The time for the app to launch. This depends on the data type pass to [Wakeup.schedule]. If a Date object was passed, this can be a string because of localStorage. | +| `data` | number | The custom `data` that was associated with the wakeup event. | +| `cookie` | number | The custom 32-bit unsigned integer `cookie` that was associated with the wakeup event. | +| `notifyIfMissed` | boolean | Whether it was requested for Pebble OS to notify the user if they missed the wakeup event. | + +````js +var wakeup = Wakeup.get(wakeupId); +console.log('Wakeup info: ' + JSON.stringify(wakeup)); +```` + + +#### Wakeup.each(callback(wakeup)) +[Wakeup.each]: #wakeup-each + +Loops through all scheduled wakeup events that have not yet triggered by calling the `callback` for each wakeup event. See [Wakeup.get] for the properties of the `wakeup` object to be passed to the callback. + +````js +var numWakeups = 0; + +// Query all wakeups +Wakeup.each(function(e) { + console.log('Wakeup ' + e.id + ': ' + JSON.stringify(e)); + ++numWakeups; +}); + +main.body('Number of wakeups: ' + numWakeups); +```` + +#### Wakeup.cancel(id) + +Cancels a particular wakeup event by id. The wakeup event id is obtained by the set result callback when setting a wakeup event. See [Wakeup.schedule]. + +#### Wakeup.cancel('all') + +Cancels all wakeup events scheduled by your app. You can check what wakeup events are set before cancelling them all. See [Wakeup.each]. + ## Libraries Pebble.js includes several libraries to help you write applications. From f21c51e8be018882121d21c4bf5706289c8d7133 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 21 Mar 2015 03:09:46 -0700 Subject: [PATCH 479/791] Add Clock docs --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 63f25c98..609333b9 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,46 @@ More specifically: If in doubt, please contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com). +## Clock + +The Clock module makes working with the Wakeup module with time utility functions. + +### Clock + +`Clock` provides a single module of the same name `Clock`. + +````js +var Clock = require('clock'); +```` + + +#### Clock.weekday(weekday, hour, minute[, seconds]) +[Clock.weekday]: #clock-weekday + +Calculates the seconds since the epoch until the next nearest moment of the given weekday and time parameters. `weekday` can either be a string representation of the weekday name such as `sunday`, or the 0-based index number, such as 0 for sunday. `hour` is a number 0-23 with 0-12 indicating the morning or a.m. times. `minute` and `seconds` numbers 0-59. `seconds` is optional. + +The weekday is always the next occurrence and is not limited by the current week. For example, if today is Wednesday, and `'tuesday'` is given for `weekday`, the resulting time will be referring to Tuesday of next week at least 5 days from now. Similarly, if today is Wednesday and `'Thursday'` is given, the time will be referring to tomorrow, the Thursday of the same week, between 0 to 2 days from now. This is useful for specifying the time for [Wakeup.schedule]. + +````js +// Next Tuesday at 6:00 a.m. +var nextTime = Clock.weekday('tuesday', 6, 0); +console.log('Seconds until then: ' + (nextTime - Date.now())); + +var Wakeup = require('wakeup'); + +// Schedule a wakeup event. +Wakeup.schedule( + { time: nextTime }, + function(e) { + if (e.failed) { + console.log('Wakeup set failed: ' + e.error); + } else { + console.log('Wakeup set! Event ID: ' + e.id); + } + } +) +```` + ## Settings The Settings module allows you to add a configurable web view to your application and share options with it. Settings also provides two data accessors `Settings.option` and `Settings.data` which are backed by localStorage. Data stored in `Settings.option` is automatically shared with the configurable web view. @@ -1055,10 +1095,12 @@ var Wakeup = require('wakeup'); #### Wakeup.schedule(options, callback(event)) -[Wakeup.schedule]: #wakeup.schedule +[Wakeup.schedule]: #wakeup-schedule Schedules a wakeup event that will wake up the app at the specified time. `callback` will be immediately called asynchronously with whether the wakeup event was successfully set or not. Wakeup events cannot be scheduled within one minute of each other regardless of what app scheduled them. Each app may only schedule up to 8 wakeup events. +See [Clock.weekday] for setting wakeup events at particular times of a weekday. + ````js Wakeup.schedule( { @@ -1070,7 +1112,7 @@ Wakeup.schedule( function(e) { if (e.failed) { // Log the error reason - console.log('Wakeup failed: ' + e.error + '!'); + console.log('Wakeup set failed: ' + e.error); } else { console.log('Wakeup set! Event ID: ' + e.id); } @@ -1239,6 +1281,7 @@ For more information, see [Vector2 in the three.js reference documentation][thre [Using Images]: #using-images [Using Fonts]: #using-fonts +[Clock]: #clock [Window]: #window [Card]: #card [Menu]: #menu From 24673c92ced863533821669d321aff6c353301c1 Mon Sep 17 00:00:00 2001 From: dantsui Date: Sat, 11 Apr 2015 12:05:03 -0700 Subject: [PATCH 480/791] add color definitions to js --- src/js/ui/simply-pebble.js | 77 ++++++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index a3fb82f0..6343508f 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -69,13 +69,76 @@ var SizeType = function(x) { this.sizeH(x.y); }; -var Color = function(x) { - switch (x) { - case 'clear': return 0x00; - case 'black': return 0xC0; - case 'white': return 0xFF; - } - return Number(x); +var colorMap = { + 'clear': 0x00, + 'black': 0xC0, + 'oxfordBlue': 0xC1, + 'dukeBlue': 0xC2, + 'blue': 0xC3, + 'darkGreen': 0xC4, + 'midnightGreen': 0xC5, + 'colbaltBlue': 0xC6, + 'blueMoon': 0xC7, + 'islamicGreen': 0xC8, + 'jaegerGreen': 0xC9, + 'tiffanyBlue': 0xCA, + 'vividCerulean': 0xCB, + 'green': 0xCC, + 'malachite': 0xCD, + 'mediumSpringGreen': 0xCE, + 'cyan': 0xCF, + 'bulgarianRose': 0xD0, + 'imperialPurple': 0xD1, + 'indigo': 0xD2, + 'electricUltramarine': 0xD3, + 'armyGreen': 0xD4, + 'darkGray': 0xD5, + 'liberty': 0xD6, + 'veryLightBlue': 0xD7, + 'kellyGreen': 0xD8, + 'mayGreen': 0xD9, + 'cadetBlue': 0xDA, + 'pictonBlue': 0xDB, + 'brightGreen': 0xDC, + 'screaminGreen': 0xDD, + 'mediumAquamarine': 0xDE, + 'electricBlue': 0xDF, + 'darkCandyAppleRed': 0xE0, + 'jazzberryJam': 0xE1, + 'purple': 0xE2, + 'vividViolet': 0xE3, + 'windsorTan': 0xE4, + 'roseVale': 0xE5, + 'purpureus': 0xE6, + 'lavenderIndigo': 0xE7, + 'limerick': 0xE8, + 'brass': 0xE9, + 'lightGray': 0xEA, + 'babyBlueEyes': 0xEB, + 'springBud': 0xEC, + 'inchworm': 0xED, + 'mintGreen': 0xEE, + 'celeste': 0xEF, + 'red': 0xF0, + 'folly': 0xF1, + 'fashionMagenta': 0xF2, + 'magenta': 0xF3, + 'orange': 0xF4, + 'sunsetOrange': 0xF5, + 'brilliantRose': 0xF6, + 'shockingPink': 0xF7, + 'chromeYellow': 0xF8, + 'rajah': 0xF9, + 'melon': 0xFA, + 'richBrilliantLavender': 0xFB, + 'yellow': 0xFC, + 'icterine': 0xFD, + 'pastelYellow': 0xFE, + 'white': 0xFF +}; + +var Color = function(color) { + return colorMap[color] ? colorMap[color] : colorMap['clear']; }; var Font = function(x) { From 5337c1da4f3f41214d62152d21f155124adb8006 Mon Sep 17 00:00:00 2001 From: dantsui Date: Sat, 11 Apr 2015 13:46:17 -0700 Subject: [PATCH 481/791] add backgroundColor to props --- src/js/ui/card.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/ui/card.js b/src/js/ui/card.js index 7156bc04..1f6a174d 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -57,6 +57,7 @@ var actionProps = [ var configProps = [ 'style', + 'backgroundColor' ]; var accessorProps = textProps.concat(imageProps).concat(configProps); From e89cfde4f5497af5964087fe5f67617c12c4fb25 Mon Sep 17 00:00:00 2001 From: dantsui Date: Sat, 11 Apr 2015 13:49:28 -0700 Subject: [PATCH 482/791] add preprocessor flags to properly set background color for cards --- src/simply/simply_ui.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index cd02c13c..7286df15 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -222,6 +222,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } } + #ifndef PBL_COLOR graphics_context_set_fill_color(ctx, GColorBlack); graphics_fill_rect(ctx, frame, 0, GCornerNone); @@ -229,6 +230,10 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, GColorWhite); graphics_fill_rect(ctx, frame, 4, GCornersAll); } + #else + graphics_context_set_fill_color(ctx, GColor8Get(self->window.background_color)); + graphics_fill_rect(ctx, frame, 4, GCornersAll); + #endif if (title_icon) { GRect icon_frame = (GRect) { From 4f94d85e2ced77c0a79bbae072fb9d73195f165b Mon Sep 17 00:00:00 2001 From: dantsui Date: Sat, 11 Apr 2015 13:50:50 -0700 Subject: [PATCH 483/791] allow card to set background color --- src/js/ui/simply-pebble.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 6343508f..e518ff80 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -944,7 +944,7 @@ SimplyPebble.card = function(def, clear, pushing) { if (clear !== undefined) { SimplyPebble.cardClear(clear); } - SimplyPebble.windowProps(def, 'white'); + SimplyPebble.windowProps(def); if (def.action !== undefined) { SimplyPebble.windowActionBar(def.action); } From 175ae663c5a4de5c8706be5dc78be719f33eaefe Mon Sep 17 00:00:00 2001 From: dantsui Date: Sat, 11 Apr 2015 14:47:17 -0700 Subject: [PATCH 484/791] add background_color to packet and use it in basalt build --- src/simply/simply_menu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 25224682..9737f157 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -31,6 +31,7 @@ typedef struct MenuPropsPacket MenuPropsPacket; struct __attribute__((__packed__)) MenuPropsPacket { Packet packet; uint16_t num_sections; + GColor8 background_color; }; typedef struct MenuSectionPacket MenuSectionPacket; @@ -407,6 +408,9 @@ static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { static void handle_menu_props_packet(Simply *simply, Packet *data) { MenuPropsPacket *packet = (MenuPropsPacket*) data; simply_menu_set_num_sections(simply->menu, packet->num_sections); + #ifdef PBL_COLOR + window_set_background_color(simply->menu->window.window, GColor8Get(packet->background_color)); + #endif } static void handle_menu_section_packet(Simply *simply, Packet *data) { From ed32ce671fa8e836f685a7ed39d05a3403c1d3e4 Mon Sep 17 00:00:00 2001 From: dantsui Date: Sat, 11 Apr 2015 14:47:54 -0700 Subject: [PATCH 485/791] add default background color to menu --- src/js/ui/menu.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index df066c54..3ec2549e 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -5,8 +5,12 @@ var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); var simply = require('ui/simply'); +var defaults = { + backgroundColor: 'white', +}; + var Menu = function(menuDef) { - Window.call(this, menuDef); + Window.call(this, myutil.shadow(defaults, menuDef || {})); this._dynamic = false; this._sections = {}; this._selection = { sectionIndex: 0, itemIndex: 0 }; From 583b1467c922b39641068a3c79446ee57148b409 Mon Sep 17 00:00:00 2001 From: dantsui Date: Sat, 11 Apr 2015 14:48:34 -0700 Subject: [PATCH 486/791] add backgroundColor to menu props packet --- src/js/ui/simply-pebble.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index e518ff80..fa727b69 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -498,6 +498,7 @@ var MenuClearSectionPacket = new struct([ var MenuPropsPacket = new struct([ [Packet, 'packet'], ['uint16', 'sections', EnumerableType], + ['uint8', 'backgroundColor', Color] ]); var MenuSectionPacket = new struct([ From f7dfdfba702e4ff5c06ab8c24f0ab015ba2f6eaf Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 13 Apr 2015 16:54:13 -0700 Subject: [PATCH 487/791] Change SimplyPebble windowProps to set the default background color Before, taking a default color param was not uniform across the SimplyPebble props functions and was non-obvious. Instead, internally set the default background color similar to the actionWindowBar function. --- src/js/ui/simply-pebble.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index fa727b69..52f58332 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -871,11 +871,10 @@ SimplyPebble.windowHide = function(id) { SimplyPebble.sendPacket(WindowHidePacket.id(id)); }; -SimplyPebble.windowProps = function(def, backgroundColor) { - WindowPropsPacket.prop(def); - if (backgroundColor) { - WindowPropsPacket.backgroundColor(backgroundColor); - } +SimplyPebble.windowProps = function(def) { + WindowPropsPacket + .prop(def) + .backgroundColor(def.backgroundColor || 'white'); SimplyPebble.sendPacket(WindowPropsPacket); }; From 8a6887e41ad54e9a6239ebc6a41ecd3104a413d0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 13 Apr 2015 16:56:21 -0700 Subject: [PATCH 488/791] Unconditionally render the background color as if on basalt To the furthest extent possible, ifdefs should only be in header files in order to simplify the codebase. Aplite has access to the full background GColor, use it anyway. Eventually there should be a color filter based on readability that would keep the background white on aplite unless the text color is black. --- src/simply/simply_menu.c | 4 +--- src/simply/simply_ui.c | 7 ------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 9737f157..f1f8e5bd 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -408,9 +408,7 @@ static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { static void handle_menu_props_packet(Simply *simply, Packet *data) { MenuPropsPacket *packet = (MenuPropsPacket*) data; simply_menu_set_num_sections(simply->menu, packet->num_sections); - #ifdef PBL_COLOR - window_set_background_color(simply->menu->window.window, GColor8Get(packet->background_color)); - #endif + window_set_background_color(simply->menu->window.window, GColor8Get(packet->background_color)); } static void handle_menu_section_packet(Simply *simply, Packet *data) { diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 7286df15..ba504d76 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -222,18 +222,11 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } } - #ifndef PBL_COLOR graphics_context_set_fill_color(ctx, GColorBlack); graphics_fill_rect(ctx, frame, 0, GCornerNone); - if (GColor8Eq(self->window.background_color, GColorWhite)) { - graphics_context_set_fill_color(ctx, GColorWhite); - graphics_fill_rect(ctx, frame, 4, GCornersAll); - } - #else graphics_context_set_fill_color(ctx, GColor8Get(self->window.background_color)); graphics_fill_rect(ctx, frame, 4, GCornersAll); - #endif if (title_icon) { GRect icon_frame = (GRect) { From 0b0c10acd4be2eb286312b36bc195f1c656cd577 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 13 Apr 2015 17:12:11 -0700 Subject: [PATCH 489/791] Add GColor8GetOr for maintaining color readability on aplite Many color combinations are readable on basalt, but aplite only supports black and white. To support cross compiling pebble.js apps, fallback to a white background in the case that the user specifies a different background color, otherwise the background will be clear. Only the card required this as it uses a black background to implement rounded corners. The menu already has a white background. --- src/simply/simply_ui.c | 2 +- src/util/color.h | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index ba504d76..494632d7 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -225,7 +225,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, GColorBlack); graphics_fill_rect(ctx, frame, 0, GCornerNone); - graphics_context_set_fill_color(ctx, GColor8Get(self->window.background_color)); + graphics_context_set_fill_color(ctx, GColor8GetOr(self->window.background_color, GColorWhite)); graphics_fill_rect(ctx, frame, 4, GCornersAll); if (title_icon) { diff --git a/src/util/color.h b/src/util/color.h index 6ff1e72b..1e01728b 100644 --- a/src/util/color.h +++ b/src/util/color.h @@ -18,6 +18,15 @@ static inline GColor GColor8Get(GColor8 color) { } } +static inline GColor GColor8GetOr(GColor8 color, GColor fallback) { + switch (color.argb) { + case GColorWhiteARGB8: return GColorWhite; + case GColorBlackARGB8: return GColorBlack; + case GColorClearARGB8: return GColorClear; + default: return fallback; + } +} + static inline GColor8 GColorGet8(GColor color) { switch (color) { case GColorWhite: return GColor8White; @@ -32,13 +41,17 @@ static inline bool GColor8Eq(GColor8 color, GColor other) { #else -static inline bool GColor8Eq(GColor8 color, GColor other) { - return (color.argb == other.argb); +static inline GColor GColor8Get(GColor8 color) { + return color; } -static inline GColor GColor8Get(GColor8 color) { +static inline GColor GColor8GetOr(GColor8 color, GColor8 fallback) { return color; } +static inline bool GColor8Eq(GColor8 color, GColor other) { + return (color.argb == other.argb); +} + #endif From fbe10ab19d1107b6f4005a0eb18951c1555777d3 Mon Sep 17 00:00:00 2001 From: Florida Ivanne Elago Date: Thu, 16 Apr 2015 19:02:27 -0700 Subject: [PATCH 490/791] Fix broken link for Simply.js --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 609333b9..663b8436 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ Coming Soon! ## Acknowledgements -Pebble.js started as [Simply.JS](http://www.simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! +Pebble.js started as [Simply.JS](http://simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). From 47f10f51f3eaedb57b171e96bdb9912c1bc69132 Mon Sep 17 00:00:00 2001 From: Alessio Bogon Date: Tue, 21 Apr 2015 15:08:35 +0200 Subject: [PATCH 491/791] Fix a bug preventing the deletion of items from the localStorage --- src/js/settings/settings.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index 37f9b866..8aeeef9b 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -106,12 +106,10 @@ var makeDataAccessor = function(type, path) { } if (typeof field !== 'object' && value === undefined || value === null) { delete data[field]; - return; } var def = myutil.toObject(field, value); util2.copy(def, data); Settings._saveData(path, type); - return value; }; }; From 5fdbdbae2df626a4558aea899ee4cd900295d96f Mon Sep 17 00:00:00 2001 From: Alessio Bogon Date: Tue, 21 Apr 2015 20:00:58 +0200 Subject: [PATCH 492/791] Wakeup API support for timeline actions --- src/js/ui/simply-pebble.js | 7 +++++-- src/js/wakeup/wakeup.js | 13 ++++++++----- src/simply/simply_wakeup.c | 10 ++++++++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 52f58332..14509361 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -253,6 +253,7 @@ var LaunchReasonTypes = [ 'wakeup', 'worker', 'quickLaunch', + 'timelineAction' ]; var LaunchReasonType = makeArrayType(LaunchReasonTypes); @@ -334,6 +335,7 @@ var ReadyPacket = new struct([ var LaunchReasonPacket = new struct([ [Packet, 'packet'], ['uint32', 'reason', LaunchReasonType], + ['uint32', 'args'], ['uint32', 'time'], ['bool', 'isTimezone'], ]); @@ -1139,6 +1141,7 @@ var toArrayBuffer = function(array, length) { SimplyPebble.onLaunchReason = function(packet) { var reason = LaunchReasonTypes[packet.reason()]; + var args = packet.args() var remoteTime = packet.time(); var isTimezone = packet.isTimezone(); if (isTimezone) { @@ -1149,7 +1152,7 @@ SimplyPebble.onLaunchReason = function(packet) { state.timeOffset = Math.round((remoteTime - time) / resolution) * resolution; } if (reason !== 'wakeup') { - Wakeup.emitWakeup('noWakeup', 0); + Wakeup.emitWakeup({reason: reason, args: args}); } }; @@ -1204,7 +1207,7 @@ SimplyPebble.onPacket = function(buffer, offset) { SimplyPebble.onWakeupSetResult(packet); break; case WakeupEventPacket: - Wakeup.emitWakeup(packet.id(), packet.cookie()); + Wakeup.emitWakeup({reason: 'wakeup', id: packet.id(), cookie: packet.cookie()}); break; case WindowHideEventPacket: ImageService.markAllUnloaded(); diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js index 1e4defc0..26959e3a 100644 --- a/src/js/wakeup/wakeup.js +++ b/src/js/wakeup/wakeup.js @@ -132,18 +132,21 @@ Wakeup.emitSetResult = function(id, cookie) { return req.callback(e); }; -Wakeup.emitWakeup = function(id, cookie) { +Wakeup.emitWakeup = function(opt) { var e; - if (typeof id === 'number') { - e = this._makeWakeupEvent(id, cookie); + if (opt.reason === 'wakeup'){ + e = this._makeWakeupEvent(opt.id, opt.cookie); e.wakeup = true; + + delete this.state.wakeups[opt.id]; } else { e = { wakeup: false, - }; + reason: opt.reason, + args: opt.args + } } - delete this.state.wakeups[id]; this._saveData(); this._launchEvent = e; diff --git a/src/simply/simply_wakeup.c b/src/simply/simply_wakeup.c index b52be3a0..99282de5 100644 --- a/src/simply/simply_wakeup.c +++ b/src/simply/simply_wakeup.c @@ -11,6 +11,7 @@ typedef struct LaunchReasonPacket LaunchReasonPacket; struct __attribute__((__packed__)) LaunchReasonPacket { Packet packet; uint32_t reason; + uint32_t args; uint32_t time; uint8_t is_timezone:8; }; @@ -46,11 +47,12 @@ struct WakeupSetContext { int32_t cookie; }; -static bool send_launch_reason(AppLaunchReason reason) { +static bool send_launch_reason(AppLaunchReason reason, uint32_t args) { LaunchReasonPacket packet = { .packet.type = CommandLaunchReason, .packet.length = sizeof(packet), .reason = reason, + .args = args, .time = time(NULL), .is_timezone = clock_is_timezone_set(), }; @@ -78,8 +80,12 @@ static void wakeup_set_timer_callback(void *data) { static void process_launch_reason() { AppLaunchReason reason = launch_reason(); + uint32_t args = 0; +#ifdef PBL_PLATFORM_BASALT + args = launch_get_args(); +#endif - send_launch_reason(reason); + send_launch_reason(reason, args); WakeupId wakeup_id; int32_t cookie; From cd2a2ddba6a83631b440a7bfdee385c6e0e65e3d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 27 Apr 2015 00:04:58 -0700 Subject: [PATCH 493/791] Fix menu layer icons to be alpha blended. Addresses #42 --- src/simply/simply_menu.c | 2 ++ src/util/graphics.h | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index f1f8e5bd..79520902 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -6,6 +6,7 @@ #include "simply.h" +#include "util/graphics.h" #include "util/menu_layer.h" #include "util/string.h" @@ -305,6 +306,7 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI list1_prepend(&self->menu_layer.items, &item->node); GBitmap *bitmap = simply_res_get_image(self->window.simply->res, item->icon); + graphics_context_set_alpha_blended(ctx, true); menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, bitmap); } diff --git a/src/util/graphics.h b/src/util/graphics.h index 6fd801ba..f0700c15 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -6,12 +6,18 @@ #ifndef PBL_COLOR +#define GCompOpAlphaBlend GCompOpAnd + static inline GBitmap *gbitmap_create_blank_with_format(GSize size, GBitmapFormat format) { return gbitmap_create_blank(size); } #define gbitmap_create_blank gbitmap_create_blank_with_format +#else + +#define GCompOpAlphaBlend GCompOpSet + #endif static inline GRect grect_center_rect(const GRect *rect_a, const GRect *rect_b) { @@ -28,3 +34,12 @@ static inline void graphics_draw_bitmap_centered(GContext *ctx, GBitmap *bitmap, GRect bounds = gbitmap_get_bounds(bitmap); graphics_draw_bitmap_in_rect(ctx, bitmap, grect_center_rect(&frame, &bounds)); } + +static inline void graphics_context_set_alpha_blended(GContext *ctx, bool enable) { + if (enable) { + graphics_context_set_compositing_mode(ctx, GCompOpAlphaBlend); + } else { + graphics_context_set_compositing_mode(ctx, GCompOpAssign); + } +} + From 318198f307099e64365edd811706e5ebdf486912 Mon Sep 17 00:00:00 2001 From: Jack Dalton Date: Sat, 2 May 2015 21:29:54 -0600 Subject: [PATCH 494/791] Add XML response type --- src/js/lib/ajax.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/js/lib/ajax.js b/src/js/lib/ajax.js index 5aa5c7b0..108152c2 100644 --- a/src/js/lib/ajax.js +++ b/src/js/lib/ajax.js @@ -87,6 +87,8 @@ var ajax = function(opt, success, failure) { if (opt.type === 'json') { req.setRequestHeader('Content-Type', 'application/json'); data = JSON.stringify(opt.data); + } else if (opt.type === 'xml') { + req.setRequestHeader('Content-Type', 'text/xml'); } else if (opt.type !== 'text') { req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); data = formify(opt.data); @@ -101,6 +103,8 @@ var ajax = function(opt, success, failure) { try { if (opt.type === 'json') { body = JSON.parse(body); + } else if (op.type === 'xml') { + body = req.responseXML; } else if (opt.type === 'form') { body = deformify(body); } From ff7fa01565f49410362ad3430f57f84e8e0256ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Tom=C3=A0s?= Date: Sun, 3 May 2015 16:47:57 +0100 Subject: [PATCH 495/791] Typo on Cobalt Blue name --- src/js/ui/simply-pebble.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 52f58332..4074d303 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -77,7 +77,7 @@ var colorMap = { 'blue': 0xC3, 'darkGreen': 0xC4, 'midnightGreen': 0xC5, - 'colbaltBlue': 0xC6, + 'cobaltBlue': 0xC6, 'blueMoon': 0xC7, 'islamicGreen': 0xC8, 'jaegerGreen': 0xC9, From f4d5e079a7a226b51c03d4c4eab4a0b07edc6355 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 3 May 2015 16:34:47 -0700 Subject: [PATCH 496/791] Add GColor8 inverter layer --- src/util/inverter_layer.h | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/util/inverter_layer.h diff --git a/src/util/inverter_layer.h b/src/util/inverter_layer.h new file mode 100644 index 00000000..76a6f0f8 --- /dev/null +++ b/src/util/inverter_layer.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#ifdef PBL_COLOR + +typedef struct InverterLayer InverterLayer; +struct InverterLayer; + +static void inverter_layer_update_proc(Layer *layer, GContext *ctx) { + GBitmap *frame_buffer = graphics_capture_frame_buffer(ctx); + const size_t bytes_per_row = gbitmap_get_bytes_per_row(frame_buffer); + GColor8 *data = (GColor8 *)gbitmap_get_data(frame_buffer); + + // This layer must not be nested in a layer not positioned at origin + GRect frame = layer_get_frame(layer); + + const int16_t min_x = frame.origin.x; + const int16_t max_x = frame.size.w + min_x; + const int16_t min_y = frame.origin.y; + const int16_t max_y = frame.size.h + min_y; + + data += bytes_per_row * min_y; + for (int16_t y = min_y; y < max_y; y++) { + for (int16_t x = min_x; x < max_x; x++) { + data[x] = (GColor8) { .argb = ~data[x].argb }; + } + data += bytes_per_row; + } + + graphics_release_frame_buffer(ctx, frame_buffer); +} + +static inline InverterLayer *inverter_layer_create(GRect bounds) { + Layer *layer = layer_create(bounds); + layer_set_update_proc(layer, inverter_layer_update_proc); + return (InverterLayer *)layer; +} + +static inline void inverter_layer_destroy(InverterLayer *inverter_layer) { + layer_destroy((Layer *)inverter_layer); +} + +static inline Layer *inverter_layer_get_layer(InverterLayer *inverter_layer) { + return (Layer *)inverter_layer; +} + +#endif From d1bd3077acdc0c39f64f9b3ba746ea9ead55bff1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 3 May 2015 16:35:07 -0700 Subject: [PATCH 497/791] Use util inverter layer --- src/simply/simply_stage.c | 1 + src/simply/simply_stage.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index cbbf8442..fe575087 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -10,6 +10,7 @@ #include "util/color.h" #include "util/compat.h" #include "util/graphics.h" +#include "util/inverter_layer.h" #include "util/memory.h" #include "util/string.h" #include "util/window.h" diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index 9f4368c3..e28d0abd 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -6,6 +6,7 @@ #include "simply.h" +#include "util/inverter_layer.h" #include "util/list1.h" #include "util/color.h" From 399c73fa9e7b30e9d2c2cb2982fbe15658a04d6b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 3 May 2015 17:09:56 -0700 Subject: [PATCH 498/791] Update compatibility headers to support SDK 3.0 beta10 and SDK 2.9 --- src/util/compat.h | 42 +++++++++++++++++++++++++++++++----------- src/util/graphics.h | 6 ------ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/util/compat.h b/src/util/compat.h index 54b9b054..9dbfe640 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -6,6 +6,29 @@ * When possible, they are copied directly without modification. */ +// Compatibility definitions for 2.9 +#if !defined(PBL_PLATFORM_APLITE) && !defined(PBL_PLATFORM_BASALT) + +//! The format of a GBitmap can either be 1-bit or 8-bit. +typedef enum GBitmapFormat { + GBitmapFormat1Bit = 0, //row_size_bytes) @@ -84,3 +96,11 @@ typedef enum GBitmapFormat { #endif #endif + +// Legacy definitions for basalt on 3.0 +// These should eventually be removed in the future +#ifdef PBL_PLATFORM_BASALT + +#define window_set_fullscreen(window, fullscreen) + +#endif diff --git a/src/util/graphics.h b/src/util/graphics.h index f0700c15..44be1162 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -8,12 +8,6 @@ #define GCompOpAlphaBlend GCompOpAnd -static inline GBitmap *gbitmap_create_blank_with_format(GSize size, GBitmapFormat format) { - return gbitmap_create_blank(size); -} - -#define gbitmap_create_blank gbitmap_create_blank_with_format - #else #define GCompOpAlphaBlend GCompOpSet From 1cd78585f239dce3a08fbdc31129e756903e88e5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 3 May 2015 21:37:20 -0700 Subject: [PATCH 499/791] Add util status bar layer --- src/util/status_bar_layer.h | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/util/status_bar_layer.h diff --git a/src/util/status_bar_layer.h b/src/util/status_bar_layer.h new file mode 100644 index 00000000..d9b3d204 --- /dev/null +++ b/src/util/status_bar_layer.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#ifndef PBL_PLATFORM_BASALT + +typedef struct StatusBarLayer StatusBarLayer; +struct StatusBarLayer; + +static inline StatusBarLayer *status_bar_layer_create(void) { + return NULL; +} + +static inline void status_bar_layer_destroy(StatusBarLayer *status_bar_layer) { +} + +static inline Layer *status_bar_layer_get_layer(StatusBarLayer *status_bar_layer) { + return (Layer *)status_bar_layer; +} + +static inline void status_bar_layer_add_to_window(Window *window, StatusBarLayer *status_bar_layer) { + window_set_fullscreen(window, false); +} + +static inline void status_bar_layer_remove_from_window(Window *window, StatusBarLayer *status_bar_layer) { + window_set_fullscreen(window, true); +} + +#else + +static inline void status_bar_layer_add_to_window(Window *window, StatusBarLayer *status_bar_layer) { + layer_add_child(window_get_root_layer(window), status_bar_layer_get_layer(status_bar_layer)); +} + +static inline void status_bar_layer_remove_from_window(Window *window, StatusBarLayer *status_bar_layer) { + layer_remove_from_parent(status_bar_layer_get_layer(status_bar_layer)); +} + +#endif From 5fbdd9f672c09bf7f0fca0e5260ef340c2367ab3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 3 May 2015 21:41:07 -0700 Subject: [PATCH 500/791] Use util status bar layer --- src/simply/simply_window.c | 13 ++++++++++++- src/simply/simply_window.h | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index da8a05da..0a7155ef 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -9,6 +9,7 @@ #include "util/graphics.h" #include "util/scroll_layer.h" +#include "util/status_bar_layer.h" #include "util/string.h" #include @@ -106,7 +107,11 @@ void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { return; } - window_set_fullscreen(self->window, is_fullscreen); + if (is_fullscreen) { + status_bar_layer_remove_from_window(self->window, self->status_bar_layer); + } else { + status_bar_layer_add_to_window(self->window, self->status_bar_layer); + } if (!self->layer) { return; @@ -316,6 +321,9 @@ SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { window_set_background_color(window, GColorClear); window_set_click_config_provider_with_context(window, click_config_provider, self); + self->status_bar_layer = status_bar_layer_create(); + status_bar_layer_add_to_window(window, self->status_bar_layer); + ActionBarLayer *action_bar_layer = self->action_bar_layer = action_bar_layer_create(); action_bar_layer_set_context(action_bar_layer, self); @@ -330,6 +338,9 @@ void simply_window_deinit(SimplyWindow *self) { action_bar_layer_destroy(self->action_bar_layer); self->action_bar_layer = NULL; + status_bar_layer_destroy(self->status_bar_layer); + self->status_bar_layer = NULL; + window_destroy(self->window); self->window = NULL; } diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index 0a4300ba..e7b87911 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -5,6 +5,7 @@ #include "simply.h" #include "util/color.h" +#include "util/status_bar_layer.h" #include @@ -13,6 +14,7 @@ typedef struct SimplyWindow SimplyWindow; struct SimplyWindow { Simply *simply; Window *window; + StatusBarLayer *status_bar_layer; ScrollLayer *scroll_layer; Layer *layer; ActionBarLayer *action_bar_layer; From 5c8b22c9c15a957184b52bc714963e0a8677075d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 3 May 2015 21:41:18 -0700 Subject: [PATCH 501/791] Default all windows to not be fullscreen --- src/js/ui/card.js | 1 + src/js/ui/menu.js | 1 + src/js/ui/window.js | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/ui/card.js b/src/js/ui/card.js index 1f6a174d..adba1695 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -65,6 +65,7 @@ var clearableProps = textProps.concat(imageProps); var defaults = { backgroundColor: 'white', + fullscreen: false, }; var Card = function(cardDef) { diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 3ec2549e..45443715 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -7,6 +7,7 @@ var simply = require('ui/simply'); var defaults = { backgroundColor: 'white', + fullscreen: false, }; var Menu = function(menuDef) { diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 822c85c8..457f8e78 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -282,7 +282,7 @@ Window.prototype._emitShow = function(type) { Window.emit = function(type, subtype, e) { var wind = WindowStack.top(); - if (window) { + if (wind) { return wind._emit(type, subtype, e); } }; From ed18216f566c04036e926a1ffac83f40eb86ce7b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 3 May 2015 21:41:39 -0700 Subject: [PATCH 502/791] Update demo to use a fullscreen stage --- src/js/app.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 87d980dc..11d22587 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -37,9 +37,11 @@ main.on('click', 'up', function(e) { }); main.on('click', 'select', function(e) { - var wind = new UI.Window(); + var wind = new UI.Window({ + fullscreen: true, + }); var textfield = new UI.Text({ - position: new Vector2(0, 50), + position: new Vector2(0, 65), size: new Vector2(144, 30), font: 'gothic-24-bold', text: 'Text Anywhere!', From 622a7d692ac99d0f44cc90920ec034dc4c007165 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 3 May 2015 23:36:05 -0700 Subject: [PATCH 503/791] Add status bar layer on basalt --- src/simply/simply_menu.c | 4 ++-- src/simply/simply_stage.c | 4 ++-- src/simply/simply_ui.c | 4 ++-- src/simply/simply_window.c | 25 +++++++++++++++++++++---- src/simply/simply_window.h | 5 ++++- src/util/compat.h | 2 ++ src/util/status_bar_layer.h | 37 ++++++++++++++++++++++++++++++++++++- 7 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 79520902..7f86d8d6 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -358,12 +358,12 @@ static void window_load(Window *window) { static void window_appear(Window *window) { SimplyMenu *self = window_get_user_data(window); - simply_window_stack_send_show(self->window.simply->window_stack, &self->window); + simply_window_appear(&self->window); } static void window_disappear(Window *window) { SimplyMenu *self = window_get_user_data(window); - simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); + simply_window_disappear(&self->window); simply_res_clear(self->window.simply->res); simply_menu_clear(self); diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index fe575087..161cccdc 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -440,14 +440,14 @@ static void window_load(Window *window) { static void window_appear(Window *window) { SimplyStage *self = window_get_user_data(window); - simply_window_stack_send_show(self->window.simply->window_stack, &self->window); + simply_window_appear(&self->window); simply_stage_update_ticker(self); } static void window_disappear(Window *window) { SimplyStage *self = window_get_user_data(window); - simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); + simply_window_disappear(&self->window); simply_res_clear(self->window.simply->res); simply_stage_clear(self); diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 494632d7..7c3177fe 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -296,12 +296,12 @@ static void window_load(Window *window) { static void window_appear(Window *window) { SimplyUi *self = window_get_user_data(window); - simply_window_stack_send_show(self->window.simply->window_stack, &self->window); + simply_window_appear(&self->window); } static void window_disappear(Window *window) { SimplyUi *self = window_get_user_data(window); - simply_window_stack_send_hide(self->window.simply->window_stack, &self->window); + simply_window_disappear(&self->window); simply_res_clear(self->window.simply->res); } diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 0a7155ef..16d634fa 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -102,8 +102,8 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { layer_mark_dirty(self->layer); } -void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { - if (self->is_fullscreen == is_fullscreen) { +void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen, bool force) { + if (!force && self->is_fullscreen == is_fullscreen) { return; } @@ -117,10 +117,11 @@ void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { return; } - GRect frame = layer_get_frame(window_get_root_layer(self->window)); + GRect frame = { GPointZero, layer_get_frame(window_get_root_layer(self->window)).size }; scroll_layer_set_frame(self->scroll_layer, frame); layer_set_frame(self->layer, frame); +#ifdef PBL_SDK_2 if (!window_stack_contains_window(self->window)) { return; } @@ -133,6 +134,7 @@ void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { window_stack_remove(window, false); window_destroy(window); self->id = id; +#endif } void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color) { @@ -250,10 +252,23 @@ void simply_window_load(SimplyWindow *self) { layer_add_child(window_layer, scroll_base_layer); scroll_layer_set_context(scroll_layer, self); + scroll_layer_set_shadow_hidden(scroll_layer, true); simply_window_set_action_bar(self, self->is_action_bar); } +void simply_window_appear(SimplyWindow *self) { + simply_window_stack_send_show(self->simply->window_stack, self); + if (self->is_status_bar) { + simply_window_set_fullscreen(self, false, true); + } +} + +void simply_window_disappear(SimplyWindow *self) { + simply_window_stack_send_hide(self->simply->window_stack, self); + simply_window_set_fullscreen(self, true, true); +} + void simply_window_unload(SimplyWindow *self) { scroll_layer_destroy(self->scroll_layer); self->scroll_layer = NULL; @@ -266,8 +281,9 @@ static void handle_window_props_packet(Simply *simply, Packet *data) { return; } window->id = packet->id; + window->is_status_bar = !packet->fullscreen; simply_window_set_background_color(window, packet->background_color); - simply_window_set_fullscreen(window, packet->fullscreen); + simply_window_set_fullscreen(window, packet->fullscreen, false); simply_window_set_scrollable(window, packet->scrollable); } @@ -323,6 +339,7 @@ SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { self->status_bar_layer = status_bar_layer_create(); status_bar_layer_add_to_window(window, self->status_bar_layer); + self->is_status_bar = true; ActionBarLayer *action_bar_layer = self->action_bar_layer = action_bar_layer_create(); action_bar_layer_set_context(action_bar_layer, self); diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index e7b87911..4cd16e71 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -24,6 +24,7 @@ struct SimplyWindow { bool is_fullscreen:1; bool is_scrollable:1; bool is_action_bar:1; + bool is_status_bar:1; }; SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply); @@ -33,11 +34,13 @@ void simply_window_hide(SimplyWindow *self); void simply_window_load(SimplyWindow *self); void simply_window_unload(SimplyWindow *self); +void simply_window_appear(SimplyWindow *self); +void simply_window_disappear(SimplyWindow *self); void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *context); void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable); -void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen); +void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen, bool force); void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color); void simply_window_set_button(SimplyWindow *self, ButtonId button, bool enable); diff --git a/src/util/compat.h b/src/util/compat.h index 9dbfe640..94c29458 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -9,6 +9,8 @@ // Compatibility definitions for 2.9 #if !defined(PBL_PLATFORM_APLITE) && !defined(PBL_PLATFORM_BASALT) +#define PBL_SDK_2 + //! The format of a GBitmap can either be 1-bit or 8-bit. typedef enum GBitmapFormat { GBitmapFormat1Bit = 0, // +#include "simply/simply.h" + #ifndef PBL_PLATFORM_BASALT typedef struct StatusBarLayer StatusBarLayer; @@ -29,11 +31,44 @@ static inline void status_bar_layer_remove_from_window(Window *window, StatusBar #else static inline void status_bar_layer_add_to_window(Window *window, StatusBarLayer *status_bar_layer) { - layer_add_child(window_get_root_layer(window), status_bar_layer_get_layer(status_bar_layer)); + Layer *status_bar_base_layer = status_bar_layer_get_layer(status_bar_layer); + GRect status_frame = layer_get_frame(status_bar_base_layer); + status_frame.origin.y = -STATUS_BAR_LAYER_HEIGHT; + layer_set_frame(status_bar_base_layer, status_frame); + + Layer *window_layer = window_get_root_layer(window); + layer_add_child(window_layer, status_bar_base_layer); + + GRect bounds = layer_get_bounds(window_layer); + if (bounds.origin.y == 0) { + bounds.origin.y = STATUS_BAR_LAYER_HEIGHT; + bounds.size.h -= STATUS_BAR_LAYER_HEIGHT; + + GRect frame = layer_get_frame(window_layer); + frame.size.h = bounds.size.h; + + layer_set_frame(window_layer, frame); + layer_set_bounds(window_layer, bounds); + layer_set_clips(window_layer, false); + } } static inline void status_bar_layer_remove_from_window(Window *window, StatusBarLayer *status_bar_layer) { layer_remove_from_parent(status_bar_layer_get_layer(status_bar_layer)); + Layer *window_layer = window_get_root_layer(window); + + GRect bounds = layer_get_bounds(window_layer); + if (bounds.origin.y == STATUS_BAR_LAYER_HEIGHT) { + bounds.origin.y = 0; + bounds.size.h += STATUS_BAR_LAYER_HEIGHT; + + GRect frame = layer_get_frame(window_layer); + frame.size.h = bounds.size.h; + + layer_set_frame(window_layer, frame); + layer_set_bounds(window_layer, bounds); + layer_set_clips(window_layer, true); + } } #endif From 67c6dd4938d97e8063d614e3e7ab8f0cb0ee3e0e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 May 2015 01:12:22 -0700 Subject: [PATCH 504/791] Fix animation teardown to be compatible with SDK 3.0. Fixes #52 --- src/simply/simply_stage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 161cccdc..88cf40fb 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -389,7 +389,7 @@ SimplyAnimation *simply_stage_animate_element(SimplyStage *self, static const PropertyAnimationImplementation implementation = { .base = { .update = (AnimationUpdateImplementation) property_animation_update_grect, - .teardown = (AnimationTeardownImplementation) free, + .teardown = (AnimationTeardownImplementation) animation_destroy, }, .accessors = { .setter = { .grect = (const GRectSetter) element_frame_setter }, From 88efa822c17b1456ed5b52ce20c89b9e6138f709 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 May 2015 02:42:58 -0700 Subject: [PATCH 505/791] Fix inverter layer to draw within the drawing box --- src/util/inverter_layer.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/util/inverter_layer.h b/src/util/inverter_layer.h index 76a6f0f8..b8807d03 100644 --- a/src/util/inverter_layer.h +++ b/src/util/inverter_layer.h @@ -2,6 +2,8 @@ #include +#include "util/math.h" + #ifdef PBL_COLOR typedef struct InverterLayer InverterLayer; @@ -12,13 +14,22 @@ static void inverter_layer_update_proc(Layer *layer, GContext *ctx) { const size_t bytes_per_row = gbitmap_get_bytes_per_row(frame_buffer); GColor8 *data = (GColor8 *)gbitmap_get_data(frame_buffer); + Layer *window_layer = window_get_root_layer(layer_get_window(layer)); + GRect window_bounds = layer_get_bounds(window_layer); + GRect window_frame = layer_get_frame(window_layer); + GRect drawing_box = { + .origin.x = window_frame.origin.x + window_bounds.origin.x, + .origin.y = window_frame.origin.y + window_bounds.origin.y, + .size = window_frame.size, + }; + // This layer must not be nested in a layer not positioned at origin GRect frame = layer_get_frame(layer); - const int16_t min_x = frame.origin.x; - const int16_t max_x = frame.size.w + min_x; - const int16_t min_y = frame.origin.y; - const int16_t max_y = frame.size.h + min_y; + const int16_t min_x = MAX(0, drawing_box.origin.x + frame.origin.x); + const int16_t min_y = MAX(0, drawing_box.origin.y + frame.origin.y); + const int16_t max_x = MIN(drawing_box.size.w, min_x + frame.size.w); + const int16_t max_y = MIN(drawing_box.size.h, min_y + frame.size.h); data += bytes_per_row * min_y; for (int16_t y = min_y; y < max_y; y++) { From b8aed6c2a79faed50b337a326093a84419898760 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 May 2015 02:44:31 -0700 Subject: [PATCH 506/791] Fix status bar to preserve offset between window transitions --- src/simply/simply_window.c | 30 ++++++++++++++---------------- src/simply/simply_window.h | 2 +- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 16d634fa..9fdabbda 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -91,7 +91,7 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { } if (!is_scrollable) { - GRect bounds = layer_get_bounds(window_get_root_layer(self->window)); + GRect bounds = { GPointZero, layer_get_bounds(window_get_root_layer(self->window)).size }; layer_set_bounds(self->layer, bounds); // TODO: change back to animated when a closing animated scroll doesn't cause a crash const bool animated = false; @@ -102,15 +102,13 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { layer_mark_dirty(self->layer); } -void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen, bool force) { - if (!force && self->is_fullscreen == is_fullscreen) { - return; - } - - if (is_fullscreen) { +void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { + if (is_fullscreen && self->is_status_bar) { status_bar_layer_remove_from_window(self->window, self->status_bar_layer); - } else { + self->is_status_bar = false; + } else if (!is_fullscreen && !self->is_status_bar) { status_bar_layer_add_to_window(self->window, self->status_bar_layer); + self->is_status_bar = true; } if (!self->layer) { @@ -259,14 +257,14 @@ void simply_window_load(SimplyWindow *self) { void simply_window_appear(SimplyWindow *self) { simply_window_stack_send_show(self->simply->window_stack, self); - if (self->is_status_bar) { - simply_window_set_fullscreen(self, false, true); - } } void simply_window_disappear(SimplyWindow *self) { simply_window_stack_send_hide(self->simply->window_stack, self); - simply_window_set_fullscreen(self, true, true); + +#ifdef PBL_PLATFORM_BASALT + simply_window_set_fullscreen(self, true); +#endif } void simply_window_unload(SimplyWindow *self) { @@ -281,9 +279,8 @@ static void handle_window_props_packet(Simply *simply, Packet *data) { return; } window->id = packet->id; - window->is_status_bar = !packet->fullscreen; simply_window_set_background_color(window, packet->background_color); - simply_window_set_fullscreen(window, packet->fullscreen, false); + simply_window_set_fullscreen(window, packet->fullscreen); simply_window_set_scrollable(window, packet->scrollable); } @@ -338,8 +335,9 @@ SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { window_set_click_config_provider_with_context(window, click_config_provider, self); self->status_bar_layer = status_bar_layer_create(); - status_bar_layer_add_to_window(window, self->status_bar_layer); - self->is_status_bar = true; + status_bar_layer_remove_from_window(window, self->status_bar_layer); + self->is_status_bar = false; + self->is_fullscreen = true; ActionBarLayer *action_bar_layer = self->action_bar_layer = action_bar_layer_create(); action_bar_layer_set_context(action_bar_layer, self); diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index 4cd16e71..83950005 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -40,7 +40,7 @@ void simply_window_disappear(SimplyWindow *self); void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *context); void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable); -void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen, bool force); +void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen); void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color); void simply_window_set_button(SimplyWindow *self, ButtonId button, bool enable); From 2b73ea2ab48d54734a9629c76538162ad2a696fc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 4 May 2015 03:14:38 -0700 Subject: [PATCH 507/791] Change gcolor utility functions to snake case --- src/simply/simply_menu.c | 2 +- src/simply/simply_stage.c | 12 ++++++------ src/simply/simply_ui.c | 5 +++-- src/simply/simply_window.c | 2 +- src/util/color.h | 16 ++++++++-------- src/util/compat.h | 2 +- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 7f86d8d6..5f72ea2a 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -410,7 +410,7 @@ static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { static void handle_menu_props_packet(Simply *simply, Packet *data) { MenuPropsPacket *packet = (MenuPropsPacket*) data; simply_menu_set_num_sections(simply->menu, packet->num_sections); - window_set_background_color(simply->menu->window.window, GColor8Get(packet->background_color)); + window_set_background_color(simply->menu->window.window, gcolor8_get(packet->background_color)); } static void handle_menu_section_packet(Simply *simply, Packet *data) { diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 88cf40fb..12049d35 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -172,14 +172,14 @@ void simply_stage_clear(SimplyStage *self) { static void rect_element_draw_background(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { if (element->background_color.a) { - graphics_context_set_fill_color(ctx, GColor8Get(element->background_color)); + graphics_context_set_fill_color(ctx, gcolor8_get(element->background_color)); graphics_fill_rect(ctx, element->frame, element->radius, GCornersAll); } } static void rect_element_draw_border(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { if (element->border_color.a) { - graphics_context_set_stroke_color(ctx, GColor8Get(element->border_color)); + graphics_context_set_stroke_color(ctx, gcolor8_get(element->border_color)); graphics_draw_round_rect(ctx, element->frame, element->radius); } } @@ -191,11 +191,11 @@ static void rect_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRec static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementCircle *element) { if (element->background_color.a) { - graphics_context_set_fill_color(ctx, GColor8Get(element->background_color)); + graphics_context_set_fill_color(ctx, gcolor8_get(element->background_color)); graphics_fill_circle(ctx, element->frame.origin, element->radius); } if (element->border_color.a) { - graphics_context_set_stroke_color(ctx, GColor8Get(element->border_color)); + graphics_context_set_stroke_color(ctx, gcolor8_get(element->border_color)); graphics_draw_circle(ctx, element->frame.origin, element->radius); } } @@ -216,7 +216,7 @@ static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementTex text = format_time(text); } GFont font = element->font ? element->font : fonts_get_system_font(FONT_KEY_GOTHIC_14); - graphics_context_set_text_color(ctx, GColor8Get(element->text_color)); + graphics_context_set_text_color(ctx, gcolor8_get(element->text_color)); graphics_draw_text(ctx, text, font, element->frame, element->overflow_mode, element->alignment, NULL); } } @@ -244,7 +244,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { frame.origin.x = -frame.origin.x; frame.origin.y = -frame.origin.y; - graphics_context_set_fill_color(ctx, GColor8Get(self->window.background_color)); + graphics_context_set_fill_color(ctx, gcolor8_get(self->window.background_color)); graphics_fill_rect(ctx, frame, 0, GCornerNone); SimplyElementCommon *element = (SimplyElementCommon*) self->stage_layer.elements; diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 7c3177fe..e135d964 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -118,7 +118,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { const SimplyStyle *style = self->ui_layer.style; GFont title_font = fonts_get_system_font(style->title_font); GFont subtitle_font = fonts_get_system_font(style->subtitle_font); - GFont body_font = self->ui_layer.custom_body_font ? self->ui_layer.custom_body_font : fonts_get_system_font(style->body_font); + GFont body_font = self->ui_layer.custom_body_font ? + self->ui_layer.custom_body_font : fonts_get_system_font(style->body_font); const int16_t margin_x = 5; const int16_t margin_y = 2; @@ -225,7 +226,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, GColorBlack); graphics_fill_rect(ctx, frame, 0, GCornerNone); - graphics_context_set_fill_color(ctx, GColor8GetOr(self->window.background_color, GColorWhite)); + graphics_context_set_fill_color(ctx, gcolor8_get_or(self->window.background_color, GColorWhite)); graphics_fill_rect(ctx, frame, 4, GCornersAll); if (title_icon) { diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 9fdabbda..26e57cf8 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -178,7 +178,7 @@ void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor8 b return; } - action_bar_layer_set_background_color(self->action_bar_layer, GColor8Get(background_color)); + action_bar_layer_set_background_color(self->action_bar_layer, gcolor8_get(background_color)); simply_window_set_action_bar(self, true); } diff --git a/src/util/color.h b/src/util/color.h index 1e01728b..696bad5e 100644 --- a/src/util/color.h +++ b/src/util/color.h @@ -10,7 +10,7 @@ #ifndef PBL_COLOR -static inline GColor GColor8Get(GColor8 color) { +static inline GColor gcolor8_get(GColor8 color) { switch (color.argb) { case GColorWhiteARGB8: return GColorWhite; case GColorBlackARGB8: return GColorBlack; @@ -18,7 +18,7 @@ static inline GColor GColor8Get(GColor8 color) { } } -static inline GColor GColor8GetOr(GColor8 color, GColor fallback) { +static inline GColor gcolor8_get_or(GColor8 color, GColor fallback) { switch (color.argb) { case GColorWhiteARGB8: return GColorWhite; case GColorBlackARGB8: return GColorBlack; @@ -27,7 +27,7 @@ static inline GColor GColor8GetOr(GColor8 color, GColor fallback) { } } -static inline GColor8 GColorGet8(GColor color) { +static inline GColor8 gcolor_get8(GColor color) { switch (color) { case GColorWhite: return GColor8White; case GColorBlack: return GColor8Black; @@ -35,21 +35,21 @@ static inline GColor8 GColorGet8(GColor color) { } } -static inline bool GColor8Eq(GColor8 color, GColor other) { - return (color.argb == GColorGet8(other).argb); +static inline bool gcolor8_equal(GColor8 color, GColor other) { + return (color.argb == gcolor_get8(other).argb); } #else -static inline GColor GColor8Get(GColor8 color) { +static inline GColor gcolor8_get(GColor8 color) { return color; } -static inline GColor GColor8GetOr(GColor8 color, GColor8 fallback) { +static inline GColor gcolor8_get_or(GColor8 color, GColor8 fallback) { return color; } -static inline bool GColor8Eq(GColor8 color, GColor other) { +static inline bool gcolor8_equal(GColor8 color, GColor other) { return (color.argb == other.argb); } diff --git a/src/util/compat.h b/src/util/compat.h index 94c29458..adf3180f 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -48,7 +48,7 @@ typedef union GColor8 { } GColor8; //! Convenience macro to enable use of SDK 3.0 function to compare equality of two colors. -#define GColorEq(a, b) ((a) == (b)) +#define gcolor_equal(a, b) ((a) == (b)) //! Convenience function to use SDK 3.0 function to get a `GBitmap`'s `row_size_bytes` field. From c0e99a1490ceafca98e5a7d441ac60b25c348f1f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 5 May 2015 02:31:34 -0700 Subject: [PATCH 508/791] Add Timeline module for handling timeline action launch events --- src/js/timeline/index.js | 5 +++++ src/js/timeline/timeline.js | 37 +++++++++++++++++++++++++++++++++++++ src/js/ui/simply-pebble.js | 9 ++++++--- src/js/wakeup/wakeup.js | 14 ++++++-------- src/simply/simply_wakeup.c | 5 +---- src/util/compat.h | 4 +++- 6 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 src/js/timeline/index.js create mode 100644 src/js/timeline/timeline.js diff --git a/src/js/timeline/index.js b/src/js/timeline/index.js new file mode 100644 index 00000000..89390e14 --- /dev/null +++ b/src/js/timeline/index.js @@ -0,0 +1,5 @@ +var Timeline = require('./timeline'); + +Timeline.init(); + +module.exports = Timeline; diff --git a/src/js/timeline/timeline.js b/src/js/timeline/timeline.js new file mode 100644 index 00000000..d1940ac5 --- /dev/null +++ b/src/js/timeline/timeline.js @@ -0,0 +1,37 @@ +var Timeline = module.exports; + +Timeline.init = function() { + this._launchCallbacks = []; +}; + +Timeline.launch = function(callback) { + if (this._launchEvent) { + callback(this._launchEvent); + } else { + this._launchCallbacks.push(callback); + } +}; + +Timeline.emitAction = function(args) { + var e; + if (args !== undefined) { + e = { + action: true, + launchCode: args, + }; + } else { + e = { + action: false, + }; + } + + this._launchEvent = e; + + var callbacks = this._launchCallbacks; + this._launchCallbacks = []; + for (var i = 0, ii = callbacks.length; i < ii; ++i) { + if (callbacks[i](e) === false) { + return false; + } + } +}; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 4bf59799..b5994c3c 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -2,6 +2,7 @@ var struct = require('struct'); var util2 = require('util2'); var myutil = require('myutil'); var Wakeup = require('wakeup'); +var Timeline = require('timeline'); var Resource = require('ui/resource'); var Accel = require('ui/accel'); var ImageService = require('ui/imageservice'); @@ -1151,8 +1152,10 @@ SimplyPebble.onLaunchReason = function(packet) { var resolution = 60 * 30; state.timeOffset = Math.round((remoteTime - time) / resolution) * resolution; } - if (reason !== 'wakeup') { - Wakeup.emitWakeup({reason: reason, args: args}); + if (reason === 'timelineAction') { + Timeline.emitAction(args); + } else { + Timeline.emitAction(); } }; @@ -1207,7 +1210,7 @@ SimplyPebble.onPacket = function(buffer, offset) { SimplyPebble.onWakeupSetResult(packet); break; case WakeupEventPacket: - Wakeup.emitWakeup({reason: 'wakeup', id: packet.id(), cookie: packet.cookie()}); + Wakeup.emitWakeup(packet.id(), packet.cookie()); break; case WindowHideEventPacket: ImageService.markAllUnloaded(); diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js index 26959e3a..2ad61819 100644 --- a/src/js/wakeup/wakeup.js +++ b/src/js/wakeup/wakeup.js @@ -132,19 +132,17 @@ Wakeup.emitSetResult = function(id, cookie) { return req.callback(e); }; -Wakeup.emitWakeup = function(opt) { +Wakeup.emitWakeup = function(id, cookie) { var e; - if (opt.reason === 'wakeup'){ - e = this._makeWakeupEvent(opt.id, opt.cookie); - e.wakeup = true; + if (id !== undefined) { + delete this.state.wakeups[id]; - delete this.state.wakeups[opt.id]; + e = this._makeWakeupEvent(id, cookie); + e.wakeup = true; } else { e = { wakeup: false, - reason: opt.reason, - args: opt.args - } + }; } this._saveData(); diff --git a/src/simply/simply_wakeup.c b/src/simply/simply_wakeup.c index 99282de5..5149df08 100644 --- a/src/simply/simply_wakeup.c +++ b/src/simply/simply_wakeup.c @@ -80,10 +80,7 @@ static void wakeup_set_timer_callback(void *data) { static void process_launch_reason() { AppLaunchReason reason = launch_reason(); - uint32_t args = 0; -#ifdef PBL_PLATFORM_BASALT - args = launch_get_args(); -#endif + uint32_t args = launch_get_args(); send_launch_reason(reason, args); diff --git a/src/util/compat.h b/src/util/compat.h index adf3180f..a2d8d757 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -6,7 +6,7 @@ * When possible, they are copied directly without modification. */ -// Compatibility definitions for 2.9 +// Compatibility definitions for aplite on 2.9 #if !defined(PBL_PLATFORM_APLITE) && !defined(PBL_PLATFORM_BASALT) #define PBL_SDK_2 @@ -28,6 +28,8 @@ static inline GBitmap *gbitmap_create_blank_with_format(GSize size, GBitmapForma #define gbitmap_create_blank gbitmap_create_blank_with_format +#define launch_get_args() ((uint32_t)0) + #endif // Compatibility definitions for aplite on 3.0 From dcd184da4b35aa8646e1f64fcf0a0cacbcb54855 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 5 May 2015 03:33:56 -0700 Subject: [PATCH 509/791] Fix SimplyPebble onLaunchReason to signal non-wakeup events Thanks youtux! --- src/js/ui/simply-pebble.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index b5994c3c..697559bb 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1157,6 +1157,9 @@ SimplyPebble.onLaunchReason = function(packet) { } else { Timeline.emitAction(); } + if (reason !== 'wakeup') { + Wakeup.emitWakeup(); + } }; SimplyPebble.onWakeupSetResult = function(packet) { From 9900c931b7f9cbc035999dc1b76baf016d0792ef Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 May 2015 02:04:55 -0700 Subject: [PATCH 510/791] Add aplite gbitmap palette compatibility definitions --- src/util/compat.h | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/util/compat.h b/src/util/compat.h index a2d8d757..59f5f515 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -20,8 +20,6 @@ typedef enum GBitmapFormat { GBitmapFormat4BitPalette, } GBitmapFormat; -#define GBITMAP_NATIVE_FORMAT GBitmapFormat1Bit - static inline GBitmap *gbitmap_create_blank_with_format(GSize size, GBitmapFormat format) { return gbitmap_create_blank(size); } @@ -32,9 +30,14 @@ static inline GBitmap *gbitmap_create_blank_with_format(GSize size, GBitmapForma #endif -// Compatibility definitions for aplite on 3.0 +// Compatibility definitions for aplite on all versions #ifndef PBL_COLOR +#define GBitmapFormat8Bit 1 +#define GBitmapFormat1BitPalette 2 +#define GBitmapFormat2BitPalette 3 +#define GBitmapFormat4BitPalette 4 + #define GColorWhiteARGB8 ((uint8_t)0b11111111) #define GColorBlackARGB8 ((uint8_t)0b11000000) #define GColorClearARGB8 ((uint8_t)0b00000000) @@ -84,6 +87,15 @@ typedef union GColor8 { __gbitmap_tmp_bmp->row_size_bytes = (rsb); \ }) +#ifndef gbitmap_get_palette +#define gbitmap_get_palette(bitmap) NULL +#endif + +#ifndef gbitmap_set_palette +#define gbitmap_set_palette(bitmap, palette, free_on_destroy) \ + ((void)(bitmap), (void)(palette), (void)(free_on_destroy)) +#endif + //! Convenience macro to use SDK 3.0 function to set a `PropertyAnimation`'s //! `values.from.grect` field. From 947eebec6c59a00fd389bd43c351151843616a0b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 May 2015 02:06:34 -0700 Subject: [PATCH 511/791] Add gbitmap black and white palette detection --- src/util/graphics.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/util/graphics.h b/src/util/graphics.h index 44be1162..140d8d64 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -1,6 +1,7 @@ #pragma once #include "util/compat.h" +#include "util/color.h" #include @@ -37,3 +38,11 @@ static inline void graphics_context_set_alpha_blended(GContext *ctx, bool enable } } +static inline bool gbitmap_is_palette_black_and_white(GBitmap *bitmap) { + if (!bitmap || gbitmap_get_format(bitmap) != GBitmapFormat1BitPalette) { + return false; + } + const GColor8 *palette = gbitmap_get_palette(bitmap); + return (gcolor8_equal(palette[0], GColorWhite) && gcolor8_equal(palette[1], GColorBlack)) || + (gcolor8_equal(palette[0], GColorBlack) && gcolor8_equal(palette[1], GColorWhite)); +} From 5a4a69681d3206e814b9a59ec78b69fd85bb2bed Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 May 2015 02:48:58 -0700 Subject: [PATCH 512/791] Change simply res image functions to return simply images --- src/simply/simply_menu.c | 7 +++++-- src/simply/simply_res.c | 17 ++++++++++------- src/simply/simply_res.h | 9 ++++++--- src/simply/simply_stage.c | 8 ++++---- src/simply/simply_ui.c | 18 +++++++++--------- src/simply/simply_window.c | 7 ++++--- src/util/compat.h | 2 ++ 7 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 5f72ea2a..462d81c3 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -77,6 +77,7 @@ struct __attribute__((__packed__)) MenuSelectionPacket { bool animated; }; + static void simply_menu_clear_section_items(SimplyMenu *self, int section_index); static void simply_menu_clear(SimplyMenu *self); @@ -296,6 +297,7 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI request_menu_section(self, cell_index->section); return; } + SimplyMenuItem *item = get_menu_item(self, cell_index->section, cell_index->row); if (!item) { request_menu_item(self, cell_index->section, cell_index->row); @@ -305,9 +307,10 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI list1_remove(&self->menu_layer.items, &item->node); list1_prepend(&self->menu_layer.items, &item->node); - GBitmap *bitmap = simply_res_get_image(self->window.simply->res, item->icon); + SimplyImage *image = simply_res_get_image(self->window.simply->res, item->icon); + graphics_context_set_alpha_blended(ctx, true); - menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, bitmap); + menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, image ? image->bitmap : NULL); } static void menu_select_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 1f4cf192..b6b5da8b 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -1,6 +1,8 @@ #include "simply_res.h" +#include "util/color.h" #include "util/graphics.h" +#include "util/memory.h" #include "util/window.h" #include @@ -12,6 +14,7 @@ static bool id_filter(List1Node *node, void *data) { static void destroy_image(SimplyRes *self, SimplyImage *image) { list1_remove(&self->images, &image->node); gbitmap_destroy(image->bitmap); + free(image->palette); } static void destroy_font(SimplyRes *self, SimplyFont *font) { @@ -20,7 +23,7 @@ static void destroy_font(SimplyRes *self, SimplyFont *font) { free(font); } -GBitmap *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { +SimplyImage *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { SimplyImage *image = malloc(sizeof(*image)); if (!image) { return NULL; @@ -41,10 +44,10 @@ GBitmap *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { window_stack_schedule_top_window_render(); - return image->bitmap; + return image; } -GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint8_t *pixels) { +SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint8_t *pixels) { SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); if (image) { @@ -69,7 +72,7 @@ GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16 window_stack_schedule_top_window_render(); - return image->bitmap; + return image; } void simply_res_remove_image(SimplyRes *self, uint32_t id) { @@ -79,18 +82,18 @@ void simply_res_remove_image(SimplyRes *self, uint32_t id) { } } -GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder) { +SimplyImage *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder) { if (!id) { return NULL; } SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); if (image) { - return image->bitmap; + return image; } if (id <= self->num_bundled_res) { return simply_res_add_bundled_image(self, id); } - if (!image && is_placeholder) { + if (is_placeholder) { return simply_res_add_image(self, id, 0, 0, NULL); } return NULL; diff --git a/src/simply/simply_res.h b/src/simply/simply_res.h index c849b5d4..8552a052 100644 --- a/src/simply/simply_res.h +++ b/src/simply/simply_res.h @@ -1,5 +1,6 @@ #pragma once +#include "util/color.h" #include "util/list1.h" #include @@ -37,6 +38,8 @@ struct SimplyImage { SimplyResItemCommonMember; uint8_t *bitmap_data; GBitmap *bitmap; + GColor8 *palette; + bool is_palette_black_and_white:1; }; typedef struct SimplyFont SimplyFont; @@ -50,9 +53,9 @@ SimplyRes *simply_res_create(); void simply_res_destroy(SimplyRes *self); void simply_res_clear(SimplyRes *self); -GBitmap *simply_res_add_bundled_image(SimplyRes *self, uint32_t id); -GBitmap *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint8_t *pixels); -GBitmap *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder); +SimplyImage *simply_res_add_bundled_image(SimplyRes *self, uint32_t id); +SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint8_t *pixels); +SimplyImage *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder); GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id); GFont simply_res_auto_font(SimplyRes *self, uint32_t id); diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 12049d35..c79ef15f 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -224,13 +224,13 @@ static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementTex static void image_element_draw(GContext *ctx, SimplyStage *self, SimplyElementImage *element) { graphics_context_set_compositing_mode(ctx, element->compositing); rect_element_draw_background(ctx, self, (SimplyElementRect*) element); - GBitmap *bitmap = simply_res_get_image(self->window.simply->res, element->image); - if (bitmap) { + SimplyImage *image = simply_res_get_image(self->window.simply->res, element->image); + if (image && image->bitmap) { GRect frame = element->frame; if (frame.size.w == 0 && frame.size.h == 0) { - frame = gbitmap_get_bounds(bitmap); + frame = gbitmap_get_bounds(image->bitmap); } - graphics_draw_bitmap_centered(ctx, bitmap, frame); + graphics_draw_bitmap_centered(ctx, image->bitmap, frame); } rect_element_draw_border(ctx, self, (SimplyElementRect*) element); graphics_context_set_compositing_mode(ctx, GCompOpAssign); diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index e135d964..40b7bfd8 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -149,11 +149,11 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { GPoint title_pos, subtitle_pos, image_pos = GPointZero; GRect body_rect; - GBitmap *title_icon = simply_res_get_image( + SimplyImage *title_icon = simply_res_get_image( self->window.simply->res, self->ui_layer.imagefields[UiTitleIcon]); - GBitmap *subtitle_icon = simply_res_get_image( + SimplyImage *subtitle_icon = simply_res_get_image( self->window.simply->res, self->ui_layer.imagefields[UiSubtitleIcon]); - GBitmap *body_image = simply_res_get_image( + SimplyImage *body_image = simply_res_get_image( self->window.simply->res, self->ui_layer.imagefields[UiBodyImage]); GRect title_icon_bounds; @@ -161,10 +161,10 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { GRect body_image_bounds; if (title_icon) { - title_icon_bounds = gbitmap_get_bounds(title_icon); + title_icon_bounds = gbitmap_get_bounds(title_icon->bitmap); } if (subtitle_icon) { - subtitle_icon_bounds = gbitmap_get_bounds(title_icon); + subtitle_icon_bounds = gbitmap_get_bounds(subtitle_icon->bitmap); } if (has_title) { @@ -200,7 +200,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } if (body_image) { - body_image_bounds = gbitmap_get_bounds(body_image); + body_image_bounds = gbitmap_get_bounds(body_image->bitmap); image_pos = cursor; cursor.y += body_image_bounds.size.h; } @@ -234,7 +234,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { .origin = { margin_x, title_pos.y + image_offset_y }, .size = { title_icon_bounds.size.w, title_size.h } }; - graphics_draw_bitmap_centered(ctx, title_icon, icon_frame); + graphics_draw_bitmap_centered(ctx, title_icon->bitmap, icon_frame); } if (has_title) { graphics_draw_text(ctx, title_text, title_font, @@ -247,7 +247,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { .origin = { margin_x, subtitle_pos.y + image_offset_y }, .size = { subtitle_icon_bounds.size.w, subtitle_size.h } }; - graphics_draw_bitmap_centered(ctx, subtitle_icon, subicon_frame); + graphics_draw_bitmap_centered(ctx, subtitle_icon->bitmap, subicon_frame); } if (has_subtitle) { graphics_draw_text(ctx, subtitle_text, subtitle_font, @@ -260,7 +260,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { .origin = { 0, image_pos.y + image_offset_y }, .size = { window_frame.size.w, body_image_bounds.size.h } }; - graphics_draw_bitmap_centered(ctx, body_image, image_frame); + graphics_draw_bitmap_centered(ctx, body_image->bitmap, image_frame); } if (has_body) { graphics_draw_text(ctx, body_text, body_font, body_rect, diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 26e57cf8..361ea849 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -164,9 +164,10 @@ void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint return; } - if (id) { - GBitmap *icon = simply_res_auto_image(self->simply->res, id, true); - action_bar_layer_set_icon(self->action_bar_layer, button, icon); + SimplyImage *icon = simply_res_auto_image(self->simply->res, id, true); + + if (icon && icon->bitmap) { + action_bar_layer_set_icon(self->action_bar_layer, button, icon->bitmap); simply_window_set_action_bar(self, true); } else { action_bar_layer_clear_icon(self->action_bar_layer, button); diff --git a/src/util/compat.h b/src/util/compat.h index 59f5f515..b76ebaca 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -1,5 +1,7 @@ #pragma once +#include + /** * aplite and SDK 2.9 compatibility utilities * These are a collection of types and compatibility macros taken from SDK 3.0. From f73572edd0a84c83baceb15e1233036e019358e4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 May 2015 02:50:15 -0700 Subject: [PATCH 513/791] Invert palette black and white images in menu. Addresses #55 The SDK is converting black and white PNGs, and the firmware is loading these PNGs as 1-bit PNGs with a palette rather than without. This is causing the firmware drawing logic to draw them as color PNGs when they are originally purely black and white. Menu Layers also no longer invert their selection. To support the usage of 1-bit images, simulate a faux inversion. --- src/simply/simply_menu.c | 17 +++++++++++++++++ src/simply/simply_res.c | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 462d81c3..94462486 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -78,6 +78,10 @@ struct __attribute__((__packed__)) MenuSelectionPacket { }; +static GColor8 s_normal_palette[] = { { GColorBlackARGB8 }, { GColorClearARGB8 } }; +static GColor8 s_inverted_palette[] = { { GColorWhiteARGB8 }, { GColorClearARGB8 } }; + + static void simply_menu_clear_section_items(SimplyMenu *self, int section_index); static void simply_menu_clear(SimplyMenu *self); @@ -308,9 +312,22 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI list1_prepend(&self->menu_layer.items, &item->node); SimplyImage *image = simply_res_get_image(self->window.simply->res, item->icon); + GColor8 *palette = NULL; + + if (image && image->is_palette_black_and_white) { + palette = gbitmap_get_palette(image->bitmap); + MenuIndex selected_index = menu_layer_get_selected_index(self->menu_layer.menu_layer); + const bool is_selected = (selected_index.section == cell_index->section && + selected_index.row == cell_index->row); + gbitmap_set_palette(image->bitmap, is_selected ? s_inverted_palette : s_normal_palette, false); + } graphics_context_set_alpha_blended(ctx, true); menu_cell_basic_draw(ctx, cell_layer, item->title, item->subtitle, image ? image->bitmap : NULL); + + if (palette) { + gbitmap_set_palette(image->bitmap, palette, false); + } } static void menu_select_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index b6b5da8b..2cbeb5af 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -23,6 +23,20 @@ static void destroy_font(SimplyRes *self, SimplyFont *font) { free(font); } +static void setup_image(SimplyImage *image) { + image->is_palette_black_and_white = gbitmap_is_palette_black_and_white(image->bitmap); + + if (!image->is_palette_black_and_white) { + return; + } + + GColor8 *palette = gbitmap_get_palette(image->bitmap); + GColor8 *palette_copy = malloc0(2 * sizeof(GColor8)); + memcpy(palette_copy, palette, 2 * sizeof(GColor8)); + gbitmap_set_palette(image->bitmap, palette_copy, false); + image->palette = palette_copy; +} + SimplyImage *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { SimplyImage *image = malloc(sizeof(*image)); if (!image) { @@ -42,6 +56,8 @@ SimplyImage *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { list1_prepend(&self->images, &image->node); + setup_image(image); + window_stack_schedule_top_window_render(); return image; @@ -68,6 +84,8 @@ SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, i uint16_t row_size_bytes = gbitmap_get_bytes_per_row(image->bitmap); size_t pixels_size = height * row_size_bytes; memcpy(image->bitmap_data, pixels, pixels_size); + + setup_image(image); } window_stack_schedule_top_window_render(); From 44d0fbfe142d049b8417e210e52e6391278131ed Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 May 2015 03:37:51 -0700 Subject: [PATCH 514/791] Fix button coloring for 1-bit images in the action bar. Related to #55 The action bar does not invert images that have palettes. PNGs without palettes are being loaded with palettes. Change the palette of black and white images to have faux inversion when used on the action bar. This has the caveat that instances of an image that is simultaneously used both inside and outside an action bar will render with unexpected colors that only make sense in the action bar. --- src/simply/simply_window.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 361ea849..03d4362f 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -49,6 +49,10 @@ struct __attribute__((__packed__)) ClickPacket { typedef ClickPacket LongClickPacket; + +static GColor8 s_button_palette[] = { { GColorWhiteARGB8 }, { GColorClearARGB8 } }; + + static void click_config_provider(void *data); static bool send_click(SimplyMsg *self, Command type, ButtonId button) { @@ -166,12 +170,17 @@ void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint SimplyImage *icon = simply_res_auto_image(self->simply->res, id, true); - if (icon && icon->bitmap) { - action_bar_layer_set_icon(self->action_bar_layer, button, icon->bitmap); - simply_window_set_action_bar(self, true); - } else { + if (!icon) { action_bar_layer_clear_icon(self->action_bar_layer, button); + return; } + + if (icon->is_palette_black_and_white) { + gbitmap_set_palette(icon->bitmap, s_button_palette, false); + } + + action_bar_layer_set_icon(self->action_bar_layer, button, icon->bitmap); + simply_window_set_action_bar(self, true); } void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor8 background_color) { @@ -179,6 +188,8 @@ void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor8 b return; } + s_button_palette[0] = gcolor8_equal(background_color, GColorWhite) ? GColor8Black : GColor8White; + action_bar_layer_set_background_color(self->action_bar_layer, gcolor8_get(background_color)); simply_window_set_action_bar(self, true); } From a2a50f14fb0fb6387f547f3b908a7d2a404563b0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 May 2015 04:42:53 -0700 Subject: [PATCH 515/791] Add safe.js pypkjs support Phonesim stack traces are being incorrectly parsed. Detect the phonesim platform and correctly translate the source line positions. --- src/js/lib/safe.js | 52 ++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index d2a8e2e1..dff95cfd 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -28,30 +28,44 @@ safe.translatePos = function(name, lineno, colno) { return name + ':' + lineno + ':' + colno; }; +/* Translates a node style stack trace line */ +var translateLineV8 = function(line, msg, name, lineno, colno) { + var pos = safe.translatePos(name, lineno, colno); + return msg + '(' + pos + ')'; +}; + +var makeTranslateStack = function(stackLineRegExp, translateLine) { + return function(stack) { + var lines = stack.split('\n'); + for (var i = lines.length - 1; i >= 0; --i) { + var line = lines[i]; + var m = line.match(stackLineRegExp); + if (m) { + line = lines[i] = translateLine.apply(this, m); + } + if (line.match(module.filename)) { + lines.splice(--i, 2); + } + } + return lines.join('\n'); + }; +}; -/* Translates an iOS stack tace line to node style */ -safe.translateLineIOS = function(line, scope, name, lineno, colno) { +/* Matches '(' ':' ':' ')' */ +var stackLineRegExpV8 = /(.*)\(([^\s@:]+):(\d+):(\d+)\)/; + +safe.translateStackV8 = makeTranslateStack(stackLineRegExpV8, translateLineV8); + +/* Translates an iOS stack trace line to node style */ +var translateLineIOS = function(line, scope, name, lineno, colno) { var pos = safe.translatePos(name, lineno, colno); return safe.indent + 'at ' + (scope ? scope + ' (' + pos + ')' : pos); }; /* Matches ( '@' )? ':' ':' */ -var stackLineRegExp = /(?:([^\s@]+)@)?([^\s@:]+):(\d+):(\d+)/; +var stackLineRegExpIOS = /(?:([^\s@]+)@)?([^\s@:]+):(\d+):(\d+)/; -safe.translateStackIOS = function(stack) { - var lines = stack.split('\n'); - for (var i = lines.length - 1; i >= 0; --i) { - var line = lines[i]; - var m = line.match(stackLineRegExp); - if (m) { - line = lines[i] = safe.translateLineIOS.apply(this, m); - } - if (line.match(module.filename)) { - lines.splice(--i, 2); - } - } - return lines.join('\n'); -}; +safe.translateStackIOS = makeTranslateStack(stackLineRegExpIOS, translateLineIOS); safe.translateStackAndroid = function(stack) { var lines = stack.split('\n'); @@ -84,7 +98,9 @@ safe.translateStackAndroid = function(stack) { /* Translates a stack trace to the originating files */ safe.translateStack = function(stack) { - if (stack.match('com.getpebble.android')) { + if (Pebble.platform === 'pypkjs') { + return safe.translateStackV8(stack); + } else if (stack.match('com.getpebble.android')) { return safe.translateStackAndroid(stack); } else { return safe.translateStackIOS(stack); From d851bb1b70bd74bf5b0d55e135509effb6c618e4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 May 2015 04:48:43 -0700 Subject: [PATCH 516/791] Remove outdated card docs --- src/js/ui/card.js | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/src/js/ui/card.js b/src/js/ui/card.js index adba1695..c9b8dc71 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -6,29 +6,6 @@ var Propable = require('ui/propable'); var Window = require('ui/window'); var simply = require('ui/simply'); -/** - * Sets the title field. The title field is the first and largest text field available. - * @memberOf simply - * @param {string} text - The desired text to display. - * @param {boolean} [clear] - If true, all other text fields will be cleared. - */ - -/** - * Sets the subtitle field. The subtitle field is the second large text field available. - * @memberOf simply - * @param {string} text - The desired text to display. - * @param {boolean} [clear] - If true, all other text fields will be cleared. - */ - -/** - * Sets the body field. The body field is the last text field available meant to display large bodies of text. - * This can be used to display entire text interfaces. - * You may even clear the title and subtitle fields in order to display more in the body field. - * @memberOf simply - * @param {string} text - The desired text to display. - * @param {boolean} [clear] - If true, all other text fields will be cleared. - */ - var textProps = [ 'title', 'subtitle', @@ -47,14 +24,6 @@ var actionProps = [ 'back', ]; -/** - * Set the Pebble UI style. - * The available styles are 'small', 'large', and 'mono'. Small and large correspond to the system notification styles. - * Mono sets a monospace font for the body textfield, enabling more complex text UIs or ASCII art. - * @memberOf simply - * @param {string} type - The type of style to set: 'small', 'large', or 'mono'. - */ - var configProps = [ 'style', 'backgroundColor' From 50f56e34721e82097d256bc30116d6cc4caf950e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 May 2015 04:49:26 -0700 Subject: [PATCH 517/791] Add simply card title, subtitle, and body text color. Addresses #54 --- src/js/ui/card.js | 10 +++++-- src/js/ui/simply-pebble.js | 24 +++++++++++---- src/js/ui/window.js | 2 +- src/simply/simply.h | 4 +++ src/simply/simply_ui.c | 60 ++++++++++++++++++++++++++------------ src/simply/simply_ui.h | 30 ++++++++++++------- 6 files changed, 92 insertions(+), 38 deletions(-) diff --git a/src/js/ui/card.js b/src/js/ui/card.js index c9b8dc71..b84180e5 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -12,6 +12,12 @@ var textProps = [ 'body', ]; +var textColorProps = [ + 'titleColor', + 'subtitleColor', + 'bodyColor', +]; + var imageProps = [ 'icon', 'subicon', @@ -29,7 +35,7 @@ var configProps = [ 'backgroundColor' ]; -var accessorProps = textProps.concat(imageProps).concat(configProps); +var accessorProps = textProps.concat(textColorProps).concat(imageProps).concat(configProps); var clearableProps = textProps.concat(imageProps); var defaults = { @@ -59,7 +65,7 @@ Card.prototype._prop = function() { Card.prototype._clear = function(flags) { flags = myutil.toFlags(flags); if (flags === true) { - clearableProps.forEach(myutil.unset.bind(this)); + clearableProps.forEach(Propable.unset.bind(this)); } if (myutil.flag(flags, 'action')) { this._clearAction(); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 697559bb..ce21019b 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -139,7 +139,7 @@ var colorMap = { }; var Color = function(color) { - return colorMap[color] ? colorMap[color] : colorMap['clear']; + return colorMap[color] ? colorMap[color] : colorMap.clear; }; var Font = function(x) { @@ -286,6 +286,12 @@ var CardTextTypes = [ var CardTextType = makeArrayType(CardTextTypes); +var CardTextColorTypes = [ + 'titleColor', + 'subtitleColor', + 'bodyColor', +]; + var CardImageTypes = [ 'icon', 'subicon', @@ -434,6 +440,7 @@ var CardClearPacket = new struct([ var CardTextPacket = new struct([ [Packet, 'packet'], ['uint8', 'index', CardTextType], + ['uint8', 'color', Color], ['cstring', 'text'], ]); @@ -928,8 +935,12 @@ SimplyPebble.cardClear = function(clear) { SimplyPebble.sendPacket(CardClearPacket.flags(toClearFlags(clear))); }; -SimplyPebble.cardText = function(field, text) { - SimplyPebble.sendPacket(CardTextPacket.index(field).text(text || '')); +SimplyPebble.cardText = function(field, text, color) { + CardTextPacket + .index(field) + .color(color || 'black') + .text(text || ''); + SimplyPebble.sendPacket(CardTextPacket); }; SimplyPebble.cardImage = function(field, image) { @@ -952,8 +963,9 @@ SimplyPebble.card = function(def, clear, pushing) { SimplyPebble.windowActionBar(def.action); } for (var k in def) { - if (CardTextTypes.indexOf(k) !== -1) { - SimplyPebble.cardText(k, def[k]); + var textIndex = CardTextTypes.indexOf(k); + if (textIndex !== -1) { + SimplyPebble.cardText(k, def[k], def[CardTextColorTypes[textIndex]]); } else if (CardImageTypes.indexOf(k) !== -1) { SimplyPebble.cardImage(k, def[k]); } else if (k === 'style') { @@ -1142,7 +1154,7 @@ var toArrayBuffer = function(array, length) { SimplyPebble.onLaunchReason = function(packet) { var reason = LaunchReasonTypes[packet.reason()]; - var args = packet.args() + var args = packet.args(); var remoteTime = packet.time(); var isTimezone = packet.isTimezone(); if (isTimezone) { diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 457f8e78..711d9923 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -111,7 +111,7 @@ Window.prototype._remove = function() { }; Window.prototype._clearAction = function() { - actionProps.forEach(myutil.unset.bind(this, this.state.action)); + actionProps.forEach(Propable.unset.bind(this.state.action)); }; Window.prototype._clear = function(flags) { diff --git a/src/simply/simply.h b/src/simply/simply.h index 2fb2cf19..60b29247 100644 --- a/src/simply/simply.h +++ b/src/simply/simply.h @@ -1,5 +1,9 @@ #pragma once +#ifdef PBL_SDK_3 +#include "basalt/src/resource_ids.auto.h" +#endif + #define LOG(...) APP_LOG(APP_LOG_LEVEL_DEBUG, __VA_ARGS__) typedef struct Simply Simply; diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 40b7bfd8..5d365c49 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -6,6 +6,7 @@ #include "simply.h" +#include "util/color.h" #include "util/graphics.h" #include "util/math.h" #include "util/string.h" @@ -56,6 +57,7 @@ typedef struct CardTextPacket CardTextPacket; struct __attribute__((__packed__)) CardTextPacket { Packet packet; uint8_t index; + GColor8 color; char text[]; }; @@ -79,8 +81,9 @@ void simply_ui_clear(SimplyUi *self, uint32_t clear_mask) { simply_window_action_bar_clear(&self->window); } if (clear_mask & (1 << ClearText)) { - for (SimplyUiTextfield textfield = 0; textfield < NumUiTextfields; ++textfield) { - simply_ui_set_text(self, textfield, NULL); + for (int textfield_id = 0; textfield_id < NumUiTextfields; ++textfield_id) { + simply_ui_set_text(self, textfield_id, NULL); + simply_ui_set_text_color(self, textfield_id, GColor8Black); } } if (clear_mask & (1 << ClearImage)) { @@ -101,14 +104,23 @@ void simply_ui_set_style(SimplyUi *self, int style_index) { layer_mark_dirty(self->ui_layer.layer); } -void simply_ui_set_text(SimplyUi *self, SimplyUiTextfield textfield, const char *str) { - char **str_field = &self->ui_layer.textfields[textfield]; +void simply_ui_set_text(SimplyUi *self, SimplyUiTextfieldId textfield_id, const char *str) { + SimplyUiTextfield *textfield = &self->ui_layer.textfields[textfield_id]; + char **str_field = &textfield->text; strset(str_field, str); if (self->ui_layer.layer) { layer_mark_dirty(self->ui_layer.layer); } } +void simply_ui_set_text_color(SimplyUi *self, SimplyUiTextfieldId textfield_id, GColor8 color) { + SimplyUiTextfield *textfield = &self->ui_layer.textfields[textfield_id]; + textfield->color = color; + if (self->ui_layer.layer) { + layer_mark_dirty(self->ui_layer.layer); + } +} + static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyUi *self = *(void**) layer_get_data(layer); @@ -137,13 +149,13 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { graphics_context_set_text_color(ctx, GColorBlack); - const char *title_text = self->ui_layer.textfields[UiTitle]; - const char *subtitle_text = self->ui_layer.textfields[UiSubtitle]; - const char *body_text = self->ui_layer.textfields[UiBody]; + const SimplyUiTextfield *title = &self->ui_layer.textfields[UiTitle]; + const SimplyUiTextfield *subtitle = &self->ui_layer.textfields[UiSubtitle]; + const SimplyUiTextfield *body = &self->ui_layer.textfields[UiBody]; - bool has_title = is_string(title_text); - bool has_subtitle = is_string(subtitle_text); - bool has_body = is_string(body_text); + bool has_title = is_string(title->text); + bool has_subtitle = is_string(subtitle->text); + bool has_body = is_string(body->text); GSize title_size, subtitle_size; GPoint title_pos, subtitle_pos, image_pos = GPointZero; @@ -173,7 +185,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { title_frame.origin.x += title_icon_bounds.size.w; title_frame.size.w -= title_icon_bounds.size.w; } - title_size = graphics_text_layout_get_content_size(title_text, + title_size = graphics_text_layout_get_content_size(title->text, title_font, title_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); title_size.w = title_frame.size.w; title_pos = cursor; @@ -189,7 +201,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { subtitle_frame.origin.x += subtitle_icon_bounds.size.w; subtitle_frame.size.w -= subtitle_icon_bounds.size.w; } - subtitle_size = graphics_text_layout_get_content_size(subtitle_text, + subtitle_size = graphics_text_layout_get_content_size(subtitle->text, title_font, subtitle_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); subtitle_size.w = subtitle_frame.size.w; subtitle_pos = cursor; @@ -210,7 +222,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { body_rect.origin = cursor; body_rect.size.w = text_frame.size.w; body_rect.size.h -= 2 * margin_y + cursor.y; - GSize body_size = graphics_text_layout_get_content_size(body_text, + GSize body_size = graphics_text_layout_get_content_size(body->text, body_font, text_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); if (self->window.is_scrollable) { body_rect.size = body_size; @@ -237,7 +249,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { graphics_draw_bitmap_centered(ctx, title_icon->bitmap, icon_frame); } if (has_title) { - graphics_draw_text(ctx, title_text, title_font, + graphics_context_set_text_color(ctx, gcolor8_get_or(title->color, GColorBlack)); + graphics_draw_text(ctx, title->text, title_font, (GRect) { .origin = title_pos, .size = title_size }, GTextOverflowModeWordWrap, GTextAlignmentLeft, NULL); } @@ -250,7 +263,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { graphics_draw_bitmap_centered(ctx, subtitle_icon->bitmap, subicon_frame); } if (has_subtitle) { - graphics_draw_text(ctx, subtitle_text, subtitle_font, + graphics_context_set_text_color(ctx, gcolor8_get_or(subtitle->color, GColorBlack)); + graphics_draw_text(ctx, subtitle->text, subtitle_font, (GRect) { .origin = subtitle_pos, .size = subtitle_size }, GTextOverflowModeWordWrap, GTextAlignmentLeft, NULL); } @@ -263,7 +277,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { graphics_draw_bitmap_centered(ctx, body_image->bitmap, image_frame); } if (has_body) { - graphics_draw_text(ctx, body_text, body_font, body_rect, + graphics_context_set_text_color(ctx, gcolor8_get_or(body->color, GColorBlack)); + graphics_draw_text(ctx, body->text, body_font, body_rect, GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); } } @@ -323,12 +338,21 @@ static void handle_card_clear_packet(Simply *simply, Packet *data) { static void handle_card_text_packet(Simply *simply, Packet *data) { CardTextPacket *packet = (CardTextPacket*) data; - simply_ui_set_text(simply->ui, MIN(NumUiTextfields - 1, packet->index), packet->text); + SimplyUiTextfieldId textfield_id = packet->index; + if (textfield_id >= NumUiTextfields) { + return; + } + simply_ui_set_text(simply->ui, textfield_id, packet->text); + simply_ui_set_text_color(simply->ui, textfield_id, packet->color); } static void handle_card_image_packet(Simply *simply, Packet *data) { CardImagePacket *packet = (CardImagePacket*) data; - simply->ui->ui_layer.imagefields[MIN(NumUiImagefields - 1, packet->index)] = packet->image; + SimplyUiImagefieldId imagefield_id = packet->index; + if (imagefield_id >= NumUiImagefields) { + return; + } + simply->ui->ui_layer.imagefields[imagefield_id] = packet->image; window_stack_schedule_top_window_render(); } diff --git a/src/simply/simply_ui.h b/src/simply/simply_ui.h index 86c0cf6f..d7097b96 100644 --- a/src/simply/simply_ui.h +++ b/src/simply/simply_ui.h @@ -8,36 +8,43 @@ typedef struct SimplyStyle SimplyStyle; -typedef struct SimplyUiLayer SimplyUiLayer; - -typedef struct SimplyUi SimplyUi; - -typedef enum SimplyUiTextfield SimplyUiTextfield; +typedef enum SimplyUiTextfieldId SimplyUiTextfieldId; -typedef enum SimplyUiImagefield SimplyUiImagefield; - -enum SimplyUiTextfield { +enum SimplyUiTextfieldId { UiTitle = 0, UiSubtitle, UiBody, NumUiTextfields, }; -enum SimplyUiImagefield { +typedef enum SimplyUiImagefieldId SimplyUiImagefieldId; + +enum SimplyUiImagefieldId { UiTitleIcon, UiSubtitleIcon, UiBodyImage, NumUiImagefields, }; +typedef struct SimplyUiTextfield SimplyUiTextfield; + +struct SimplyUiTextfield { + char *text; + GColor8 color; +}; + +typedef struct SimplyUiLayer SimplyUiLayer; + struct SimplyUiLayer { Layer *layer; const SimplyStyle *style; - char *textfields[3]; + SimplyUiTextfield textfields[3]; uint32_t imagefields[3]; GFont custom_body_font; }; +typedef struct SimplyUi SimplyUi; + struct SimplyUi { SimplyWindow window; SimplyUiLayer ui_layer; @@ -49,6 +56,7 @@ void simply_ui_destroy(SimplyUi *self); void simply_ui_clear(SimplyUi *self, uint32_t clear_mask); void simply_ui_set_style(SimplyUi *self, int style_index); -void simply_ui_set_text(SimplyUi *self, SimplyUiTextfield textfield, const char *str); +void simply_ui_set_text(SimplyUi *self, SimplyUiTextfieldId textfield_id, const char *str); +void simply_ui_set_text_color(SimplyUi *self, SimplyUiTextfieldId textfield_id, GColor8 color); bool simply_ui_handle_packet(Simply *simply, Packet *packet); From 3af938221e1a114535b0a429a1879d31960b0a86 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 15 May 2015 14:02:08 -0700 Subject: [PATCH 518/791] Fix SDK 2.9 compatibility --- src/simply/simply_wakeup.c | 2 ++ src/util/compat.h | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/simply/simply_wakeup.c b/src/simply/simply_wakeup.c index 5149df08..a5922480 100644 --- a/src/simply/simply_wakeup.c +++ b/src/simply/simply_wakeup.c @@ -4,6 +4,8 @@ #include "simply.h" +#include "util/compat.h" + #include typedef struct LaunchReasonPacket LaunchReasonPacket; diff --git a/src/util/compat.h b/src/util/compat.h index b76ebaca..0256f20b 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -98,6 +98,10 @@ typedef union GColor8 { ((void)(bitmap), (void)(palette), (void)(free_on_destroy)) #endif +#ifndef gbitmap_get_format +#define gbitmap_get_format(bitmap) \ + (GBitmapFormat1Bit) +#endif //! Convenience macro to use SDK 3.0 function to set a `PropertyAnimation`'s //! `values.from.grect` field. From 94a3c13e0b2a162d540affb68284249ebdac57e2 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 15 May 2015 14:30:57 -0700 Subject: [PATCH 519/791] Fix window appear and disappear interaction with clearing. Addresses #56 Aplite requires an app chrome refresh hack that falsely triggers the appear and disappear handlers. This causes all resources on-screen to be deallocated. Fix the appear and disappear handling to only clear resources when it is a true apppear or disappear. --- src/simply/simply_menu.c | 8 ++++---- src/simply/simply_stage.c | 8 ++++---- src/simply/simply_ui.c | 6 +++--- src/simply/simply_window.c | 18 +++++++++++++++--- src/simply/simply_window.h | 4 ++-- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 94462486..cfe825e4 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -383,10 +383,10 @@ static void window_appear(Window *window) { static void window_disappear(Window *window) { SimplyMenu *self = window_get_user_data(window); - simply_window_disappear(&self->window); - - simply_res_clear(self->window.simply->res); - simply_menu_clear(self); + if (simply_window_disappear(&self->window)) { + simply_res_clear(self->window.simply->res); + simply_menu_clear(self); + } } static void window_unload(Window *window) { diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index c79ef15f..b2beb4a5 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -447,10 +447,10 @@ static void window_appear(Window *window) { static void window_disappear(Window *window) { SimplyStage *self = window_get_user_data(window); - simply_window_disappear(&self->window); - - simply_res_clear(self->window.simply->res); - simply_stage_clear(self); + if (simply_window_disappear(&self->window)) { + simply_res_clear(self->window.simply->res); + simply_stage_clear(self); + } } static void window_unload(Window *window) { diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 5d365c49..f7b8cce3 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -317,9 +317,9 @@ static void window_appear(Window *window) { static void window_disappear(Window *window) { SimplyUi *self = window_get_user_data(window); - simply_window_disappear(&self->window); - - simply_res_clear(self->window.simply->res); + if (simply_window_disappear(&self->window)) { + simply_res_clear(self->window.simply->res); + } } static void window_unload(Window *window) { diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 03d4362f..5b5f27f3 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -107,15 +107,18 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { } void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { + bool changed = false; if (is_fullscreen && self->is_status_bar) { status_bar_layer_remove_from_window(self->window, self->status_bar_layer); self->is_status_bar = false; + changed = true; } else if (!is_fullscreen && !self->is_status_bar) { status_bar_layer_add_to_window(self->window, self->status_bar_layer); self->is_status_bar = true; + changed = true; } - if (!self->layer) { + if (!changed || !self->layer) { return; } @@ -267,16 +270,25 @@ void simply_window_load(SimplyWindow *self) { simply_window_set_action_bar(self, self->is_action_bar); } -void simply_window_appear(SimplyWindow *self) { +bool simply_window_appear(SimplyWindow *self) { + if (!self->id) { + return false; + } simply_window_stack_send_show(self->simply->window_stack, self); + return true; } -void simply_window_disappear(SimplyWindow *self) { +bool simply_window_disappear(SimplyWindow *self) { + if (!self->id) { + return false; + } simply_window_stack_send_hide(self->simply->window_stack, self); #ifdef PBL_PLATFORM_BASALT simply_window_set_fullscreen(self, true); #endif + + return true; } void simply_window_unload(SimplyWindow *self) { diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index 83950005..ca41fb93 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -34,8 +34,8 @@ void simply_window_hide(SimplyWindow *self); void simply_window_load(SimplyWindow *self); void simply_window_unload(SimplyWindow *self); -void simply_window_appear(SimplyWindow *self); -void simply_window_disappear(SimplyWindow *self); +bool simply_window_appear(SimplyWindow *self); +bool simply_window_disappear(SimplyWindow *self); void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *context); From 9e572e898b235c0c89d428433be8825ff8104c29 Mon Sep 17 00:00:00 2001 From: Ben Combee Date: Tue, 19 May 2015 14:39:14 -0500 Subject: [PATCH 520/791] Update README.md fix copy-and-paste error for Text.color property documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 663b8436..e1f66db4 100644 --- a/README.md +++ b/README.md @@ -992,7 +992,7 @@ Sets the font property. See [Text]. #### Text.color(color) -Sets the textOverflow property. See [Text]. +Sets the color property. See [Text]. #### Text.textOverflow(textOverflow) From eb1f1f7abe1f8fd995ee4bcafe66dc163716acd9 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Sat, 23 May 2015 23:09:49 -0400 Subject: [PATCH 521/791] Fix card layer icons to be alpha blended. --- src/simply/simply_ui.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index f7b8cce3..7327cc32 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -246,6 +246,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { .origin = { margin_x, title_pos.y + image_offset_y }, .size = { title_icon_bounds.size.w, title_size.h } }; + graphics_context_set_alpha_blended(ctx, true); graphics_draw_bitmap_centered(ctx, title_icon->bitmap, icon_frame); } if (has_title) { @@ -260,6 +261,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { .origin = { margin_x, subtitle_pos.y + image_offset_y }, .size = { subtitle_icon_bounds.size.w, subtitle_size.h } }; + graphics_context_set_alpha_blended(ctx, true); graphics_draw_bitmap_centered(ctx, subtitle_icon->bitmap, subicon_frame); } if (has_subtitle) { @@ -274,6 +276,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { .origin = { 0, image_pos.y + image_offset_y }, .size = { window_frame.size.w, body_image_bounds.size.h } }; + graphics_context_set_alpha_blended(ctx, true); graphics_draw_bitmap_centered(ctx, body_image->bitmap, image_frame); } if (has_body) { From 58bed7798a873e2a1238f8b76706a0cd657e676b Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Sun, 7 Jun 2015 17:47:05 -0400 Subject: [PATCH 522/791] Text, highlight and background colors for menu items --- src/js/ui/menu.js | 5 ++++- src/js/ui/simply-pebble.js | 5 ++++- src/simply/simply_menu.c | 12 +++++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 45443715..9948d4dd 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -7,6 +7,9 @@ var simply = require('ui/simply'); var defaults = { backgroundColor: 'white', + textColor: 'black', + highlightBackgroundColor: 'black', + highlightTextColor: 'white', fullscreen: false, }; @@ -31,7 +34,7 @@ Menu.prototype._show = function() { simply.impl.menuSelection(select.sectionIndex, select.itemIndex); }; -Menu.prototype._numPreloadItems = 5; +Menu.prototype._numPreloadItems = 50; Menu.prototype._prop = function(state, clear, pushing) { if (this === WindowStack.top()) { diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index ce21019b..244faea0 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -508,7 +508,10 @@ var MenuClearSectionPacket = new struct([ var MenuPropsPacket = new struct([ [Packet, 'packet'], ['uint16', 'sections', EnumerableType], - ['uint8', 'backgroundColor', Color] + ['uint8', 'backgroundColor', Color], + ['uint8', 'textColor', Color], + ['uint8', 'highlightBackgroundColor', Color], + ['uint8', 'highlightTextColor', Color], ]); var MenuSectionPacket = new struct([ diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index cfe825e4..535432fb 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -6,6 +6,7 @@ #include "simply.h" +#include "util/color.h" #include "util/graphics.h" #include "util/menu_layer.h" #include "util/string.h" @@ -33,6 +34,9 @@ struct __attribute__((__packed__)) MenuPropsPacket { Packet packet; uint16_t num_sections; GColor8 background_color; + GColor8 text_color; + GColor8 highlight_background_color; + GColor8 highlight_text_color; }; typedef struct MenuSectionPacket MenuSectionPacket; @@ -430,7 +434,13 @@ static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { static void handle_menu_props_packet(Simply *simply, Packet *data) { MenuPropsPacket *packet = (MenuPropsPacket*) data; simply_menu_set_num_sections(simply->menu, packet->num_sections); - window_set_background_color(simply->menu->window.window, gcolor8_get(packet->background_color)); + #ifdef PBL_SDK_3 + window_set_background_color(simply->menu->window.window, gcolor8_get(packet->background_color)); + menu_layer_set_highlight_colors(simply->menu->menu_layer.menu_layer, gcolor8_get(packet->highlight_background_color), gcolor8_get(packet->highlight_text_color)); + menu_layer_set_normal_colors(simply->menu->menu_layer.menu_layer, gcolor8_get(packet->background_color), gcolor8_get(packet->text_color)); + #else + window_set_background_color(simply->menu->window.window, gcolor8_get(packet->background_color)); + #endif } static void handle_menu_section_packet(Simply *simply, Packet *data) { From bb856fb52faaf52b7270aa271157354fda3e0eda Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Sun, 7 Jun 2015 17:56:24 -0400 Subject: [PATCH 523/791] Update README.md Added documentation regarding new menu item properties --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e1f66db4..b2b46c88 100644 --- a/README.md +++ b/README.md @@ -707,14 +707,22 @@ Just like any window, you can initialize a Menu by passing an object to the cons The properties available on a [Menu] are: -| Name | Type | Default | Description | -| ---- |:-------:|---------|-------------| -| `sections` | Array | `[]` | A list of all the sections to display. | +| Name | Type | Default | Description | +| ---- |:-------:|---------|-------------| +| `sections` | Array | `[]` | A list of all the sections to display. | +| `backgroundColor` | Color | `white` | The background color of a menu item. | +| `textColor` | Color | `black` | The text color of a menu item. | +| `highlightBackgroundColor` | Color | `black` | The background color of a selected menu item. | +| `highlightTextColor` | Color | `white` | The text color of a selected menu item. | A menu contains one or more sections. Each section has a title and contains zero or more items. An item must have a title. It can also have a subtitle and an icon. ````js var menu = new UI.Menu({ + backgroundColor: 'black', + textColor: 'blue', + highlightBackgroundColor: 'blue', + highlightTextColor: 'black', sections: [{ title: 'First section', items: [{ From 83e5e9b3da143aaf016c78f3c5d78894c88610ae Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Sun, 7 Jun 2015 18:08:43 -0400 Subject: [PATCH 524/791] Use platform instead of SDK version --- src/simply/simply_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 535432fb..2747c63a 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -434,7 +434,7 @@ static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { static void handle_menu_props_packet(Simply *simply, Packet *data) { MenuPropsPacket *packet = (MenuPropsPacket*) data; simply_menu_set_num_sections(simply->menu, packet->num_sections); - #ifdef PBL_SDK_3 + #ifdef PBL_PLATFORM_BASALT window_set_background_color(simply->menu->window.window, gcolor8_get(packet->background_color)); menu_layer_set_highlight_colors(simply->menu->menu_layer.menu_layer, gcolor8_get(packet->highlight_background_color), gcolor8_get(packet->highlight_text_color)); menu_layer_set_normal_colors(simply->menu->menu_layer.menu_layer, gcolor8_get(packet->background_color), gcolor8_get(packet->text_color)); From 19121fcd186820114e3d803957d2171900d49af9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 7 Jun 2015 23:06:30 -0700 Subject: [PATCH 525/791] Add menu layer set colors aplite no-op macros --- src/js/ui/simply-pebble.js | 6 +++--- src/simply/simply_menu.c | 14 +++++++------- src/util/compat.h | 8 ++++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 244faea0..9445905c 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -509,9 +509,9 @@ var MenuPropsPacket = new struct([ [Packet, 'packet'], ['uint16', 'sections', EnumerableType], ['uint8', 'backgroundColor', Color], - ['uint8', 'textColor', Color], - ['uint8', 'highlightBackgroundColor', Color], - ['uint8', 'highlightTextColor', Color], + ['uint8', 'textColor', Color], + ['uint8', 'highlightBackgroundColor', Color], + ['uint8', 'highlightTextColor', Color], ]); var MenuSectionPacket = new struct([ diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 2747c63a..3058c9a6 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -434,13 +434,13 @@ static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { static void handle_menu_props_packet(Simply *simply, Packet *data) { MenuPropsPacket *packet = (MenuPropsPacket*) data; simply_menu_set_num_sections(simply->menu, packet->num_sections); - #ifdef PBL_PLATFORM_BASALT - window_set_background_color(simply->menu->window.window, gcolor8_get(packet->background_color)); - menu_layer_set_highlight_colors(simply->menu->menu_layer.menu_layer, gcolor8_get(packet->highlight_background_color), gcolor8_get(packet->highlight_text_color)); - menu_layer_set_normal_colors(simply->menu->menu_layer.menu_layer, gcolor8_get(packet->background_color), gcolor8_get(packet->text_color)); - #else - window_set_background_color(simply->menu->window.window, gcolor8_get(packet->background_color)); - #endif + window_set_background_color(simply->menu->window.window, gcolor8_get(packet->background_color)); + menu_layer_set_highlight_colors(simply->menu->menu_layer.menu_layer, + gcolor8_get(packet->highlight_background_color), + gcolor8_get(packet->highlight_text_color)); + menu_layer_set_normal_colors(simply->menu->menu_layer.menu_layer, + gcolor8_get(packet->background_color), + gcolor8_get(packet->text_color)); } static void handle_menu_section_packet(Simply *simply, Packet *data) { diff --git a/src/util/compat.h b/src/util/compat.h index 0256f20b..e0078773 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -103,6 +103,14 @@ typedef union GColor8 { (GBitmapFormat1Bit) #endif +#ifndef menu_layer_set_normal_colors +#define menu_layer_set_normal_colors(menu_layer, background_color, text_color) +#endif + +#ifndef menu_layer_set_highlight_colors +#define menu_layer_set_highlight_colors(menu_layer, background_color, text_color) +#endif + //! Convenience macro to use SDK 3.0 function to set a `PropertyAnimation`'s //! `values.from.grect` field. #ifndef property_animation_set_from_grect From 819c8ecb4aba7ee49426f3868e3e32f006a0db69 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Sat, 13 Jun 2015 16:59:43 -0400 Subject: [PATCH 526/791] Remove request delay (not used) Added loading text --- src/simply/simply_menu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 3058c9a6..74c9cc1a 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -15,9 +15,7 @@ #define MAX_CACHED_SECTIONS 10 -#define MAX_CACHED_ITEMS 6 - -#define REQUEST_DELAY_MS 10 +#define MAX_CACHED_ITEMS 51 typedef Packet MenuClearPacket; @@ -97,6 +95,7 @@ static MenuIndex simply_menu_get_selection(SimplyMenu *self); static void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, MenuRowAlign align, bool animated); static char EMPTY_TITLE[] = ""; +static char LOADING_TEXT[] = "Loading..."; static bool send_menu_item(Command type, uint16_t section, uint16_t item) { MenuItemEventPacket packet = { @@ -220,6 +219,7 @@ static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t *item = (SimplyMenuItem) { .section = section_index, .item = item_index, + .title = strdup2(LOADING_TEXT) }; add_item(self, item); send_menu_get_item(section_index, item_index); From 967b10e61e3cbeb308cae0a1f4ee0e0b2595dc94 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Sun, 14 Jun 2015 13:37:38 -0400 Subject: [PATCH 527/791] Minor formatting corrections --- src/js/ui/menu.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 9948d4dd..a0e1f23d 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -7,9 +7,9 @@ var simply = require('ui/simply'); var defaults = { backgroundColor: 'white', - textColor: 'black', - highlightBackgroundColor: 'black', - highlightTextColor: 'white', + textColor: 'black', + highlightBackgroundColor: 'black', + highlightTextColor: 'white', fullscreen: false, }; From f50a08576b84bd7970f1b27c458baee6b9a18d50 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Sun, 14 Jun 2015 15:10:47 -0400 Subject: [PATCH 528/791] Ensure the menu layer has been initialized before setting colors --- src/simply/simply_menu.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 74c9cc1a..0e67264d 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -434,13 +434,16 @@ static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { static void handle_menu_props_packet(Simply *simply, Packet *data) { MenuPropsPacket *packet = (MenuPropsPacket*) data; simply_menu_set_num_sections(simply->menu, packet->num_sections); - window_set_background_color(simply->menu->window.window, gcolor8_get(packet->background_color)); + window_set_background_color(simply->menu->window.window, gcolor8_get_or(packet->background_color, GColorWhite)); + if (!simply->menu->menu_layer.menu_layer) { + return; + } menu_layer_set_highlight_colors(simply->menu->menu_layer.menu_layer, - gcolor8_get(packet->highlight_background_color), - gcolor8_get(packet->highlight_text_color)); + gcolor8_get_or(packet->highlight_background_color, GColorBlack), + gcolor8_get_or(packet->highlight_text_color, GColorWhite)); menu_layer_set_normal_colors(simply->menu->menu_layer.menu_layer, - gcolor8_get(packet->background_color), - gcolor8_get(packet->text_color)); + gcolor8_get_or(packet->background_color, GColorWhite), + gcolor8_get_or(packet->text_color, GColorBlack)); } static void handle_menu_section_packet(Simply *simply, Packet *data) { @@ -536,4 +539,4 @@ void simply_menu_destroy(SimplyMenu *self) { simply_window_deinit(&self->window); free(self); -} +} \ No newline at end of file From e1b266738e3873fa86aa1a0e5feb028ba7b69149 Mon Sep 17 00:00:00 2001 From: Jack Dalton Date: Mon, 15 Jun 2015 17:22:02 -0600 Subject: [PATCH 529/791] Fix capitalization, punctuation Changed "Coffeescript" to "CoffeeScript", and added a comma. --- wscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wscript b/wscript index 4137f460..0a220a3f 100644 --- a/wscript +++ b/wscript @@ -87,7 +87,7 @@ def concat_javascript(ctx, js_path=None): import coffeescript except ImportError: ctx.fatal(""" - Coffeescript file '%s' found but coffeescript module isn't installed. + CoffeeScript file '%s' found, but coffeescript module isn't installed. You may try `pip install coffeescript` or `easy_install coffeescript`. """ % (relpath)) body = coffeescript.compile(body) From f3e27d00614d445e8faff4acaa6bc63dc78f5a5a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 24 Jun 2015 03:00:20 -0700 Subject: [PATCH 530/791] Change Wakeup each to break on false --- src/js/wakeup/wakeup.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js index 2ad61819..8d3caa26 100644 --- a/src/js/wakeup/wakeup.js +++ b/src/js/wakeup/wakeup.js @@ -59,7 +59,9 @@ Wakeup.get = function(id) { Wakeup.each = function(callback) { var i = 0; for (var id in this.state.wakeups) { - callback(this.get(id), i++); + if (callback(this.get(id), i++) === false) { + break; + } } }; From b01936355bf74d247a8db79abe2174f18f4c8bd9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 24 Jun 2015 03:00:48 -0700 Subject: [PATCH 531/791] Add WindowStack each for iterating through windows on the stack. Addresses #47 --- src/js/ui/windowstack.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index 3f9c2024..ab16f258 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -101,6 +101,15 @@ WindowStack.prototype.get = function(windowId) { } }; +WindowStack.prototype.each = function(callback) { + var items = this._items; + for (var i = 0, ii = items.length; i < ii; ++i) { + if (callback(items[i], i) === false) { + break; + } + } +}; + WindowStack.prototype.emitHide = function(windowId) { var wind = this.get(windowId); if (wind !== this.top()) { return; } From 9a4b10b835fedb64563ce488c2f898f2a2b4d4ab Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 8 Jul 2015 13:23:24 -0700 Subject: [PATCH 532/791] Prefix private simply menu functions with static --- src/simply/simply_menu.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 0e67264d..b7584965 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -230,7 +230,7 @@ static void mark_dirty(SimplyMenu *self) { menu_layer_reload_data(self->menu_layer.menu_layer); } -void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections) { +static void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections) { if (num_sections == 0) { num_sections = 1; } @@ -238,7 +238,7 @@ void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections) { mark_dirty(self); } -void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section) { +static void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section) { if (section->title == NULL) { section->title = EMPTY_TITLE; } @@ -246,7 +246,7 @@ void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section) { mark_dirty(self); } -void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item) { +static void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item) { if (item->title == NULL) { item->title = EMPTY_TITLE; } @@ -254,11 +254,11 @@ void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item) { mark_dirty(self); } -MenuIndex simply_menu_get_selection(SimplyMenu *self) { +static MenuIndex simply_menu_get_selection(SimplyMenu *self) { return menu_layer_get_selected_index(self->menu_layer.menu_layer); } -void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, MenuRowAlign align, bool animated) { +static void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, MenuRowAlign align, bool animated) { menu_layer_set_selected_index(self->menu_layer.menu_layer, menu_index, align, animated); } @@ -284,7 +284,7 @@ static int16_t menu_get_header_height_callback(MenuLayer *menu_layer, uint16_t s return section && section->title && section->title != EMPTY_TITLE ? MENU_CELL_BASIC_HEADER_HEIGHT : 0; } -static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer, uint16_t section_index, void *data) { +static void menu_draw_header_callback(GContext *ctx, const Layer *cell_layer, uint16_t section_index, void *data) { SimplyMenu *self = data; SimplyMenuSection *section = get_menu_section(self, section_index); if (!section) { @@ -402,7 +402,7 @@ static void window_unload(Window *window) { simply_window_unload(&self->window); } -void simply_menu_clear_section_items(SimplyMenu *self, int section_index) { +static void simply_menu_clear_section_items(SimplyMenu *self, int section_index) { SimplyMenuItem *item = NULL; do { item = (SimplyMenuItem*) list1_find(self->menu_layer.items, section_filter, (void*)(uintptr_t) section_index); @@ -410,7 +410,7 @@ void simply_menu_clear_section_items(SimplyMenu *self, int section_index) { } while (item); } -void simply_menu_clear(SimplyMenu *self) { +static void simply_menu_clear(SimplyMenu *self) { while (self->menu_layer.sections) { destroy_section(self, (SimplyMenuSection*) self->menu_layer.sections); } @@ -539,4 +539,4 @@ void simply_menu_destroy(SimplyMenu *self) { simply_window_deinit(&self->window); free(self); -} \ No newline at end of file +} From d65379f168c569209d073f3473ffcdb859f430b7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 8 Jul 2015 20:45:21 -0700 Subject: [PATCH 533/791] Fix Clock weekday to always return a time in the future --- src/js/clock/clock.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/js/clock/clock.js b/src/js/clock/clock.js index 19045379..ee9fa780 100644 --- a/src/js/clock/clock.js +++ b/src/js/clock/clock.js @@ -1,5 +1,10 @@ var Clock = module.exports; Clock.weekday = function(weekday, hour, minute, seconds) { - return moment({ hour: hour, minute: minute, seconds: seconds }).day(weekday).unix(); + var now = moment(); + var target = moment({ hour: hour, minute: minute, seconds: seconds }).day(weekday); + if (moment.max(now, target) === now) { + target.add(1, 'week'); + } + return target.unix(); }; From a565af86df5eb3029d2f15e14042d6c803725259 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 9 Jul 2015 13:06:13 -0700 Subject: [PATCH 534/791] Enable simply stage antialiasing by default. Addresses #72 --- src/simply/simply_stage.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index b2beb4a5..24bffddf 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -244,6 +244,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { frame.origin.x = -frame.origin.x; frame.origin.y = -frame.origin.y; + graphics_context_set_antialiased(ctx, true); + graphics_context_set_fill_color(ctx, gcolor8_get(self->window.background_color)); graphics_fill_rect(ctx, frame, 0, GCornerNone); From 25da1867f08b7f707893e38a0259f56a8f0ff23e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 8 Jul 2015 13:24:03 -0700 Subject: [PATCH 535/791] Enable ms extensions for inlining typedef'd structs --- wscript | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/wscript b/wscript index 4137f460..62ef7ba3 100644 --- a/wscript +++ b/wscript @@ -13,6 +13,12 @@ def options(ctx): def configure(ctx): ctx.load('pebble_sdk') + if ctx.env.TARGET_PLATFORMS: + for platform in ctx.env.TARGET_PLATFORMS: + ctx.configure_platform(platform) + else: + ctx.configure_platform() + def build(ctx): ctx.load('pebble_sdk') @@ -34,32 +40,40 @@ def build(ctx): worker_elf=elfs['worker_elf'] if 'worker_elf' in elfs else None, js=js_target) +@conf +def configure_platform(ctx, platform=None): + if platform is not None: + ctx.setenv(platform, ctx.all_envs[platform]) + + cflags = ctx.env.CFLAGS + cflags = [ x for x in cflags if not x.startswith('-std=') ] + cflags.extend(['-std=c11', + '-fms-extensions', + '-Wno-address', + '-Wno-type-limits', + '-Wno-missing-field-initializers']) + + ctx.env.CFLAGS = cflags + @conf def build_platform(ctx, platform=None, binaries=None): if platform is not None: ctx.set_env(ctx.all_envs[platform]) - cflags = ['-Wno-address', - '-Wno-type-limits', - '-Wno-missing-field-initializers'] - build_worker = os.path.exists('worker_src') app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), - cflags=cflags, target=app_elf) if build_worker: worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) binaries.append({'platform': platform, 'app_elf': app_elf, 'worker_elf': worker_elf}) ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), - cflags=cflags, target=worker_elf) else: binaries.append({'platform': platform, 'app_elf': app_elf}) - @conf def concat_javascript(ctx, js_path=None): js_nodes = (ctx.path.ant_glob(js_path + '/**/*.js') + From 00f642d67d4ef7ad2a7c8e2a4b4b7ac583ef4478 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 8 Jul 2015 13:25:18 -0700 Subject: [PATCH 536/791] Change SimplyMenuCommonMember to a struct --- src/simply/simply_menu.h | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/simply/simply_menu.h b/src/simply/simply_menu.h index 090640aa..1702b4c7 100644 --- a/src/simply/simply_menu.h +++ b/src/simply/simply_menu.h @@ -40,19 +40,20 @@ struct SimplyMenu { typedef struct SimplyMenuCommon SimplyMenuCommon; -#define SimplyMenuCommonDef { \ - List1Node node; \ - uint16_t section; \ - char *title; \ -} - -struct SimplyMenuCommon SimplyMenuCommonDef; - -#define SimplyMenuCommonMember \ - union { \ - struct SimplyMenuCommon common; \ - struct SimplyMenuCommonDef; \ - } +struct SimplyMenuCommon { + List1Node node; + uint16_t section; + char *title; +}; + +typedef struct SimplyMenuCommonMember SimplyMenuCommonMember; + +struct SimplyMenuCommonMember { + union { + SimplyMenuCommon common; + SimplyMenuCommon; + }; +}; struct SimplyMenuSection { SimplyMenuCommonMember; From b2e388af2f6c3621f18081cbb7817a663ca79207 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 8 Jul 2015 17:38:47 -0700 Subject: [PATCH 537/791] Add antialiased and menu cell highlighted compatibility macros --- src/util/compat.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/util/compat.h b/src/util/compat.h index e0078773..301e53b0 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -57,6 +57,9 @@ typedef union GColor8 { //! Convenience macro to enable use of SDK 3.0 function to compare equality of two colors. #define gcolor_equal(a, b) ((a) == (b)) +#ifndef graphics_context_set_antialiased +#define graphics_context_set_antialiased(ctx, enable) +#endif //! Convenience function to use SDK 3.0 function to get a `GBitmap`'s `row_size_bytes` field. #ifndef gbitmap_get_bytes_per_row @@ -111,6 +114,10 @@ typedef union GColor8 { #define menu_layer_set_highlight_colors(menu_layer, background_color, text_color) #endif +#ifndef menu_cell_layer_is_highlighted +#define menu_cell_layer_is_highlighted(cell_layer) (false) +#endif + //! Convenience macro to use SDK 3.0 function to set a `PropertyAnimation`'s //! `values.from.grect` field. #ifndef property_animation_set_from_grect From 674ac8ce54815be1e031e29431affde115e4e389 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 8 Jul 2015 17:39:50 -0700 Subject: [PATCH 538/791] Add gpoint add and gpoint polar --- src/util/graphics.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/util/graphics.h b/src/util/graphics.h index 140d8d64..3caff022 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -15,6 +15,15 @@ #endif +static inline GPoint gpoint_add(const GPoint a, const GPoint b) { + return GPoint(a.x + b.x, a.y + b.y); +} + +static GPoint gpoint_polar(int32_t angle, int16_t radius) { + return GPoint(sin_lookup(angle) * radius / TRIG_MAX_RATIO, + cos_lookup(angle) * radius / TRIG_MAX_RATIO); +} + static inline GRect grect_center_rect(const GRect *rect_a, const GRect *rect_b) { return (GRect) { .origin = { From b368abcb07a41be1ff2463f71e1d3923667127f9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 8 Jul 2015 17:40:42 -0700 Subject: [PATCH 539/791] Add simply menu item loading spinner --- src/simply/simply_menu.c | 111 +++++++++++++++++++++++++++++++++------ src/simply/simply_menu.h | 21 +++++--- 2 files changed, 108 insertions(+), 24 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index b7584965..93368912 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -17,6 +17,8 @@ #define MAX_CACHED_ITEMS 51 +static const time_t SPINNER_MS = 66; + typedef Packet MenuClearPacket; typedef struct MenuClearSectionPacket MenuClearSectionPacket; @@ -83,6 +85,8 @@ struct __attribute__((__packed__)) MenuSelectionPacket { static GColor8 s_normal_palette[] = { { GColorBlackARGB8 }, { GColorClearARGB8 } }; static GColor8 s_inverted_palette[] = { { GColorWhiteARGB8 }, { GColorClearARGB8 } }; +static char EMPTY_TITLE[] = ""; + static void simply_menu_clear_section_items(SimplyMenu *self, int section_index); static void simply_menu_clear(SimplyMenu *self); @@ -94,8 +98,15 @@ static void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item); static MenuIndex simply_menu_get_selection(SimplyMenu *self); static void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, MenuRowAlign align, bool animated); -static char EMPTY_TITLE[] = ""; -static char LOADING_TEXT[] = "Loading..."; +static void refresh_spinner_timer(SimplyMenu *self); + + +static int64_t get_milliseconds(void) { + time_t now_s; + uint16_t now_ms_part; + time_ms(&now_s, &now_ms_part); + return ((int64_t) now_s) * 1000 + now_ms_part; +} static bool send_menu_item(Command type, uint16_t section, uint16_t item) { MenuItemEventPacket packet = { @@ -137,6 +148,11 @@ static bool item_filter(List1Node *node, void *data) { return (item->section == section_index && item->item == row); } +static bool request_item_filter(List1Node *node, void *data) { + SimplyMenuItem *item = (SimplyMenuItem*) node; + return (item->title == NULL); +} + static SimplyMenuSection *get_menu_section(SimplyMenu *self, int index) { return (SimplyMenuSection*) list1_find(self->menu_layer.sections, section_filter, (void*)(uintptr_t) index); } @@ -219,13 +235,17 @@ static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t *item = (SimplyMenuItem) { .section = section_index, .item = item_index, - .title = strdup2(LOADING_TEXT) }; add_item(self, item); send_menu_get_item(section_index, item_index); } static void mark_dirty(SimplyMenu *self) { + if (!self->menu_layer.menu_layer) { return; } + layer_mark_dirty(menu_layer_get_layer(self->menu_layer.menu_layer)); +} + +static void reload_data(SimplyMenu *self) { if (!self->menu_layer.menu_layer) { return; } menu_layer_reload_data(self->menu_layer.menu_layer); } @@ -235,7 +255,7 @@ static void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections num_sections = 1; } self->menu_layer.num_sections = num_sections; - mark_dirty(self); + reload_data(self); } static void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section) { @@ -243,7 +263,7 @@ static void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section section->title = EMPTY_TITLE; } add_section(self, section); - mark_dirty(self); + reload_data(self); } static void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item) { @@ -251,7 +271,7 @@ static void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item) { item->title = EMPTY_TITLE; } add_item(self, item); - mark_dirty(self); + reload_data(self); } static MenuIndex simply_menu_get_selection(SimplyMenu *self) { @@ -267,6 +287,22 @@ static bool send_menu_selection(SimplyMenu *self) { return send_menu_item(CommandMenuSelectionEvent, menu_index.section, menu_index.row); } +static void spinner_timer_callback(void *data) { + SimplyMenu *self = data; + self->spinner_timer = NULL; + mark_dirty(self); + refresh_spinner_timer(self); +} + +static void refresh_spinner_timer(SimplyMenu *self) { + if (!list1_find(self->menu_layer.items, request_item_filter, NULL)) { + return; + } + if (!self->spinner_timer) { + self->spinner_timer = app_timer_register(SPINNER_MS, spinner_timer_callback, self); + } +} + static uint16_t menu_get_num_sections_callback(MenuLayer *menu_layer, void *data) { SimplyMenu *self = data; return self->menu_layer.num_sections; @@ -298,7 +334,33 @@ static void menu_draw_header_callback(GContext *ctx, const Layer *cell_layer, ui menu_cell_basic_header_draw(ctx, cell_layer, section->title); } -static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index, void *data) { +static void simply_menu_draw_row_spinner(SimplyMenu *self, GContext *ctx, const Layer *cell_layer) { + GRect bounds = layer_get_bounds(cell_layer); + GPoint center = grect_center_point(&bounds); + + const int16_t min_radius = 4 * bounds.size.h / 24; + const int16_t max_radius = 9 * bounds.size.h / 24; + const int16_t num_lines = 16; + const int16_t num_drawn_lines = 3; + + const int64_t now_ms = get_milliseconds(); + const uint32_t start_index = (now_ms / SPINNER_MS) % num_lines; + + graphics_context_set_antialiased(ctx, true); + + GColor8 stroke_color = menu_cell_layer_is_highlighted(cell_layer) ? self->menu_layer.highlight_foreground + : self->menu_layer.normal_foreground; + graphics_context_set_stroke_color(ctx, gcolor8_get_or(stroke_color, GColorBlack)); + + for (int16_t i = 0; i < num_drawn_lines; i++) { + const uint32_t angle = (i + start_index) * TRIG_MAX_ANGLE / num_lines; + GPoint a = gpoint_add(center, gpoint_polar(angle, min_radius)); + GPoint b = gpoint_add(center, gpoint_polar(angle, max_radius)); + graphics_draw_line(ctx, a, b); + } +} + +static void menu_draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *data) { SimplyMenu *self = data; SimplyMenuSection *section = get_menu_section(self, cell_index->section); if (!section) { @@ -312,6 +374,12 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI return; } + if (item->title == NULL) { + simply_menu_draw_row_spinner(self, ctx, cell_layer); + refresh_spinner_timer(self); + return; + } + list1_remove(&self->menu_layer.items, &item->node); list1_prepend(&self->menu_layer.items, &item->node); @@ -419,7 +487,7 @@ static void simply_menu_clear(SimplyMenu *self) { destroy_item(self, (SimplyMenuItem*) self->menu_layer.items); } - mark_dirty(self); + reload_data(self); } static void handle_menu_clear_packet(Simply *simply, Packet *data) { @@ -433,17 +501,28 @@ static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { static void handle_menu_props_packet(Simply *simply, Packet *data) { MenuPropsPacket *packet = (MenuPropsPacket*) data; - simply_menu_set_num_sections(simply->menu, packet->num_sections); - window_set_background_color(simply->menu->window.window, gcolor8_get_or(packet->background_color, GColorWhite)); - if (!simply->menu->menu_layer.menu_layer) { + SimplyMenu *self = simply->menu; + + simply_menu_set_num_sections(self, packet->num_sections); + window_set_background_color(self->window.window, gcolor8_get_or(packet->background_color, GColorWhite)); + + if (!self->menu_layer.menu_layer) { return; } - menu_layer_set_highlight_colors(simply->menu->menu_layer.menu_layer, - gcolor8_get_or(packet->highlight_background_color, GColorBlack), - gcolor8_get_or(packet->highlight_text_color, GColorWhite)); + + self->menu_layer.normal_background = packet->background_color; + self->menu_layer.normal_foreground = packet->text_color; + + self->menu_layer.highlight_background = packet->highlight_background_color; + self->menu_layer.highlight_foreground = packet->highlight_text_color; + menu_layer_set_normal_colors(simply->menu->menu_layer.menu_layer, - gcolor8_get_or(packet->background_color, GColorWhite), - gcolor8_get_or(packet->text_color, GColorBlack)); + gcolor8_get_or(self->menu_layer.normal_background, GColorWhite), + gcolor8_get_or(self->menu_layer.normal_foreground, GColorBlack)); + + menu_layer_set_highlight_colors(simply->menu->menu_layer.menu_layer, + gcolor8_get_or(self->menu_layer.highlight_background, GColorBlack), + gcolor8_get_or(self->menu_layer.highlight_foreground, GColorWhite)); } static void handle_menu_section_packet(Simply *simply, Packet *data) { diff --git a/src/simply/simply_menu.h b/src/simply/simply_menu.h index 1702b4c7..0f59e134 100644 --- a/src/simply/simply_menu.h +++ b/src/simply/simply_menu.h @@ -10,14 +10,6 @@ #include -typedef struct SimplyMenuLayer SimplyMenuLayer; - -typedef struct SimplyMenu SimplyMenu; - -typedef struct SimplyMenuSection SimplyMenuSection; - -typedef struct SimplyMenuItem SimplyMenuItem; - typedef enum SimplyMenuType SimplyMenuType; enum SimplyMenuType { @@ -26,16 +18,25 @@ enum SimplyMenuType { SimplyMenuTypeItem, }; +typedef struct SimplyMenuLayer SimplyMenuLayer; + struct SimplyMenuLayer { MenuLayer *menu_layer; List1Node *sections; List1Node *items; uint16_t num_sections; + GColor8 normal_foreground; + GColor8 normal_background; + GColor8 highlight_foreground; + GColor8 highlight_background; }; +typedef struct SimplyMenu SimplyMenu; + struct SimplyMenu { SimplyWindow window; SimplyMenuLayer menu_layer; + AppTimer *spinner_timer; }; typedef struct SimplyMenuCommon SimplyMenuCommon; @@ -55,11 +56,15 @@ struct SimplyMenuCommonMember { }; }; +typedef struct SimplyMenuSection SimplyMenuSection; + struct SimplyMenuSection { SimplyMenuCommonMember; uint16_t num_items; }; +typedef struct SimplyMenuItem SimplyMenuItem; + struct SimplyMenuItem { SimplyMenuCommonMember; char *subtitle; From 70ecd2c1dd27381e21f7698317e732be4bf463c7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 8 Jul 2015 18:48:43 -0700 Subject: [PATCH 540/791] Add list1 find last --- src/util/list1.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/util/list1.h b/src/util/list1.h index e962ab75..c9b81cf2 100644 --- a/src/util/list1.h +++ b/src/util/list1.h @@ -90,6 +90,16 @@ static inline List1Node *list1_find_prev(List1Node *node, return NULL; } +static inline List1Node *list1_find_last(List1Node *node, List1FilterCallback callback, void *data) { + List1Node *match = NULL; + for (; node; node = node->next) { + if (callback(node, data)) { + match = node; + } + } + return match; +} + static inline List1Node *list1_find(List1Node *node, List1FilterCallback callback, void *data) { return list1_find_prev(node, callback, data, NULL); } From e0aae749d3b81aeafa88002c73f41f242aa010a9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 8 Jul 2015 18:22:02 -0700 Subject: [PATCH 541/791] Only draw the spinner for the first unloaded menu item --- src/simply/simply_menu.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 93368912..750b03fd 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -294,11 +294,16 @@ static void spinner_timer_callback(void *data) { refresh_spinner_timer(self); } +static SimplyMenuItem *get_first_request_item(SimplyMenu *self) { + return (SimplyMenuItem*) list1_find(self->menu_layer.items, request_item_filter, NULL); +} + +static SimplyMenuItem *get_last_request_item(SimplyMenu *self) { + return (SimplyMenuItem*) list1_find_last(self->menu_layer.items, request_item_filter, NULL); +} + static void refresh_spinner_timer(SimplyMenu *self) { - if (!list1_find(self->menu_layer.items, request_item_filter, NULL)) { - return; - } - if (!self->spinner_timer) { + if (!self->spinner_timer && get_first_request_item(self)) { self->spinner_timer = app_timer_register(SPINNER_MS, spinner_timer_callback, self); } } @@ -375,8 +380,11 @@ static void menu_draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuI } if (item->title == NULL) { - simply_menu_draw_row_spinner(self, ctx, cell_layer); - refresh_spinner_timer(self); + SimplyMenuItem *last_request = get_last_request_item(self); + if (last_request == item) { + simply_menu_draw_row_spinner(self, ctx, cell_layer); + refresh_spinner_timer(self); + } return; } From 1915c49c8791bb23d8a72cc2ca7fce15a0108dc9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 8 Jul 2015 18:25:05 -0700 Subject: [PATCH 542/791] Fix simply menu scrolling coarseness caused by reloading on item add --- src/simply/simply_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 750b03fd..161c9ace 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -271,7 +271,7 @@ static void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item) { item->title = EMPTY_TITLE; } add_item(self, item); - reload_data(self); + mark_dirty(self); } static MenuIndex simply_menu_get_selection(SimplyMenu *self) { From 68815086d894b89a8b00536fce7134dae80fc52c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 9 Jul 2015 16:59:18 -0700 Subject: [PATCH 543/791] Evict images to load new images when out of memory. Addresses #71 --- src/simply/simply_res.c | 98 +++++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 28 deletions(-) diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 2cbeb5af..e84bddb2 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -12,12 +12,20 @@ static bool id_filter(List1Node *node, void *data) { } static void destroy_image(SimplyRes *self, SimplyImage *image) { + if (!image) { + return; + } + list1_remove(&self->images, &image->node); gbitmap_destroy(image->bitmap); free(image->palette); } static void destroy_font(SimplyRes *self, SimplyFont *font) { + if (!font) { + return; + } + list1_remove(&self->fonts, &font->node); fonts_unload_custom_font(font->font); free(font); @@ -37,32 +45,71 @@ static void setup_image(SimplyImage *image) { image->palette = palette_copy; } -SimplyImage *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { - SimplyImage *image = malloc(sizeof(*image)); - if (!image) { - return NULL; +static bool evict_image(SimplyRes *self) { + SimplyImage *last_image = (SimplyImage *)list1_last(self->images); + if (!last_image) { + return false; } - GBitmap *bitmap = gbitmap_create_with_resource(id); - if (!bitmap) { - free(image); - return NULL; - } - - *image = (SimplyImage) { - .id = id, - .bitmap = bitmap, - }; + destroy_image(self, last_image); + return true; +} +static void add_image(SimplyRes *self, SimplyImage *image) { list1_prepend(&self->images, &image->node); setup_image(image); window_stack_schedule_top_window_render(); +} + +typedef GBitmap *(*GBitmapCreator)(SimplyImage *image, void *data); +static SimplyImage *create_image(SimplyRes *self, GBitmapCreator creator, void *data) { + SimplyImage *image = NULL; + if (!(image = malloc0(sizeof(*image)))) { + if (!evict_image(self)) { + return NULL; + } + } + + GBitmap *bitmap = NULL; + while (!(bitmap = creator(image, data))) { + if (!evict_image(self)) { + free(image); + return NULL; + } + } + + image->bitmap = bitmap; + + return image; +} + +static GBitmap *create_bitmap_with_id(SimplyImage *image, void *data) { + const uint32_t id = (uint32_t)(uintptr_t) data; + GBitmap *bitmap = gbitmap_create_with_resource(id); + if (bitmap) { + image->id = id; + } + return bitmap; +} + +SimplyImage *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { + SimplyImage *image = create_image(self, create_bitmap_with_id, (void*)(uintptr_t) id); + add_image(self, image); return image; } +static GBitmap *create_blank_bitmap(SimplyImage *image, void *data) { + GSize *size = data; + GBitmap *bitmap = gbitmap_create_blank(*size, GBitmapFormat1Bit); + if (bitmap) { + image->bitmap_data = gbitmap_get_data(bitmap); + } + return bitmap; +} + SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint8_t *pixels) { SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); @@ -71,24 +118,19 @@ SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, i uint16_t row_size_bytes = gbitmap_get_bytes_per_row(image->bitmap); gbitmap_set_data(image->bitmap, pixels, GBitmapFormat1Bit, row_size_bytes, true); image->bitmap_data = pixels; - } else { - image = malloc(sizeof(*image)); - if (!image) { - return NULL; - } - *image = (SimplyImage) { .id = id }; - list1_prepend(&self->images, &image->node); - image->bitmap = gbitmap_create_blank(GSize(width, height), GBitmapFormat1Bit); - image->bitmap_data = gbitmap_get_data(image->bitmap); - uint16_t row_size_bytes = gbitmap_get_bytes_per_row(image->bitmap); - size_t pixels_size = height * row_size_bytes; - memcpy(image->bitmap_data, pixels, pixels_size); + window_stack_schedule_top_window_render(); - setup_image(image); + return image; } - window_stack_schedule_top_window_render(); + image = create_image(self, create_blank_bitmap, &GSize(width, height)); + + uint16_t row_size_bytes = gbitmap_get_bytes_per_row(image->bitmap); + size_t pixels_size = height * row_size_bytes; + memcpy(image->bitmap_data, pixels, pixels_size); + + add_image(self, image); return image; } From 999106991a1dc99d21515febc4194b32f3407ffa Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 9 Jul 2015 17:04:27 -0700 Subject: [PATCH 544/791] Add simply stage image eviction when out of memory --- src/simply/simply_res.c | 6 +++--- src/simply/simply_res.h | 1 + src/simply/simply_stage.c | 17 +++++++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index e84bddb2..d9ccda42 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -45,7 +45,7 @@ static void setup_image(SimplyImage *image) { image->palette = palette_copy; } -static bool evict_image(SimplyRes *self) { +bool simply_res_evict_image(SimplyRes *self) { SimplyImage *last_image = (SimplyImage *)list1_last(self->images); if (!last_image) { return false; @@ -68,14 +68,14 @@ typedef GBitmap *(*GBitmapCreator)(SimplyImage *image, void *data); static SimplyImage *create_image(SimplyRes *self, GBitmapCreator creator, void *data) { SimplyImage *image = NULL; if (!(image = malloc0(sizeof(*image)))) { - if (!evict_image(self)) { + if (!simply_res_evict_image(self)) { return NULL; } } GBitmap *bitmap = NULL; while (!(bitmap = creator(image, data))) { - if (!evict_image(self)) { + if (!simply_res_evict_image(self)) { free(image); return NULL; } diff --git a/src/simply/simply_res.h b/src/simply/simply_res.h index 8552a052..8753c728 100644 --- a/src/simply/simply_res.h +++ b/src/simply/simply_res.h @@ -56,6 +56,7 @@ void simply_res_clear(SimplyRes *self); SimplyImage *simply_res_add_bundled_image(SimplyRes *self, uint32_t id); SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint8_t *pixels); SimplyImage *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder); +bool simply_res_evict_image(SimplyRes *self); GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id); GFont simply_res_auto_font(SimplyRes *self, uint32_t id); diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 24bffddf..0a5d0166 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -292,6 +292,9 @@ static SimplyElementCommon *alloc_element(SimplyElementType type) { case SimplyElementTypeImage: return malloc0(sizeof(SimplyElementImage)); case SimplyElementTypeInverter: { SimplyElementInverter *element = malloc0(sizeof(SimplyElementInverter)); + if (!element) { + return NULL; + } element->inverter_layer = inverter_layer_create(GRect(0, 0, 0, 0)); return &element->common; } @@ -308,9 +311,10 @@ SimplyElementCommon *simply_stage_auto_element(SimplyStage *self, uint32_t id, S if (element) { return element; } - element = alloc_element(type); - if (!element) { - return NULL; + while (!(element = alloc_element(type))) { + if (!simply_res_evict_image(self->window.simply->res)) { + return NULL; + } } element->id = id; element->type = type; @@ -586,7 +590,12 @@ static void handle_element_animate_packet(Simply *simply, Packet *data) { if (!element) { return; } - SimplyAnimation *animation = malloc0(sizeof(*animation)); + SimplyAnimation *animation = NULL; + while (!(animation = malloc0(sizeof(*animation)))) { + if (!simply_res_evict_image(simply->res)) { + return; + } + } animation->duration = packet->duration; animation->curve = packet->curve; simply_stage_animate_element(simply->stage, element, animation, packet->frame); From fa97c70f3267f9619a7e8b42e3a5fedbf56c363d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 9 Jul 2015 18:02:33 -0700 Subject: [PATCH 545/791] Change Element animate to copy animation changes after dispatch --- src/js/ui/element.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ui/element.js b/src/js/ui/element.js index ffdc27f2..b1a44b1e 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -79,9 +79,9 @@ StageElement.prototype.animate = function(field, value, duration) { duration = value; } var animateDef = myutil.toObject(field, value); - util2.copy(animateDef, this.state); function animate() { this._animate(animateDef, duration); + util2.copy(animateDef, this.state); } if (this._queue.length === 0) { animate.call(this); From 8942c0ec00acfe11b074900914bf05c563176288 Mon Sep 17 00:00:00 2001 From: C-D-Lewis Date: Fri, 10 Jul 2015 10:23:24 -0700 Subject: [PATCH 546/791] Fix link to HyperDither --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2b46c88..acdbdec9 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ You can use images in your Pebble.js application. Currently all images must be e We recommend that you follow these guidelines when preparing your images for Pebble: * Resize all images for the screen of Pebble. A fullscreen image will be 144 pixels wide by 168 pixels high. - * Use an image editor or [HyperDither](http://www.tinrocket.com/hyperdither/) to dither your image in black and white. + * Use an image editor or [HyperDither](http://2002-2010.tinrocket.com/software/hyperdither/index.html) to dither your image in black and white. * Remember that the maximum size for a Pebble application is 100kB. You will quickly reach that limit if you add too many images. To add an image in your application, edit the `appinfo.json` file and add your image: From 8d5d62623676716607e76d5d1b922424131a7571 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 10 Jul 2015 14:00:00 -0700 Subject: [PATCH 547/791] Fix menu icon flashing by changing palette based on highlighted state --- src/simply/simply_menu.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 161c9ace..28dea76b 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -396,10 +396,8 @@ static void menu_draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuI if (image && image->is_palette_black_and_white) { palette = gbitmap_get_palette(image->bitmap); - MenuIndex selected_index = menu_layer_get_selected_index(self->menu_layer.menu_layer); - const bool is_selected = (selected_index.section == cell_index->section && - selected_index.row == cell_index->row); - gbitmap_set_palette(image->bitmap, is_selected ? s_inverted_palette : s_normal_palette, false); + const bool is_highlighted = menu_cell_layer_is_highlighted(cell_layer); + gbitmap_set_palette(image->bitmap, is_highlighted ? s_inverted_palette : s_normal_palette, false); } graphics_context_set_alpha_blended(ctx, true); From 8518904c365b4d96ca67895ed9318718589b1e9f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 10 Jul 2015 14:32:56 -0700 Subject: [PATCH 548/791] Change gcolor8_equal to gcolor8_equal_native comparing 8-bit with native --- src/simply/simply_window.c | 2 +- src/util/color.h | 9 +++++---- src/util/graphics.h | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 5b5f27f3..58e4bc8a 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -191,7 +191,7 @@ void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor8 b return; } - s_button_palette[0] = gcolor8_equal(background_color, GColorWhite) ? GColor8Black : GColor8White; + s_button_palette[0] = gcolor8_equal(background_color, GColor8White) ? GColor8Black : GColor8White; action_bar_layer_set_background_color(self->action_bar_layer, gcolor8_get(background_color)); simply_window_set_action_bar(self, true); diff --git a/src/util/color.h b/src/util/color.h index 696bad5e..9ca29ba0 100644 --- a/src/util/color.h +++ b/src/util/color.h @@ -35,7 +35,7 @@ static inline GColor8 gcolor_get8(GColor color) { } } -static inline bool gcolor8_equal(GColor8 color, GColor other) { +static inline bool gcolor8_equal_native(GColor8 color, GColor other) { return (color.argb == gcolor_get8(other).argb); } @@ -49,9 +49,10 @@ static inline GColor gcolor8_get_or(GColor8 color, GColor8 fallback) { return color; } -static inline bool gcolor8_equal(GColor8 color, GColor other) { - return (color.argb == other.argb); -} +#define gcolor8_equal_native gcolor8_equal #endif +static inline bool gcolor8_equal(GColor8 color, GColor8 other) { + return (color.argb == other.argb); +} diff --git a/src/util/graphics.h b/src/util/graphics.h index 3caff022..6f783d51 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -52,6 +52,6 @@ static inline bool gbitmap_is_palette_black_and_white(GBitmap *bitmap) { return false; } const GColor8 *palette = gbitmap_get_palette(bitmap); - return (gcolor8_equal(palette[0], GColorWhite) && gcolor8_equal(palette[1], GColorBlack)) || - (gcolor8_equal(palette[0], GColorBlack) && gcolor8_equal(palette[1], GColorWhite)); + return (gcolor8_equal(palette[0], GColor8White) && gcolor8_equal(palette[1], GColor8Black)) || + (gcolor8_equal(palette[0], GColor8Black) && gcolor8_equal(palette[1], GColor8White)); } From 944cb74c2a7059599ee68e3b5cfb2a4e0ed984a4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 10 Jul 2015 15:01:04 -0700 Subject: [PATCH 549/791] Retain text color when changing text content in a card textfield --- src/js/ui/simply-pebble.js | 5 +++-- src/simply/simply_ui.c | 4 +++- src/util/color.h | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 9445905c..4c69e733 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -135,7 +135,8 @@ var colorMap = { 'yellow': 0xFC, 'icterine': 0xFD, 'pastelYellow': 0xFE, - 'white': 0xFF + 'white': 0xFF, + 'clearWhite': 0x3F, }; var Color = function(color) { @@ -941,7 +942,7 @@ SimplyPebble.cardClear = function(clear) { SimplyPebble.cardText = function(field, text, color) { CardTextPacket .index(field) - .color(color || 'black') + .color(color || 'clearWhite') .text(text || ''); SimplyPebble.sendPacket(CardTextPacket); }; diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 7327cc32..31ce53fe 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -346,7 +346,9 @@ static void handle_card_text_packet(Simply *simply, Packet *data) { return; } simply_ui_set_text(simply->ui, textfield_id, packet->text); - simply_ui_set_text_color(simply->ui, textfield_id, packet->color); + if (!gcolor8_equal(packet->color, GColor8ClearWhite)) { + simply_ui_set_text_color(simply->ui, textfield_id, packet->color); + } } static void handle_card_image_packet(Simply *simply, Packet *data) { diff --git a/src/util/color.h b/src/util/color.h index 9ca29ba0..ad367fa4 100644 --- a/src/util/color.h +++ b/src/util/color.h @@ -7,6 +7,7 @@ #define GColor8White (GColor8){.argb=GColorWhiteARGB8} #define GColor8Black (GColor8){.argb=GColorBlackARGB8} #define GColor8Clear (GColor8){.argb=GColorClearARGB8} +#define GColor8ClearWhite (GColor8){.argb=0x3F} #ifndef PBL_COLOR From fa651024703fd8a2000766cdb2ade0232d02e352 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 10 Jul 2015 18:26:46 -0700 Subject: [PATCH 550/791] Fix the disconnect window to allow exiting upon pressing back --- src/simply/simply_stage.c | 1 - src/simply/simply_ui.c | 1 - src/simply/simply_window.c | 16 ++++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 0a5d0166..20491fa4 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -441,7 +441,6 @@ static void window_load(Window *window) { *(void**) layer_get_data(layer) = self; layer_set_update_proc(layer, layer_update_callback); scroll_layer_add_child(self->window.scroll_layer, layer); - scroll_layer_set_click_config_onto_window(self->window.scroll_layer, window); } static void window_appear(Window *window) { diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 31ce53fe..7f068aed 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -308,7 +308,6 @@ static void window_load(Window *window) { *(void**) layer_get_data(layer) = self; layer_set_update_proc(layer, layer_update_callback); scroll_layer_add_child(self->window.scroll_layer, layer); - scroll_layer_set_click_config_onto_window(self->window.scroll_layer, window); simply_ui_set_style(self, 1); } diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 58e4bc8a..5a1d1b9e 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -217,12 +217,12 @@ void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *con SimplyWindow *self = context; ButtonId button = click_recognizer_get_button_id(recognizer); bool is_enabled = (self->button_mask & (1 << button)); - if (button == BUTTON_ID_BACK && !is_enabled) { - if (simply_msg_has_communicated()) { - simply_window_stack_back(self->simply->window_stack, self); - } else { + if (button == BUTTON_ID_BACK) { + if (!simply_msg_has_communicated()) { bool animated = true; window_stack_pop(animated); + } else if (!is_enabled) { + simply_window_stack_back(self->simply->window_stack, self); } } if (is_enabled) { @@ -274,7 +274,9 @@ bool simply_window_appear(SimplyWindow *self) { if (!self->id) { return false; } - simply_window_stack_send_show(self->simply->window_stack, self); + if (simply_msg_has_communicated()) { + simply_window_stack_send_show(self->simply->window_stack, self); + } return true; } @@ -282,7 +284,9 @@ bool simply_window_disappear(SimplyWindow *self) { if (!self->id) { return false; } - simply_window_stack_send_hide(self->simply->window_stack, self); + if (simply_msg_has_communicated()) { + simply_window_stack_send_hide(self->simply->window_stack, self); + } #ifdef PBL_PLATFORM_BASALT simply_window_set_fullscreen(self, true); From 72b019bebab319450ecf2f5239c749ded3363ba8 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Sun, 2 Aug 2015 22:06:31 -0400 Subject: [PATCH 551/791] Document Timeline module and functionality --- README.md | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index acdbdec9..569985f1 100644 --- a/README.md +++ b/README.md @@ -1089,6 +1089,44 @@ Restore the normal behaviour. #### Light.trigger() Trigger the backlight to turn on momentarily, just like if the user shook their wrist. +## Timeline + +The Timeline module allows your app to handle a launch via a timeline action. This allows you to write a custom handler to manage lanuch events outside of the app menu. With the Timeline module, you can preform a specific set of actions based on the action which launched the app. + +### Timeline + +`Timeline` provides a single module of the same name `Timeline`. + +````js +var Timeline = require('timeline'); +```` + + +#### Timeline.launch(callback(event)) +[Timeline.launch]: #timeline-launch + +If you wish to change the behavior of your app depending on whether it was launched by a timeline event, and further configure the behavior based on the data associated with the timeline event, use `Timeline.launch` on startup. `Timeline.launch` will immediately call your launch callback asynchronously with a launch event detailing whether or not your app was launched by a timeline event. + +````js +// Query whether we launched by a timeline event +Timeline.launch(function(e) { + if (e.action) { + console.log('Woke up to timeline event: ' + e.launchCode + '!'); + } else { + console.log('Regular launch not by a timeline event.'); + } +}); +```` + +The `callback` will be called with a timeline launch event. The event has the following properties: + +| Name | Type | Description | +| ---- | :----: | ------------- | +| `action` | boolean | `true` if the app woke up by a imeline event, otherwise `false`. | +| `launchCode` | number | If woken by a timeline event, the code of the action. | + +Note that this means you may have to move portions of your startup logic into the `Timeline.launch` callback or a function called by the callback. This can also add a very small delay to startup behavior because the underlying implementation must query the watch for the launch information. + ## Wakeup The Wakeup module allows you to schedule your app to wakeup at a specified time using Pebble's wakeup functionality. Whether the user is in a watchface or in a different app, your app while launch by the specified time. This allows you to write a custom alarm app, for example. With the Wakeup module, you can save data to be read on launch and configure your app to behave differently based on launch data. The Wakeup module, like the Settings module, is backed by localStorage. @@ -1160,7 +1198,7 @@ Finally, there are multiple reasons why setting a wakeup event can fail. When a #### Wakeup.launch(callback(event)) [Wakeup.launch]: #wakeup-launch -If you wish to change the behavior of your app depending on whether it was launched by a wakeup event, and further configure the behavior based on the data associated with the wakeup event, use `Wakeup.launch` on startup. `Wakeup.launch` will immediately called your launch callback asynchronously with a launch event detailing whether or not your app was launched by a wakeup event. +If you wish to change the behavior of your app depending on whether it was launched by a wakeup event, and further configure the behavior based on the data associated with the wakeup event, use `Wakeup.launch` on startup. `Wakeup.launch` will immediately call your launch callback asynchronously with a launch event detailing whether or not your app was launched by a wakeup event. ````js // Query whether we launched by a wakeup event From 662b407c34a4c23cab32f7f09f6472bfd6083463 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 3 Aug 2015 03:07:11 -0700 Subject: [PATCH 552/791] Fix scheduling a wakeup event immediately. Addresses #82 The Wakeup module was created when the timezone API was exposed, but not yet enabled. This required falling back to the phone for calculating the corrected timestamp if communicating with a non-timezone Pebble. However, scheduling a wakeup doesn't wait until the fallback logic decides whether it is necessary in the first place, causing issues with immediately scheduling a startup with timezone enabled Pebbles. The fix wraps the schedule message in a Wakeup launch callback, deferring the schedule process until the Pebble has communicated whether it supports timezones or not. --- src/js/wakeup/wakeup.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js index 8d3caa26..2df8b165 100644 --- a/src/js/wakeup/wakeup.js +++ b/src/js/wakeup/wakeup.js @@ -75,7 +75,9 @@ Wakeup.schedule = function(opt, callback) { data: opt.data, callback: callback, }); - simply.impl.wakeupSet(opt.time, cookie, opt.notifyIfMissed); + Wakeup.launch(function() { + simply.impl.wakeupSet(opt.time, cookie, opt.notifyIfMissed); + }); }; Wakeup.cancel = function(id) { From e22b1f1441a218e6e8c60b5bb252bf256d1a4302 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Mon, 3 Aug 2015 10:46:36 -0400 Subject: [PATCH 553/791] Fix some typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 569985f1..483a6d16 100644 --- a/README.md +++ b/README.md @@ -1091,7 +1091,7 @@ Trigger the backlight to turn on momentarily, just like if the user shook their ## Timeline -The Timeline module allows your app to handle a launch via a timeline action. This allows you to write a custom handler to manage lanuch events outside of the app menu. With the Timeline module, you can preform a specific set of actions based on the action which launched the app. +The Timeline module allows your app to handle a launch via a timeline action. This allows you to write a custom handler to manage launch events outside of the app menu. With the Timeline module, you can preform a specific set of actions based on the action which launched the app. ### Timeline @@ -1122,7 +1122,7 @@ The `callback` will be called with a timeline launch event. The event has the fo | Name | Type | Description | | ---- | :----: | ------------- | -| `action` | boolean | `true` if the app woke up by a imeline event, otherwise `false`. | +| `action` | boolean | `true` if the app woke up by a timeline event, otherwise `false`. | | `launchCode` | number | If woken by a timeline event, the code of the action. | Note that this means you may have to move portions of your startup logic into the `Timeline.launch` callback or a function called by the callback. This can also add a very small delay to startup behavior because the underlying implementation must query the watch for the launch information. From 7698620a49912740809a5c59cd5f796c5fdeb048 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 6 Aug 2015 02:31:46 -0700 Subject: [PATCH 554/791] Fix remote images for aplite Remote images were being created without their associated id, causing them to not be displayed on the watch. --- src/simply/simply_res.c | 17 +++++++++++------ src/simply/simply_res.h | 2 ++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index d9ccda42..6869553d 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -67,7 +67,7 @@ typedef GBitmap *(*GBitmapCreator)(SimplyImage *image, void *data); static SimplyImage *create_image(SimplyRes *self, GBitmapCreator creator, void *data) { SimplyImage *image = NULL; - if (!(image = malloc0(sizeof(*image)))) { + while (!(image = malloc0(sizeof(*image)))) { if (!simply_res_evict_image(self)) { return NULL; } @@ -97,7 +97,9 @@ static GBitmap *create_bitmap_with_id(SimplyImage *image, void *data) { SimplyImage *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { SimplyImage *image = create_image(self, create_bitmap_with_id, (void*)(uintptr_t) id); - add_image(self, image); + if (image) { + add_image(self, image); + } return image; } @@ -125,12 +127,15 @@ SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, i } image = create_image(self, create_blank_bitmap, &GSize(width, height)); + if (image) { + image->id = id; - uint16_t row_size_bytes = gbitmap_get_bytes_per_row(image->bitmap); - size_t pixels_size = height * row_size_bytes; - memcpy(image->bitmap_data, pixels, pixels_size); + uint16_t row_size_bytes = gbitmap_get_bytes_per_row(image->bitmap); + size_t pixels_size = height * row_size_bytes; + memcpy(image->bitmap_data, pixels, pixels_size); - add_image(self, image); + add_image(self, image); + } return image; } diff --git a/src/simply/simply_res.h b/src/simply/simply_res.h index 8753c728..33ab0d7f 100644 --- a/src/simply/simply_res.h +++ b/src/simply/simply_res.h @@ -1,5 +1,7 @@ #pragma once +#include "simply.h" + #include "util/color.h" #include "util/list1.h" From 97d06e60db5db84c05fe78159cc2d57ace5a9a1a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 11 Aug 2015 11:50:30 -0700 Subject: [PATCH 555/791] Fix support for element frames with negative dimensions graphics_fill_rect infinitely loops when given a grect with a negative height on basalt. Workaround this issue by standardizing the element frame before it is used for rendering. --- src/simply/simply_stage.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 20491fa4..faddec70 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -344,6 +344,7 @@ SimplyElementCommon *simply_stage_remove_element(SimplyStage *self, SimplyElemen } void simply_stage_set_element_frame(SimplyStage *self, SimplyElementCommon *element, GRect frame) { + grect_standardize(&frame); element->frame = frame; switch (element->type) { default: break; From fccd4eb7bc9e3038e3c4d761061285e00771e68a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 11 Aug 2015 02:59:53 -0700 Subject: [PATCH 556/791] Fix safe.protect to return undefined if given a value that evaluates as false safe.protect should be close to an invisible wrapper. Since it always returns a function, it hides some errors. The fix is to return undefined if a value that evaluates as false is passed in. For now, the value is not checked to be a function in case a native function does not report itself as a function. --- src/js/lib/safe.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index dff95cfd..5c947e16 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -135,14 +135,13 @@ safe.dumpError = function(err) { /* Takes a function and return a new function with a call to it wrapped in a try/catch statement */ safe.protect = function(fn) { - return function() { + return fn ? function() { try { - return fn.apply(this, arguments); - } - catch (err) { + fn.apply(this, arguments); + } catch (err) { safe.dumpError(err); } - }; + } : undefined; }; /* Wrap event handlers added by Pebble.addEventListener */ From ec93c7d0fa5253c47a79515876ad79a4a6c649a4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 11 Aug 2015 03:03:43 -0700 Subject: [PATCH 557/791] Remove debug output from safe translateStackAndroid --- src/js/lib/safe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 5c947e16..e29f41a8 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -75,6 +75,7 @@ safe.translateStackAndroid = function(stack) { if (line.match(/jskit_startup\.html/)) { lines.splice(i, 1); } else { + /* Matches ':' ':' */ var m = line.match(/^.*\/(.*?):(\d+):(\d+)/); if (m) { name = m[1]; @@ -84,7 +85,6 @@ safe.translateStackAndroid = function(stack) { } if (name) { var pos = safe.translatePos(name, lineno, colno); - console.log(pos, name, lineno, colno); if (line.match(/\(.*\)/)) { line = line.replace(/\(.*\)/, '(' + pos + ')'); } else { From e7f65ddb5cbc6bca8e071774b7c2cbdbd0c11b2a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 11 Aug 2015 03:04:02 -0700 Subject: [PATCH 558/791] Add safe warn for reporting runtime warnings --- src/js/lib/safe.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index e29f41a8..f0c2089a 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -107,11 +107,11 @@ safe.translateStack = function(stack) { } }; -safe.translateError = function(err) { +safe.translateError = function(err, intro) { var name = err.name; var message = err.message || err.toString(); var stack = err.stack; - var result = ['JavaScript Error:']; + var result = [intro || 'JavaScript Error:']; if (message && (!stack || !stack.match(message))) { if (name && !message.match(message)) { message = name + ': ' + message; @@ -124,15 +124,22 @@ safe.translateError = function(err) { return result.join('\n'); }; -/* We use this function to dump error messages to the console. */ -safe.dumpError = function(err) { +/* Dumps error messages to the console. */ +safe.dumpError = function(err, intro) { if (typeof err === 'object') { - console.log(safe.translateError(err)); + console.log(safe.translateError(err, intro)); } else { - console.log('dumpError :: argument is not an object'); + console.log('Error: dumpError argument is not an object'); } }; +/* Logs runtime warnings to the console. */ +safe.warn = function(message, name) { + var err = new Error(message); + err.name = name || 'Warning'; + safe.dumpError(err, 'Warning:'); +}; + /* Takes a function and return a new function with a call to it wrapped in a try/catch statement */ safe.protect = function(fn) { return fn ? function() { From 22928d825ea1de5e2937e0e2640db133f1ffe897 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 11 Aug 2015 03:04:20 -0700 Subject: [PATCH 559/791] Add setTimeout and setInterval not function one-time warnings --- src/js/lib/safe.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index f0c2089a..dd766930 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -165,10 +165,21 @@ Pebble.sendAppMessage = function(message, success, failure) { /* Wrap setTimeout and setInterval */ var originalSetTimeout = setTimeout; window.setTimeout = function(callback, delay) { + if (safe.warnSetTimeoutNotFunction !== false && typeof callback !== 'function') { + safe.warn('setTimeout was called with a `' + (typeof callback) + '` type. ' + + 'Did you mean to pass a function?'); + safe.warnSetTimeoutNotFunction = false; + } return originalSetTimeout(safe.protect(callback), delay); }; + var originalSetInterval = setInterval; window.setInterval = function(callback, delay) { + if (safe.warnSetIntervalNotFunction !== false && typeof callback !== 'function') { + safe.warn('setInterval was called with a `' + (typeof callback) + '` type. ' + + 'Did you mean to pass a function?'); + safe.warnSetIntervalNotFunction = false; + } return originalSetInterval(safe.protect(callback), delay); }; From b1b183ca56656cffdc4c62c8d8d62b06a2323f71 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 11 Aug 2015 03:54:48 -0700 Subject: [PATCH 560/791] Change safe to wrap ajax only if it exists safe assumed the ajax module always existed. This changes safe to be usable in projects that don't have the ajax module. --- src/js/lib/safe.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index dd766930..b2785bbd 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -7,8 +7,6 @@ /* global __loader */ -var ajax = require('ajax'); - var safe = {}; /* The name of the concatenated file to translate */ @@ -183,19 +181,29 @@ window.setInterval = function(callback, delay) { return originalSetInterval(safe.protect(callback), delay); }; -/* Wrap the success and failure callback of the ajax library */ -ajax.onHandler = function(eventName, callback) { - return safe.protect(callback); -}; - /* Wrap the geolocation API Callbacks */ var watchPosition = navigator.geolocation.watchPosition; navigator.geolocation.watchPosition = function(success, error, options) { return watchPosition.call(this, safe.protect(success), safe.protect(error), options); }; + var getCurrentPosition = navigator.geolocation.getCurrentPosition; navigator.geolocation.getCurrentPosition = function(success, error, options) { return getCurrentPosition.call(this, safe.protect(success), safe.protect(error), options); }; +var ajax; + +/* Try to load the ajax library if available and silently fail if it is not found. */ +try { + ajax = require('ajax'); +} catch (err) {} + +/* Wrap the success and failure callback of the ajax library */ +if (ajax) { + ajax.onHandler = function(eventName, callback) { + return safe.protect(callback); + }; +} + module.exports = safe; From 70c2d1f626b739972d0f056400b6b339262ceaad Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 11 Aug 2015 03:55:33 -0700 Subject: [PATCH 561/791] Fix safe v8 to translate lines that do not have a scope Some v8 stack traces do not have a scope beside the line number in parenthesis. This updates the line translation to take into account when the scope is missing, such as in stack trace lines that feature no parenthesis. --- src/js/lib/safe.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index b2785bbd..21704035 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -26,12 +26,6 @@ safe.translatePos = function(name, lineno, colno) { return name + ':' + lineno + ':' + colno; }; -/* Translates a node style stack trace line */ -var translateLineV8 = function(line, msg, name, lineno, colno) { - var pos = safe.translatePos(name, lineno, colno); - return msg + '(' + pos + ')'; -}; - var makeTranslateStack = function(stackLineRegExp, translateLine) { return function(stack) { var lines = stack.split('\n'); @@ -42,15 +36,21 @@ var makeTranslateStack = function(stackLineRegExp, translateLine) { line = lines[i] = translateLine.apply(this, m); } if (line.match(module.filename)) { - lines.splice(--i, 2); + lines.splice(i, 1); } } return lines.join('\n'); }; }; -/* Matches '(' ':' ':' ')' */ -var stackLineRegExpV8 = /(.*)\(([^\s@:]+):(\d+):(\d+)\)/; +/* Translates a node style stack trace line */ +var translateLineV8 = function(line, msg, scope, name, lineno, colno) { + var pos = safe.translatePos(name, lineno, colno); + return msg + (scope ? ' ' + scope + ' (' + pos + ')' : pos); +}; + +/* Matches ( '(')? ':' ':' ')'? */ +var stackLineRegExpV8 = /(.+?)(?:\s+([^\s]+)\s+\()?([^\s@:]+):(\d+):(\d+)\)?/; safe.translateStackV8 = makeTranslateStack(stackLineRegExpV8, translateLineV8); From 0d6794b79fb8b67a2e4fe560ba8cf923e5c7dbf4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 11 Aug 2015 03:57:37 -0700 Subject: [PATCH 562/791] Fix android line translation to hide safe The android stack trace line translator was leaving all lines in beyond the error reporting depth. Stack trace lines that point to safe are not particularly useful for those that are not debugging safe. This removes strack trace lines referring to safe for a more natural stack trace. --- src/js/lib/safe.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 21704035..3d7313dd 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -90,6 +90,9 @@ safe.translateStackAndroid = function(stack) { } lines[i] = line; } + if (line.match(module.filename)) { + lines.splice(i, 1); + } } return lines.join('\n'); }; From 3b796eb6a3d79fad28c24d472270daf69943af9c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 16 Aug 2015 12:44:16 -0700 Subject: [PATCH 563/791] Consolidate layer mark dirty into layer checking functions --- src/simply/simply_menu.c | 10 ++++++---- src/simply/simply_ui.c | 16 +++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 28dea76b..1898dabe 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -241,13 +241,15 @@ static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t } static void mark_dirty(SimplyMenu *self) { - if (!self->menu_layer.menu_layer) { return; } - layer_mark_dirty(menu_layer_get_layer(self->menu_layer.menu_layer)); + if (self->menu_layer.menu_layer) { + layer_mark_dirty(menu_layer_get_layer(self->menu_layer.menu_layer)); + } } static void reload_data(SimplyMenu *self) { - if (!self->menu_layer.menu_layer) { return; } - menu_layer_reload_data(self->menu_layer.menu_layer); + if (self->menu_layer.menu_layer) { + menu_layer_reload_data(self->menu_layer.menu_layer); + } } static void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections) { diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 7f068aed..aa8284e5 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -76,6 +76,12 @@ struct __attribute__((__packed__)) CardStylePacket { uint8_t style; }; +static void mark_dirty(SimplyUi *self) { + if (self->ui_layer.layer) { + layer_mark_dirty(self->ui_layer.layer); + } +} + void simply_ui_clear(SimplyUi *self, uint32_t clear_mask) { if (clear_mask & (1 << ClearAction)) { simply_window_action_bar_clear(&self->window); @@ -101,24 +107,20 @@ void simply_ui_set_style(SimplyUi *self, int style_index) { self->ui_layer.custom_body_font = fonts_load_custom_font( resource_get_handle(self->ui_layer.style->custom_body_font_id)); } - layer_mark_dirty(self->ui_layer.layer); + mark_dirty(self); } void simply_ui_set_text(SimplyUi *self, SimplyUiTextfieldId textfield_id, const char *str) { SimplyUiTextfield *textfield = &self->ui_layer.textfields[textfield_id]; char **str_field = &textfield->text; strset(str_field, str); - if (self->ui_layer.layer) { - layer_mark_dirty(self->ui_layer.layer); - } + mark_dirty(self); } void simply_ui_set_text_color(SimplyUi *self, SimplyUiTextfieldId textfield_id, GColor8 color) { SimplyUiTextfield *textfield = &self->ui_layer.textfields[textfield_id]; textfield->color = color; - if (self->ui_layer.layer) { - layer_mark_dirty(self->ui_layer.layer); - } + mark_dirty(self); } static void layer_update_callback(Layer *layer, GContext *ctx) { From 50af5ef1f476b1a765f1e393f43c2c95017c4994 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 16 Aug 2015 12:52:05 -0700 Subject: [PATCH 564/791] Change simply window set fullscreen to set the status bar state first --- src/simply/simply_window.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 5a1d1b9e..babf6816 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -107,14 +107,15 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { } void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { + const bool was_status_bar = self->is_status_bar; + self->is_status_bar = !is_fullscreen; + bool changed = false; - if (is_fullscreen && self->is_status_bar) { + if (is_fullscreen && was_status_bar) { status_bar_layer_remove_from_window(self->window, self->status_bar_layer); - self->is_status_bar = false; changed = true; - } else if (!is_fullscreen && !self->is_status_bar) { + } else if (!is_fullscreen && !was_status_bar) { status_bar_layer_add_to_window(self->window, self->status_bar_layer); - self->is_status_bar = true; changed = true; } From 0217b82ad8b3ab42745806399835c72435a06c7d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 16 Aug 2015 12:56:27 -0700 Subject: [PATCH 565/791] Scroll layer now properly deallocates its animation --- src/simply/simply_window.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index babf6816..e1cf20bc 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -97,8 +97,7 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { if (!is_scrollable) { GRect bounds = { GPointZero, layer_get_bounds(window_get_root_layer(self->window)).size }; layer_set_bounds(self->layer, bounds); - // TODO: change back to animated when a closing animated scroll doesn't cause a crash - const bool animated = false; + const bool animated = true; scroll_layer_set_content_offset(self->scroll_layer, GPointZero, animated); scroll_layer_set_content_size(self->scroll_layer, bounds.size); } From 2f30171e03d904a1717a3bfd6cbe4118fae31147 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 16 Aug 2015 13:16:32 -0700 Subject: [PATCH 566/791] Change windows to destroy on unload This changes windows to destroy on unload in order to efficiently use memory for only the window that is on screen. An additional window state "preload" is added in order to accomplish this since windows cannot be created during window load. --- src/simply/simply_menu.c | 12 ++++++------ src/simply/simply_stage.c | 12 ++++++------ src/simply/simply_ui.c | 12 ++++++------ src/simply/simply_window.c | 26 +++++++++++++++++++------- src/simply/simply_window.h | 2 ++ src/simply/simply_window_stack.c | 1 + 6 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 1898dabe..ce782122 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -604,16 +604,16 @@ SimplyMenu *simply_menu_create(Simply *simply) { .menu_layer.num_sections = 1, }; - simply_window_init(&self->window, simply); - - window_set_user_data(self->window.window, self); - window_set_background_color(self->window.window, GColorWhite); - window_set_window_handlers(self->window.window, (WindowHandlers) { + static const WindowHandlers s_window_handlers = { .load = window_load, .appear = window_appear, .disappear = window_disappear, .unload = window_unload, - }); + }; + self->window.window_handlers = &s_window_handlers; + + simply_window_init(&self->window, simply); + simply_window_set_background_color(&self->window, GColor8White); return self; } diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index faddec70..31eee809 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -638,16 +638,16 @@ SimplyStage *simply_stage_create(Simply *simply) { SimplyStage *self = malloc(sizeof(*self)); *self = (SimplyStage) { .window.simply = simply }; - simply_window_init(&self->window, simply); - simply_window_set_background_color(&self->window, GColor8Black); - - window_set_user_data(self->window.window, self); - window_set_window_handlers(self->window.window, (WindowHandlers) { + static const WindowHandlers s_window_handlers = { .load = window_load, .appear = window_appear, .disappear = window_disappear, .unload = window_unload, - }); + }; + self->window.window_handlers = &s_window_handlers; + + simply_window_init(&self->window, simply); + simply_window_set_background_color(&self->window, GColor8Black); return self; } diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index aa8284e5..964152b5 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -389,16 +389,16 @@ SimplyUi *simply_ui_create(Simply *simply) { SimplyUi *self = malloc(sizeof(*self)); *self = (SimplyUi) { .window.layer = NULL }; - simply_window_init(&self->window, simply); - simply_window_set_background_color(&self->window, GColor8White); - - window_set_user_data(self->window.window, self); - window_set_window_handlers(self->window.window, (WindowHandlers) { + static const WindowHandlers s_window_handlers = { .load = window_load, .appear = window_appear, .disappear = window_disappear, .unload = window_unload, - }); + }; + self->window.window_handlers = &s_window_handlers; + + simply_window_init(&self->window, simply); + simply_window_set_background_color(&self->window, GColor8White); app_timer_register(10000, (AppTimerCallback) show_welcome_text, self); diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index e1cf20bc..c5fef579 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -253,6 +253,20 @@ static void click_config_provider(void *context) { } } +void simply_window_preload(SimplyWindow *self) { + if (self->window) { + return; + } + + Window *window = self->window = window_create(); + window_set_background_color(window, GColorClear); + window_set_click_config_provider_with_context(window, click_config_provider, self); + window_set_user_data(window, self); + if (self->window_handlers) { + window_set_window_handlers(window, *self->window_handlers); + } +} + void simply_window_load(SimplyWindow *self) { Window *window = self->window; @@ -298,6 +312,9 @@ bool simply_window_disappear(SimplyWindow *self) { void simply_window_unload(SimplyWindow *self) { scroll_layer_destroy(self->scroll_layer); self->scroll_layer = NULL; + + window_destroy(self->window); + self->window = NULL; } static void handle_window_props_packet(Simply *simply, Packet *data) { @@ -358,12 +375,10 @@ SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { } } - Window *window = self->window = window_create(); - window_set_background_color(window, GColorClear); - window_set_click_config_provider_with_context(window, click_config_provider, self); + simply_window_preload(self); self->status_bar_layer = status_bar_layer_create(); - status_bar_layer_remove_from_window(window, self->status_bar_layer); + status_bar_layer_remove_from_window(self->window, self->status_bar_layer); self->is_status_bar = false; self->is_fullscreen = true; @@ -383,7 +398,4 @@ void simply_window_deinit(SimplyWindow *self) { status_bar_layer_destroy(self->status_bar_layer); self->status_bar_layer = NULL; - - window_destroy(self->window); - self->window = NULL; } diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index ca41fb93..0ad1d1c2 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -18,6 +18,7 @@ struct SimplyWindow { ScrollLayer *scroll_layer; Layer *layer; ActionBarLayer *action_bar_layer; + const WindowHandlers *window_handlers; uint32_t id; ButtonId button_mask:4; GColor8 background_color; @@ -32,6 +33,7 @@ void simply_window_deinit(SimplyWindow *self); void simply_window_show(SimplyWindow *self); void simply_window_hide(SimplyWindow *self); +void simply_window_preload(SimplyWindow *self); void simply_window_load(SimplyWindow *self); void simply_window_unload(SimplyWindow *self); bool simply_window_appear(SimplyWindow *self); diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index da512294..31a41c19 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -92,6 +92,7 @@ void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, boo window_stack_push(self->pusher, false); } + simply_window_preload(window); window_stack_push(window->window, animated); if (is_push) { From 6e3e19b155f5ff90f8e6680c946c50bdb16ef7dc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 16 Aug 2015 13:18:46 -0700 Subject: [PATCH 567/791] Fix window initialization on basalt In some cases on basalt, the click config provider can be called before window load. Since the action bar layer and scroll layer are initialized in window load, this can cause undefined behavior. Set the click config prover during window load at the earliest. --- src/simply/simply_window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index c5fef579..17cda150 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -260,7 +260,6 @@ void simply_window_preload(SimplyWindow *self) { Window *window = self->window = window_create(); window_set_background_color(window, GColorClear); - window_set_click_config_provider_with_context(window, click_config_provider, self); window_set_user_data(window, self); if (self->window_handlers) { window_set_window_handlers(window, *self->window_handlers); @@ -281,6 +280,7 @@ void simply_window_load(SimplyWindow *self) { scroll_layer_set_context(scroll_layer, self); scroll_layer_set_shadow_hidden(scroll_layer, true); + window_set_click_config_provider_with_context(window, click_config_provider, self); simply_window_set_action_bar(self, self->is_action_bar); } From dd7eca2495c7380f42df4e91eaaa0c98696f083b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 16 Aug 2015 13:20:21 -0700 Subject: [PATCH 568/791] Fix window stack handling on basalt Basalt animates windows by animating the window directly, therefore performing multiple window popping and pushing simultaneously can cause memory mismanagement edge cases because of unexpected usage of the window stack. This fix disables the pusher on basalt. The pusher is the additional window that is used in aplite to manipulate the window stack to perform the desired window animation. --- src/simply/simply_window_stack.c | 10 ++++++++++ src/simply/simply_window_stack.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index 31a41c19..0f0ebdd3 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -88,16 +88,20 @@ void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, boo window_stack_pop_all(!is_push); self->is_showing = false; +#ifdef PBK_SDK_2 if (is_push) { window_stack_push(self->pusher, false); } +#endif simply_window_preload(window); window_stack_push(window->window, animated); +#ifdef PBK_SDK_2 if (is_push) { window_stack_remove(self->pusher, false); } +#endif } void simply_window_stack_pop(SimplyWindowStack *self, SimplyWindow *window) { @@ -130,9 +134,11 @@ void simply_window_stack_send_hide(SimplyWindowStack *self, SimplyWindow *window return; } send_window_hide(self->simply->msg, window->id); +#ifdef PBK_SDK_2 if (!self->is_hiding) { window_stack_push(self->pusher, false); } +#endif } static void handle_window_show_packet(Simply *simply, Packet *data) { @@ -168,7 +174,9 @@ SimplyWindowStack *simply_window_stack_create(Simply *simply) { SimplyWindowStack *self = malloc(sizeof(*self)); *self = (SimplyWindowStack) { .simply = simply }; +#ifdef PBK_SDK_2 self->pusher = window_create(); +#endif return self; } @@ -178,8 +186,10 @@ void simply_window_stack_destroy(SimplyWindowStack *self) { return; } +#ifdef PBK_SDK_2 window_destroy(self->pusher); self->pusher = NULL; +#endif free(self); } diff --git a/src/simply/simply_window_stack.h b/src/simply/simply_window_stack.h index c55144c1..3da3c848 100644 --- a/src/simply/simply_window_stack.h +++ b/src/simply/simply_window_stack.h @@ -12,7 +12,9 @@ typedef struct SimplyWindowStack SimplyWindowStack; struct SimplyWindowStack { Simply *simply; +#ifdef PBK_SDK_2 Window *pusher; +#endif bool is_showing:1; bool is_hiding:1; }; From 369d85a94bc47ab89ded1af2ef1c619c36da2844 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 17 Aug 2015 10:50:34 -0400 Subject: [PATCH 569/791] Fix menu to send props only after the window is pushed Menu was sending props twice, once before the menu is loaded and once after. This is because the menu properties packet is responsible for both background color and the number of sections. Menu has multiple layers of resolving in order to dynamically update menus, but since the number of sections is in the same packet as background color, resolving the menu before the window is shown has the unintended side affect of sending menu window properties twice. This fix changes the highest layer of menu resolving to be aware that it can also push the window so that the window property handling can use the menu resolving as well. In the future, menu props may be separated, and window and menu properties may be additionally cached in simply window and menu. --- src/js/ui/menu.js | 7 +++---- src/js/ui/simply-pebble.js | 2 +- src/js/ui/window.js | 2 +- src/simply/simply_menu.c | 5 +++++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index a0e1f23d..1e6244a5 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -28,7 +28,6 @@ util2.inherit(Menu, Window); util2.copy(Emitter.prototype, Menu.prototype); Menu.prototype._show = function() { - this._resolveMenu(); Window.prototype._show.apply(this, arguments); var select = this._selection; simply.impl.menuSelection(select.sectionIndex, select.itemIndex); @@ -38,7 +37,7 @@ Menu.prototype._numPreloadItems = 50; Menu.prototype._prop = function(state, clear, pushing) { if (this === WindowStack.top()) { - simply.impl.menu.call(this, state, clear, pushing); + this._resolveMenu(clear, pushing); this._resolveSection(this._selection); } }; @@ -140,10 +139,10 @@ Menu.prototype._getItem = function(e, create) { return (items[e.itemIndex] = {}); }; -Menu.prototype._resolveMenu = function() { +Menu.prototype._resolveMenu = function(clear, pushing) { var sections = this._getSections(this); if (this === WindowStack.top()) { - simply.impl.menu.call(this, this.state); + simply.impl.menu(this.state, clear, pushing); return true; } }; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 4c69e733..2a5232a4 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1042,7 +1042,7 @@ SimplyPebble.menuSelection = function(section, item, align) { }; SimplyPebble.menu = function(def, clear, pushing) { - if (arguments.length === 3) { + if (typeof pushing === 'boolean') { SimplyPebble.windowShow({ type: 'menu', pushing: pushing }); } if (clear !== undefined) { diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 711d9923..a9e862e6 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -86,7 +86,7 @@ Window.prototype.hide = function() { }; Window.prototype._show = function(pushing) { - this._prop(this.state, true, pushing); + this._prop(this.state, true, pushing || false); this._buttonConfig({}); if (this._dynamic) { Stage.prototype._show.call(this, pushing); diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index ce782122..c08f6257 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -512,6 +512,11 @@ static void handle_menu_props_packet(Simply *simply, Packet *data) { SimplyMenu *self = simply->menu; simply_menu_set_num_sections(self, packet->num_sections); + + if (!self->window.window) { + return; + } + window_set_background_color(self->window.window, gcolor8_get_or(packet->background_color, GColorWhite)); if (!self->menu_layer.menu_layer) { From fe5247f956c6418628fe25477f5387b5528ab701 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 17 Aug 2015 14:06:27 -0400 Subject: [PATCH 570/791] Add SDK_SELECT for selecting based on the sdk 3 vs sdk 2 --- src/util/sdk.h | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/util/sdk.h diff --git a/src/util/sdk.h b/src/util/sdk.h new file mode 100644 index 00000000..59abdc76 --- /dev/null +++ b/src/util/sdk.h @@ -0,0 +1,7 @@ +#pragma once + +#ifdef PBL_SDK_3 +#define SDK_SELECT(sdk3, sdk2) sdk3 +#elif PBL_SDK_2 +#define SDK_SELECT(sdk3, sdk2) sdk2 +#endif From 3367afcdcfeeeacd06b8ca3868db445344dac232 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 17 Aug 2015 14:07:00 -0400 Subject: [PATCH 571/791] Restore window animations for basalt across different types Window animations on basalt are more restrictive than on aplite. The window must be a different window than the previous window, and window animations don't occur when removing the top window immediately before. This patch restores window animations when transitioning among windows of different types while retaining window handler calls when transitioning to the same type. --- src/simply/simply_window_stack.c | 32 +++++++++++++++++++++++++++----- src/simply/simply_window_stack.h | 4 +++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index 0f0ebdd3..cdca747b 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -6,6 +6,7 @@ #include "simply.h" #include "util/math.h" +#include "util/sdk.h" #include @@ -81,27 +82,48 @@ SimplyWindow *simply_window_stack_get_top_window(Simply *simply) { return window; } -void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, bool is_push) { - bool animated = (self->simply->splash == NULL); +#ifdef PBL_SDK_3 +static void show_window_sdk_3(SimplyWindowStack *self, SimplyWindow *window, bool is_push) { + const bool animated = (self->simply->splash == NULL); + + Window *prev_window = window_stack_get_top_window(); + const bool is_same_window = (prev_window == window->window); + if (is_same_window) { + window_stack_remove(prev_window, false); + } + + simply_window_preload(window); + window_stack_push(window->window, animated); + + if (!is_same_window) { + window_stack_remove(prev_window, false); + } +} +#endif + +#ifdef PBL_SDK_2 +static void show_window_sdk_2(SimplyWindowStack *self, SimplyWindow *window, bool is_push) { + const bool animated = (self->simply->splash == NULL); self->is_showing = true; window_stack_pop_all(!is_push); self->is_showing = false; -#ifdef PBK_SDK_2 if (is_push) { window_stack_push(self->pusher, false); } -#endif simply_window_preload(window); window_stack_push(window->window, animated); -#ifdef PBK_SDK_2 if (is_push) { window_stack_remove(self->pusher, false); } +} #endif + +void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, bool is_push) { + SDK_SELECT(show_window_sdk_3, show_window_sdk_2)(self, window, is_push); } void simply_window_stack_pop(SimplyWindowStack *self, SimplyWindow *window) { diff --git a/src/simply/simply_window_stack.h b/src/simply/simply_window_stack.h index 3da3c848..5f909b6d 100644 --- a/src/simply/simply_window_stack.h +++ b/src/simply/simply_window_stack.h @@ -6,13 +6,15 @@ #include "simply.h" +#include "util/sdk.h" + #include typedef struct SimplyWindowStack SimplyWindowStack; struct SimplyWindowStack { Simply *simply; -#ifdef PBK_SDK_2 +#ifdef PBL_SDK_2 Window *pusher; #endif bool is_showing:1; From 978cb271ab5a988b18423c728ff37fc3d88d5d12 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 18 Aug 2015 07:58:03 -0400 Subject: [PATCH 572/791] Don't remove windows when pushing windows on basalt Basalt automatically removes a window from the window stack when it is pushed onto the stack again. Removing a window from the window stack at any time is dangerous because it may be animating, and basalt does not appear to check if it is still animating when removing. --- src/simply/simply_window_stack.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index cdca747b..f9b7af65 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -86,18 +86,8 @@ SimplyWindow *simply_window_stack_get_top_window(Simply *simply) { static void show_window_sdk_3(SimplyWindowStack *self, SimplyWindow *window, bool is_push) { const bool animated = (self->simply->splash == NULL); - Window *prev_window = window_stack_get_top_window(); - const bool is_same_window = (prev_window == window->window); - if (is_same_window) { - window_stack_remove(prev_window, false); - } - simply_window_preload(window); window_stack_push(window->window, animated); - - if (!is_same_window) { - window_stack_remove(prev_window, false); - } } #endif From 5e3e7c1114ad427a7bca2fd9cac001d2f8bb56c5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 18 Aug 2015 08:50:10 -0400 Subject: [PATCH 573/791] Disable window stack animations on basalt --- src/simply/simply_window.c | 6 +++--- src/simply/simply_window_stack.c | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 17cda150..9ba6e3ac 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -312,9 +312,6 @@ bool simply_window_disappear(SimplyWindow *self) { void simply_window_unload(SimplyWindow *self) { scroll_layer_destroy(self->scroll_layer); self->scroll_layer = NULL; - - window_destroy(self->window); - self->window = NULL; } static void handle_window_props_packet(Simply *simply, Packet *data) { @@ -398,4 +395,7 @@ void simply_window_deinit(SimplyWindow *self) { status_bar_layer_destroy(self->status_bar_layer); self->status_bar_layer = NULL; + + window_destroy(self->window); + self->window = NULL; } diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index f9b7af65..690605fd 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -84,10 +84,12 @@ SimplyWindow *simply_window_stack_get_top_window(Simply *simply) { #ifdef PBL_SDK_3 static void show_window_sdk_3(SimplyWindowStack *self, SimplyWindow *window, bool is_push) { - const bool animated = (self->simply->splash == NULL); + self->is_showing = true; + window_stack_pop_all(false); + self->is_showing = false; simply_window_preload(window); - window_stack_push(window->window, animated); + window_stack_push(window->window, false); } #endif From a59e34648733d7c3ab7af97d50087f702c3f9e76 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 19 Aug 2015 10:41:35 -0400 Subject: [PATCH 574/791] Add empty macro NONE --- src/util/none.h | 3 +++ src/util/sdk.h | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 src/util/none.h diff --git a/src/util/none.h b/src/util/none.h new file mode 100644 index 00000000..eda5568b --- /dev/null +++ b/src/util/none.h @@ -0,0 +1,3 @@ +#pragma once + +#define NONE diff --git a/src/util/sdk.h b/src/util/sdk.h index 59abdc76..20f7a19a 100644 --- a/src/util/sdk.h +++ b/src/util/sdk.h @@ -1,5 +1,7 @@ #pragma once +#include "util/none.h" + #ifdef PBL_SDK_3 #define SDK_SELECT(sdk3, sdk2) sdk3 #elif PBL_SDK_2 From 462c27f5c0fbf2a874db3f372cb5965e578fff29 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 19 Aug 2015 10:54:10 -0400 Subject: [PATCH 575/791] Use SDK_SELECT instead of ifdef for the pusher. Addresses #86 Using ifdef is error prone since the compiler cannot check for existence to infer whether it is a typo or not. Use SDK_SELECT instead. --- src/simply/simply_window_stack.c | 37 ++++++++++++++------------------ 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index 690605fd..41e9908a 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -134,25 +134,20 @@ void simply_window_stack_back(SimplyWindowStack *self, SimplyWindow *window) { } void simply_window_stack_send_show(SimplyWindowStack *self, SimplyWindow *window) { - if (self->is_showing) { - return; + if (window->id && self->is_showing) { + send_window_show(self->simply->msg, window->id); } - send_window_show(self->simply->msg, window->id); } void simply_window_stack_send_hide(SimplyWindowStack *self, SimplyWindow *window) { - if (!window->id) { - return; + if (window->id && !self->is_showing) { + send_window_hide(self->simply->msg, window->id); + SDK_SELECT(NONE, { + if (!self->is_hiding) { + window_stack_push(self->pusher, false); + } + }); } - if (self->is_showing) { - return; - } - send_window_hide(self->simply->msg, window->id); -#ifdef PBK_SDK_2 - if (!self->is_hiding) { - window_stack_push(self->pusher, false); - } -#endif } static void handle_window_show_packet(Simply *simply, Packet *data) { @@ -188,9 +183,9 @@ SimplyWindowStack *simply_window_stack_create(Simply *simply) { SimplyWindowStack *self = malloc(sizeof(*self)); *self = (SimplyWindowStack) { .simply = simply }; -#ifdef PBK_SDK_2 - self->pusher = window_create(); -#endif + SDK_SELECT(NONE, { + self->pusher = window_create(); + }); return self; } @@ -200,10 +195,10 @@ void simply_window_stack_destroy(SimplyWindowStack *self) { return; } -#ifdef PBK_SDK_2 - window_destroy(self->pusher); - self->pusher = NULL; -#endif + SDK_SELECT(NONE, { + window_destroy(self->pusher); + self->pusher = NULL; + }); free(self); } From 7e902c796f6b6009fa4b821062292908d71ab3e0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 19 Aug 2015 10:56:24 -0400 Subject: [PATCH 576/791] Fix gpoint_polar to be static inline --- src/util/graphics.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/graphics.h b/src/util/graphics.h index 6f783d51..ac577e82 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -19,7 +19,7 @@ static inline GPoint gpoint_add(const GPoint a, const GPoint b) { return GPoint(a.x + b.x, a.y + b.y); } -static GPoint gpoint_polar(int32_t angle, int16_t radius) { +static inline GPoint gpoint_polar(int32_t angle, int16_t radius) { return GPoint(sin_lookup(angle) * radius / TRIG_MAX_RATIO, cos_lookup(angle) * radius / TRIG_MAX_RATIO); } From 4df12635d6584754bdc9ec5c150f651374a96f35 Mon Sep 17 00:00:00 2001 From: Alessio Bogon Date: Wed, 19 Aug 2015 19:33:56 +0200 Subject: [PATCH 577/791] Draw menu section header manually using the foreground text color. --- src/simply/simply_menu.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index c08f6257..b5bcfb80 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -338,7 +338,13 @@ static void menu_draw_header_callback(GContext *ctx, const Layer *cell_layer, ui list1_remove(&self->menu_layer.sections, §ion->node); list1_prepend(&self->menu_layer.sections, §ion->node); - menu_cell_basic_header_draw(ctx, cell_layer, section->title); + GRect bounds = layer_get_bounds(cell_layer); + bounds.origin.x += 2; + bounds.origin.y -= 1; + + graphics_context_set_text_color(ctx, gcolor8_get_or(self->menu_layer.normal_foreground, GColorBlack)); + graphics_draw_text(ctx, section->title, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD), + bounds, GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); } static void simply_menu_draw_row_spinner(SimplyMenu *self, GContext *ctx, const Layer *cell_layer) { From e1e5b704393cc348af07e62e1ec69521f9a6ab9e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 19 Aug 2015 11:02:19 -0400 Subject: [PATCH 578/791] Use SDK_SELECT for the window stack pusher member field --- src/simply/simply_window_stack.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/simply/simply_window_stack.h b/src/simply/simply_window_stack.h index 5f909b6d..cbcc2813 100644 --- a/src/simply/simply_window_stack.h +++ b/src/simply/simply_window_stack.h @@ -14,9 +14,7 @@ typedef struct SimplyWindowStack SimplyWindowStack; struct SimplyWindowStack { Simply *simply; -#ifdef PBL_SDK_2 - Window *pusher; -#endif + SDK_SELECT(NONE, Window *pusher); bool is_showing:1; bool is_hiding:1; }; From 085ba91a562365d51689a0ac1a6070e7215577ee Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 19 Aug 2015 11:05:11 -0400 Subject: [PATCH 579/791] Load JavaScript vendor scripts as CommonJS modules moment has always been bundled with pebble.js for convenience, but using it directly without require is an odd combination with the module style of pebble.js. Previously, vendor scripts were executed directly in order to require no modification for inclusion, but this encourages bad practices. Change the build to wrap vendor scripts as CommonJS modules. --- src/js/clock/clock.js | 2 ++ src/js/lib/image.js | 2 +- src/js/main.js | 3 +++ src/js/vendor/png.js | 13 +++++++++++-- src/js/vendor/zlib.js | 9 ++++++++- wscript | 2 -- 6 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/js/clock/clock.js b/src/js/clock/clock.js index ee9fa780..fcb760cc 100644 --- a/src/js/clock/clock.js +++ b/src/js/clock/clock.js @@ -1,3 +1,5 @@ +var moment = require('vendor/moment'); + var Clock = module.exports; Clock.weekday = function(weekday, hour, minute, seconds) { diff --git a/src/js/lib/image.js b/src/js/lib/image.js index eb9d59cb..7a40b9e5 100644 --- a/src/js/lib/image.js +++ b/src/js/lib/image.js @@ -1,4 +1,4 @@ -/* global PNG */ +var PNG = require('vendor/png'); var image = {}; diff --git a/src/js/main.js b/src/js/main.js index d852abe3..d8a8ecfa 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -11,6 +11,9 @@ require('safe'); Pebble.addEventListener('ready', function(e) { // Initialize the Pebble protocol require('ui/simply-pebble.js').init(); + // Backwards compatibility: place moment.js in global scope + // This will be removed in a future update + window.moment = require('vendor/moment'); // Load local file require('app.js'); }); diff --git a/src/js/vendor/png.js b/src/js/vendor/png.js index 3d69ad69..83cb41a6 100644 --- a/src/js/vendor/png.js +++ b/src/js/vendor/png.js @@ -20,6 +20,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +var FlateStream; +if (typeof require !== 'undefined') { + FlateStream = require('zlib').FlateStream; +} (function() { var PNG; @@ -224,7 +228,8 @@ if (data.length === 0) { return new Uint8Array(0); } - data = new FlateStream(data); + var flateStream = FlateStream || window.FlateStream; + data = new flateStream(data); data = data.getBytes(); pixelBytes = this.pixelBitlength / 8; scanlineLength = pixelBytes * this.width; @@ -449,6 +454,10 @@ })(); - window.PNG = PNG; + if (typeof module !== 'undefined') { + module.exports = PNG; + } else { + window.PNG = PNG; + } }).call(this); diff --git a/src/js/vendor/zlib.js b/src/js/vendor/zlib.js index 2d0f40b2..ae22ba34 100644 --- a/src/js/vendor/zlib.js +++ b/src/js/vendor/zlib.js @@ -461,4 +461,11 @@ var FlateStream = (function() { }; return constructor; -})(); \ No newline at end of file +})(); + +if (typeof module !== 'undefined') { + module.exports = { + DecodeStream: DecodeStream, + FlateStream: FlateStream, + }; +} diff --git a/wscript b/wscript index ae409737..203f7943 100644 --- a/wscript +++ b/wscript @@ -128,8 +128,6 @@ def concat_javascript(ctx, js_path=None): if relpath == LOADER_PATH: sources.insert(0, body) - elif relpath.startswith('vendor/'): - sources.append(body) else: sources.append({ 'relpath': relpath, 'body': body }) From 43f61c5c50c6e8fcbfd1156749ee004765172a57 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 19 Aug 2015 15:53:59 -0400 Subject: [PATCH 580/791] Change safe translate android to use the shared line translator --- src/js/lib/safe.js | 56 +++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 3d7313dd..03093e27 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -29,14 +29,20 @@ safe.translatePos = function(name, lineno, colno) { var makeTranslateStack = function(stackLineRegExp, translateLine) { return function(stack) { var lines = stack.split('\n'); + var firstStackLine = -1; for (var i = lines.length - 1; i >= 0; --i) { - var line = lines[i]; - var m = line.match(stackLineRegExp); - if (m) { - line = lines[i] = translateLine.apply(this, m); + var m = lines[i].match(stackLineRegExp); + if (!m) { + continue; } - if (line.match(module.filename)) { - lines.splice(i, 1); + var line = lines[i] = translateLine.apply(this, m); + if (line) { + firstStackLine = i; + if (line.indexOf(module.filename) !== -1) { + lines.splice(i, 1); + } + } else { + lines.splice(i, lines.length - i); } } return lines.join('\n'); @@ -65,38 +71,18 @@ var stackLineRegExpIOS = /(?:([^\s@]+)@)?([^\s@:]+):(\d+):(\d+)/; safe.translateStackIOS = makeTranslateStack(stackLineRegExpIOS, translateLineIOS); -safe.translateStackAndroid = function(stack) { - var lines = stack.split('\n'); - for (var i = lines.length - 1; i > 0; --i) { - var line = lines[i]; - var name, lineno, colno; - if (line.match(/jskit_startup\.html/)) { - lines.splice(i, 1); - } else { - /* Matches ':' ':' */ - var m = line.match(/^.*\/(.*?):(\d+):(\d+)/); - if (m) { - name = m[1]; - lineno = m[2]; - colno = m[3]; - } - } - if (name) { - var pos = safe.translatePos(name, lineno, colno); - if (line.match(/\(.*\)/)) { - line = line.replace(/\(.*\)/, '(' + pos + ')'); - } else { - line = line.replace(/[^\s\/]*\/.*$/, pos); - } - lines[i] = line; - } - if (line.match(module.filename)) { - lines.splice(i, 1); - } +/* Translates an Android stack trace line to node style */ +var translateLineAndroid = function(line, msg, scope, name, lineno, colno) { + if (name !== 'jskit_startup.js') { + return translateLineV8(line, msg, scope, name, lineno, colno); } - return lines.join('\n'); }; +/* Matches '('? filepath ':' ':' ')'? */ +var stackLineRegExpAndroid = /^(.*?)(?:\s+([^\s]+)\s+\()?[^\s\(]*?([^\/]*?):(\d+):(\d+)\)?/; + +safe.translateStackAndroid = makeTranslateStack(stackLineRegExpAndroid, translateLineAndroid); + /* Translates a stack trace to the originating files */ safe.translateStack = function(stack) { if (Pebble.platform === 'pypkjs') { From 5eeac4f77aae85468c454e9a6674e2c66b97a953 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 19 Aug 2015 15:55:23 -0400 Subject: [PATCH 581/791] Add safe warn ignore stack level optional argument --- src/js/lib/safe.js | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 03093e27..5b6624ab 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -27,7 +27,7 @@ safe.translatePos = function(name, lineno, colno) { }; var makeTranslateStack = function(stackLineRegExp, translateLine) { - return function(stack) { + return function(stack, level) { var lines = stack.split('\n'); var firstStackLine = -1; for (var i = lines.length - 1; i >= 0; --i) { @@ -45,7 +45,10 @@ var makeTranslateStack = function(stackLineRegExp, translateLine) { lines.splice(i, lines.length - i); } } - return lines.join('\n'); + if (firstStackLine > -1) { + lines.splice(firstStackLine, level); + } + return lines; }; }; @@ -84,47 +87,48 @@ var stackLineRegExpAndroid = /^(.*?)(?:\s+([^\s]+)\s+\()?[^\s\(]*?([^\/]*?):(\d+ safe.translateStackAndroid = makeTranslateStack(stackLineRegExpAndroid, translateLineAndroid); /* Translates a stack trace to the originating files */ -safe.translateStack = function(stack) { +safe.translateStack = function(stack, level) { + level = level || 0; if (Pebble.platform === 'pypkjs') { - return safe.translateStackV8(stack); + return safe.translateStackV8(stack, level); } else if (stack.match('com.getpebble.android')) { - return safe.translateStackAndroid(stack); + return safe.translateStackAndroid(stack, level); } else { - return safe.translateStackIOS(stack); + return safe.translateStackIOS(stack, level); } }; -safe.translateError = function(err, intro) { +safe.translateError = function(err, intro, level) { var name = err.name; var message = err.message || err.toString(); var stack = err.stack; var result = [intro || 'JavaScript Error:']; - if (message && (!stack || !stack.match(message))) { - if (name && !message.match(message)) { + if (message && (!stack || stack.indexOf(message) === -1)) { + if (name && message.indexOf(name + ':') === -1) { message = name + ': ' + message; } result.push(message); } if (stack) { - result.push(safe.translateStack(stack)); + Array.prototype.push.apply(result, safe.translateStack(stack, level)); } return result.join('\n'); }; /* Dumps error messages to the console. */ -safe.dumpError = function(err, intro) { +safe.dumpError = function(err, intro, level) { if (typeof err === 'object') { - console.log(safe.translateError(err, intro)); + console.log(safe.translateError(err, intro, level)); } else { console.log('Error: dumpError argument is not an object'); } }; /* Logs runtime warnings to the console. */ -safe.warn = function(message, name) { +safe.warn = function(message, level, name) { var err = new Error(message); err.name = name || 'Warning'; - safe.dumpError(err, 'Warning:'); + safe.dumpError(err, 'Warning:', 1); }; /* Takes a function and return a new function with a call to it wrapped in a try/catch statement */ From 99a1b9136b60f00042f05eecd5d6c465dd5bc6c4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 19 Aug 2015 15:56:12 -0400 Subject: [PATCH 582/791] Add safe normalized indent for detailed error messages --- src/js/lib/safe.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 5b6624ab..58b1404f 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -98,6 +98,18 @@ safe.translateStack = function(stack, level) { } }; +var normalizeIndent = function(lines, pos) { + pos = pos || 0; + var label = lines[pos].match(/^[^\s]* /); + if (label) { + var indent = label[0].replace(/./g, ' '); + for (var i = pos + 1, ii = lines.length; i < ii; i++) { + lines[i] = lines[i].replace(/^\t/, indent); + } + } + return lines; +}; + safe.translateError = function(err, intro, level) { var name = err.name; var message = err.message || err.toString(); @@ -112,7 +124,7 @@ safe.translateError = function(err, intro, level) { if (stack) { Array.prototype.push.apply(result, safe.translateStack(stack, level)); } - return result.join('\n'); + return normalizeIndent(result, 1).join('\n'); }; /* Dumps error messages to the console. */ From 3c460877f9ad60d48f32259606b4523c4d56c066 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 19 Aug 2015 15:56:36 -0400 Subject: [PATCH 583/791] Add moment global usage warning --- src/js/main.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index d8a8ecfa..10301593 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -6,14 +6,37 @@ * By default, this will run app.js */ -require('safe'); +var safe = require('safe'); +var util2 = require('util2'); Pebble.addEventListener('ready', function(e) { // Initialize the Pebble protocol require('ui/simply-pebble.js').init(); + // Backwards compatibility: place moment.js in global scope // This will be removed in a future update - window.moment = require('vendor/moment'); + var moment = require('vendor/moment'); + + var momentPasser = function(methodName) { + return function() { + if (safe.warnGlobalMoment !== false) { + safe.warn("You've accessed moment globally. Pleae use `var moment = require('moment')` instead.\n\t" + + 'moment will not be automatically loaded as a global in future versions.', 5); + safe.warnGlobalMoment = false; + } + return (methodName ? moment[methodName] : moment).apply(this, arguments); + }; + }; + + var globalMoment = momentPasser(); + util2.copy(moment.prototype, globalMoment.prototype); + for (var k in moment) { + var v = moment[k]; + globalMoment[k] = typeof v === 'function' ? momentPasser(k) : v; + } + + window.moment = globalMoment; + // Load local file require('app.js'); }); From 975ef2fd625d5beaab5b9b8605b577628ad16a8a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 7 Aug 2015 13:17:06 -0700 Subject: [PATCH 584/791] Add PNG Encoder from data-demo --- src/js/lib/png-encoder.js | 439 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 439 insertions(+) create mode 100644 src/js/lib/png-encoder.js diff --git a/src/js/lib/png-encoder.js b/src/js/lib/png-encoder.js new file mode 100644 index 00000000..e2822a48 --- /dev/null +++ b/src/js/lib/png-encoder.js @@ -0,0 +1,439 @@ +/** + * PNG Encoder from data-demo + * https://code.google.com/p/data-demo/ + * + * @author mccalluc@yahoo.com + * @license MIT + */ + +var png = {}; + +png.Bytes = function(data, optional) { + var datum, i; + this.array = []; + + if (!optional) { + + if (data instanceof Array) { + for (i = 0; i < data.length; i++) { + datum = data[i]; + if (datum !== null) { // nulls and undefineds are silently skipped. + if (typeof datum !== "number") { + throw new Error("Expected number, not "+(typeof datum)); + } else if (Math.floor(datum) != datum) { + throw new Error("Expected integer, not "+datum); + } else if (datum < 0 || datum > 255) { + throw new Error("Expected integer in [0,255], not "+datum); + } + this.array.push(datum); + } + } + } + + else if (typeof data == "string") { + for (i = 0; i < data.length; i++) { + datum = data.charCodeAt(i); + if (datum < 0 || datum > 255) { + throw new Error("Characters above 255 not allowed without explicit encoding: "+datum); + } + this.array.push(datum); + } + } + + else if (data instanceof png.Bytes) { + this.array.push.apply(this.array, data.array); + } + + else if (typeof data == "number") { + return new png.Bytes([data]); + } + + else { + throw new Error("Unexpected data type: "+data); + } + + } + + else { // optional is defined. + + // TODO: generalize when generalization is required. + if (typeof data == "number" && + Math.floor(data) == data && + data >= 0 && + (optional.bytes in {4:1, 2:1}) && + // don't change this last one to bit shifts: in JS, 0x100 << 24 == 0. + data < Math.pow(256, optional.bytes)) { + this.array = [ + (data & 0xFF000000) >>> 24, + (data & 0x00FF0000) >>> 16, + (data & 0x0000FF00) >>> 8, + (data & 0x000000FF) + ].slice(-optional.bytes); + } + + else throw new Error("Unexpected data/optional args combination: "+data); + + } +}; + +png.Bytes.prototype.add = function(data, optional) { + // Takes the same arguments as the constructor, + // but appends the new data instead, and returns the modified object. + // (suitable for chaining.) + this.array.push.apply(this.array, new png.Bytes(data, optional).array); + return this; +}; + +png.Bytes.prototype.chunk = function(n) { + // Split the array into chunks of length n. + // Returns an array of arrays. + var buffer = []; + for (var i = 0; i < this.array.length; i += n) { + var slice = this.array.slice(i, i+n); + buffer.push(this.array.slice(i, i+n)); + } + return buffer; +}; + +png.Bytes.prototype.toString = function(n) { + // one optional argument specifies line length in bytes. + // returns a hex dump of the Bytes object. + var chunks = this.chunk(n || 8); + var byte; + var lines = []; + var hex; + var chr; + for (var i = 0; i < chunks.length; i++) { + hex = []; + chr = []; + for (var j = 0; j < chunks[i].length; j++) { + byte = chunks[i][j]; + hex.push( + ((byte < 16) ? "0" : "") + + byte.toString(16) + ); + chr.push( + (byte >=32 && byte <= 126 ) ? + String.fromCharCode(byte) + : "_" + ); + } + lines.push(hex.join(" ")+" "+chr.join("")); + } + return lines.join("\n"); +}; + +png.Bytes.prototype.serialize = function() { + // returns a string whose char codes correspond to the bytes of the array. + // TODO: get rid of this once transition is complete? + return String.fromCharCode.apply(null, this.array); +}; + +png.fromRaster = function(raster, optional_palette, optional_transparency) { + // Given a Raster object, + // and optionally a list of rgb triples, + // and optionally a corresponding list of transparency values (0: clear - 255: opaque) + // return the corresponding PNG as a Bytes object. + + var signature = new png.Bytes([ + 137, 80 /* P */, 78 /* N */, 71 /* G */, 13, 10, 26, 10 + ]); + var ihdr = new png.Chunk.IHDR(raster.width, raster.height, raster.bit_depth, raster.color_type); + var plte = (optional_palette instanceof Array) ? + new png.Chunk.PLTE(optional_palette) : + new png.Bytes([]); + var trns = (optional_transparency instanceof Array) ? + new png.Chunk.tRNS(optional_transparency) : + new png.Bytes([]); + var idat = new png.Chunk.IDAT(raster); + var iend = new png.Chunk.IEND(); // intentionally blank + + // order matters. + return signature.add(ihdr).add(plte).add(trns).add(idat).add(iend); +}; + +png.Chunk = function(type, data) { + // given a four character type, and Bytes, + // calculates the length and the checksum, and creates + // a Bytes object for that png chunk. + + if (!type.match(/^[A-Za-z]{4}$/)) { + throw new Error("Creating PNG chunk: provided type should be four letters, not "+type); + } + + if (!(data instanceof png.Bytes)) { + throw new Error("Creating PNG "+type+" chunk: provided data is not Bytes: "+data); + } + + // CRC calculations are a literal translation of the C code at + // http://www.libpng.org/pub/png/spec/1.0/PNG-CRCAppendix.html + if (!png.crc_table) { + png.crc_table = []; // Table of CRCs of all 8-bit messages. + for (var n = 0; n < 256; n++) { + var c = n; + for (var k = 0; k < 8; k++) { + if (c & 1) { + c = 0xedb88320 ^ (c >>> 1); // C ">>" is JS ">>>" + } else { + c = c >>> 1; // C ">>" is JS ">>>" + } + } + png.crc_table[n] = c; + } + } + + function update_crc(crc, buffer) { + // Update a running CRC with the buffer--the CRC + // should be initialized to all 1's, and the transmitted value + // is the 1's complement of the final running CRC + var c = crc; + var n; + for (n = 0; n < buffer.length; n++) { + c = png.crc_table[(c ^ buffer[n]) & 0xff] ^ (c >>> 8); // C ">>" is JS ">>>" + } + return c; + } + + var type_and_data = new png.Bytes(type).add(data); + var crc = (update_crc(0xffffffff, type_and_data.array) ^ 0xffffffff)>>>0; + // >>>0 converts to unsigned, without changing the bits. + + var length_type_data_checksum = + new png.Bytes(data.array.length,{bytes:4}) + .add(type_and_data) + .add(crc,{bytes:4}); + + return length_type_data_checksum; +}; + +png.Chunk.IHDR = function(width, height, bit_depth, color_type) { + if (!( + // grayscale + (color_type === 0) && (bit_depth in {1:1, 2:1, 4:1, 8:1, 16:1}) || + // rgb + (color_type === 2) && (bit_depth in {8:1, 16:1}) || + // palette + (color_type === 3) && (bit_depth in {1:1, 2:1, 4:1, 8:1}) || + // grayscale + alpha + (color_type === 4) && (bit_depth in {8:1, 16:1}) || + // rgb + alpha + (color_type === 6) && (bit_depth in {8:1, 16:1}) + // http://www.libpng.org/pub/png/spec/1.0/PNG-Chunks.html#C.IHDR + )) { + throw new Error("Invalid color type ("+color_type+") / bit depth ("+bit_depth+") combo"); + } + return new png.Chunk( + "IHDR", + new png.Bytes(width,{bytes:4}) + .add(height,{bytes:4}) + .add([ + bit_depth, + color_type, + 0, // compression method + 0, // filter method + 0 // interlace method + ]) + ); +}; + +png.Chunk.PLTE = function(rgb_list) { + // given a list of RGB triples, + // returns the corresponding PNG PLTE (palette) chunk. + for (var i = 0, ii = rgb_list.length; i < ii; i++) { + var triple = rgb_list[i]; + if (triple.length !== 3) { + throw new Error("This is not a valid RGB triple: "+triple); + } + } + return new png.Chunk( + "PLTE", + new png.Bytes(Array.prototype.concat.apply([], rgb_list)) + ); +}; + +png.Chunk.tRNS = function(alpha_list) { + // given a list of alpha values corresponding to the palette entries, + // returns the corresponding PNG tRNS (transparency) chunk. + return new png.Chunk( + "tRNS", + new png.Bytes(alpha_list) + ); +}; + +png.Raster = function(bit_depth, color_type, raster) { + // takes an array of arrays of greyscale or palette values. + // provides encode(), which returns bytes ready for a PNG IDAT chunk. + + // validate depth and type + if (color_type !== 0 && color_type !== 3) throw new Error("Color type "+color_type+" is unsupported."); + if (bit_depth > 8) throw new Error("Bit depths greater than 8 are unsupported."); + + this.bit_depth = bit_depth; + this.color_type = color_type; + + // validate raster data. + var max_value = (1 << bit_depth) - 1; + var cols = raster[0].length; + for (var row = 0; row < raster.length; row++) { + if (raster[row].length != cols) + throw new Error("Row "+row+" does not have the expected "+cols+" columns."); + for (var col = 0; col < cols; col++) { + if (!(raster[row][col] >= 0 && raster[row][col] <= max_value)) + throw new Error("Image data ("+raster[row][col]+") out of bounds at ("+row+","+col+")"); + } + } + + this.height = raster.length; + this.width = cols; + + this.encode = function() { + // Returns the image data as a single array of bytes, using filter method 0. + var buffer = []; + for (var row = 0; row < raster.length; row++) { + buffer.push(0); // each row gets filter type 0. + for (var col = 0; col < cols; col += 8/bit_depth) { + var byte = 0; + for (var sub = 0; sub < 8/bit_depth; sub++) { + byte <<= bit_depth; + if (col + sub < cols) { + byte |= raster[row][col+sub]; + } + } + if (byte & ~0xFF) throw new Error("Encoded raster byte out of bounds at ("+row+","+col+")"); + buffer.push(byte); + } + } + return buffer; + }; +}; + +png.Raster_rgb = function(bit_depth, color_type, raster) { + // takes an array of arrays of RGB triples. + // provides encode(), which returns bytes ready for a PNG IDAT chunk. + + // validate depth and type + if (color_type != 2 && color_type != 6) throw new Error("Only color types 2 and 6 for RGB."); + if (bit_depth != 8) throw new Error("Bit depths other than 8 are unsupported for RGB."); + + this.bit_depth = bit_depth; + this.color_type = color_type; + + // validate raster data. + var cols = raster[0].length; + for (var row = 0; row < raster.length; row++) { + if (raster[row].length != cols) { + throw new Error("Row "+row+" does not have the expected "+cols+" columns."); + } + for (var col = 0; col < cols; col++) { + if (!(color_type == 2 && raster[row][col].length == 3) && + !(color_type == 6 && raster[row][col].length == 4)) { + throw new Error("Not RGB[A] at ("+row+","+col+")"); + } + for (var i = 0; i < (color_type == 2 ? 3 : 4); i++) { + if (raster[row][col][i]<0 || raster[row][col][i]>255) { + throw new Error("RGB out of range at ("+row+","+col+")"); + } + } + } + } + + this.height = raster.length; + this.width = cols; + + this.encode = function() { + // Returns the image data as a single array of bytes, using filter method 0. + var buffer = []; + for (var row = 0; row < raster.length; row++) { + buffer.push(0); // each row gets filter type 0. + for (var col = 0; col < cols; col++) { + buffer.push.apply(buffer, raster[row][col]); + } + } + return buffer; + }; +}; + +png.Raster.Zlib = function(buffer) { + // implementing http://www.ietf.org/rfc/rfc1950.txt + + var compression_method = 8; + // The only value defined by the RFC. + var compression_info = 0; + // "CINFO is the base-2 logarithm of the LZ77 window size, minus eight" + // so, 0 means 256. (Not that it matters, since I'm planning just to do literal data.) + + var fdict = 0; + // no preset dictionary. + var flevel = 0; + // "compressor used fastest algorithm" + // "The information in FLEVEL is not needed for decompression; it + // is there to indicate if recompression might be worthwhile." + + var fcheck = 31 - ( + (compression_info << 12) | + (compression_method << 8) | + (flevel << 5) | + (fdict << 4) + ) % 31; + + this.checksum = adler32(buffer); + + function deflate(bytes) { + // implementing a bit of http://www.ietf.org/rfc/rfc1951.txt + // returns the compressed data block as a string. + var header_char = String.fromCharCode(1); + // (5 bits unused, 2 bits type (uncompressed), 1 bit final flag (on)) + // * little endian * + var len = bytes.length; + var len_string = String.fromCharCode(len & 0xFF,(len & 0xFF00)>>8); + var nlen = ~bytes.length; + var nlen_string = String.fromCharCode(nlen & 0xFF,(nlen & 0xFF00)>>8); + + return header_char + + len_string + + nlen_string + + String.fromCharCode.apply(null, bytes); + } + + function adler32(bytes) { + var s1 = 1; + var s2 = 0; + for (var i = 0; i < bytes.length; i++) { + s1 += bytes[i]; + s1 %= 65521; + s2 += s1; + s2 %= 65521; + } // This could be made more efficient by defering the modulos. + return (s2 << 16) | s1; + } + + this.compression_method_char = String.fromCharCode( + compression_method | (compression_info << 4) + ); + this.additional_flags_char = String.fromCharCode( + fcheck | (fdict << 4) | (flevel << 5) + ); + this.compressed_data_blocks = deflate(buffer); + + this.compress = function() { + // TODO: return Bytes + return this.compression_method_char + + this.additional_flags_char + + this.compressed_data_blocks + + new png.Bytes(this.checksum>>>0,{bytes:4}).serialize(); + }; +}; + +png.Chunk.IDAT = function(raster) { + var encoded = raster.encode(); + var zipped = new png.Raster.Zlib(encoded).compress(); + return new png.Chunk("IDAT", new png.Bytes(zipped)); +}; + +png.Chunk.IEND = function() { + return new png.Chunk("IEND", new png.Bytes([])); +}; + +if (typeof module !== 'undefined') { + module.exports = png; +} From 58b4270def05aeaa640adfc84db3cfedf112dabc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 17 Aug 2015 18:22:20 -0400 Subject: [PATCH 585/791] Add platform detection --- src/js/platform/index.js | 3 +++ src/js/platform/platform.js | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 src/js/platform/index.js create mode 100644 src/js/platform/platform.js diff --git a/src/js/platform/index.js b/src/js/platform/index.js new file mode 100644 index 00000000..6feb903c --- /dev/null +++ b/src/js/platform/index.js @@ -0,0 +1,3 @@ +var Platform = require('./platform'); + +module.exports = Platform; diff --git a/src/js/platform/platform.js b/src/js/platform/platform.js new file mode 100644 index 00000000..79721e53 --- /dev/null +++ b/src/js/platform/platform.js @@ -0,0 +1,9 @@ +var Platform = module.exports; + +Platform.version = function() { + if (Pebble.getActiveWatchInfo) { + return Pebble.getActiveWatchInfo().platform; + } else { + return 'aplite'; + } +}; From 0aa296605d3bf6fdd3d71e4c3645c325674dcf80 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 17 Aug 2015 18:35:44 -0400 Subject: [PATCH 586/791] Add support for remote images on basalt. Addresses #62, #73 Basalt introduces GBitmaps with bitdepth 8, but images in this uncompressed format are too large to be practically sent over AppMessage. Basalt also introduces a png decoder. This patch enables remote images on basalt by sending pngs re-encoded in a Pebble compatible png format. --- src/js/lib/image.js | 147 +++++++++++++++++++++++++++++-------- src/js/lib/png-encoder.js | 9 +++ src/js/ui/imageservice.js | 6 +- src/js/ui/simply-pebble.js | 1 + src/simply/simply_msg.c | 4 +- src/simply/simply_res.c | 48 ++++++------ src/simply/simply_res.h | 3 +- src/util/compat.h | 4 + 8 files changed, 168 insertions(+), 54 deletions(-) diff --git a/src/js/lib/image.js b/src/js/lib/image.js index 7a40b9e5..91dc2413 100644 --- a/src/js/lib/image.js +++ b/src/js/lib/image.js @@ -1,20 +1,38 @@ var PNG = require('vendor/png'); +var PNGEncoder = require('lib/png-encoder'); + var image = {}; var getPos = function(width, x, y) { return y * width * 4 + x * 4; }; +//! Convert an RGB pixel array into a single grey color var getPixelGrey = function(pixels, pos) { return ((pixels[pos] + pixels[pos + 1] + pixels[pos + 2]) / 3) & 0xFF; }; -image.greyscale = function(pixels, width, height) { +//! Convert an RGB pixel array into a single uint8 2 bitdepth per channel color +var getPixelColorUint8 = function(pixels, pos) { + var r = Math.min(Math.max(parseInt(pixels[pos ] / 64 + 0.5), 0), 3); + var g = Math.min(Math.max(parseInt(pixels[pos + 1] / 64 + 0.5), 0), 3); + var b = Math.min(Math.max(parseInt(pixels[pos + 2] / 64 + 0.5), 0), 3); + return (0x3 << 6) | (r << 4) | (g << 2) | b; +}; + +//! Get an RGB vector from an RGB pixel array +var getPixelColorRGB8 = function(pixels, pos) { + return [pixels[pos], pixels[pos + 1], pixels[pos + 2]]; +}; + +//! Normalize the color channels to be identical +image.greyscale = function(pixels, width, height, converter) { + converter = converter || getPixelGrey; for (var y = 0, yy = height; y < yy; ++y) { for (var x = 0, xx = width; x < xx; ++x) { var pos = getPos(width, x, y); - var newColor = getPixelGrey(pixels, pos); + var newColor = converter(pixels, pos); for (var i = 0; i < 3; ++i) { pixels[pos + i] = newColor; } @@ -22,6 +40,20 @@ image.greyscale = function(pixels, width, height) { } }; +//! Convert to an RGBA pixel array into a row major matrix raster +image.toRaster = function(pixels, width, height, converter) { + converter = converter || getPixelColorRGB8; + var matrix = []; + for (var y = 0, yy = height; y < yy; ++y) { + var row = matrix[y] = []; + for (var x = 0, xx = width; x < xx; ++x) { + var pos = getPos(width, x, y); + row[x] = converter(pixels, pos); + } + } + return matrix; +}; + image.dithers = {}; image.dithers['floyd-steinberg'] = [ @@ -82,6 +114,14 @@ image.dither = function(pixels, width, height, dithers) { } }; +//! Dither a pixel buffer by image properties +image.ditherByProps = function(pixels, img) { + if (img.dither) { + var dithers = image.dithers[img.dither]; + image.dither(pixels, img.width, img.height, dithers); + } +}; + image.resizeNearest = function(pixels, width, height, newWidth, newHeight) { var newPixels = new Array(newWidth * newHeight * 4); var widthRatio = width / newWidth; @@ -122,13 +162,23 @@ image.resizeSample = function(pixels, width, height, newWidth, newHeight) { image.resize = function(pixels, width, height, newWidth, newHeight) { if (newWidth < width || newHeight < height) { - return image.resizeSample.apply(this, arguments); + return image.resizeSample(pixels, width, height, newWidth, newHeight); + } else { + return image.resizeNearest(pixels, width, height, newWidth, newHeight); + } +}; + +//! Resize a pixel buffer by image properties +image.resizeByProps = function(pixels, img) { + if (img.width !== img.originalWidth || img.height !== img.originalHeight) { + return image.resize(pixels, img.originalWidth, img.originalHeight, img.width, img.height); } else { - return image.resizeNearest.apply(this, arguments); + return pixels; } }; -image.toGbitmap = function(pixels, width, height) { +//! Convert to a GBitmap with bitdepth 1 +image.toGbitmap1 = function(pixels, width, height) { var rowBytes = width * 4; var gpixels = []; @@ -155,40 +205,79 @@ image.toGbitmap = function(pixels, width, height) { var gbitmap = { width: width, height: height, + pixelsLength: gpixels.length, pixels: gpixels, }; return gbitmap; }; -image.load = function(img, callback) { - PNG.load(img.url, function(png) { - var pixels = png.decode(); - var width = png.width; - var height = png.height; - image.greyscale(pixels, width, height); - if (img.width) { - if (!img.height) { - img.height = parseInt(height * (img.width / width)); - } - } else if (img.height) { - if (!img.width) { - img.width = parseInt(width * (img.height / height)); +//! Convert to a PNG with total color bitdepth 8 +image.toPng8 = function(pixels, width, height) { + var raster = image.toRaster(pixels, width, height, getPixelColorRGB8); + + var palette = []; + var colorMap = {}; + var numColors = 0; + for (var y = 0, yy = height; y < yy; ++y) { + var row = raster[y]; + for (var x = 0, xx = width; x < xx; ++x) { + var color = row[x]; + var hash = getPixelColorUint8(color, 0); + if (!(hash in colorMap)) { + colorMap[hash] = numColors; + palette[numColors++] = color; } - } else { - img.width = width; - img.height = height; + row[x] = colorMap[hash]; } - if (img.width !== width || img.height !== height) { - pixels = image.resize(pixels, width, height, img.width, img.height); - width = img.width; - height = img.height; + } + + var bitdepth = 8; + var colorType = 3; // 8-bit palette + var bytes = PNGEncoder.encode(raster, bitdepth, colorType, palette); + + var png = { + width: width, + height: height, + pixelsLength: bytes.array.length, + pixels: bytes.array, + }; + + return png; +}; + +//! Set the size maintaining the aspect ratio +image.setSizeAspect = function(img, width, height) { + img.originalWidth = width; + img.originalHeight = height; + if (img.width) { + if (!img.height) { + img.height = parseInt(height * (img.width / width)); + } + } else if (img.height) { + if (!img.width) { + img.width = parseInt(width * (img.height / height)); + } + } else { + img.width = width; + img.height = height; + } +}; + +image.load = function(img, bitdepth, callback) { + PNG.load(img.url, function(png) { + var pixels = png.decode(); + if (bitdepth === 1) { + image.greyscale(pixels, png.width, png.height); } - if (img.dither) { - var dithers = image.dithers[img.dither]; - image.dither(pixels, width, height, dithers); + image.setSizeAspect(img, png.width, png.height); + pixels = image.resizeByProps(pixels, img); + image.ditherByProps(pixels, img); + if (bitdepth === 8) { + img.image = image.toPng8(pixels, img.width, img.height); + } else if (bitdepth === 1) { + img.image = image.toGbitmap1(pixels, img.width, img.height); } - img.gbitmap = image.toGbitmap(pixels, width, height); if (callback) { callback(img); } diff --git a/src/js/lib/png-encoder.js b/src/js/lib/png-encoder.js index e2822a48..6baeccf1 100644 --- a/src/js/lib/png-encoder.js +++ b/src/js/lib/png-encoder.js @@ -152,6 +152,15 @@ png.fromRaster = function(raster, optional_palette, optional_transparency) { return signature.add(ihdr).add(plte).add(trns).add(idat).add(iend); }; +png.encode = function(raster, bit_depth, color_type, optional_palette, optional_transparency) { + if (color_type === 0 || color_type === 3) { + raster = new png.Raster(bit_depth, color_type, raster); + } else if (color_type === 2 || color_type === 6) { + raster = new png.Raster_rgb(bit_depth, color_type, raster); + } + return png.fromRaster(raster, optional_palette, optional_transparency); +}; + png.Chunk = function(type, data) { // given a four character type, and Bytes, // calculates the length and the checksum, and creates diff --git a/src/js/ui/imageservice.js b/src/js/ui/imageservice.js index c1e4885c..67fa5b48 100644 --- a/src/js/ui/imageservice.js +++ b/src/js/ui/imageservice.js @@ -1,5 +1,6 @@ var imagelib = require('lib/image'); var myutil = require('myutil'); +var Platform = require('platform'); var Resource = require('ui/resource'); var simply = require('ui/simply'); @@ -88,7 +89,7 @@ ImageService.load = function(opt, reset, callback) { image.loaded = true; state.cache[hash] = image; var onLoad = function() { - simply.impl.image(image.id, image.gbitmap); + simply.impl.image(image.id, image.image); if (callback) { var e = { type: 'image', @@ -99,7 +100,8 @@ ImageService.load = function(opt, reset, callback) { } }; if (fetch) { - imagelib.load(image, onLoad); + var bitdepth = Platform.version() === 'basalt' ? 8 : 1; + imagelib.load(image, bitdepth, onLoad); } else { onLoad(); } diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 2a5232a4..9a40f273 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -430,6 +430,7 @@ var ImagePacket = new struct([ ['uint32', 'id'], ['int16', 'width'], ['int16', 'height'], + ['uint16', 'pixelsLength'], ['data', 'pixels'], ]); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index efd78de1..4adbfb6c 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -55,6 +55,7 @@ struct __attribute__((__packed__)) ImagePacket { uint32_t id; int16_t width; int16_t height; + uint16_t pixels_length; uint8_t pixels[]; }; @@ -153,7 +154,8 @@ static void handle_segment_packet(Simply *simply, Packet *data) { static void handle_image_packet(Simply *simply, Packet *data) { ImagePacket *packet = (ImagePacket*) data; - simply_res_add_image(simply->res, packet->id, packet->width, packet->height, packet->pixels); + simply_res_add_image(simply->res, packet->id, packet->width, packet->height, packet->pixels, + packet->pixels_length); } static void handle_vibe_packet(Simply *simply, Packet *data) { diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 6869553d..43bfba16 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -3,6 +3,7 @@ #include "util/color.h" #include "util/graphics.h" #include "util/memory.h" +#include "util/sdk.h" #include "util/window.h" #include @@ -103,40 +104,45 @@ SimplyImage *simply_res_add_bundled_image(SimplyRes *self, uint32_t id) { return image; } -static GBitmap *create_blank_bitmap(SimplyImage *image, void *data) { - GSize *size = data; - GBitmap *bitmap = gbitmap_create_blank(*size, GBitmapFormat1Bit); +typedef struct { + GSize size; + size_t data_length; + const uint8_t *data; +} CreateDataContext; + +static GBitmap *create_bitmap_with_data(SimplyImage *image, void *data) { + CreateDataContext *ctx = data; + GBitmap *bitmap = gbitmap_create_blank(ctx->size, GBitmapFormat1Bit); if (bitmap) { image->bitmap_data = gbitmap_get_data(bitmap); + memcpy(image->bitmap_data, data, ctx->data_length); } return bitmap; } -SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint8_t *pixels) { - SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); +static GBitmap *create_bitmap_with_png_data(SimplyImage *image, void *data) { + CreateDataContext *ctx = data; + return ctx->data ? gbitmap_create_from_png_data(ctx->data, ctx->data_length) : NULL; +} +SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, + uint8_t *pixels, uint16_t pixels_length) { + SimplyImage *image = (SimplyImage*) list1_find(self->images, id_filter, (void*)(uintptr_t) id); if (image) { - free(gbitmap_get_data(image->bitmap)); - uint16_t row_size_bytes = gbitmap_get_bytes_per_row(image->bitmap); - gbitmap_set_data(image->bitmap, pixels, GBitmapFormat1Bit, row_size_bytes, true); - image->bitmap_data = pixels; - - window_stack_schedule_top_window_render(); - - return image; + destroy_image(self, image); } - image = create_image(self, create_blank_bitmap, &GSize(width, height)); + CreateDataContext context = { + .size = GSize(width, height), + .data_length = pixels_length, + .data = pixels, + }; + image = SDK_SELECT(create_image(self, create_bitmap_with_png_data, &context), + create_image(self, create_bitmap_with_data, &context)); if (image) { image->id = id; - - uint16_t row_size_bytes = gbitmap_get_bytes_per_row(image->bitmap); - size_t pixels_size = height * row_size_bytes; - memcpy(image->bitmap_data, pixels, pixels_size); - add_image(self, image); } - return image; } @@ -159,7 +165,7 @@ SimplyImage *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeho return simply_res_add_bundled_image(self, id); } if (is_placeholder) { - return simply_res_add_image(self, id, 0, 0, NULL); + return simply_res_add_image(self, id, 0, 0, NULL, 0); } return NULL; } diff --git a/src/simply/simply_res.h b/src/simply/simply_res.h index 33ab0d7f..8c52d38b 100644 --- a/src/simply/simply_res.h +++ b/src/simply/simply_res.h @@ -56,7 +56,8 @@ void simply_res_destroy(SimplyRes *self); void simply_res_clear(SimplyRes *self); SimplyImage *simply_res_add_bundled_image(SimplyRes *self, uint32_t id); -SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, uint8_t *pixels); +SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, int16_t height, + uint8_t *pixels, uint16_t pixels_length); SimplyImage *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeholder); bool simply_res_evict_image(SimplyRes *self); diff --git a/src/util/compat.h b/src/util/compat.h index 301e53b0..459c3bc8 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -61,6 +61,10 @@ typedef union GColor8 { #define graphics_context_set_antialiased(ctx, enable) #endif +#ifndef gbitmap_create_from_png_data +#define gbitmap_create_from_png_data(png_data, png_data_size) NULL +#endif + //! Convenience function to use SDK 3.0 function to get a `GBitmap`'s `row_size_bytes` field. #ifndef gbitmap_get_bytes_per_row #define gbitmap_get_bytes_per_row(bmp) ((bmp)->row_size_bytes) From 4fb3755332c27e38b546ff5a908c11e8b016529a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 17 Aug 2015 19:12:15 -0400 Subject: [PATCH 587/791] Replace zlib.js with imaya's version which supports compression --- src/js/vendor/png.js | 11 +- src/js/vendor/zlib.js | 514 ++++-------------------------------------- 2 files changed, 52 insertions(+), 473 deletions(-) diff --git a/src/js/vendor/png.js b/src/js/vendor/png.js index 83cb41a6..e8f22bbd 100644 --- a/src/js/vendor/png.js +++ b/src/js/vendor/png.js @@ -20,9 +20,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -var FlateStream; +var Zlib; if (typeof require !== 'undefined') { - FlateStream = require('zlib').FlateStream; + Zlib = require('zlib'); +} else { + Zlib = window.Zlib; } (function() { @@ -228,9 +230,8 @@ if (typeof require !== 'undefined') { if (data.length === 0) { return new Uint8Array(0); } - var flateStream = FlateStream || window.FlateStream; - data = new flateStream(data); - data = data.getBytes(); + data = new Zlib.Inflate(data); + data = data.decompress(); pixelBytes = this.pixelBitlength / 8; scanlineLength = pixelBytes * this.width; pixels = new Uint8Array(scanlineLength * this.height); diff --git a/src/js/vendor/zlib.js b/src/js/vendor/zlib.js index ae22ba34..83683fea 100644 --- a/src/js/vendor/zlib.js +++ b/src/js/vendor/zlib.js @@ -1,471 +1,49 @@ -/* - * Extracted from pdf.js - * https://github.com/andreasgal/pdf.js +/** + * zlib.js Deflate + Inflate * - * Copyright (c) 2011 Mozilla Foundation - * - * Contributors: Andreas Gal - * Chris G Jones - * Shaon Barman - * Vivien Nicolas <21@vingtetun.org> - * Justin D'Arcangelo - * Yury Delendik - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -var DecodeStream = (function() { - function constructor() { - this.pos = 0; - this.bufferLength = 0; - this.eof = false; - this.buffer = null; - } - - constructor.prototype = { - ensureBuffer: function decodestream_ensureBuffer(requested) { - var buffer = this.buffer; - var current = buffer ? buffer.byteLength : 0; - if (requested < current) - return buffer; - var size = 512; - while (size < requested) - size <<= 1; - var buffer2 = new Uint8Array(size); - for (var i = 0; i < current; ++i) - buffer2[i] = buffer[i]; - return this.buffer = buffer2; - }, - getByte: function decodestream_getByte() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) - return null; - this.readBlock(); - } - return this.buffer[this.pos++]; - }, - getBytes: function decodestream_getBytes(length) { - var pos = this.pos; - - if (length) { - this.ensureBuffer(pos + length); - var end = pos + length; - - while (!this.eof && this.bufferLength < end) - this.readBlock(); - - var bufEnd = this.bufferLength; - if (end > bufEnd) - end = bufEnd; - } else { - while (!this.eof) - this.readBlock(); - - var end = this.bufferLength; - } - - this.pos = end; - return this.buffer.subarray(pos, end); - }, - lookChar: function decodestream_lookChar() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) - return null; - this.readBlock(); - } - return String.fromCharCode(this.buffer[this.pos]); - }, - getChar: function decodestream_getChar() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) - return null; - this.readBlock(); - } - return String.fromCharCode(this.buffer[this.pos++]); - }, - makeSubStream: function decodestream_makeSubstream(start, length, dict) { - var end = start + length; - while (this.bufferLength <= end && !this.eof) - this.readBlock(); - return new Stream(this.buffer, start, length, dict); - }, - skip: function decodestream_skip(n) { - if (!n) - n = 1; - this.pos += n; - }, - reset: function decodestream_reset() { - this.pos = 0; - } - }; - - return constructor; -})(); - -var FlateStream = (function() { - var codeLenCodeMap = new Uint32Array([ - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 - ]); - - var lengthDecode = new Uint32Array([ - 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, - 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, - 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, - 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102 - ]); - - var distDecode = new Uint32Array([ - 0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, - 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, - 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, - 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001 - ]); - - var fixedLitCodeTab = [new Uint32Array([ - 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, - 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, - 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, - 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, - 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, - 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, - 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, - 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, - 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, - 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, - 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, - 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, - 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, - 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, - 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, - 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, - 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, - 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, - 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, - 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, - 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, - 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, - 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, - 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, - 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, - 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, - 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, - 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, - 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, - 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, - 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, - 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, - 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, - 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, - 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, - 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, - 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, - 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, - 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, - 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, - 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, - 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, - 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, - 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, - 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, - 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, - 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, - 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, - 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, - 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, - 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, - 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, - 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, - 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, - 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, - 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, - 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, - 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, - 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, - 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, - 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, - 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, - 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, - 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff - ]), 9]; - - var fixedDistCodeTab = [new Uint32Array([ - 0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, - 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, - 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, - 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000 - ]), 5]; - - function error(e) { - throw new Error(e) - } - - function constructor(bytes) { - //var bytes = stream.getBytes(); - var bytesPos = 0; - - var cmf = bytes[bytesPos++]; - var flg = bytes[bytesPos++]; - if (cmf == -1 || flg == -1) - error('Invalid header in flate stream'); - if ((cmf & 0x0f) != 0x08) - error('Unknown compression method in flate stream'); - if ((((cmf << 8) + flg) % 31) != 0) - error('Bad FCHECK in flate stream'); - if (flg & 0x20) - error('FDICT bit set in flate stream'); - - this.bytes = bytes; - this.bytesPos = bytesPos; - - this.codeSize = 0; - this.codeBuf = 0; - - DecodeStream.call(this); - } - - constructor.prototype = Object.create(DecodeStream.prototype); - - constructor.prototype.getBits = function(bits) { - var codeSize = this.codeSize; - var codeBuf = this.codeBuf; - var bytes = this.bytes; - var bytesPos = this.bytesPos; - - var b; - while (codeSize < bits) { - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad encoding in flate stream'); - codeBuf |= b << codeSize; - codeSize += 8; - } - b = codeBuf & ((1 << bits) - 1); - this.codeBuf = codeBuf >> bits; - this.codeSize = codeSize -= bits; - this.bytesPos = bytesPos; - return b; - }; - - constructor.prototype.getCode = function(table) { - var codes = table[0]; - var maxLen = table[1]; - var codeSize = this.codeSize; - var codeBuf = this.codeBuf; - var bytes = this.bytes; - var bytesPos = this.bytesPos; - - while (codeSize < maxLen) { - var b; - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad encoding in flate stream'); - codeBuf |= (b << codeSize); - codeSize += 8; - } - var code = codes[codeBuf & ((1 << maxLen) - 1)]; - var codeLen = code >> 16; - var codeVal = code & 0xffff; - if (codeSize == 0 || codeSize < codeLen || codeLen == 0) - error('Bad encoding in flate stream'); - this.codeBuf = (codeBuf >> codeLen); - this.codeSize = (codeSize - codeLen); - this.bytesPos = bytesPos; - return codeVal; - }; - - constructor.prototype.generateHuffmanTable = function(lengths) { - var n = lengths.length; - - // find max code length - var maxLen = 0; - for (var i = 0; i < n; ++i) { - if (lengths[i] > maxLen) - maxLen = lengths[i]; - } - - // build the table - var size = 1 << maxLen; - var codes = new Uint32Array(size); - for (var len = 1, code = 0, skip = 2; - len <= maxLen; - ++len, code <<= 1, skip <<= 1) { - for (var val = 0; val < n; ++val) { - if (lengths[val] == len) { - // bit-reverse the code - var code2 = 0; - var t = code; - for (var i = 0; i < len; ++i) { - code2 = (code2 << 1) | (t & 1); - t >>= 1; - } - - // fill the table entries - for (var i = code2; i < size; i += skip) - codes[i] = (len << 16) | val; - - ++code; - } - } - } - - return [codes, maxLen]; - }; - - constructor.prototype.readBlock = function() { - function repeat(stream, array, len, offset, what) { - var repeat = stream.getBits(len) + offset; - while (repeat-- > 0) - array[i++] = what; - } - - // read block header - var hdr = this.getBits(3); - if (hdr & 1) - this.eof = true; - hdr >>= 1; - - if (hdr == 0) { // uncompressed block - var bytes = this.bytes; - var bytesPos = this.bytesPos; - var b; - - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad block header in flate stream'); - var blockLen = b; - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad block header in flate stream'); - blockLen |= (b << 8); - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad block header in flate stream'); - var check = b; - if (typeof (b = bytes[bytesPos++]) == 'undefined') - error('Bad block header in flate stream'); - check |= (b << 8); - if (check != (~blockLen & 0xffff)) - error('Bad uncompressed block length in flate stream'); - - this.codeBuf = 0; - this.codeSize = 0; - - var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + blockLen); - var end = bufferLength + blockLen; - this.bufferLength = end; - for (var n = bufferLength; n < end; ++n) { - if (typeof (b = bytes[bytesPos++]) == 'undefined') { - this.eof = true; - break; - } - buffer[n] = b; - } - this.bytesPos = bytesPos; - return; - } - - var litCodeTable; - var distCodeTable; - if (hdr == 1) { // compressed block, fixed codes - litCodeTable = fixedLitCodeTab; - distCodeTable = fixedDistCodeTab; - } else if (hdr == 2) { // compressed block, dynamic codes - var numLitCodes = this.getBits(5) + 257; - var numDistCodes = this.getBits(5) + 1; - var numCodeLenCodes = this.getBits(4) + 4; - - // build the code lengths code table - var codeLenCodeLengths = Array(codeLenCodeMap.length); - var i = 0; - while (i < numCodeLenCodes) - codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3); - var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); - - // build the literal and distance code tables - var len = 0; - var i = 0; - var codes = numLitCodes + numDistCodes; - var codeLengths = new Array(codes); - while (i < codes) { - var code = this.getCode(codeLenCodeTab); - if (code == 16) { - repeat(this, codeLengths, 2, 3, len); - } else if (code == 17) { - repeat(this, codeLengths, 3, 3, len = 0); - } else if (code == 18) { - repeat(this, codeLengths, 7, 11, len = 0); - } else { - codeLengths[i++] = len = code; - } - } - - litCodeTable = - this.generateHuffmanTable(codeLengths.slice(0, numLitCodes)); - distCodeTable = - this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes)); - } else { - error('Unknown block type in flate stream'); - } - - var buffer = this.buffer; - var limit = buffer ? buffer.length : 0; - var pos = this.bufferLength; - while (true) { - var code1 = this.getCode(litCodeTable); - if (code1 < 256) { - if (pos + 1 >= limit) { - buffer = this.ensureBuffer(pos + 1); - limit = buffer.length; - } - buffer[pos++] = code1; - continue; - } - if (code1 == 256) { - this.bufferLength = pos; - return; - } - code1 -= 257; - code1 = lengthDecode[code1]; - var code2 = code1 >> 16; - if (code2 > 0) - code2 = this.getBits(code2); - var len = (code1 & 0xffff) + code2; - code1 = this.getCode(distCodeTable); - code1 = distDecode[code1]; - code2 = code1 >> 16; - if (code2 > 0) - code2 = this.getBits(code2); - var dist = (code1 & 0xffff) + code2; - if (pos + len >= limit) { - buffer = this.ensureBuffer(pos + len); - limit = buffer.length; - } - for (var k = 0; k < len; ++k, ++pos) - buffer[pos] = buffer[pos - dist]; - } - }; - - return constructor; -})(); - + * @link https://github.com/imaya/zlib.js + * @author imaya + * @license MIT + **/ +(function() {'use strict';function l(d){throw d;}var v=void 0,x=!0,aa=this;function D(d,a){var c=d.split("."),e=aa;!(c[0]in e)&&e.execScript&&e.execScript("var "+c[0]);for(var b;c.length&&(b=c.shift());)!c.length&&a!==v?e[b]=a:e=e[b]?e[b]:e[b]={}};var F="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;function H(d,a){this.index="number"===typeof a?a:0;this.i=0;this.buffer=d instanceof(F?Uint8Array:Array)?d:new (F?Uint8Array:Array)(32768);2*this.buffer.length<=this.index&&l(Error("invalid index"));this.buffer.length<=this.index&&this.f()}H.prototype.f=function(){var d=this.buffer,a,c=d.length,e=new (F?Uint8Array:Array)(c<<1);if(F)e.set(d);else for(a=0;a>>8&255]<<16|N[d>>>16&255]<<8|N[d>>>24&255])>>32-a:N[d]>>8-a);if(8>a+f)g=g<>a-h-1&1,8===++f&&(f=0,e[b++]=N[g],g=0,b===e.length&&(e=this.f()));e[b]=g;this.buffer=e;this.i=f;this.index=b};H.prototype.finish=function(){var d=this.buffer,a=this.index,c;0O;++O){for(var P=O,Q=P,ga=7,P=P>>>1;P;P>>>=1)Q<<=1,Q|=P&1,--ga;fa[O]=(Q<>>0}var N=fa;function ha(d){this.buffer=new (F?Uint16Array:Array)(2*d);this.length=0}ha.prototype.getParent=function(d){return 2*((d-2)/4|0)};ha.prototype.push=function(d,a){var c,e,b=this.buffer,f;c=this.length;b[this.length++]=a;for(b[this.length++]=d;0b[e])f=b[c],b[c]=b[e],b[e]=f,f=b[c+1],b[c+1]=b[e+1],b[e+1]=f,c=e;else break;return this.length}; +ha.prototype.pop=function(){var d,a,c=this.buffer,e,b,f;a=c[0];d=c[1];this.length-=2;c[0]=c[this.length];c[1]=c[this.length+1];for(f=0;;){b=2*f+2;if(b>=this.length)break;b+2c[b]&&(b+=2);if(c[b]>c[f])e=c[f],c[f]=c[b],c[b]=e,e=c[f+1],c[f+1]=c[b+1],c[b+1]=e;else break;f=b}return{index:d,value:a,length:this.length}};function R(d){var a=d.length,c=0,e=Number.POSITIVE_INFINITY,b,f,g,h,k,n,q,r,p,m;for(r=0;rc&&(c=d[r]),d[r]>=1;m=g<<16|r;for(p=n;pS;S++)switch(x){case 143>=S:oa.push([S+48,8]);break;case 255>=S:oa.push([S-144+400,9]);break;case 279>=S:oa.push([S-256+0,7]);break;case 287>=S:oa.push([S-280+192,8]);break;default:l("invalid literal: "+S)} +ia.prototype.j=function(){var d,a,c,e,b=this.input;switch(this.h){case 0:c=0;for(e=b.length;c>>8&255;p[m++]=n&255;p[m++]=n>>>8&255;if(F)p.set(f,m),m+=f.length,p=p.subarray(0,m);else{q=0;for(r=f.length;qu)for(;0< +u--;)G[E++]=0,K[0]++;else for(;0u?u:138,B>u-3&&B=B?(G[E++]=17,G[E++]=B-3,K[17]++):(G[E++]=18,G[E++]=B-11,K[18]++),u-=B;else if(G[E++]=I[t],K[I[t]]++,u--,3>u)for(;0u?u:6,B>u-3&&Bz;z++)ra[z]=ka[gb[z]];for(W=19;4=b:return[265,b-11,1];case 14>=b:return[266,b-13,1];case 16>=b:return[267,b-15,1];case 18>=b:return[268,b-17,1];case 22>=b:return[269,b-19,2];case 26>=b:return[270,b-23,2];case 30>=b:return[271,b-27,2];case 34>=b:return[272, +b-31,2];case 42>=b:return[273,b-35,3];case 50>=b:return[274,b-43,3];case 58>=b:return[275,b-51,3];case 66>=b:return[276,b-59,3];case 82>=b:return[277,b-67,4];case 98>=b:return[278,b-83,4];case 114>=b:return[279,b-99,4];case 130>=b:return[280,b-115,4];case 162>=b:return[281,b-131,5];case 194>=b:return[282,b-163,5];case 226>=b:return[283,b-195,5];case 257>=b:return[284,b-227,5];case 258===b:return[285,b-258,0];default:l("invalid length: "+b)}}var a=[],c,e;for(c=3;258>=c;c++)e=d(c),a[c]=e[2]<<24|e[1]<< +16|e[0];return a}(),wa=F?new Uint32Array(va):va; +function pa(d,a){function c(b,c){var a=b.H,d=[],e=0,f;f=wa[b.length];d[e++]=f&65535;d[e++]=f>>16&255;d[e++]=f>>24;var g;switch(x){case 1===a:g=[0,a-1,0];break;case 2===a:g=[1,a-2,0];break;case 3===a:g=[2,a-3,0];break;case 4===a:g=[3,a-4,0];break;case 6>=a:g=[4,a-5,1];break;case 8>=a:g=[5,a-7,1];break;case 12>=a:g=[6,a-9,2];break;case 16>=a:g=[7,a-13,2];break;case 24>=a:g=[8,a-17,3];break;case 32>=a:g=[9,a-25,3];break;case 48>=a:g=[10,a-33,4];break;case 64>=a:g=[11,a-49,4];break;case 96>=a:g=[12,a- +65,5];break;case 128>=a:g=[13,a-97,5];break;case 192>=a:g=[14,a-129,6];break;case 256>=a:g=[15,a-193,6];break;case 384>=a:g=[16,a-257,7];break;case 512>=a:g=[17,a-385,7];break;case 768>=a:g=[18,a-513,8];break;case 1024>=a:g=[19,a-769,8];break;case 1536>=a:g=[20,a-1025,9];break;case 2048>=a:g=[21,a-1537,9];break;case 3072>=a:g=[22,a-2049,10];break;case 4096>=a:g=[23,a-3073,10];break;case 6144>=a:g=[24,a-4097,11];break;case 8192>=a:g=[25,a-6145,11];break;case 12288>=a:g=[26,a-8193,12];break;case 16384>= +a:g=[27,a-12289,12];break;case 24576>=a:g=[28,a-16385,13];break;case 32768>=a:g=[29,a-24577,13];break;default:l("invalid distance")}f=g;d[e++]=f[0];d[e++]=f[1];d[e++]=f[2];var h,k;h=0;for(k=d.length;h=f;)w[f++]=0;for(f=0;29>=f;)y[f++]=0}w[256]=1;e=0;for(b=a.length;e=b){r&&c(r,-1);f=0;for(g=b-e;fg&&a+gf&&(b=e,f=g);if(258===g)break}return new ta(f,a-b)} +function qa(d,a){var c=d.length,e=new ha(572),b=new (F?Uint8Array:Array)(c),f,g,h,k,n;if(!F)for(k=0;k2*b[m-1]+f[m]&&(b[m]=2*b[m-1]+f[m]),h[m]=Array(b[m]),k[m]=Array(b[m]);for(p=0;pd[p]?(h[m][s]=w,k[m][s]=a,y+=2):(h[m][s]=d[p],k[m][s]=p,++p);n[m]=0;1===f[m]&&e(m)}return g} +function sa(d){var a=new (F?Uint16Array:Array)(d.length),c=[],e=[],b=0,f,g,h,k;f=0;for(g=d.length;f>>=1}return a};function T(d,a){this.l=[];this.m=32768;this.e=this.g=this.c=this.q=0;this.input=F?new Uint8Array(d):d;this.s=!1;this.n=za;this.C=!1;if(a||!(a={}))a.index&&(this.c=a.index),a.bufferSize&&(this.m=a.bufferSize),a.bufferType&&(this.n=a.bufferType),a.resize&&(this.C=a.resize);switch(this.n){case Aa:this.b=32768;this.a=new (F?Uint8Array:Array)(32768+this.m+258);break;case za:this.b=0;this.a=new (F?Uint8Array:Array)(this.m);this.f=this.K;this.t=this.I;this.o=this.J;break;default:l(Error("invalid inflate mode"))}} +var Aa=0,za=1,Ba={F:Aa,D:za}; +T.prototype.p=function(){for(;!this.s;){var d=Y(this,3);d&1&&(this.s=x);d>>>=1;switch(d){case 0:var a=this.input,c=this.c,e=this.a,b=this.b,f=a.length,g=v,h=v,k=e.length,n=v;this.e=this.g=0;c+1>=f&&l(Error("invalid uncompressed block header: LEN"));g=a[c++]|a[c++]<<8;c+1>=f&&l(Error("invalid uncompressed block header: NLEN"));h=a[c++]|a[c++]<<8;g===~h&&l(Error("invalid uncompressed block header: length verify"));c+g>a.length&&l(Error("input buffer is broken"));switch(this.n){case Aa:for(;b+g>e.length;){n= +k-b;g-=n;if(F)e.set(a.subarray(c,c+n),b),b+=n,c+=n;else for(;n--;)e[b++]=a[c++];this.b=b;e=this.f();b=this.b}break;case za:for(;b+g>e.length;)e=this.f({v:2});break;default:l(Error("invalid inflate mode"))}if(F)e.set(a.subarray(c,c+g),b),b+=g,c+=g;else for(;g--;)e[b++]=a[c++];this.c=c;this.b=b;this.a=e;break;case 1:this.o(Ca,Da);break;case 2:Sa(this);break;default:l(Error("unknown BTYPE: "+d))}}return this.t()}; +var Ta=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],Ua=F?new Uint16Array(Ta):Ta,Va=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],Wa=F?new Uint16Array(Va):Va,Xa=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],Ya=F?new Uint8Array(Xa):Xa,Za=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],$a=F?new Uint16Array(Za):Za,ab=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10, +10,11,11,12,12,13,13],bb=F?new Uint8Array(ab):ab,cb=new (F?Uint8Array:Array)(288),Z,db;Z=0;for(db=cb.length;Z=Z?8:255>=Z?9:279>=Z?7:8;var Ca=R(cb),eb=new (F?Uint8Array:Array)(30),fb,hb;fb=0;for(hb=eb.length;fb=g&&l(Error("input buffer is broken")),c|=b[f++]<>>a;d.e=e-a;d.c=f;return h} +function ib(d,a){for(var c=d.g,e=d.e,b=d.input,f=d.c,g=b.length,h=a[0],k=a[1],n,q;e=g);)c|=b[f++]<>>16;d.g=c>>q;d.e=e-q;d.c=f;return n&65535} +function Sa(d){function a(a,b,c){var d,e=this.z,f,g;for(g=0;gf)e>=b&&(this.b=e,c=this.f(),e=this.b),c[e++]=f;else{g=f-257;k=Wa[g];0=b&&(this.b=e,c=this.f(),e=this.b);for(;k--;)c[e]=c[e++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=e}; +T.prototype.J=function(d,a){var c=this.a,e=this.b;this.u=d;for(var b=c.length,f,g,h,k;256!==(f=ib(this,d));)if(256>f)e>=b&&(c=this.f(),b=c.length),c[e++]=f;else{g=f-257;k=Wa[g];0b&&(c=this.f(),b=c.length);for(;k--;)c[e]=c[e++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=e}; +T.prototype.f=function(){var d=new (F?Uint8Array:Array)(this.b-32768),a=this.b-32768,c,e,b=this.a;if(F)d.set(b.subarray(32768,d.length));else{c=0;for(e=d.length;cc;++c)b[c]=b[a+c];this.b=32768;return b}; +T.prototype.K=function(d){var a,c=this.input.length/this.c+1|0,e,b,f,g=this.input,h=this.a;d&&("number"===typeof d.v&&(c=d.v),"number"===typeof d.G&&(c+=d.G));2>c?(e=(g.length-this.c)/this.u[2],f=258*(e/2)|0,b=fa&&(this.a.length=a),d=this.a);return this.buffer=d};function jb(d){if("string"===typeof d){var a=d.split(""),c,e;c=0;for(e=a.length;c>>0;d=a}for(var b=1,f=0,g=d.length,h,k=0;0>>0};function kb(d,a){var c,e;this.input=d;this.c=0;if(a||!(a={}))a.index&&(this.c=a.index),a.verify&&(this.N=a.verify);c=d[this.c++];e=d[this.c++];switch(c&15){case lb:this.method=lb;break;default:l(Error("unsupported compression method"))}0!==((c<<8)+e)%31&&l(Error("invalid fcheck flag:"+((c<<8)+e)%31));e&32&&l(Error("fdict flag is not supported"));this.B=new T(d,{index:this.c,bufferSize:a.bufferSize,bufferType:a.bufferType,resize:a.resize})} +kb.prototype.p=function(){var d=this.input,a,c;a=this.B.p();this.c=this.B.c;this.N&&(c=(d[this.c++]<<24|d[this.c++]<<16|d[this.c++]<<8|d[this.c++])>>>0,c!==jb(a)&&l(Error("invalid adler-32 checksum")));return a};var lb=8;function mb(d,a){this.input=d;this.a=new (F?Uint8Array:Array)(32768);this.h=$.k;var c={},e;if((a||!(a={}))&&"number"===typeof a.compressionType)this.h=a.compressionType;for(e in a)c[e]=a[e];c.outputBuffer=this.a;this.A=new ia(this.input,c)}var $=na; +mb.prototype.j=function(){var d,a,c,e,b,f,g,h=0;g=this.a;d=lb;switch(d){case lb:a=Math.LOG2E*Math.log(32768)-8;break;default:l(Error("invalid compression method"))}c=a<<4|d;g[h++]=c;switch(d){case lb:switch(this.h){case $.NONE:b=0;break;case $.r:b=1;break;case $.k:b=2;break;default:l(Error("unsupported compression type"))}break;default:l(Error("invalid compression method"))}e=b<<6|0;g[h++]=e|31-(256*c+e)%31;f=jb(this.input);this.A.b=h;g=this.A.j();h=g.length;F&&(g=new Uint8Array(g.buffer),g.length<= +h+4&&(this.a=new Uint8Array(g.length+4),this.a.set(g),g=this.a),g=g.subarray(0,h+4));g[h++]=f>>24&255;g[h++]=f>>16&255;g[h++]=f>>8&255;g[h++]=f&255;return g};function nb(d,a){var c,e,b,f;if(Object.keys)c=Object.keys(a);else for(e in c=[],b=0,a)c[b++]=e;b=0;for(f=c.length;b Date: Mon, 17 Aug 2015 19:12:56 -0400 Subject: [PATCH 588/791] Use zlib.js in PNG Encoder --- src/js/lib/png-encoder.js | 77 ++------------------------------------- 1 file changed, 4 insertions(+), 73 deletions(-) diff --git a/src/js/lib/png-encoder.js b/src/js/lib/png-encoder.js index 6baeccf1..3bfd929b 100644 --- a/src/js/lib/png-encoder.js +++ b/src/js/lib/png-encoder.js @@ -6,6 +6,8 @@ * @license MIT */ +var Zlib = require('vendor/zlib'); + var png = {}; png.Bytes = function(data, optional) { @@ -14,7 +16,7 @@ png.Bytes = function(data, optional) { if (!optional) { - if (data instanceof Array) { + if (data instanceof Array || data instanceof Uint8Array) { for (i = 0; i < data.length; i++) { datum = data[i]; if (datum !== null) { // nulls and undefineds are silently skipped. @@ -362,80 +364,9 @@ png.Raster_rgb = function(bit_depth, color_type, raster) { }; }; -png.Raster.Zlib = function(buffer) { - // implementing http://www.ietf.org/rfc/rfc1950.txt - - var compression_method = 8; - // The only value defined by the RFC. - var compression_info = 0; - // "CINFO is the base-2 logarithm of the LZ77 window size, minus eight" - // so, 0 means 256. (Not that it matters, since I'm planning just to do literal data.) - - var fdict = 0; - // no preset dictionary. - var flevel = 0; - // "compressor used fastest algorithm" - // "The information in FLEVEL is not needed for decompression; it - // is there to indicate if recompression might be worthwhile." - - var fcheck = 31 - ( - (compression_info << 12) | - (compression_method << 8) | - (flevel << 5) | - (fdict << 4) - ) % 31; - - this.checksum = adler32(buffer); - - function deflate(bytes) { - // implementing a bit of http://www.ietf.org/rfc/rfc1951.txt - // returns the compressed data block as a string. - var header_char = String.fromCharCode(1); - // (5 bits unused, 2 bits type (uncompressed), 1 bit final flag (on)) - // * little endian * - var len = bytes.length; - var len_string = String.fromCharCode(len & 0xFF,(len & 0xFF00)>>8); - var nlen = ~bytes.length; - var nlen_string = String.fromCharCode(nlen & 0xFF,(nlen & 0xFF00)>>8); - - return header_char + - len_string + - nlen_string + - String.fromCharCode.apply(null, bytes); - } - - function adler32(bytes) { - var s1 = 1; - var s2 = 0; - for (var i = 0; i < bytes.length; i++) { - s1 += bytes[i]; - s1 %= 65521; - s2 += s1; - s2 %= 65521; - } // This could be made more efficient by defering the modulos. - return (s2 << 16) | s1; - } - - this.compression_method_char = String.fromCharCode( - compression_method | (compression_info << 4) - ); - this.additional_flags_char = String.fromCharCode( - fcheck | (fdict << 4) | (flevel << 5) - ); - this.compressed_data_blocks = deflate(buffer); - - this.compress = function() { - // TODO: return Bytes - return this.compression_method_char + - this.additional_flags_char + - this.compressed_data_blocks + - new png.Bytes(this.checksum>>>0,{bytes:4}).serialize(); - }; -}; - png.Chunk.IDAT = function(raster) { var encoded = raster.encode(); - var zipped = new png.Raster.Zlib(encoded).compress(); + var zipped = new Zlib.Deflate(encoded).compress(); return new png.Chunk("IDAT", new png.Bytes(zipped)); }; From 5e660a24258131d351e9f01cda2b90d4e397a217 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 17 Aug 2015 20:04:53 -0400 Subject: [PATCH 589/791] Update the dithering algorithm to dither each color channel separately --- src/js/lib/image.js | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/js/lib/image.js b/src/js/lib/image.js index 91dc2413..76be387f 100644 --- a/src/js/lib/image.js +++ b/src/js/lib/image.js @@ -90,35 +90,45 @@ image.dithers.sierra = [ image.dithers['default'] = image.dithers.sierra; -image.dither = function(pixels, width, height, dithers) { +//! Get the nearest normalized grey color +var getChannelGrey = function(color) { + return color >= 128 ? 255 : 0; +}; + +//! Get the nearest normalized 2 bitdepth color +var getChannel2 = function(color) { + return Math.min(Math.max(parseInt(color / 64 + 0.5), 0) * 64, 255); +}; + +image.dither = function(pixels, width, height, dithers, converter) { + converter = converter || getChannel2; dithers = dithers || image.dithers['default']; - var numdithers = dithers.length; + var numDithers = dithers.length; for (var y = 0, yy = height; y < yy; ++y) { for (var x = 0, xx = width; x < xx; ++x) { var pos = getPos(width, x, y); - var oldColor = pixels[pos]; - var newColor = oldColor >= 128 ? 255 : 0; - var error = oldColor - newColor; - pixels[pos] = newColor; - for (var i = 0; i < numdithers; ++i) { - var dither = dithers[i]; - var x2 = x + dither[0], y2 = y + dither[1]; - if (x2 >= 0 && x2 < width && y < height) { - pixels[getPos(width, x2, y2)] += parseInt(error * dither[2]); + for (var i = 0; i < 3; ++i) { + var oldColor = pixels[pos + i]; + var newColor = converter(oldColor); + var error = oldColor - newColor; + pixels[pos + i] = newColor; + for (var j = 0; j < numDithers; ++j) { + var dither = dithers[j]; + var x2 = x + dither[0], y2 = y + dither[1]; + if (x2 >= 0 && x2 < width && y < height) { + pixels[getPos(width, x2, y2) + i] += parseInt(error * dither[2]); + } } } - for (var j = 1; j < 3; ++j) { - pixels[pos + j] = newColor; - } } } }; //! Dither a pixel buffer by image properties -image.ditherByProps = function(pixels, img) { +image.ditherByProps = function(pixels, img, converter) { if (img.dither) { var dithers = image.dithers[img.dither]; - image.dither(pixels, img.width, img.height, dithers); + image.dither(pixels, img.width, img.height, dithers, converter); } }; @@ -272,7 +282,8 @@ image.load = function(img, bitdepth, callback) { } image.setSizeAspect(img, png.width, png.height); pixels = image.resizeByProps(pixels, img); - image.ditherByProps(pixels, img); + image.ditherByProps(pixels, img, + bitdepth === 1 ? getChannelGrey : getChannel2); if (bitdepth === 8) { img.image = image.toPng8(pixels, img.width, img.height); } else if (bitdepth === 1) { From 119a2d38d78131bb5c538f7e0257eb78e31dbfc0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 22 Aug 2015 22:16:45 -0700 Subject: [PATCH 590/791] Work around Pebble.sendAppMessage double callback call issue If the JavaScript thread immediately moves onto a job that takes longer than a few seconds after successfully sending an app message, upon returning from the long job, Pebble.sendAppMessage will call the failure callback with a timed out error even though it has already called the success handler before the long job. MessageQueue expects Pebble.sendAppMessage calls and callback calls to be one-to-one and will fail when it is skewed either way. This fixes the issue by giving the callbacks references to the message they were sent with. If the last sent message of the queue itself is different from the reference given to the callback, then it is an extra callback call and is subsequently ignored. --- src/js/ui/simply-pebble.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 9a40f273..5b1a9b44 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -770,13 +770,21 @@ MessageQueue.prototype.stop = function() { }; MessageQueue.prototype.consume = function() { - this._queue.splice(0, 1); + this._queue.shift(); if (this._queue.length === 0) { return this.stop(); } this.cycle(); }; +MessageQueue.prototype.checkSent = function(message, fn) { + return function() { + if (message === this._sent) { + fn(); + } + }.bind(this); +}; + MessageQueue.prototype.cycle = function() { if (!this._sending) { return; @@ -785,7 +793,10 @@ MessageQueue.prototype.cycle = function() { if (!head) { return this.stop(); } - Pebble.sendAppMessage(head, this._consume, this._cycle); + this._sent = head; + var success = this.checkSent(head, this._consume); + var failure = this.checkSent(head, this._cycle); + Pebble.sendAppMessage(head, success, failure); }; MessageQueue.prototype.send = function(message) { From cf6203ee43f263cfa524c136915d0d503bc7c514 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 16 Aug 2015 14:11:16 -0700 Subject: [PATCH 591/791] Add Wakeup 'wakeup' event for receiving wakeup events while running --- src/js/wakeup/index.js | 2 - src/js/wakeup/wakeup.js | 97 ++++++++++++++++++++++++++++------------- 2 files changed, 67 insertions(+), 32 deletions(-) diff --git a/src/js/wakeup/index.js b/src/js/wakeup/index.js index 158f7335..cc54e4fb 100644 --- a/src/js/wakeup/index.js +++ b/src/js/wakeup/index.js @@ -1,5 +1,3 @@ var Wakeup = require('./wakeup'); -Wakeup.init(); - module.exports = Wakeup; diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js index 2df8b165..2b7b939c 100644 --- a/src/js/wakeup/wakeup.js +++ b/src/js/wakeup/wakeup.js @@ -1,34 +1,39 @@ var util2 = require('util2'); +var Emitter = require('emitter'); var Settings = require('settings'); var simply = require('ui/simply'); -var Wakeup = module.exports; +var Wakeup = function() { + this.init(); +}; + +Wakeup.prototype.cleanupGracePeriod = 60 * 5; -var cleanupGracePeriod = 60 * 5; +util2.copy(Emitter.prototype, Wakeup.prototype); -Wakeup.init = function() { +Wakeup.prototype.init = function() { this._setRequests = []; this._launchCallbacks = []; this._loadData(); this._cleanup(); }; -Wakeup._loadData = function() { +Wakeup.prototype._loadData = function() { this.state = Settings._loadData(null, 'wakeup', true) || {}; this.state.wakeups = this.state.wakeups || {}; }; -Wakeup._saveData = function() { +Wakeup.prototype._saveData = function() { Settings._saveData(null, 'wakeup', this.state); }; -Wakeup._cleanup = function() { +Wakeup.prototype._cleanup = function() { var id; var ids = []; for (id in this.state.wakeups) { ids.push(id); } - var cleanupTime = new Date().getTime() / 1000 - cleanupGracePeriod; + var cleanupTime = Date.now() / 1000 - Wakeup.cleanupGracePeriod; var deleted = false; for (var i = 0, ii = ids.length; i < ii; ++i) { id = ids[i]; @@ -43,7 +48,7 @@ Wakeup._cleanup = function() { } }; -Wakeup.get = function(id) { +Wakeup.prototype.get = function(id) { var wakeup = this.state.wakeups[id]; if (wakeup) { return { @@ -56,7 +61,7 @@ Wakeup.get = function(id) { } }; -Wakeup.each = function(callback) { +Wakeup.prototype.each = function(callback) { var i = 0; for (var id in this.state.wakeups) { if (callback(this.get(id), i++) === false) { @@ -65,7 +70,7 @@ Wakeup.each = function(callback) { } }; -Wakeup.schedule = function(opt, callback) { +Wakeup.prototype.schedule = function(opt, callback) { if (typeof opt !== 'object' || opt instanceof Date) { opt = { time: opt }; } @@ -75,12 +80,12 @@ Wakeup.schedule = function(opt, callback) { data: opt.data, callback: callback, }); - Wakeup.launch(function() { + this.launch(function() { simply.impl.wakeupSet(opt.time, cookie, opt.notifyIfMissed); }); }; -Wakeup.cancel = function(id) { +Wakeup.prototype.cancel = function(id) { if (id === 'all') { this.state.wakeups = {}; } else { @@ -89,7 +94,7 @@ Wakeup.cancel = function(id) { simply.impl.wakeupCancel(id); }; -Wakeup.launch = function(callback) { +Wakeup.prototype.launch = function(callback) { if (this._launchEvent) { callback(this._launchEvent); } else { @@ -97,7 +102,7 @@ Wakeup.launch = function(callback) { } }; -Wakeup._makeWakeupEvent = function(id, cookie) { +Wakeup.prototype._makeBaseEvent = function(id, cookie) { var wakeup = this.state.wakeups[id]; var e = { id: id, @@ -109,21 +114,43 @@ Wakeup._makeWakeupEvent = function(id, cookie) { return e; }; -Wakeup.emitSetResult = function(id, cookie) { - var req = this._setRequests.splice(0, 1)[0]; +Wakeup.prototype._makeWakeupEvent = function(id, cookie) { + var e; + if (id !== undefined) { + e = this._makeBaseEvent(id, cookie); + e.wakeup = true; + } else { + e = { wakeup: false }; + } + return e; +}; + +Wakeup.prototype._setWakeup = function(id, wakeup) { + this.state.wakeups[id] = wakeup; + this._saveData(); +}; + +Wakeup.prototype._removeWakeup = function(id) { + if (id in this.state.wakeups) { + delete this.state.wakeups[id]; + this._saveData(); + } +}; + +Wakeup.prototype.emitSetResult = function(id, cookie) { + var req = this._setRequests.shift(); if (!req) { return; } var e; if (typeof id === 'number') { - this.state.wakeups[id] = { + this._setWakeup(id, { id: id, cookie: cookie, data: req.data, params: req.params, - }; - this._saveData(); - e = this._makeWakeupEvent(id, cookie); + }); + e = this._makeBaseEvent(id, cookie); e.failed = false; } else { e = { @@ -136,27 +163,37 @@ Wakeup.emitSetResult = function(id, cookie) { return req.callback(e); }; -Wakeup.emitWakeup = function(id, cookie) { - var e; - if (id !== undefined) { - delete this.state.wakeups[id]; +Wakeup.prototype.emitWakeup = function(id, cookie) { + var e = this._makeWakeupEvent(id, cookie); - e = this._makeWakeupEvent(id, cookie); - e.wakeup = true; + if (!this._launchEvent) { + e.launch = true; + if (this._emitWakeupLaunch(e) === false) { + return false; + } } else { - e = { - wakeup: false, - }; + e.launch = false; } - this._saveData(); + if (e.wakeup) { + this._removeWakeup(id); + if (this.emit('wakeup', e) === false) { + return false; + } + } +}; + +Wakeup.prototype._emitWakeupLaunch = function(e) { this._launchEvent = e; var callbacks = this._launchCallbacks; this._launchCallbacks = []; + for (var i = 0, ii = callbacks.length; i < ii; ++i) { if (callbacks[i](e) === false) { return false; } } }; + +module.exports = new Wakeup(); From 99a9e6e60a3ff34eb0e9a7f89f5e41a5422f3d98 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 23 Aug 2015 01:56:13 -0700 Subject: [PATCH 592/791] Fix Wakeup get and each to return the id as the right type --- src/js/wakeup/wakeup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/wakeup/wakeup.js b/src/js/wakeup/wakeup.js index 2b7b939c..eeda6565 100644 --- a/src/js/wakeup/wakeup.js +++ b/src/js/wakeup/wakeup.js @@ -52,7 +52,7 @@ Wakeup.prototype.get = function(id) { var wakeup = this.state.wakeups[id]; if (wakeup) { return { - id: id, + id: wakeup.id, cookie: wakeup.cookie, data: wakeup.data, time: wakeup.params.time, From 21301658550238cc5c1ac9f1b626378ec99d1b47 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 23 Aug 2015 03:07:46 -0700 Subject: [PATCH 593/791] Change Settings to invalidate corrupt data rather than loading as a string --- src/js/settings/settings.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index 8aeeef9b..a47bbc47 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -1,6 +1,7 @@ -var util2 = require('util2'); -var ajax = require('ajax'); -var myutil = require('myutil'); +var util2 = require('lib/util2'); +var myutil = require('lib/myutil'); +var safe = require('lib/safe'); +var ajax = require('lib/ajax'); var appinfo = require('appinfo'); var Settings = module.exports; @@ -9,7 +10,7 @@ var parseJson = function(data) { try { return JSON.parse(data); } catch (e) { - return data; + safe.warn('Invalid JSON in localStorage: ' + (e.message || '') + '\n\t' + data); } }; @@ -80,7 +81,12 @@ Settings._loadData = function(path, field, nocache) { field = field || 'data'; state[field] = {}; var key = Settings._getDataKey(path, field); - var data = parseJson(localStorage.getItem(key)); + var value = localStorage.getItem(key); + var data = parseJson(value); + if (value && typeof data === 'undefined') { + // There was an issue loading the data, remove it + localStorage.removeItem(key); + } if (!nocache && typeof data === 'object' && data !== null) { state[field] = data; } From cf976fc1cfbc4ad04e724ab5734acfff2c59f82f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 23 Aug 2015 03:55:08 -0700 Subject: [PATCH 594/791] Fix aplite remote image support caused by data typo --- src/js/lib/image.js | 2 +- src/simply/simply_res.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/lib/image.js b/src/js/lib/image.js index 76be387f..7f7d02ad 100644 --- a/src/js/lib/image.js +++ b/src/js/lib/image.js @@ -200,7 +200,7 @@ image.toGbitmap1 = function(pixels, width, height) { for (var y = 0, yy = height; y < yy; ++y) { for (var x = 0, xx = width; x < xx; ++x) { var grey = 0; - var pos = y * rowBytes + parseInt(x * 4); + var pos = getPos(width, x, y); for (var j = 0; j < 3; ++j) { grey += pixels[pos + j]; } diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 43bfba16..d61bc16f 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -115,7 +115,7 @@ static GBitmap *create_bitmap_with_data(SimplyImage *image, void *data) { GBitmap *bitmap = gbitmap_create_blank(ctx->size, GBitmapFormat1Bit); if (bitmap) { image->bitmap_data = gbitmap_get_data(bitmap); - memcpy(image->bitmap_data, data, ctx->data_length); + memcpy(image->bitmap_data, ctx->data, ctx->data_length); } return bitmap; } From 5412d65490da5d7a92684325683929c35a44b741 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Sat, 26 Sep 2015 15:11:26 -0400 Subject: [PATCH 595/791] Ability to enter in hex color codes w/ rounding --- src/js/app.js | 4 +- src/js/ui/simply-pebble.js | 131 ++++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 3 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 11d22587..da6ccdbc 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -11,7 +11,9 @@ var main = new UI.Card({ title: 'Pebble.js', icon: 'images/menu_icon.png', subtitle: 'Hello World!', - body: 'Press any button.' + body: 'Press any button.', + titleColor: 'indigo', //named colors + bodyColor: '#6699FF' // Hex colors }); main.show(); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 5b1a9b44..a139be7f 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -70,7 +70,74 @@ var SizeType = function(x) { this.sizeH(x.y); }; -var colorMap = { +var hexColorMap = { + '#000000': 0xC0, + '#000055': 0xC1, + '#0000AA': 0xC2, + '#0000FF': 0xC3, + '#005500': 0xC4, + '#005555': 0xC5, + '#0055AA': 0xC6, + '#0055FF': 0xC7, + '#00AA00': 0xC8, + '#00AA55': 0xC9, + '#00AAAA': 0xCA, + '#00AAFF': 0xCB, + '#00FF00': 0xCC, + '#00FF55': 0xCD, + '#00FFAA': 0xCE, + '#00FFFF': 0xCF, + '#550000': 0xD0, + '#550055': 0xD1, + '#5500AA': 0xD2, + '#5500FF': 0xD3, + '#555500': 0xD4, + '#555555': 0xD5, + '#5555AA': 0xD6, + '#5555FF': 0xD7, + '#55AA00': 0xD8, + '#55AA55': 0xD9, + '#55AAAA': 0xDA, + '#55AAFF': 0xDB, + '#55FF00': 0xDC, + '#55FF55': 0xDD, + '#55FFAA': 0xDE, + '#55FFFF': 0xDF, + '#AA0000': 0xE0, + '#AA0055': 0xE1, + '#AA00AA': 0xE2, + '#AA00FF': 0xE3, + '#AA5500': 0xE4, + '#AA5555': 0xE5, + '#AA55AA': 0xE6, + '#AA55FF': 0xE7, + '#AAAA00': 0xE8, + '#AAAA55': 0xE9, + '#AAAAAA': 0xEA, + '#AAAAFF': 0xEB, + '#AAFF00': 0xEC, + '#AAFF55': 0xED, + '#AAFFAA': 0xEE, + '#AAFFFF': 0xEF, + '#FF0000': 0xF0, + '#FF0055': 0xF1, + '#FF00AA': 0xF2, + '#FF00FF': 0xF3, + '#FF5500': 0xF4, + '#FF5555': 0xF5, + '#FF55AA': 0xF6, + '#FF55FF': 0xF7, + '#FFAA00': 0xF8, + '#FFAA55': 0xF9, + '#FFAAAA': 0xFA, + '#FFAAFF': 0xFB, + '#FFFF00': 0xFC, + '#FFFF55': 0xFD, + '#FFFFAA': 0xFE, + '#FFFFFF': 0xFF, +}; + +var namedColorMap = { 'clear': 0x00, 'black': 0xC0, 'oxfordBlue': 0xC1, @@ -140,7 +207,67 @@ var colorMap = { }; var Color = function(color) { - return colorMap[color] ? colorMap[color] : colorMap.clear; + if (color.charAt(0)=='#') { + color = color.toUpperCase(); + return hexColorMap[findClosestColor(color)]; + } + return namedColorMap[color] ? namedColorMap[color] : namedColorMap.clear; +}; + +//As seen in: http://stackoverflow.com/questions/4057475/rounding-colour-values-to-the-nearest-of-a-small-set-of-colours +var findClosestColor = function getSimilarColors (color) { + var pebble_colors=Object.keys(hexColorMap); + + //Convert to RGB, then R, G, B + var color_rgb = hex2rgb(color); + var color_r = color_rgb.split(',')[0]; + var color_g = color_rgb.split(',')[1]; + var color_b = color_rgb.split(',')[2]; + + //Create an empty array for the difference between the colors + var differenceArray=[]; + + //Function to find the smallest value in an array + Array.min = function( array ){ + return Math.min.apply( Math, array ); + }; + + //Convert the HEX color in the array to RGB colors, split them up to R-G-B, then find out the difference between the "color" and the colors in the array + pebble_colors.forEach(function(pebble_color) { + var base_color_rgb = hex2rgb(pebble_color); + var base_colors_r = base_color_rgb.split(',')[0]; + var base_colors_g = base_color_rgb.split(',')[1]; + var base_colors_b = base_color_rgb.split(',')[2]; + + //Add the difference to the differenceArray + differenceArray.push(Math.sqrt((color_r-base_colors_r)*(color_r-base_colors_r)+(color_g-base_colors_g)*(color_g-base_colors_g)+(color_b-base_colors_b)*(color_b-base_colors_b))); + }); + + //Get the lowest number from the differenceArray + var lowest = Array.min(differenceArray); + + //Get the index for that lowest number + var index = differenceArray.indexOf(lowest); + + //Function to convert HEX to RGB + function hex2rgb(colour) { + var r,g,b; + if ( colour.charAt(0) == '#' ) { + colour = colour.substr(1); + } + + r = colour.charAt(0) + colour.charAt(1); + g = colour.charAt(2) + colour.charAt(3); + b = colour.charAt(4) + colour.charAt(5); + + r = parseInt( r,16 ); + g = parseInt( g,16 ); + b = parseInt( b ,16); + return r+','+g+','+b; + } + + //Return the HEX code + return pebble_colors[index]; }; var Font = function(x) { From fb404f9a394a87b14c5cdd027950c3cc58a414b9 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Sat, 26 Sep 2015 23:34:08 -0400 Subject: [PATCH 596/791] Better color rounding algorithm --- src/js/app.js | 2 +- src/js/ui/simply-pebble.js | 82 ++++++++++++++------------------------ 2 files changed, 30 insertions(+), 54 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index da6ccdbc..d92577e1 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -13,7 +13,7 @@ var main = new UI.Card({ subtitle: 'Hello World!', body: 'Press any button.', titleColor: 'indigo', //named colors - bodyColor: '#6699FF' // Hex colors + bodyColor: '#9a0036' // Hex colors }); main.show(); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index a139be7f..d683eadf 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -208,66 +208,42 @@ var namedColorMap = { var Color = function(color) { if (color.charAt(0)=='#') { + //Convert full hex to shorthand + if (color.length==4){ + var r = color.charAt(1); + var g = color.charAt(2); + var b = color.charAt(3); + color = '#'+r+r+g+g+b+b; + } + //Ensure upper case color = color.toUpperCase(); - return hexColorMap[findClosestColor(color)]; + return hexColorMap[roundColor(color)]; } return namedColorMap[color] ? namedColorMap[color] : namedColorMap.clear; }; -//As seen in: http://stackoverflow.com/questions/4057475/rounding-colour-values-to-the-nearest-of-a-small-set-of-colours -var findClosestColor = function getSimilarColors (color) { - var pebble_colors=Object.keys(hexColorMap); - - //Convert to RGB, then R, G, B - var color_rgb = hex2rgb(color); - var color_r = color_rgb.split(',')[0]; - var color_g = color_rgb.split(',')[1]; - var color_b = color_rgb.split(',')[2]; - - //Create an empty array for the difference between the colors - var differenceArray=[]; - - //Function to find the smallest value in an array - Array.min = function( array ){ - return Math.min.apply( Math, array ); +var roundColor = function (color) { + var closestTo = function(color, colors) { + var dist = Infinity; + var result = color; + colors.forEach(function(col) { + var localdist = Math.abs(parseInt(color, 16)-parseInt(col, 16)); + if (localdist < dist) { + dist = localdist; + result = col; + } + }); + return result; }; - //Convert the HEX color in the array to RGB colors, split them up to R-G-B, then find out the difference between the "color" and the colors in the array - pebble_colors.forEach(function(pebble_color) { - var base_color_rgb = hex2rgb(pebble_color); - var base_colors_r = base_color_rgb.split(',')[0]; - var base_colors_g = base_color_rgb.split(',')[1]; - var base_colors_b = base_color_rgb.split(',')[2]; - - //Add the difference to the differenceArray - differenceArray.push(Math.sqrt((color_r-base_colors_r)*(color_r-base_colors_r)+(color_g-base_colors_g)*(color_g-base_colors_g)+(color_b-base_colors_b)*(color_b-base_colors_b))); - }); - - //Get the lowest number from the differenceArray - var lowest = Array.min(differenceArray); - - //Get the index for that lowest number - var index = differenceArray.indexOf(lowest); - - //Function to convert HEX to RGB - function hex2rgb(colour) { - var r,g,b; - if ( colour.charAt(0) == '#' ) { - colour = colour.substr(1); - } - - r = colour.charAt(0) + colour.charAt(1); - g = colour.charAt(2) + colour.charAt(3); - b = colour.charAt(4) + colour.charAt(5); - - r = parseInt( r,16 ); - g = parseInt( g,16 ); - b = parseInt( b ,16); - return r+','+g+','+b; - } - - //Return the HEX code - return pebble_colors[index]; + var colors = ['00','55','AA','FF']; + var r_hex = color.charAt(1) + color.charAt(2); + var g_hex = color.charAt(3) + color.charAt(4); + var b_hex = color.charAt(5) + color.charAt(6); + var r = closestTo(r_hex, colors); + var g = closestTo(g_hex, colors); + var b = closestTo(b_hex, colors); + return '#'+r+g+b; }; var Font = function(x) { From 0b636e2ef7c61d36e1c73300195da3e6002b4106 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Wed, 30 Sep 2015 16:28:28 -0400 Subject: [PATCH 597/791] Cleanup to meet PebbleJS standards --- src/js/app.js | 2 +- src/js/ui/simply-pebble.js | 324 ++++++++++++++++++------------------- 2 files changed, 163 insertions(+), 163 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index d92577e1..bebcdab4 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -12,7 +12,7 @@ var main = new UI.Card({ icon: 'images/menu_icon.png', subtitle: 'Hello World!', body: 'Press any button.', - titleColor: 'indigo', //named colors + titleColor: 'indigo', // Named colors bodyColor: '#9a0036' // Hex colors }); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index d683eadf..01081b88 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -71,179 +71,179 @@ var SizeType = function(x) { }; var hexColorMap = { - '#000000': 0xC0, - '#000055': 0xC1, - '#0000AA': 0xC2, - '#0000FF': 0xC3, - '#005500': 0xC4, - '#005555': 0xC5, - '#0055AA': 0xC6, - '#0055FF': 0xC7, - '#00AA00': 0xC8, - '#00AA55': 0xC9, - '#00AAAA': 0xCA, - '#00AAFF': 0xCB, - '#00FF00': 0xCC, - '#00FF55': 0xCD, - '#00FFAA': 0xCE, - '#00FFFF': 0xCF, - '#550000': 0xD0, - '#550055': 0xD1, - '#5500AA': 0xD2, - '#5500FF': 0xD3, - '#555500': 0xD4, - '#555555': 0xD5, - '#5555AA': 0xD6, - '#5555FF': 0xD7, - '#55AA00': 0xD8, - '#55AA55': 0xD9, - '#55AAAA': 0xDA, - '#55AAFF': 0xDB, - '#55FF00': 0xDC, - '#55FF55': 0xDD, - '#55FFAA': 0xDE, - '#55FFFF': 0xDF, - '#AA0000': 0xE0, - '#AA0055': 0xE1, - '#AA00AA': 0xE2, - '#AA00FF': 0xE3, - '#AA5500': 0xE4, - '#AA5555': 0xE5, - '#AA55AA': 0xE6, - '#AA55FF': 0xE7, - '#AAAA00': 0xE8, - '#AAAA55': 0xE9, - '#AAAAAA': 0xEA, - '#AAAAFF': 0xEB, - '#AAFF00': 0xEC, - '#AAFF55': 0xED, - '#AAFFAA': 0xEE, - '#AAFFFF': 0xEF, - '#FF0000': 0xF0, - '#FF0055': 0xF1, - '#FF00AA': 0xF2, - '#FF00FF': 0xF3, - '#FF5500': 0xF4, - '#FF5555': 0xF5, - '#FF55AA': 0xF6, - '#FF55FF': 0xF7, - '#FFAA00': 0xF8, - '#FFAA55': 0xF9, - '#FFAAAA': 0xFA, - '#FFAAFF': 0xFB, - '#FFFF00': 0xFC, - '#FFFF55': 0xFD, - '#FFFFAA': 0xFE, - '#FFFFFF': 0xFF, + '#000000': 0xC0, + '#000055': 0xC1, + '#0000AA': 0xC2, + '#0000FF': 0xC3, + '#005500': 0xC4, + '#005555': 0xC5, + '#0055AA': 0xC6, + '#0055FF': 0xC7, + '#00AA00': 0xC8, + '#00AA55': 0xC9, + '#00AAAA': 0xCA, + '#00AAFF': 0xCB, + '#00FF00': 0xCC, + '#00FF55': 0xCD, + '#00FFAA': 0xCE, + '#00FFFF': 0xCF, + '#550000': 0xD0, + '#550055': 0xD1, + '#5500AA': 0xD2, + '#5500FF': 0xD3, + '#555500': 0xD4, + '#555555': 0xD5, + '#5555AA': 0xD6, + '#5555FF': 0xD7, + '#55AA00': 0xD8, + '#55AA55': 0xD9, + '#55AAAA': 0xDA, + '#55AAFF': 0xDB, + '#55FF00': 0xDC, + '#55FF55': 0xDD, + '#55FFAA': 0xDE, + '#55FFFF': 0xDF, + '#AA0000': 0xE0, + '#AA0055': 0xE1, + '#AA00AA': 0xE2, + '#AA00FF': 0xE3, + '#AA5500': 0xE4, + '#AA5555': 0xE5, + '#AA55AA': 0xE6, + '#AA55FF': 0xE7, + '#AAAA00': 0xE8, + '#AAAA55': 0xE9, + '#AAAAAA': 0xEA, + '#AAAAFF': 0xEB, + '#AAFF00': 0xEC, + '#AAFF55': 0xED, + '#AAFFAA': 0xEE, + '#AAFFFF': 0xEF, + '#FF0000': 0xF0, + '#FF0055': 0xF1, + '#FF00AA': 0xF2, + '#FF00FF': 0xF3, + '#FF5500': 0xF4, + '#FF5555': 0xF5, + '#FF55AA': 0xF6, + '#FF55FF': 0xF7, + '#FFAA00': 0xF8, + '#FFAA55': 0xF9, + '#FFAAAA': 0xFA, + '#FFAAFF': 0xFB, + '#FFFF00': 0xFC, + '#FFFF55': 0xFD, + '#FFFFAA': 0xFE, + '#FFFFFF': 0xFF, }; var namedColorMap = { - 'clear': 0x00, - 'black': 0xC0, - 'oxfordBlue': 0xC1, - 'dukeBlue': 0xC2, - 'blue': 0xC3, - 'darkGreen': 0xC4, - 'midnightGreen': 0xC5, - 'cobaltBlue': 0xC6, - 'blueMoon': 0xC7, - 'islamicGreen': 0xC8, - 'jaegerGreen': 0xC9, - 'tiffanyBlue': 0xCA, - 'vividCerulean': 0xCB, - 'green': 0xCC, - 'malachite': 0xCD, - 'mediumSpringGreen': 0xCE, - 'cyan': 0xCF, - 'bulgarianRose': 0xD0, - 'imperialPurple': 0xD1, - 'indigo': 0xD2, - 'electricUltramarine': 0xD3, - 'armyGreen': 0xD4, - 'darkGray': 0xD5, - 'liberty': 0xD6, - 'veryLightBlue': 0xD7, - 'kellyGreen': 0xD8, - 'mayGreen': 0xD9, - 'cadetBlue': 0xDA, - 'pictonBlue': 0xDB, - 'brightGreen': 0xDC, - 'screaminGreen': 0xDD, - 'mediumAquamarine': 0xDE, - 'electricBlue': 0xDF, - 'darkCandyAppleRed': 0xE0, - 'jazzberryJam': 0xE1, - 'purple': 0xE2, - 'vividViolet': 0xE3, - 'windsorTan': 0xE4, - 'roseVale': 0xE5, - 'purpureus': 0xE6, - 'lavenderIndigo': 0xE7, - 'limerick': 0xE8, - 'brass': 0xE9, - 'lightGray': 0xEA, - 'babyBlueEyes': 0xEB, - 'springBud': 0xEC, - 'inchworm': 0xED, - 'mintGreen': 0xEE, - 'celeste': 0xEF, - 'red': 0xF0, - 'folly': 0xF1, - 'fashionMagenta': 0xF2, - 'magenta': 0xF3, - 'orange': 0xF4, - 'sunsetOrange': 0xF5, - 'brilliantRose': 0xF6, - 'shockingPink': 0xF7, - 'chromeYellow': 0xF8, - 'rajah': 0xF9, - 'melon': 0xFA, - 'richBrilliantLavender': 0xFB, - 'yellow': 0xFC, - 'icterine': 0xFD, - 'pastelYellow': 0xFE, - 'white': 0xFF, - 'clearWhite': 0x3F, + 'clear': 0x00, + 'black': 0xC0, + 'oxfordBlue': 0xC1, + 'dukeBlue': 0xC2, + 'blue': 0xC3, + 'darkGreen': 0xC4, + 'midnightGreen': 0xC5, + 'cobaltBlue': 0xC6, + 'blueMoon': 0xC7, + 'islamicGreen': 0xC8, + 'jaegerGreen': 0xC9, + 'tiffanyBlue': 0xCA, + 'vividCerulean': 0xCB, + 'green': 0xCC, + 'malachite': 0xCD, + 'mediumSpringGreen': 0xCE, + 'cyan': 0xCF, + 'bulgarianRose': 0xD0, + 'imperialPurple': 0xD1, + 'indigo': 0xD2, + 'electricUltramarine': 0xD3, + 'armyGreen': 0xD4, + 'darkGray': 0xD5, + 'liberty': 0xD6, + 'veryLightBlue': 0xD7, + 'kellyGreen': 0xD8, + 'mayGreen': 0xD9, + 'cadetBlue': 0xDA, + 'pictonBlue': 0xDB, + 'brightGreen': 0xDC, + 'screaminGreen': 0xDD, + 'mediumAquamarine': 0xDE, + 'electricBlue': 0xDF, + 'darkCandyAppleRed': 0xE0, + 'jazzberryJam': 0xE1, + 'purple': 0xE2, + 'vividViolet': 0xE3, + 'windsorTan': 0xE4, + 'roseVale': 0xE5, + 'purpureus': 0xE6, + 'lavenderIndigo': 0xE7, + 'limerick': 0xE8, + 'brass': 0xE9, + 'lightGray': 0xEA, + 'babyBlueEyes': 0xEB, + 'springBud': 0xEC, + 'inchworm': 0xED, + 'mintGreen': 0xEE, + 'celeste': 0xEF, + 'red': 0xF0, + 'folly': 0xF1, + 'fashionMagenta': 0xF2, + 'magenta': 0xF3, + 'orange': 0xF4, + 'sunsetOrange': 0xF5, + 'brilliantRose': 0xF6, + 'shockingPink': 0xF7, + 'chromeYellow': 0xF8, + 'rajah': 0xF9, + 'melon': 0xFA, + 'richBrilliantLavender': 0xFB, + 'yellow': 0xFC, + 'icterine': 0xFD, + 'pastelYellow': 0xFE, + 'white': 0xFF, + 'clearWhite': 0x3F, }; var Color = function(color) { - if (color.charAt(0)=='#') { - //Convert full hex to shorthand - if (color.length==4){ - var r = color.charAt(1); - var g = color.charAt(2); - var b = color.charAt(3); - color = '#'+r+r+g+g+b+b; - } - //Ensure upper case - color = color.toUpperCase(); - return hexColorMap[roundColor(color)]; + if (color.charAt(0) === '#') { + // Convert full hex to shorthand + if (color.length === 4){ + var r = color.charAt(1); + var g = color.charAt(2); + var b = color.charAt(3); + color = '#'+r+r+g+g+b+b; + } + // Ensure upper case + color = color.toUpperCase(); + return hexColorMap[roundColor(color)]; } return namedColorMap[color] ? namedColorMap[color] : namedColorMap.clear; }; var roundColor = function (color) { - var closestTo = function(color, colors) { - var dist = Infinity; - var result = color; - colors.forEach(function(col) { - var localdist = Math.abs(parseInt(color, 16)-parseInt(col, 16)); - if (localdist < dist) { - dist = localdist; - result = col; - } - }); - return result; - }; - - var colors = ['00','55','AA','FF']; - var r_hex = color.charAt(1) + color.charAt(2); - var g_hex = color.charAt(3) + color.charAt(4); - var b_hex = color.charAt(5) + color.charAt(6); - var r = closestTo(r_hex, colors); - var g = closestTo(g_hex, colors); - var b = closestTo(b_hex, colors); - return '#'+r+g+b; + var closestTo = function(color, colors) { + var nearestDist = Infinity; + var result = color; + colors.forEach(function(col) { + var dist = Math.abs(parseInt(color, 16) - parseInt(col, 16)); + if (dist < nearestDist) { + nearestDist = dist; + result = col; + } + }); + return result; + }; + + var colors = ['00', '55', 'AA', 'FF']; + var rHex = color.charAt(1) + color.charAt(2); + var gHex = color.charAt(3) + color.charAt(4); + var bHex = color.charAt(5) + color.charAt(6); + var r = closestTo(rHex, colors); + var g = closestTo(gHex, colors); + var b = closestTo(bHex, colors); + return '#'+r+g+b; }; var Font = function(x) { From cfdf5cc212ba57811d4b4a0a2607250509ea2869 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Wed, 30 Sep 2015 16:29:40 -0400 Subject: [PATCH 598/791] Fix some indentation --- src/js/ui/simply-pebble.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 01081b88..c32801b2 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -227,11 +227,11 @@ var roundColor = function (color) { var nearestDist = Infinity; var result = color; colors.forEach(function(col) { - var dist = Math.abs(parseInt(color, 16) - parseInt(col, 16)); - if (dist < nearestDist) { - nearestDist = dist; - result = col; - } + var dist = Math.abs(parseInt(color, 16) - parseInt(col, 16)); + if (dist < nearestDist) { + nearestDist = dist; + result = col; + } }); return result; }; From 5dedecb237737b4ca1d60eb0863894803e458e3a Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Wed, 30 Sep 2015 16:52:07 -0400 Subject: [PATCH 599/791] Further optimize color rounding algorithm --- src/js/ui/simply-pebble.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index c32801b2..b93ce2fb 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -208,8 +208,8 @@ var namedColorMap = { var Color = function(color) { if (color.charAt(0) === '#') { - // Convert full hex to shorthand - if (color.length === 4){ + // Convert shorthand hex to full length for rounding + if (color.length === 4) { var r = color.charAt(1); var g = color.charAt(2); var b = color.charAt(3); @@ -222,28 +222,29 @@ var Color = function(color) { return namedColorMap[color] ? namedColorMap[color] : namedColorMap.clear; }; +var pebbleColors = [0x00, 0x55, 0xAA, 0xFF]; + var roundColor = function (color) { - var closestTo = function(color, colors) { + var rHex = color.substr(1,2); + var gHex = color.substr(3,2); + var bHex = color.substr(5,2); + var r = findClosestColor(rHex, pebbleColors); + var g = findClosestColor(gHex, pebbleColors); + var b = findClosestColor(bHex, pebbleColors); + return '#'+r+g+b; +}; + +var findClosestColor = function(color, colors) { var nearestDist = Infinity; var result = color; colors.forEach(function(col) { - var dist = Math.abs(parseInt(color, 16) - parseInt(col, 16)); + var dist = Math.abs(parseInt(color, 16) - col); if (dist < nearestDist) { nearestDist = dist; result = col; } }); return result; - }; - - var colors = ['00', '55', 'AA', 'FF']; - var rHex = color.charAt(1) + color.charAt(2); - var gHex = color.charAt(3) + color.charAt(4); - var bHex = color.charAt(5) + color.charAt(6); - var r = closestTo(rHex, colors); - var g = closestTo(gHex, colors); - var b = closestTo(bHex, colors); - return '#'+r+g+b; }; var Font = function(x) { From 92ee04a8859b6831a9ba20e2b17682a89d5a7879 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Wed, 30 Sep 2015 17:01:36 -0400 Subject: [PATCH 600/791] Spaces after comma to follow standards --- src/js/ui/simply-pebble.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index b93ce2fb..4de7b45e 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -225,9 +225,9 @@ var Color = function(color) { var pebbleColors = [0x00, 0x55, 0xAA, 0xFF]; var roundColor = function (color) { - var rHex = color.substr(1,2); - var gHex = color.substr(3,2); - var bHex = color.substr(5,2); + var rHex = color.substr(1, 2); + var gHex = color.substr(3, 2); + var bHex = color.substr(5, 2); var r = findClosestColor(rHex, pebbleColors); var g = findClosestColor(gHex, pebbleColors); var b = findClosestColor(bHex, pebbleColors); From 0a5cb15800e41f5ff72e5f424543357130784935 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Wed, 30 Sep 2015 17:03:16 -0400 Subject: [PATCH 601/791] Further fix indentation --- src/js/ui/simply-pebble.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 4de7b45e..2e9581e9 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -235,16 +235,16 @@ var roundColor = function (color) { }; var findClosestColor = function(color, colors) { - var nearestDist = Infinity; - var result = color; - colors.forEach(function(col) { - var dist = Math.abs(parseInt(color, 16) - col); - if (dist < nearestDist) { - nearestDist = dist; - result = col; - } - }); - return result; + var nearestDist = Infinity; + var result = color; + colors.forEach(function(col) { + var dist = Math.abs(parseInt(color, 16) - col); + if (dist < nearestDist) { + nearestDist = dist; + result = col; + } + }); + return result; }; var Font = function(x) { From b8fbb7820fb33e4190b04df23b57ccaf9df85b67 Mon Sep 17 00:00:00 2001 From: Matthew Langlois Date: Wed, 30 Sep 2015 17:04:10 -0400 Subject: [PATCH 602/791] Correct indentation of return --- src/js/ui/simply-pebble.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 2e9581e9..35b3dd80 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -244,7 +244,7 @@ var findClosestColor = function(color, colors) { result = col; } }); - return result; + return result; }; var Font = function(x) { From f67c19adaaa96b1071f2e64f03a0983aeed05844 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 8 Oct 2015 14:19:06 -0700 Subject: [PATCH 603/791] Add compile compatibility for chalk * Greetings from Pebble Developer Retreat 2015! --- appinfo.json | 3 ++- src/simply/simply.h | 4 ---- src/util/compat.h | 4 ++-- src/util/status_bar_layer.h | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/appinfo.json b/appinfo.json index 4c5f4d1e..dcfa9dfe 100644 --- a/appinfo.json +++ b/appinfo.json @@ -39,7 +39,8 @@ }, "targetPlatforms": [ "aplite", - "basalt" + "basalt", + "chalk" ], "sdkVersion": "3" } diff --git a/src/simply/simply.h b/src/simply/simply.h index 60b29247..2fb2cf19 100644 --- a/src/simply/simply.h +++ b/src/simply/simply.h @@ -1,9 +1,5 @@ #pragma once -#ifdef PBL_SDK_3 -#include "basalt/src/resource_ids.auto.h" -#endif - #define LOG(...) APP_LOG(APP_LOG_LEVEL_DEBUG, __VA_ARGS__) typedef struct Simply Simply; diff --git a/src/util/compat.h b/src/util/compat.h index 459c3bc8..c2a4e79d 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -9,7 +9,7 @@ */ // Compatibility definitions for aplite on 2.9 -#if !defined(PBL_PLATFORM_APLITE) && !defined(PBL_PLATFORM_BASALT) +#if !defined(PBL_PLATFORM_APLITE) && !defined(PBL_PLATFORM_BASALT) && !defined(PBL_PLATFORM_CHALK) #define PBL_SDK_2 @@ -140,7 +140,7 @@ typedef union GColor8 { // Legacy definitions for basalt on 3.0 // These should eventually be removed in the future -#ifdef PBL_PLATFORM_BASALT +#ifdef PBL_SDK_3 #define window_set_fullscreen(window, fullscreen) diff --git a/src/util/status_bar_layer.h b/src/util/status_bar_layer.h index 8b683da9..7ec02556 100644 --- a/src/util/status_bar_layer.h +++ b/src/util/status_bar_layer.h @@ -4,7 +4,7 @@ #include "simply/simply.h" -#ifndef PBL_PLATFORM_BASALT +#ifdef PBL_SDK_2 typedef struct StatusBarLayer StatusBarLayer; struct StatusBarLayer; From 1dd25d2030e689cfcd7681039e65e259e0177d1f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 8 Oct 2015 16:08:41 -0700 Subject: [PATCH 604/791] Add text flow support to simply ui cards --- src/simply/simply_ui.c | 20 ++++++++++++-------- src/util/compat.h | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 964152b5..b0e3b9dc 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -124,7 +124,11 @@ void simply_ui_set_text_color(SimplyUi *self, SimplyUiTextfieldId textfield_id, } static void layer_update_callback(Layer *layer, GContext *ctx) { - SimplyUi *self = *(void**) layer_get_data(layer); + SimplyUi *self = *(void **)layer_get_data(layer); + + GTextAttributes *text_attributes = graphics_text_attributes_create(); + const uint8_t inset = 8; + graphics_text_attributes_enable_screen_text_flow(text_attributes, inset); GRect window_frame = layer_get_frame(window_get_root_layer(self->window.window)); GRect frame = layer_get_frame(layer); @@ -253,9 +257,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } if (has_title) { graphics_context_set_text_color(ctx, gcolor8_get_or(title->color, GColorBlack)); - graphics_draw_text(ctx, title->text, title_font, - (GRect) { .origin = title_pos, .size = title_size }, - GTextOverflowModeWordWrap, GTextAlignmentLeft, NULL); + graphics_draw_text(ctx, title->text, title_font, (GRect) { title_pos, title_size }, + GTextOverflowModeWordWrap, GTextAlignmentLeft, text_attributes); } if (subtitle_icon) { @@ -268,9 +271,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } if (has_subtitle) { graphics_context_set_text_color(ctx, gcolor8_get_or(subtitle->color, GColorBlack)); - graphics_draw_text(ctx, subtitle->text, subtitle_font, - (GRect) { .origin = subtitle_pos, .size = subtitle_size }, - GTextOverflowModeWordWrap, GTextAlignmentLeft, NULL); + graphics_draw_text(ctx, subtitle->text, subtitle_font, (GRect) { subtitle_pos, subtitle_size }, + GTextOverflowModeWordWrap, GTextAlignmentLeft, text_attributes); } if (body_image) { @@ -284,8 +286,10 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { if (has_body) { graphics_context_set_text_color(ctx, gcolor8_get_or(body->color, GColorBlack)); graphics_draw_text(ctx, body->text, body_font, body_rect, - GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); + GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, text_attributes); } + + graphics_text_attributes_destroy(text_attributes); } static void show_welcome_text(SimplyUi *self) { diff --git a/src/util/compat.h b/src/util/compat.h index c2a4e79d..40edbffd 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -110,6 +110,20 @@ typedef union GColor8 { (GBitmapFormat1Bit) #endif +typedef TextLayout GTextAttributes; + +#ifndef graphics_text_attributes_create +#define graphics_text_attributes_create() NULL +#endif + +#ifndef graphics_text_attributes_destroy +#define graphics_text_attributes_destroy(text_attributes) +#endif + +#ifndef graphics_text_attributes_enable_screen_text_flow +#define graphics_text_attributes_enable_screen_text_flow(text_attributes, inset) +#endif + #ifndef menu_layer_set_normal_colors #define menu_layer_set_normal_colors(menu_layer, background_color, text_color) #endif From 08d8d8fcb87e4c7e156386c9c1d5bdfcc90f0a0a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 10 Oct 2015 11:49:09 -0700 Subject: [PATCH 605/791] Add text flow paging shims --- src/util/compat.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/util/compat.h b/src/util/compat.h index 40edbffd..3378320d 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -124,6 +124,16 @@ typedef TextLayout GTextAttributes; #define graphics_text_attributes_enable_screen_text_flow(text_attributes, inset) #endif +#ifndef graphics_text_attributes_enable_paging +#define graphics_text_attributes_enable_paging(text_attributes, origin_on_screen, page_frame) +#endif + +#ifndef graphics_text_layout_get_content_size_with_attributes +#define graphics_text_layout_get_content_size_with_attributes(text, font, box, overflow_mode, \ + alignment, text_attributes) \ + graphics_text_layout_get_content_size(text, font, box, overflow_mode, alignment) +#endif + #ifndef menu_layer_set_normal_colors #define menu_layer_set_normal_colors(menu_layer, background_color, text_color) #endif @@ -150,6 +160,10 @@ typedef TextLayout GTextAttributes; ((prop_anim)->values.to.grect = *(value_ptr)) #endif +#ifndef scroll_layer_set_paging +#define scroll_layer_set_paging(scroll_layer, paging_enabled) +#endif + #endif // Legacy definitions for basalt on 3.0 From 4ec42f2b022311e101a5c3646d49bc9bb3c433ba Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 10 Oct 2015 11:49:26 -0700 Subject: [PATCH 606/791] Add gpoint_neg --- src/util/graphics.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/util/graphics.h b/src/util/graphics.h index ac577e82..ca7504c3 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -15,6 +15,10 @@ #endif +static inline GPoint gpoint_neg(const GPoint a) { + return GPoint(-a.x, -a.y); +} + static inline GPoint gpoint_add(const GPoint a, const GPoint b) { return GPoint(a.x + b.x, a.y + b.y); } From 114d6f07d541dd37287e654dc56b617cde6adc6a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 10 Oct 2015 11:49:43 -0700 Subject: [PATCH 607/791] Add NOOP for use in IF_ELSE macros --- src/util/noop.h | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/util/noop.h diff --git a/src/util/noop.h b/src/util/noop.h new file mode 100644 index 00000000..a5e39b95 --- /dev/null +++ b/src/util/noop.h @@ -0,0 +1,3 @@ +#pragma once + +#define NOOP ((void) 0) From 5fb7bece2580e32fb41eeb67f4e1d758384a029e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 10 Oct 2015 11:50:08 -0700 Subject: [PATCH 608/791] Add text flow and paging to simply ui card --- src/simply/simply_ui.c | 130 +++++++++++++++++++++++-------------- src/simply/simply_window.c | 2 + 2 files changed, 83 insertions(+), 49 deletions(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index b0e3b9dc..fe386e4b 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -9,11 +9,14 @@ #include "util/color.h" #include "util/graphics.h" #include "util/math.h" +#include "util/noop.h" #include "util/string.h" #include "util/window.h" #include +#define TEXT_FLOW_INSET 8 + struct SimplyStyle { const char* title_font; const char* subtitle_font; @@ -123,12 +126,19 @@ void simply_ui_set_text_color(SimplyUi *self, SimplyUiTextfieldId textfield_id, mark_dirty(self); } +static void enable_text_flow_and_paging(SimplyUi *self, GTextAttributes *text_attributes, + const GRect *box) { + graphics_text_attributes_enable_screen_text_flow(text_attributes, TEXT_FLOW_INSET); + graphics_text_attributes_enable_paging( + text_attributes, box->origin, layer_get_bounds((Layer *)self->window.scroll_layer)); +} + static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyUi *self = *(void **)layer_get_data(layer); - GTextAttributes *text_attributes = graphics_text_attributes_create(); - const uint8_t inset = 8; - graphics_text_attributes_enable_screen_text_flow(text_attributes, inset); + const GTextAlignment text_align = + PBL_IF_ROUND_ELSE((self->window.is_action_bar ? GTextAlignmentRight : GTextAlignmentCenter), + GTextAlignmentLeft); GRect window_frame = layer_get_frame(window_get_root_layer(self->window.window)); GRect frame = layer_get_frame(layer); @@ -149,7 +159,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { GPoint cursor = { margin_x, margin_y }; if (self->window.is_action_bar) { - text_frame.size.w -= ACTION_BAR_WIDTH; + text_frame.size.w -= ACTION_BAR_WIDTH + PBL_IF_ROUND_ELSE(TEXT_FLOW_INSET, 0); window_frame.size.w -= ACTION_BAR_WIDTH; } @@ -159,6 +169,10 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { const SimplyUiTextfield *subtitle = &self->ui_layer.textfields[UiSubtitle]; const SimplyUiTextfield *body = &self->ui_layer.textfields[UiBody]; + GTextAttributes *title_attributes = graphics_text_attributes_create(); + GTextAttributes *subtitle_attributes = graphics_text_attributes_create(); + GTextAttributes *body_attributes = graphics_text_attributes_create(); + bool has_title = is_string(title->text); bool has_subtitle = is_string(subtitle->text); bool has_body = is_string(body->text); @@ -174,62 +188,73 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyImage *body_image = simply_res_get_image( self->window.simply->res, self->ui_layer.imagefields[UiBodyImage]); - GRect title_icon_bounds; - GRect subtitle_icon_bounds; + GRect title_icon_bounds = + title_icon ? gbitmap_get_bounds(title_icon->bitmap) : GRectZero; + GRect subtitle_icon_bounds = + subtitle_icon ? gbitmap_get_bounds(subtitle_icon->bitmap) : GRectZero; GRect body_image_bounds; - if (title_icon) { - title_icon_bounds = gbitmap_get_bounds(title_icon->bitmap); - } - if (subtitle_icon) { - subtitle_icon_bounds = gbitmap_get_bounds(subtitle_icon->bitmap); - } - if (has_title) { - GRect title_frame = text_frame; + GRect title_frame = { cursor, text_frame.size }; if (title_icon) { - title_frame.origin.x += title_icon_bounds.size.w; - title_frame.size.w -= title_icon_bounds.size.w; + title_icon_bounds.origin = title_frame.origin; + title_icon_bounds.origin.y += image_offset_y; + PBL_IF_RECT_ELSE({ + title_frame.origin.x += title_icon_bounds.size.w; + title_frame.size.w -= title_icon_bounds.size.w; + }, { + title_frame.origin.y += title_icon_bounds.size.h; + }); } - title_size = graphics_text_layout_get_content_size(title->text, - title_font, title_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); + enable_text_flow_and_paging(self, title_attributes, &title_frame); + title_size = graphics_text_layout_get_content_size_with_attributes( + title->text, title_font, title_frame, GTextOverflowModeWordWrap, text_align, + title_attributes); title_size.w = title_frame.size.w; - title_pos = cursor; - if (title_icon) { - title_pos.x += title_icon_bounds.size.w; - } - cursor.y += title_size.h; + title_pos = title_frame.origin; + cursor.y = title_frame.origin.y + title_size.h; } if (has_subtitle) { - GRect subtitle_frame = text_frame; + GRect subtitle_frame = { cursor, text_frame.size }; if (subtitle_icon) { - subtitle_frame.origin.x += subtitle_icon_bounds.size.w; - subtitle_frame.size.w -= subtitle_icon_bounds.size.w; + subtitle_icon_bounds.origin = subtitle_frame.origin; + subtitle_icon_bounds.origin.y += image_offset_y; + PBL_IF_RECT_ELSE({ + subtitle_frame.origin.x += subtitle_icon_bounds.size.w; + subtitle_frame.size.w -= subtitle_icon_bounds.size.w; + }, { + subtitle_frame.origin.y += subtitle_icon_bounds.size.h; + }); } - subtitle_size = graphics_text_layout_get_content_size(subtitle->text, - title_font, subtitle_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); + enable_text_flow_and_paging(self, title_attributes, &subtitle_frame); + subtitle_size = graphics_text_layout_get_content_size_with_attributes( + subtitle->text, title_font, subtitle_frame, GTextOverflowModeWordWrap, text_align, + subtitle_attributes); subtitle_size.w = subtitle_frame.size.w; - subtitle_pos = cursor; + subtitle_pos = subtitle_frame.origin; if (subtitle_icon) { subtitle_pos.x += subtitle_icon_bounds.size.w; } - cursor.y += subtitle_size.h; + cursor.y = subtitle_frame.origin.y + subtitle_size.h; } if (body_image) { body_image_bounds = gbitmap_get_bounds(body_image->bitmap); - image_pos = cursor; - cursor.y += body_image_bounds.size.h; } if (has_body) { - body_rect = frame; + body_rect = (GRect) { cursor, (self->window.is_scrollable ? text_frame.size : frame.size) }; body_rect.origin = cursor; body_rect.size.w = text_frame.size.w; body_rect.size.h -= 2 * margin_y + cursor.y; - GSize body_size = graphics_text_layout_get_content_size(body->text, - body_font, text_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); + if (body_image) { + image_pos = body_rect.origin; + body_rect.origin.y += body_image_bounds.size.h; + } + enable_text_flow_and_paging(self, body_attributes, &body_rect); + GSize body_size = graphics_text_layout_get_content_size_with_attributes( + body->text, body_font, body_rect, GTextOverflowModeWordWrap, text_align, body_attributes); if (self->window.is_scrollable) { body_rect.size = body_size; int16_t new_height = cursor.y + 2 * margin_y + body_size.h; @@ -239,45 +264,50 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } else if (!self->ui_layer.custom_body_font && body_size.h > body_rect.size.h) { body_font = fonts_get_system_font(FONT_KEY_GOTHIC_18); } + cursor.y = body_rect.origin.y + body_size.h; } graphics_context_set_fill_color(ctx, GColorBlack); graphics_fill_rect(ctx, frame, 0, GCornerNone); + const int radius = SDK_SELECT(0, 4); graphics_context_set_fill_color(ctx, gcolor8_get_or(self->window.background_color, GColorWhite)); - graphics_fill_rect(ctx, frame, 4, GCornersAll); + graphics_fill_rect(ctx, frame, radius, GCornersAll); if (title_icon) { - GRect icon_frame = (GRect) { - .origin = { margin_x, title_pos.y + image_offset_y }, - .size = { title_icon_bounds.size.w, title_size.h } - }; + GRect icon_frame = title_icon_bounds; + icon_frame.origin.x = + PBL_IF_ROUND_ELSE((frame.size.w - title_icon_bounds.size.w) / 2, margin_x); + PBL_IF_RECT_ELSE(icon_frame.size.h = title_size.h, NOOP); graphics_context_set_alpha_blended(ctx, true); graphics_draw_bitmap_centered(ctx, title_icon->bitmap, icon_frame); } if (has_title) { graphics_context_set_text_color(ctx, gcolor8_get_or(title->color, GColorBlack)); graphics_draw_text(ctx, title->text, title_font, (GRect) { title_pos, title_size }, - GTextOverflowModeWordWrap, GTextAlignmentLeft, text_attributes); + GTextOverflowModeWordWrap, text_align, title_attributes); } if (subtitle_icon) { - GRect subicon_frame = (GRect) { - .origin = { margin_x, subtitle_pos.y + image_offset_y }, - .size = { subtitle_icon_bounds.size.w, subtitle_size.h } - }; + GRect subicon_frame = subtitle_icon_bounds; + subicon_frame.origin.x = + PBL_IF_ROUND_ELSE((frame.size.w - subtitle_icon_bounds.size.w) / 2, margin_x); + PBL_IF_RECT_ELSE(subicon_frame.size.h = subtitle_size.h, NOOP); graphics_context_set_alpha_blended(ctx, true); graphics_draw_bitmap_centered(ctx, subtitle_icon->bitmap, subicon_frame); } if (has_subtitle) { graphics_context_set_text_color(ctx, gcolor8_get_or(subtitle->color, GColorBlack)); graphics_draw_text(ctx, subtitle->text, subtitle_font, (GRect) { subtitle_pos, subtitle_size }, - GTextOverflowModeWordWrap, GTextAlignmentLeft, text_attributes); + GTextOverflowModeWordWrap, text_align, subtitle_attributes); } if (body_image) { GRect image_frame = (GRect) { - .origin = { 0, image_pos.y + image_offset_y }, + .origin = { + PBL_IF_ROUND_ELSE(((frame.size.w - body_rect.size.w) / 2), 0), + image_pos.y + image_offset_y, + }, .size = { window_frame.size.w, body_image_bounds.size.h } }; graphics_context_set_alpha_blended(ctx, true); @@ -286,10 +316,12 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { if (has_body) { graphics_context_set_text_color(ctx, gcolor8_get_or(body->color, GColorBlack)); graphics_draw_text(ctx, body->text, body_font, body_rect, - GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, text_attributes); + GTextOverflowModeTrailingEllipsis, text_align, body_attributes); } - graphics_text_attributes_destroy(text_attributes); + graphics_text_attributes_destroy(title_attributes); + graphics_text_attributes_destroy(subtitle_attributes); + graphics_text_attributes_destroy(body_attributes); } static void show_welcome_text(SimplyUi *self) { diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 9ba6e3ac..4339cf7c 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -144,6 +144,7 @@ void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color) { self->background_color = background_color; + window_set_background_color(self->window, gcolor8_get_or(background_color, GColorBlack)); } void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { @@ -279,6 +280,7 @@ void simply_window_load(SimplyWindow *self) { scroll_layer_set_context(scroll_layer, self); scroll_layer_set_shadow_hidden(scroll_layer, true); + scroll_layer_set_paging(scroll_layer, PBL_IF_ROUND_ELSE(true, false)); // TODO: Expose this to JS window_set_click_config_provider_with_context(window, click_config_provider, self); simply_window_set_action_bar(self, self->is_action_bar); From 7ad9a298184f448f6fb8c039dcc206bf447159ee Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 13 Oct 2015 21:22:45 -0700 Subject: [PATCH 609/791] Add new time styles --- src/js/ui/simply-pebble.js | 3 ++ src/simply/simply_ui.c | 76 +++++++++++++++++++++++++++----------- src/util/sdk.h | 2 +- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 646183da..29de0a82 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -409,6 +409,9 @@ var CardStyleTypes = [ 'small', 'large', 'mono', + 'time-small', + 'time-large', + 'time-mono', ]; var CardStyleType = makeArrayType(CardStyleTypes); diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index fe386e4b..337e2a8f 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -6,6 +6,7 @@ #include "simply.h" +#include "util/compat.h" #include "util/color.h" #include "util/graphics.h" #include "util/math.h" @@ -17,35 +18,63 @@ #define TEXT_FLOW_INSET 8 -struct SimplyStyle { - const char* title_font; - const char* subtitle_font; - const char* body_font; +struct __attribute__((__packed__)) SimplyStyle { + const char *title_font; + const char *subtitle_font; + const char *body_font; int custom_body_font_id; + int8_t title_icon_padding; + int8_t title_padding; + int8_t subtitle_padding; }; enum ClearIndex { - ClearAction = 0, - ClearText, - ClearImage, + ClearIndex_Action = 0, + ClearIndex_Text, + ClearIndex_Image, }; -static SimplyStyle STYLES[] = { - { - .title_font = FONT_KEY_GOTHIC_24_BOLD, +enum StyleIndex { + StyleIndex_Small = 0, + StyleIndex_Large, + StyleIndex_Mono, + StyleIndex_TimeSmall, + StyleIndex_TimeLarge, + StyleIndexCount, +}; + +static const SimplyStyle STYLES[StyleIndexCount] = { + [StyleIndex_Small] = { + .title_font = FONT_KEY_GOTHIC_18_BOLD, .subtitle_font = FONT_KEY_GOTHIC_18_BOLD, .body_font = FONT_KEY_GOTHIC_18, }, - { + [StyleIndex_Large] = { .title_font = FONT_KEY_GOTHIC_28_BOLD, .subtitle_font = FONT_KEY_GOTHIC_28, .body_font = FONT_KEY_GOTHIC_24_BOLD, }, - { + [StyleIndex_Mono] = { .title_font = FONT_KEY_GOTHIC_24_BOLD, .subtitle_font = FONT_KEY_GOTHIC_18_BOLD, .custom_body_font_id = RESOURCE_ID_MONO_FONT_14, }, + [StyleIndex_TimeSmall] = { + .title_icon_padding = 4, + .title_font = FONT_KEY_GOTHIC_18_BOLD, + .title_padding = 2, + .subtitle_font = FONT_KEY_GOTHIC_18_BOLD, + .subtitle_padding = 3, + .body_font = FONT_KEY_GOTHIC_18, + }, + [StyleIndex_TimeLarge] = { + .title_icon_padding = 4, + .title_font = FONT_KEY_GOTHIC_18_BOLD, + .title_padding = 3, + .subtitle_font = FONT_KEY_GOTHIC_24_BOLD, + .subtitle_padding = 4, + .body_font = FONT_KEY_GOTHIC_24_BOLD, + }, }; typedef struct CardClearPacket CardClearPacket; @@ -86,16 +115,16 @@ static void mark_dirty(SimplyUi *self) { } void simply_ui_clear(SimplyUi *self, uint32_t clear_mask) { - if (clear_mask & (1 << ClearAction)) { + if (clear_mask & (1 << ClearIndex_Action)) { simply_window_action_bar_clear(&self->window); } - if (clear_mask & (1 << ClearText)) { + if (clear_mask & (1 << ClearIndex_Text)) { for (int textfield_id = 0; textfield_id < NumUiTextfields; ++textfield_id) { simply_ui_set_text(self, textfield_id, NULL); simply_ui_set_text_color(self, textfield_id, GColor8Black); } } - if (clear_mask & (1 << ClearImage)) { + if (clear_mask & (1 << ClearIndex_Image)) { memset(self->ui_layer.imagefields, 0, sizeof(self->ui_layer.imagefields)); } } @@ -203,16 +232,17 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { title_frame.origin.x += title_icon_bounds.size.w; title_frame.size.w -= title_icon_bounds.size.w; }, { - title_frame.origin.y += title_icon_bounds.size.h; + title_frame.origin.y += title_icon_bounds.size.h + style->title_icon_padding; }); } - enable_text_flow_and_paging(self, title_attributes, &title_frame); + PBL_IF_ROUND_ELSE( + enable_text_flow_and_paging(self, title_attributes, &title_frame), NOOP); title_size = graphics_text_layout_get_content_size_with_attributes( title->text, title_font, title_frame, GTextOverflowModeWordWrap, text_align, title_attributes); title_size.w = title_frame.size.w; title_pos = title_frame.origin; - cursor.y = title_frame.origin.y + title_size.h; + cursor.y = title_frame.origin.y + title_size.h + style->title_padding; } if (has_subtitle) { @@ -227,16 +257,17 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { subtitle_frame.origin.y += subtitle_icon_bounds.size.h; }); } - enable_text_flow_and_paging(self, title_attributes, &subtitle_frame); + PBL_IF_ROUND_ELSE( + enable_text_flow_and_paging(self, subtitle_attributes, &subtitle_frame), NOOP); subtitle_size = graphics_text_layout_get_content_size_with_attributes( - subtitle->text, title_font, subtitle_frame, GTextOverflowModeWordWrap, text_align, + subtitle->text, subtitle_font, subtitle_frame, GTextOverflowModeWordWrap, text_align, subtitle_attributes); subtitle_size.w = subtitle_frame.size.w; subtitle_pos = subtitle_frame.origin; if (subtitle_icon) { subtitle_pos.x += subtitle_icon_bounds.size.w; } - cursor.y = subtitle_frame.origin.y + subtitle_size.h; + cursor.y = subtitle_frame.origin.y + subtitle_size.h + style->subtitle_padding; } if (body_image) { @@ -252,7 +283,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { image_pos = body_rect.origin; body_rect.origin.y += body_image_bounds.size.h; } - enable_text_flow_and_paging(self, body_attributes, &body_rect); + PBL_IF_ROUND_ELSE( + enable_text_flow_and_paging(self, body_attributes, &body_rect), NOOP); GSize body_size = graphics_text_layout_get_content_size_with_attributes( body->text, body_font, body_rect, GTextOverflowModeWordWrap, text_align, body_attributes); if (self->window.is_scrollable) { diff --git a/src/util/sdk.h b/src/util/sdk.h index 20f7a19a..76e7b8cf 100644 --- a/src/util/sdk.h +++ b/src/util/sdk.h @@ -4,6 +4,6 @@ #ifdef PBL_SDK_3 #define SDK_SELECT(sdk3, sdk2) sdk3 -#elif PBL_SDK_2 +#else #define SDK_SELECT(sdk3, sdk2) sdk2 #endif From 77cc349a5fb23d1ef40b1b0759476aaadfe8739c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 13 Oct 2015 21:24:18 -0700 Subject: [PATCH 610/791] Change time styles to be the default, and rename old styles to classic --- src/js/ui/simply-pebble.js | 7 +++---- src/simply/simply_ui.c | 16 ++++++++-------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 29de0a82..8a5f5a7f 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -406,12 +406,11 @@ var CardImageTypes = [ var CardImageType = makeArrayType(CardImageTypes); var CardStyleTypes = [ + 'classic-small', + 'classic-large', + 'mono', 'small', 'large', - 'mono', - 'time-small', - 'time-large', - 'time-mono', ]; var CardStyleType = makeArrayType(CardStyleTypes); diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 337e2a8f..35e79210 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -35,21 +35,21 @@ enum ClearIndex { }; enum StyleIndex { - StyleIndex_Small = 0, - StyleIndex_Large, + StyleIndex_ClassicSmall = 0, + StyleIndex_ClassicLarge, StyleIndex_Mono, - StyleIndex_TimeSmall, - StyleIndex_TimeLarge, + StyleIndex_Small, + StyleIndex_Large, StyleIndexCount, }; static const SimplyStyle STYLES[StyleIndexCount] = { - [StyleIndex_Small] = { + [StyleIndex_ClassicSmall] = { .title_font = FONT_KEY_GOTHIC_18_BOLD, .subtitle_font = FONT_KEY_GOTHIC_18_BOLD, .body_font = FONT_KEY_GOTHIC_18, }, - [StyleIndex_Large] = { + [StyleIndex_ClassicLarge] = { .title_font = FONT_KEY_GOTHIC_28_BOLD, .subtitle_font = FONT_KEY_GOTHIC_28, .body_font = FONT_KEY_GOTHIC_24_BOLD, @@ -59,7 +59,7 @@ static const SimplyStyle STYLES[StyleIndexCount] = { .subtitle_font = FONT_KEY_GOTHIC_18_BOLD, .custom_body_font_id = RESOURCE_ID_MONO_FONT_14, }, - [StyleIndex_TimeSmall] = { + [StyleIndex_Small] = { .title_icon_padding = 4, .title_font = FONT_KEY_GOTHIC_18_BOLD, .title_padding = 2, @@ -67,7 +67,7 @@ static const SimplyStyle STYLES[StyleIndexCount] = { .subtitle_padding = 3, .body_font = FONT_KEY_GOTHIC_18, }, - [StyleIndex_TimeLarge] = { + [StyleIndex_Large] = { .title_icon_padding = 4, .title_font = FONT_KEY_GOTHIC_18_BOLD, .title_padding = 3, From 5e8e82b221e5528aecbd614a26b62259f4254ab1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 Oct 2015 00:21:07 -0700 Subject: [PATCH 611/791] Add SDK IF_ELSE and USAGE macros --- src/util/sdk.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/util/sdk.h b/src/util/sdk.h index 20f7a19a..1fb1e855 100644 --- a/src/util/sdk.h +++ b/src/util/sdk.h @@ -4,6 +4,14 @@ #ifdef PBL_SDK_3 #define SDK_SELECT(sdk3, sdk2) sdk3 +#define IF_SDK_3_ELSE(sdk3, other) sdk3 +#define IF_SDK_2_ELSE(sdk2, other) other +#define SDK_3_USAGE +#define SDK_2_USAGE __attribute__((unused)) #elif PBL_SDK_2 #define SDK_SELECT(sdk3, sdk2) sdk2 +#define IF_SDK_3_ELSE(sdk3, other) other +#define IF_SDK_2_ELSE(sdk2, other) sdk2 +#define SDK_3_USAGE __attribute__((unused)) +#define SDK_2_USAGE #endif From 05383333210119417a5bc9c5e8082b08cbfce2bb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 Oct 2015 00:23:30 -0700 Subject: [PATCH 612/791] Remove SDK_SELECT in favor of SDK IF_ELSE macros --- src/simply/simply_res.c | 4 ++-- src/simply/simply_window_stack.c | 14 +++++++------- src/simply/simply_window_stack.h | 2 +- src/util/sdk.h | 2 -- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index d61bc16f..22ec4300 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -137,8 +137,8 @@ SimplyImage *simply_res_add_image(SimplyRes *self, uint32_t id, int16_t width, i .data_length = pixels_length, .data = pixels, }; - image = SDK_SELECT(create_image(self, create_bitmap_with_png_data, &context), - create_image(self, create_bitmap_with_data, &context)); + image = IF_SDK_3_ELSE(create_image(self, create_bitmap_with_png_data, &context), + create_image(self, create_bitmap_with_data, &context)); if (image) { image->id = id; add_image(self, image); diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index 41e9908a..f956bb99 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -115,7 +115,7 @@ static void show_window_sdk_2(SimplyWindowStack *self, SimplyWindow *window, boo #endif void simply_window_stack_show(SimplyWindowStack *self, SimplyWindow *window, bool is_push) { - SDK_SELECT(show_window_sdk_3, show_window_sdk_2)(self, window, is_push); + IF_SDK_3_ELSE(show_window_sdk_3, show_window_sdk_2)(self, window, is_push); } void simply_window_stack_pop(SimplyWindowStack *self, SimplyWindow *window) { @@ -142,11 +142,11 @@ void simply_window_stack_send_show(SimplyWindowStack *self, SimplyWindow *window void simply_window_stack_send_hide(SimplyWindowStack *self, SimplyWindow *window) { if (window->id && !self->is_showing) { send_window_hide(self->simply->msg, window->id); - SDK_SELECT(NONE, { + IF_SDK_2_ELSE({ if (!self->is_hiding) { window_stack_push(self->pusher, false); } - }); + }, NULL); } } @@ -183,9 +183,9 @@ SimplyWindowStack *simply_window_stack_create(Simply *simply) { SimplyWindowStack *self = malloc(sizeof(*self)); *self = (SimplyWindowStack) { .simply = simply }; - SDK_SELECT(NONE, { + IF_SDK_2_ELSE({ self->pusher = window_create(); - }); + }, NULL); return self; } @@ -195,10 +195,10 @@ void simply_window_stack_destroy(SimplyWindowStack *self) { return; } - SDK_SELECT(NONE, { + IF_SDK_2_ELSE({ window_destroy(self->pusher); self->pusher = NULL; - }); + }, NULL); free(self); } diff --git a/src/simply/simply_window_stack.h b/src/simply/simply_window_stack.h index cbcc2813..7f18d5c2 100644 --- a/src/simply/simply_window_stack.h +++ b/src/simply/simply_window_stack.h @@ -14,7 +14,7 @@ typedef struct SimplyWindowStack SimplyWindowStack; struct SimplyWindowStack { Simply *simply; - SDK_SELECT(NONE, Window *pusher); + IF_SDK_2_ELSE(Window *pusher, NONE); bool is_showing:1; bool is_hiding:1; }; diff --git a/src/util/sdk.h b/src/util/sdk.h index 1fb1e855..5314cc30 100644 --- a/src/util/sdk.h +++ b/src/util/sdk.h @@ -3,13 +3,11 @@ #include "util/none.h" #ifdef PBL_SDK_3 -#define SDK_SELECT(sdk3, sdk2) sdk3 #define IF_SDK_3_ELSE(sdk3, other) sdk3 #define IF_SDK_2_ELSE(sdk2, other) other #define SDK_3_USAGE #define SDK_2_USAGE __attribute__((unused)) #elif PBL_SDK_2 -#define SDK_SELECT(sdk3, sdk2) sdk2 #define IF_SDK_3_ELSE(sdk3, other) other #define IF_SDK_2_ELSE(sdk2, other) sdk2 #define SDK_3_USAGE __attribute__((unused)) From 511335d168d72231845cceaba741cf3e88c5929c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 Oct 2015 00:23:56 -0700 Subject: [PATCH 613/791] Mark simply res create_bitmap_with_data with SDK_2_USAGE --- src/simply/simply_res.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 22ec4300..b7742c0a 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -110,7 +110,7 @@ typedef struct { const uint8_t *data; } CreateDataContext; -static GBitmap *create_bitmap_with_data(SimplyImage *image, void *data) { +static GBitmap *SDK_2_USAGE create_bitmap_with_data(SimplyImage *image, void *data) { CreateDataContext *ctx = data; GBitmap *bitmap = gbitmap_create_blank(ctx->size, GBitmapFormat1Bit); if (bitmap) { From 9265ef7ca05e674bc61fa4eaa0723f7f16b54399 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 Oct 2015 00:24:28 -0700 Subject: [PATCH 614/791] Fix basalt window handling When unloading a window, the click config provider is not unset. If elements that the click config provider depends on are destroyed, the click config provider must be unset. --- src/simply/simply_window.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 9ba6e3ac..227b414f 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -310,6 +310,9 @@ bool simply_window_disappear(SimplyWindow *self) { } void simply_window_unload(SimplyWindow *self) { + // Unregister the click config provider + window_set_click_config_provider_with_context(self->window, NULL, NULL); + scroll_layer_destroy(self->scroll_layer); self->scroll_layer = NULL; } From b6e56626279957f5b6cf0f4545fe637992b800a7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 21 Oct 2015 00:26:20 -0700 Subject: [PATCH 615/791] Re-enable animations on basalt between different window types --- src/simply/simply_window.h | 1 + src/simply/simply_window_stack.c | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index 0ad1d1c2..f36c4a9c 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -5,6 +5,7 @@ #include "simply.h" #include "util/color.h" +#include "util/sdk.h" #include "util/status_bar_layer.h" #include diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index f956bb99..06006b8f 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -84,12 +84,28 @@ SimplyWindow *simply_window_stack_get_top_window(Simply *simply) { #ifdef PBL_SDK_3 static void show_window_sdk_3(SimplyWindowStack *self, SimplyWindow *window, bool is_push) { - self->is_showing = true; - window_stack_pop_all(false); - self->is_showing = false; + const bool animated = (self->simply->splash == NULL); + + if (!animated) { + self->is_showing = true; + window_stack_pop_all(false); + self->is_showing = false; + } + + Window *prev_window = window_stack_get_top_window(); simply_window_preload(window); - window_stack_push(window->window, false); + + if (window->window == prev_window) { + // It's the same window, we can't animate for now + return; + } + + window_stack_push(window->window, animated); + + if (animated) { + window_stack_remove(prev_window, animated); + } } #endif From 9046b213d3fed87c0b4606b33e2b849c7b1f7df1 Mon Sep 17 00:00:00 2001 From: Jack Dalton Date: Tue, 3 Nov 2015 16:59:20 -0700 Subject: [PATCH 616/791] Update link to avoid redirect --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b58bad09..87cc1380 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Pebble.js applications run on your phone. They have access to all the resources The main entry point for your application is in the `src/js/app.js` file. - [Install the Pebble SDK on your computer >](http://developer.getpebble.com/2/getting-started/) + [Install the Pebble SDK on your computer >](http://developer.getpebble.com/sdk/install/) Pebble.js applications follow modern JavaScript best practices. To get started, you just need to call `require('ui')` to load the UI module and start building user interfaces. From f1bf36b76eed6796357131d4dcb009dea0ecc872 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 10 Nov 2015 01:11:51 -0800 Subject: [PATCH 617/791] Fix simply ui subtitle to use the subtitle font for height calculation --- src/simply/simply_ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 964152b5..c7c199ec 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -204,7 +204,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { subtitle_frame.size.w -= subtitle_icon_bounds.size.w; } subtitle_size = graphics_text_layout_get_content_size(subtitle->text, - title_font, subtitle_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); + subtitle_font, subtitle_frame, GTextOverflowModeWordWrap, GTextAlignmentLeft); subtitle_size.w = subtitle_frame.size.w; subtitle_pos = cursor; if (subtitle_icon) { From 3a9e12fcec719b80737e731df41ef359aa30fb9b Mon Sep 17 00:00:00 2001 From: Jack Dalton Date: Tue, 10 Nov 2015 06:09:31 -0700 Subject: [PATCH 618/791] Replace other occurences of getpebble.com All other occurences of `getpebble.com` have been replaced with `pebble.com`, except for `devsupport@getpebble.com`, as I'm not sure if `devsupport@pebble.com` has been created. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 87cc1380..8150a7d8 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,11 @@ Pebble.js applications run on your phone. They have access to all the resources * With the Pebble SDK - This option allows you to customize Pebble.js. Follow the [Pebble SDK installation instructions](http://developer.getpebble.com/2/getting-started/) to install the SDK on your computer and [fork this project](http://github.com/pebble/pebblejs) on Github. + This option allows you to customize Pebble.js. Follow the [Pebble SDK installation instructions](https://developer.pebble.com/sdk/install/) to install the SDK on your computer and [fork this project](http://github.com/pebble/pebblejs) on Github. The main entry point for your application is in the `src/js/app.js` file. - [Install the Pebble SDK on your computer >](http://developer.getpebble.com/sdk/install/) + [Install the Pebble SDK on your computer >](http://developer.pebble.com/sdk/install/) Pebble.js applications follow modern JavaScript best practices. To get started, you just need to call `require('ui')` to load the UI module and start building user interfaces. @@ -130,7 +130,7 @@ wind.show(); ## Using Fonts -You can use any of the Pebble system fonts in your Pebble.js applications. Please refer to [this Pebble Developer's blog post](https://developer.getpebble.com/blog/2013/07/24/Using-Pebble-System-Fonts/) for a list of all the Pebble system fonts. When referring to a font, using lowercase with dashes is recommended. For example, `GOTHIC_18_BOLD` becomes `gothic-18-bold`. +You can use any of the Pebble system fonts in your Pebble.js applications. Please refer to [this Pebble Developer's blog post](https://developer.pebble.com/blog/2013/07/24/Using-Pebble-System-Fonts/) for a list of all the Pebble system fonts. When referring to a font, using lowercase with dashes is recommended. For example, `GOTHIC_18_BOLD` becomes `gothic-18-bold`. ````js var Vector2 = require('vector2'); @@ -173,7 +173,7 @@ Exporting is possible by modifying or setting `module.exports` within the requir ### Pebble -The `Pebble` object from [PebbleKit JavaScript](https://developer.getpebble.com/2/guides/javascript-guide.html) is available as a global variable. Its usage is discouraged in Pebble.js, instead you should use the objects documented below who provide a cleaner object interface to the same functionalities. +The `Pebble` object from [PebbleKit JavaScript](https://developer.pebble.com/guides/pebble-apps/pebblekit-js/) is available as a global variable. Its usage is discouraged in Pebble.js, instead you should use the objects documented below who provide a cleaner object interface to the same functionalities. ### window -- browser From c8c6bc3c1bcbbce4daff553ac5330d36083b07f5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 10 Nov 2015 15:00:36 -0800 Subject: [PATCH 619/791] Fix simply ui to reset the text style upon clearing text --- src/simply/simply_ui.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index c7c199ec..ef0fbc5d 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -27,18 +27,25 @@ enum ClearIndex { ClearImage, }; +enum SimplyStyleId { + SimplyStyleId_Small = 0, + SimplyStyleId_Large, + SimplyStyleId_Mono, + SimplyStyleId_Default = SimplyStyleId_Large, +}; + static SimplyStyle STYLES[] = { - { + [SimplyStyleId_Small] = { .title_font = FONT_KEY_GOTHIC_24_BOLD, .subtitle_font = FONT_KEY_GOTHIC_18_BOLD, .body_font = FONT_KEY_GOTHIC_18, }, - { + [SimplyStyleId_Large] = { .title_font = FONT_KEY_GOTHIC_28_BOLD, .subtitle_font = FONT_KEY_GOTHIC_28, .body_font = FONT_KEY_GOTHIC_24_BOLD, }, - { + [SimplyStyleId_Mono] = { .title_font = FONT_KEY_GOTHIC_24_BOLD, .subtitle_font = FONT_KEY_GOTHIC_18_BOLD, .custom_body_font_id = RESOURCE_ID_MONO_FONT_14, @@ -87,6 +94,7 @@ void simply_ui_clear(SimplyUi *self, uint32_t clear_mask) { simply_window_action_bar_clear(&self->window); } if (clear_mask & (1 << ClearText)) { + simply_ui_set_style(self, SimplyStyleId_Default); for (int textfield_id = 0; textfield_id < NumUiTextfields; ++textfield_id) { simply_ui_set_text(self, textfield_id, NULL); simply_ui_set_text_color(self, textfield_id, GColor8Black); From 5f1c18f2430c33f5f2d00db9c1a9ce2047104c37 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 10 Nov 2015 15:10:09 -0800 Subject: [PATCH 620/791] Fix usage of USAGE macros for silencing unused create_bitmap helpers --- src/simply/simply_res.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index b7742c0a..9a7ece91 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -110,7 +110,7 @@ typedef struct { const uint8_t *data; } CreateDataContext; -static GBitmap *SDK_2_USAGE create_bitmap_with_data(SimplyImage *image, void *data) { +SDK_2_USAGE static GBitmap *create_bitmap_with_data(SimplyImage *image, void *data) { CreateDataContext *ctx = data; GBitmap *bitmap = gbitmap_create_blank(ctx->size, GBitmapFormat1Bit); if (bitmap) { @@ -120,7 +120,7 @@ static GBitmap *SDK_2_USAGE create_bitmap_with_data(SimplyImage *image, void *da return bitmap; } -static GBitmap *create_bitmap_with_png_data(SimplyImage *image, void *data) { +SDK_3_USAGE static GBitmap *create_bitmap_with_png_data(SimplyImage *image, void *data) { CreateDataContext *ctx = data; return ctx->data ? gbitmap_create_from_png_data(ctx->data, ctx->data_length) : NULL; } From 05fde82d55801ddb6e5dbc2d4f00cd4169e19a1d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 11 Nov 2015 14:10:12 -0800 Subject: [PATCH 621/791] Add clarifications and new foreground wakeup event --- README.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b58bad09..bec4189a 100644 --- a/README.md +++ b/README.md @@ -1129,7 +1129,7 @@ Note that this means you may have to move portions of your startup logic into th ## Wakeup -The Wakeup module allows you to schedule your app to wakeup at a specified time using Pebble's wakeup functionality. Whether the user is in a watchface or in a different app, your app while launch by the specified time. This allows you to write a custom alarm app, for example. With the Wakeup module, you can save data to be read on launch and configure your app to behave differently based on launch data. The Wakeup module, like the Settings module, is backed by localStorage. +The Wakeup module allows you to schedule your app to wakeup at a specified time using Pebble's wakeup functionality. Whether the user is in a different watchface or app, your app while launch by the specified time. This allows you to write a custom alarm app, for example. If your app is already running, you may also subscribe to receive the wakeup event, which can be useful for more longer lived timers. With the Wakeup module, you can save data to be read on launch and configure your app to behave differently based on launch data. The Wakeup module, like the Settings module, is backed by localStorage. ### Wakeup @@ -1200,6 +1200,8 @@ Finally, there are multiple reasons why setting a wakeup event can fail. When a If you wish to change the behavior of your app depending on whether it was launched by a wakeup event, and further configure the behavior based on the data associated with the wakeup event, use `Wakeup.launch` on startup. `Wakeup.launch` will immediately call your launch callback asynchronously with a launch event detailing whether or not your app was launched by a wakeup event. +If you require knowing when a wakeup event occurs while your app is already running, refer to [Wakeup.on('wakeup')] to register a wakeup callback that will be called for both launch wakeup events and wakeup events while already running. + ````js // Query whether we were launched by a wakeup event Wakeup.launch(function(e) { @@ -1216,11 +1218,56 @@ The `callback` will be called with a wakeup launch event. The event has the foll | Name | Type | Description | | ---- | :----: | ------------- | | `id` | number | If woken by a wakeup event, the wakeup event id. | -| `wakeup` | boolean | `true` if the app woke up by a wakeup event, otherwise `false`. | +| `wakeup` | boolean | `true` if the launch event is a wakeup event, otherwise `false`. | +| `launch` | boolean | `true` if the launch was caused by this wakeup event, otherwise `false`. | | `data` | number | If woken by a wakeup event, the custom `data` that was associated with the wakeup event. | | `cookie` | number | If woken by a wakeup event, the custom 32-bit unsigned integer `cookie` that was associated with the wakeup event. | -Note that this means you may have to move portions of your startup logic into the `Wakeup.launch` callback or a function called by the callback. This can also add a very small delay to startup behavior because the underlying implementation must query the watch for the launch information. +**Note:** You may have to move portions of your startup logic into the `Wakeup.launch` callback or a function called by the callback. This can also add a very small delay to startup behavior because the underlying implementation must query the watch for the launch information. + + +#### Wakeup.on('wakeup', handler) +[Wakeup.on('wakeup')]: #wakeup-on-wakeup + +Registers a handler to call when a wakeup event occurs. This includes launch wakeup events and wakeup events while already running. See [Wakeup.launch] for the properties of the wakeup event object to be passed to the handler. + +````js +// Single wakeup event handler example: +Wakeup.on('wakeup', function(e) { + console.log('Wakeup event! ' + JSON.stringify(e)); +}); +```` + +If you want your wakeup handler to only receive wakeup events while already running, you can either test against the `.launch` boolean property, or use a wakeup launch handler to block the event from being sent to additional handlers. Wakeup events are sent to launch wakeup handlers first, then to general wakeup handlers next. + +````js +// Single already-running example: +Wakeup.on('wakeup', function(e) { + if (!e.launch) { + console.log('Already-running wakeup: ' + JSON.stringify(e)); + } +}); +```` + +**Note:** Returning false will also prevent further handlers of the same type from receiving the event. Within each type of handlers, they are passed in registration order. The passing process ends if any handler returns false. + +````js +// Launch + Already-running example: +// Launch wakeup handler +Wakeup.launch(function(e) { + if (e.wakeup) { + console.log('Launch wakeup: ' + JSON.stringify(e)); + } + // Do not pass the event to additional handlers + return false; +}); + +// Since the launch wakeup handler returns false, +// this becomes an already-running wakeup handler +Wakeup.on('wakeup', function(e) { + console.log('Wakeup: ' + JSON.stringify(e)); +}); +```` #### Wakeup.get(id) From b505b7cb4c8fed89e895f15fe8d3f0b95c24862c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 11 Nov 2015 15:04:33 -0800 Subject: [PATCH 622/791] Fix simply window stack to use NONE Some compilers warn on unused values by default. Use NONE instead of NULL for empty statements. --- src/simply/simply_window_stack.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index 06006b8f..ceb67297 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -6,6 +6,7 @@ #include "simply.h" #include "util/math.h" +#include "util/none.h" #include "util/sdk.h" #include @@ -162,7 +163,7 @@ void simply_window_stack_send_hide(SimplyWindowStack *self, SimplyWindow *window if (!self->is_hiding) { window_stack_push(self->pusher, false); } - }, NULL); + }, NONE); } } @@ -201,7 +202,7 @@ SimplyWindowStack *simply_window_stack_create(Simply *simply) { IF_SDK_2_ELSE({ self->pusher = window_create(); - }, NULL); + }, NONE); return self; } @@ -214,7 +215,7 @@ void simply_window_stack_destroy(SimplyWindowStack *self) { IF_SDK_2_ELSE({ window_destroy(self->pusher); self->pusher = NULL; - }, NULL); + }, NONE); free(self); } From 19d8e5df4d8dbae70be9c48a54e6630dbcc39e86 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 11 Nov 2015 15:23:39 -0800 Subject: [PATCH 623/791] Add .travis.yml Thanks @Neal! --- .travis.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..caa3236e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,25 @@ +language: python +python: + - "2.7" + +env: + - PEBBLE_SDK_VERSION=3.6 + +before_install: + - wget https://sdk.getpebble.com/download/$PEBBLE_SDK_VERSION?source=travis -O PebbleSDK-$PEBBLE_SDK_VERSION.tar.gz + - wget http://assets.getpebble.com.s3-website-us-east-1.amazonaws.com/sdk/arm-cs-tools-ubuntu-universal.tar.gz + - mkdir -p ~/pebble-dev + - tar -zxf PebbleSDK-$PEBBLE_SDK_VERSION.tar.gz -C ~/pebble-dev + - tar -zxf arm-cs-tools-ubuntu-universal.tar.gz -C ~/pebble-dev/PebbleSDK-$PEBBLE_SDK_VERSION + - touch ~/pebble-dev/ENABLE_ANALYTICS + +install: + - pushd ~/pebble-dev/PebbleSDK-$PEBBLE_SDK_VERSION + - virtualenv --no-site-packages .env + - source .env/bin/activate + - pip install -r requirements.txt + - deactivate + - popd + +script: + - ~/pebble-dev/PebbleSDK-$PEBBLE_SDK_VERSION/bin/pebble build From 3dc00ae50ad8a9e26ad7e33b5ba39631111db148 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 11 Nov 2015 15:35:48 -0800 Subject: [PATCH 624/791] Add travis ci image to README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b58bad09..349f6f50 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Pebble.js ========= +[![Build Status](https://travis-ci.org/pebble/pebblejs.svg?branch=master)](https://travis-ci.org/pebble/pebblejs) + Pebble.js lets you write beautiful Pebble applications completely in JavaScript. Pebble.js applications run on your phone. They have access to all the resources of your phone (internet connectivity, GPS, almost unlimited memory, etc). Because they are written in JavaScript they are also perfect to make HTTP requests and connect your Pebble to the internet. From f779e7b08f17b0dfca73ef29de907b6b02d28efa Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 11 Nov 2015 17:28:26 -0800 Subject: [PATCH 625/791] Fix #if to use defined to check macro existence --- src/simply/simply_splash.c | 4 ++-- src/util/sdk.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/simply/simply_splash.c b/src/simply/simply_splash.c index 2dc82ad1..2aa68756 100644 --- a/src/simply/simply_splash.c +++ b/src/simply/simply_splash.c @@ -11,7 +11,7 @@ void layer_update_callback(Layer *layer, GContext *ctx) { GRect frame = layer_get_frame(layer); -#if SPLASH_LOGO +#if defined(SPLASH_LOGO) graphics_draw_bitmap_centered(ctx, self->image, frame); #else graphics_draw_bitmap_in_rect(ctx, self->image, frame); @@ -22,7 +22,7 @@ void layer_update_callback(Layer *layer, GContext *ctx) { static void window_load(Window *window) { SimplySplash *self = window_get_user_data(window); -#if SPLASH_LOGO +#if defined(SPLASH_LOGO) self->image = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_LOGO_SPLASH); #else self->image = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_TILE_SPLASH); diff --git a/src/util/sdk.h b/src/util/sdk.h index 5314cc30..c1d94362 100644 --- a/src/util/sdk.h +++ b/src/util/sdk.h @@ -2,12 +2,12 @@ #include "util/none.h" -#ifdef PBL_SDK_3 +#if defined(PBL_SDK_3) #define IF_SDK_3_ELSE(sdk3, other) sdk3 #define IF_SDK_2_ELSE(sdk2, other) other #define SDK_3_USAGE #define SDK_2_USAGE __attribute__((unused)) -#elif PBL_SDK_2 +#elif defined(PBL_SDK_2) #define IF_SDK_3_ELSE(sdk3, other) other #define IF_SDK_2_ELSE(sdk2, other) sdk2 #define SDK_3_USAGE __attribute__((unused)) From f06b0c17141534070b0bab43954c7d73609220c8 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Thu, 12 Nov 2015 18:51:27 -0800 Subject: [PATCH 626/791] Dictation API proof of concept --- appinfo.json | 1 - src/js/app.js | 14 ++++-- src/js/lib/struct.js | 1 + src/js/ui/simply-pebble.js | 47 ++++++++++++++++++ src/js/ui/voice.js | 9 ++++ src/simply/simply.c | 3 ++ src/simply/simply.h | 1 + src/simply/simply_msg.c | 4 +- src/simply/simply_msg_commands.h | 2 + src/simply/simply_voice.c | 83 ++++++++++++++++++++++++++++++++ src/simply/simply_voice.h | 22 +++++++++ 11 files changed, 180 insertions(+), 7 deletions(-) create mode 100644 src/js/ui/voice.js create mode 100644 src/simply/simply_voice.c create mode 100644 src/simply/simply_voice.h diff --git a/appinfo.json b/appinfo.json index dcfa9dfe..bb0be4a4 100644 --- a/appinfo.json +++ b/appinfo.json @@ -38,7 +38,6 @@ ] }, "targetPlatforms": [ - "aplite", "basalt", "chalk" ], diff --git a/src/js/app.js b/src/js/app.js index 1e3e9dc3..f9b48ffb 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -6,6 +6,7 @@ var UI = require('ui'); var Vector2 = require('vector2'); +var Voice = require('ui/voice'); var main = new UI.Card({ title: 'Pebble.js', @@ -54,9 +55,12 @@ main.on('click', 'select', function(e) { }); main.on('click', 'down', function(e) { - var card = new UI.Card(); - card.title('A Card'); - card.subtitle('Is a Window'); - card.body('The simplest window type in Pebble.js.'); - card.show(); + Voice.startDictationSession(function(err, transcription) { + if (err) { + console.log('Error: ' + err); + return; + } + + main.subtitle(transcription); + }); }); diff --git a/src/js/lib/struct.js b/src/js/lib/struct.js index 3ebae271..f3e472ec 100644 --- a/src/js/lib/struct.js +++ b/src/js/lib/struct.js @@ -139,6 +139,7 @@ struct.prototype._prevField = function(field) { struct.prototype._makeAccessor = function(field) { this[field.name] = function(value) { var type = field.type; + if (field.dynamic) { var prevField = this._prevField(field); if (prevField === undefined) { diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 646183da..5df8aa4d 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -5,6 +5,7 @@ var Wakeup = require('wakeup'); var Timeline = require('timeline'); var Resource = require('ui/resource'); var Accel = require('ui/accel'); +var Voice = require('ui/voice'); var ImageService = require('ui/imageservice'); var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); @@ -764,6 +765,22 @@ var ElementAnimateDonePacket = new struct([ ['uint32', 'id'], ]); +var NumCommandsPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], +]); + +var VoiceDictationStartPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], +]); + +var VoiceDictationDataPacket = new struct([ + [Packet, 'packet'], + ['uint8', 'err'], + ['cstring', 'result'], +]); + var CommandPackets = [ Packet, SegmentPacket, @@ -815,6 +832,9 @@ var CommandPackets = [ ElementImagePacket, ElementAnimatePacket, ElementAnimateDonePacket, + NumCommandsPacket, + VoiceDictationStartPacket, + VoiceDictationDataPacket, ]; var accelAxes = [ @@ -1113,6 +1133,29 @@ SimplyPebble.accelConfig = function(def) { SimplyPebble.sendPacket(AccelConfigPacket.prop(def)); }; +SimplyPebble.voiceDictationSession = function(callback) { + if (SimplyPebble.dictationCallback) { + callback(-1, null); + return; + } + + SimplyPebble.dictationCallback = callback; + SimplyPebble.window = WindowStack.top(); + + SimplyPebble.sendPacket(VoiceDictationStartPacket); +} + +SimplyPebble.onVoiceData = function(packet) { + if (!SimplyPebble.dictationCallback) { + console.log("No callback specified for dictation session"); + } else { + SimplyPebble.dictationCallback(packet.err(), packet.result()); + SimplyPebble.dictationCallback = null; + } + + SimplyPebble.window.show(); +} + SimplyPebble.menuClear = function() { SimplyPebble.sendPacket(MenuClearPacket); }; @@ -1381,11 +1424,15 @@ SimplyPebble.onPacket = function(buffer, offset) { case ElementAnimateDonePacket: StageElement.emitAnimateDone(packet.id()); break; + case VoiceDictationDataPacket: + SimplyPebble.onVoiceData(packet); + break; } }; SimplyPebble.onAppMessage = function(e) { var data = e.payload[0]; + Packet._view = toArrayBuffer(data); var offset = 0; diff --git a/src/js/ui/voice.js b/src/js/ui/voice.js new file mode 100644 index 00000000..1773cc56 --- /dev/null +++ b/src/js/ui/voice.js @@ -0,0 +1,9 @@ +var simply = require('ui/simply'); + +var Voice = {}; + +Voice.startDictationSession = function(cb) { + simply.impl.voiceDictationSession(cb); +}; + +module.exports = Voice; \ No newline at end of file diff --git a/src/simply/simply.c b/src/simply/simply.c index 4b27f9b0..b0a21007 100644 --- a/src/simply/simply.c +++ b/src/simply/simply.c @@ -9,12 +9,14 @@ #include "simply_ui.h" #include "simply_window_stack.h" #include "simply_wakeup.h" +#include "simply_voice.h" #include Simply *simply_init(void) { Simply *simply = malloc(sizeof(*simply)); simply->accel = simply_accel_create(simply); + simply->voice = simply_voice_create(simply); simply->res = simply_res_create(simply); simply->splash = simply_splash_create(simply); simply->stage = simply_stage_create(simply); @@ -39,5 +41,6 @@ void simply_deinit(Simply *simply) { simply_stage_destroy(simply->stage); simply_res_destroy(simply->res); simply_accel_destroy(simply->accel); + simply_voice_destroy(simply->voice); free(simply); } diff --git a/src/simply/simply.h b/src/simply/simply.h index 2fb2cf19..ad47e955 100644 --- a/src/simply/simply.h +++ b/src/simply/simply.h @@ -6,6 +6,7 @@ typedef struct Simply Simply; struct Simply { struct SimplyAccel *accel; + struct SimplyVoice *voice; struct SimplyRes *res; struct SimplyMsg *msg; struct SimplyWindowStack *window_stack; diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 4adbfb6c..4f7feabc 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -1,6 +1,7 @@ #include "simply_msg.h" #include "simply_accel.h" +#include "simply_voice.h" #include "simply_res.h" #include "simply_stage.h" #include "simply_menu.h" @@ -22,7 +23,7 @@ static const size_t APP_MSG_SIZE_INBOUND = 2044; -static const size_t APP_MSG_SIZE_OUTBOUND = 512; +static const size_t APP_MSG_SIZE_OUTBOUND = 1024; typedef enum VibeType VibeType; @@ -201,6 +202,7 @@ static void handle_packet(Simply *simply, Packet *packet) { if (simply_window_handle_packet(simply, packet)) { return; } if (simply_ui_handle_packet(simply, packet)) { return; } if (simply_accel_handle_packet(simply, packet)) { return; } + if (simply_voice_handle_packet(simply, packet)) { return; } if (simply_menu_handle_packet(simply, packet)) { return; } if (simply_stage_handle_packet(simply, packet)) { return; } } diff --git a/src/simply/simply_msg_commands.h b/src/simply/simply_msg_commands.h index e5232eba..58e587ff 100644 --- a/src/simply/simply_msg_commands.h +++ b/src/simply/simply_msg_commands.h @@ -53,4 +53,6 @@ enum Command { CommandElementAnimate, CommandElementAnimateDone, NumCommands, + CommandVoiceStart, + CommandVoiceData, }; diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c new file mode 100644 index 00000000..d83b7669 --- /dev/null +++ b/src/simply/simply_voice.c @@ -0,0 +1,83 @@ +#include "simply_voice.h" + +#include "simply_msg.h" + +#include "simply.h" + +#include + + +typedef struct VoiceDataPacket VoiceDataPacket; + +struct __attribute__((__packed__)) VoiceDataPacket { + Packet packet; + uint8_t err; + char result[SIMPLY_VOICE_BUFFER_LENGTH]; +}; + +static SimplyVoice *s_voice = NULL; + +static bool send_voice_data(int err, char *transcription) { + VoiceDataPacket packet = { + .packet.type = CommandVoiceData, + .packet.length = sizeof(packet), + .err = (uint8_t) err, + }; + + snprintf(packet.result, sizeof(packet.result), "%s", transcription); + + return simply_msg_send_packet(&packet.packet); +} + +static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) { + s_voice->inProgress = false; + + // Send the result + send_voice_data(status, transcription); +} + + +static void handle_voice_start_packet(Simply *simply, Packet *data) { + if (s_voice->inProgress) { + return; + } + + s_voice->inProgress = true; + dictation_session_start(s_voice->session); +} + +bool simply_voice_handle_packet(Simply *simply, Packet *packet) { + switch (packet->type) { + case CommandVoiceStart: + handle_voice_start_packet(simply, packet); + return true; + } + + return false; +} + +SimplyVoice *simply_voice_create(Simply *simply) { + if(s_voice) { + return s_voice; + } + + SimplyVoice *self = malloc(sizeof(*self)); + *self = (SimplyVoice) { + .simply = simply, + .session = dictation_session_create(SIMPLY_VOICE_BUFFER_LENGTH, dictation_session_callback, NULL), + + .inProgress = false, + }; + + s_voice = self; + return self; +} + +void simply_voice_destroy(SimplyVoice *self) { + if (!self) { + return; + } + + free(self); + s_voice = NULL; +} diff --git a/src/simply/simply_voice.h b/src/simply/simply_voice.h new file mode 100644 index 00000000..cc6c0ec4 --- /dev/null +++ b/src/simply/simply_voice.h @@ -0,0 +1,22 @@ +#pragma once + +#include "simply_msg.h" +#include "simply.h" + +#include + +#define SIMPLY_VOICE_BUFFER_LENGTH 512 + +typedef struct SimplyVoice SimplyVoice; + +struct SimplyVoice { + Simply *simply; + DictationSession *session; + + bool inProgress; +}; + +SimplyVoice *simply_voice_create(Simply *simply); +void simply_voice_destroy(SimplyVoice *self); + +bool simply_voice_handle_packet(Simply *simply, Packet *packet); From bcb58f5a71aa82833a72695d95f8363f285d8dc3 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Thu, 12 Nov 2015 20:30:55 -0800 Subject: [PATCH 627/791] Added aplite 'support' --- appinfo.json | 1 + src/js/app.js | 2 +- src/js/ui/simply-pebble.js | 2 +- src/simply/simply_voice.c | 32 ++++++++++++++++++++------------ src/simply/simply_voice.h | 8 +++++++- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/appinfo.json b/appinfo.json index bb0be4a4..dcfa9dfe 100644 --- a/appinfo.json +++ b/appinfo.json @@ -38,6 +38,7 @@ ] }, "targetPlatforms": [ + "aplite", "basalt", "chalk" ], diff --git a/src/js/app.js b/src/js/app.js index f9b48ffb..c0072977 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -57,7 +57,7 @@ main.on('click', 'select', function(e) { main.on('click', 'down', function(e) { Voice.startDictationSession(function(err, transcription) { if (err) { - console.log('Error: ' + err); + main.subtitle("An error occured :("); return; } diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 5df8aa4d..70e32a9d 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -777,7 +777,7 @@ var VoiceDictationStartPacket = new struct([ var VoiceDictationDataPacket = new struct([ [Packet, 'packet'], - ['uint8', 'err'], + ['int8', 'err'], ['cstring', 'result'], ]); diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index d83b7669..6da5b482 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -6,22 +6,21 @@ #include - typedef struct VoiceDataPacket VoiceDataPacket; struct __attribute__((__packed__)) VoiceDataPacket { Packet packet; - uint8_t err; + int8_t status; char result[SIMPLY_VOICE_BUFFER_LENGTH]; }; static SimplyVoice *s_voice = NULL; -static bool send_voice_data(int err, char *transcription) { +static bool send_voice_data(int status, char *transcription) { VoiceDataPacket packet = { .packet.type = CommandVoiceData, .packet.length = sizeof(packet), - .err = (uint8_t) err, + .status = (uint8_t) status, }; snprintf(packet.result, sizeof(packet.result), "%s", transcription); @@ -29,21 +28,28 @@ static bool send_voice_data(int err, char *transcription) { return simply_msg_send_packet(&packet.packet); } -static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) { - s_voice->inProgress = false; +#ifndef PBL_SDK_2 + static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) { + s_voice->inProgress = false; - // Send the result - send_voice_data(status, transcription); -} - + // Send the result + send_voice_data(status, transcription); + } +#endif static void handle_voice_start_packet(Simply *simply, Packet *data) { + #ifdef PBL_SDK_2 + send_voice_data(-1, ""); + #else + if (s_voice->inProgress) { return; } s_voice->inProgress = true; dictation_session_start(s_voice->session); + + #endif } bool simply_voice_handle_packet(Simply *simply, Packet *packet) { @@ -64,11 +70,13 @@ SimplyVoice *simply_voice_create(Simply *simply) { SimplyVoice *self = malloc(sizeof(*self)); *self = (SimplyVoice) { .simply = simply, - .session = dictation_session_create(SIMPLY_VOICE_BUFFER_LENGTH, dictation_session_callback, NULL), - .inProgress = false, }; + #ifndef PBL_SDK_2 + self->session = dictation_session_create(SIMPLY_VOICE_BUFFER_LENGTH, dictation_session_callback, NULL), + #endif + s_voice = self; return self; } diff --git a/src/simply/simply_voice.h b/src/simply/simply_voice.h index cc6c0ec4..280e1323 100644 --- a/src/simply/simply_voice.h +++ b/src/simply/simply_voice.h @@ -7,12 +7,18 @@ #define SIMPLY_VOICE_BUFFER_LENGTH 512 +#ifdef PBL_SDK_2 +typedef struct DictationSession DictationSession; +typedef struct DictationSessionStatus DictationSessionStatus; +void dictation_session_start(DictationSession *session); +#endif + typedef struct SimplyVoice SimplyVoice; struct SimplyVoice { Simply *simply; DictationSession *session; - + bool inProgress; }; From 1da117cd3bc2470f298c6d705de32da44e0f522d Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 09:53:57 -0800 Subject: [PATCH 628/791] Reverted example back to standard example --- src/js/app.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index c0072977..ee445ebe 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -55,12 +55,9 @@ main.on('click', 'select', function(e) { }); main.on('click', 'down', function(e) { - Voice.startDictationSession(function(err, transcription) { - if (err) { - main.subtitle("An error occured :("); - return; - } - - main.subtitle(transcription); - }); + var card = new UI.Card(); + card.title('A Card'); + card.subtitle('Is a Window'); + card.body('The simplest window type in Pebble.js.'); + card.show(); }); From 8fe5cf909c8f9bcc7b62a5415c8b928f28acb8dd Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 11:02:50 -0800 Subject: [PATCH 629/791] Moved Examples and Acknowledgments to bottom --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b58bad09..2e54bc66 100644 --- a/README.md +++ b/README.md @@ -146,16 +146,6 @@ wind.add(textfield); wind.show(); ```` -## Examples - -Coming Soon! - -## Acknowledgements - -Pebble.js started as [Simply.JS](http://simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! - -This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). - # API Reference ## Global namespace @@ -1338,3 +1328,13 @@ For more information, see [Vector2 in the three.js reference documentation][thre [Text]: #text [TimeText]: #timetext [three.js Vector2]: http://threejs.org/docs/#Reference/Math/Vector2 + +## Examples + +Coming Soon! + +## Acknowledgements + +Pebble.js started as [Simply.JS](http://simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! + +This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). \ No newline at end of file From da59fb684313bd5c87b1cccae94ad3342afc060d Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 11:03:12 -0800 Subject: [PATCH 630/791] Removed Accel.init() as it's called internally --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 2e54bc66..43f259bd 100644 --- a/README.md +++ b/README.md @@ -397,14 +397,6 @@ You can use the accelerometer in two different ways: var Accel = require('ui/accel'); ```` -#### Accel.init() - -Before you can use the accelerometer, you must call `Accel.init()`. - -````js -Accel.init(); -```` - #### Accel.config(accelConfig) This function configures the accelerometer `data` events to your liking. The `tap` event requires no configuration for use. Configuring the accelerometer is a very error prone process, so it is recommended to not configure the accelerometer and use `data` events with the default configuration without calling `Accel.config`. From 57a6c82949ed2625d9d4ce7aa115a54cc3fa9d36 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 12:09:00 -0800 Subject: [PATCH 631/791] Docs for voice --- README.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 43f259bd..620a2ae2 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Pebble.js applications run on your phone. They have access to all the resources ## Getting Started - * In CloudPebble + * In CloudPebble The easiest way to use Pebble.js is in [CloudPebble](https://cloudpebble.net). Select the 'Pebble.js' project type when creating a new project. @@ -476,6 +476,34 @@ wind.on('accelData', function(e) { }); ```` +### Voice + +The `Voice` module allows you to interact with Pebble's dictation API on supported platforms (Basalt and Chalk). + +````js +var Voice = require('ui/voice'); +```` + +#### Voice.startDictationSession(callback) + +This function starts the dictation UI, and invokes the callback upon completion. The callback is invoked with an event with the following fields: + +* `status`: The [DictationSessionStatus](https://developer.getpebble.com/docs/c/Foundation/Dictation/#DictationSessionStatus) (or -1 if the platform is not supported). +* `transcription`: The transcribed string + +```js +Voice.startDictationSession(function(e) { + if (e.status != 0) { + // if there was an error + console.log('Error: ' + e.status); + return; + } + + // Log the result + console.log('Success: ' + e.transcription); +}); +``` + ### Window `Window` is the basic building block in your Pebble.js application. All windows share some common properties and methods. From 884f39e9c7d097f82f0c700a3444b489239f7e7c Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 12:11:17 -0800 Subject: [PATCH 632/791] Updated packet names and callback signature --- src/js/ui/simply-pebble.js | 17 +++++++++++------ src/js/ui/voice.js | 4 ++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 70e32a9d..2c61f4b1 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -772,13 +772,12 @@ var NumCommandsPacket = new struct([ var VoiceDictationStartPacket = new struct([ [Packet, 'packet'], - ['uint32', 'id'], ]); var VoiceDictationDataPacket = new struct([ [Packet, 'packet'], - ['int8', 'err'], - ['cstring', 'result'], + ['int8', 'status'], + ['cstring', 'transcription'], ]); var CommandPackets = [ @@ -1134,25 +1133,31 @@ SimplyPebble.accelConfig = function(def) { }; SimplyPebble.voiceDictationSession = function(callback) { + // If there's a transcription in progress if (SimplyPebble.dictationCallback) { - callback(-1, null); + callback( { 'status': -1, 'transcription': null } ); return; } - SimplyPebble.dictationCallback = callback; + // Grab the current window to re-show once we're done SimplyPebble.window = WindowStack.top(); + // Set the callback and send the packet + SimplyPebble.dictationCallback = callback; SimplyPebble.sendPacket(VoiceDictationStartPacket); } SimplyPebble.onVoiceData = function(packet) { if (!SimplyPebble.dictationCallback) { + // Something bad happened console.log("No callback specified for dictation session"); } else { - SimplyPebble.dictationCallback(packet.err(), packet.result()); + // invoke and clear the callback + SimplyPebble.dictationCallback( { 'status': packet.status(), 'transcription': packet.transcription() } ); SimplyPebble.dictationCallback = null; } + // show the top window to re-register handlers, etc. SimplyPebble.window.show(); } diff --git a/src/js/ui/voice.js b/src/js/ui/voice.js index 1773cc56..c8b565ec 100644 --- a/src/js/ui/voice.js +++ b/src/js/ui/voice.js @@ -2,8 +2,8 @@ var simply = require('ui/simply'); var Voice = {}; -Voice.startDictationSession = function(cb) { - simply.impl.voiceDictationSession(cb); +Voice.startDictationSession = function(e) { + simply.impl.voiceDictationSession(e); }; module.exports = Voice; \ No newline at end of file From 5dcb30dce7979eb080f207d6e5eef6909be21861 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 12:12:19 -0800 Subject: [PATCH 633/791] Starting dictation session with am app_timer - Allows simply_voice_handle_packet to return before dictation completes --- src/simply/simply_voice.c | 17 +++++++++++++---- src/simply/simply_voice.h | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index 6da5b482..cfad1fb8 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -14,7 +14,7 @@ struct __attribute__((__packed__)) VoiceDataPacket { char result[SIMPLY_VOICE_BUFFER_LENGTH]; }; -static SimplyVoice *s_voice = NULL; +static SimplyVoice *s_voice; static bool send_voice_data(int status, char *transcription) { VoiceDataPacket packet = { @@ -22,13 +22,13 @@ static bool send_voice_data(int status, char *transcription) { .packet.length = sizeof(packet), .status = (uint8_t) status, }; - snprintf(packet.result, sizeof(packet.result), "%s", transcription); return simply_msg_send_packet(&packet.packet); } #ifndef PBL_SDK_2 + // Define a callback for the dictation session static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) { s_voice->inProgress = false; @@ -37,18 +37,27 @@ static bool send_voice_data(int status, char *transcription) { } #endif +static void timer_callback_start_dictation(void *data) { + dictation_session_start(s_voice->session); +} + + static void handle_voice_start_packet(Simply *simply, Packet *data) { #ifdef PBL_SDK_2 + // send an immediate reply if we don't support voice send_voice_data(-1, ""); #else + // Send an immediate response if there's already a dictation session in progress if (s_voice->inProgress) { + send_voice_data(-1, ""); return; } + // Otherwise, start the timer as soon as possible + // (we start a timer so we can return true as quickly as possible) s_voice->inProgress = true; - dictation_session_start(s_voice->session); - + s_voice->timer = app_timer_register(0, timer_callback_start_dictation, NULL); #endif } diff --git a/src/simply/simply_voice.h b/src/simply/simply_voice.h index 280e1323..20dfcdfd 100644 --- a/src/simply/simply_voice.h +++ b/src/simply/simply_voice.h @@ -18,6 +18,7 @@ typedef struct SimplyVoice SimplyVoice; struct SimplyVoice { Simply *simply; DictationSession *session; + AppTimer *timer; bool inProgress; }; From c1a88c0dd807881fa02e925085136c0a76f622e4 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 12:29:15 -0800 Subject: [PATCH 634/791] Whitespace --- src/simply/simply_voice.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index cfad1fb8..5800e9d1 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -28,13 +28,13 @@ static bool send_voice_data(int status, char *transcription) { } #ifndef PBL_SDK_2 - // Define a callback for the dictation session - static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) { - s_voice->inProgress = false; +// Define a callback for the dictation session +static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) { + s_voice->inProgress = false; - // Send the result - send_voice_data(status, transcription); - } + // Send the result + send_voice_data(status, transcription); +} #endif static void timer_callback_start_dictation(void *data) { From ff21db352aabaac3e72dea75e49e99c14dfc15e6 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 12:45:26 -0800 Subject: [PATCH 635/791] removed stray require in app.js --- src/js/app.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/app.js b/src/js/app.js index ee445ebe..1e3e9dc3 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -6,7 +6,6 @@ var UI = require('ui'); var Vector2 = require('vector2'); -var Voice = require('ui/voice'); var main = new UI.Card({ title: 'Pebble.js', From 3fbf578111126b7fb96102169a558c8920fad645 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 15:13:39 -0800 Subject: [PATCH 636/791] Added optional parameter for startDictationSession to enable/disable confirmation screen --- src/js/ui/simply-pebble.js | 5 +++-- src/js/ui/voice.js | 7 +++++-- src/simply/simply_voice.c | 11 +++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 2c61f4b1..8b68950d 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -772,6 +772,7 @@ var NumCommandsPacket = new struct([ var VoiceDictationStartPacket = new struct([ [Packet, 'packet'], + ['bool', 'enableConfirmation'], ]); var VoiceDictationDataPacket = new struct([ @@ -1132,7 +1133,7 @@ SimplyPebble.accelConfig = function(def) { SimplyPebble.sendPacket(AccelConfigPacket.prop(def)); }; -SimplyPebble.voiceDictationSession = function(callback) { +SimplyPebble.voiceDictationSession = function(callback, enableConfirmation) { // If there's a transcription in progress if (SimplyPebble.dictationCallback) { callback( { 'status': -1, 'transcription': null } ); @@ -1144,7 +1145,7 @@ SimplyPebble.voiceDictationSession = function(callback) { // Set the callback and send the packet SimplyPebble.dictationCallback = callback; - SimplyPebble.sendPacket(VoiceDictationStartPacket); + SimplyPebble.sendPacket(VoiceDictationStartPacket.enableConfirmation(enableConfirmation)); } SimplyPebble.onVoiceData = function(packet) { diff --git a/src/js/ui/voice.js b/src/js/ui/voice.js index c8b565ec..d2fe0521 100644 --- a/src/js/ui/voice.js +++ b/src/js/ui/voice.js @@ -2,8 +2,11 @@ var simply = require('ui/simply'); var Voice = {}; -Voice.startDictationSession = function(e) { - simply.impl.voiceDictationSession(e); +Voice.startDictationSession = function(e, enableConfirmation) { + // default parameter value for enableDictation = true + enableConfirmation = typeof enableConfirmation !== 'undefined' ? enableConfirmation : true; + + simply.impl.voiceDictationSession(e, enableConfirmation); }; module.exports = Voice; \ No newline at end of file diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index 5800e9d1..42a54f9a 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -6,6 +6,14 @@ #include +typedef struct VoiceStartPacket VoiceStartPacket; + +struct __attribute__((__packed__)) VoiceStartPacket { + Packet packet; + bool enableConfirmation; +}; + + typedef struct VoiceDataPacket VoiceDataPacket; struct __attribute__((__packed__)) VoiceDataPacket { @@ -57,6 +65,9 @@ static void handle_voice_start_packet(Simply *simply, Packet *data) { // Otherwise, start the timer as soon as possible // (we start a timer so we can return true as quickly as possible) s_voice->inProgress = true; + + VoiceStartPacket *packet = (VoiceStartPacket*) data; + dictation_session_enable_confirmation(s_voice->session, packet->enableConfirmation); s_voice->timer = app_timer_register(0, timer_callback_start_dictation, NULL); #endif } From 1fc5c11e9c57e8ebf34ef0f81036e034d355078e Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 15:17:22 -0800 Subject: [PATCH 637/791] Updated docs with new param for startDictationSession --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 620a2ae2..c3411ead 100644 --- a/README.md +++ b/README.md @@ -484,14 +484,17 @@ The `Voice` module allows you to interact with Pebble's dictation API on support var Voice = require('ui/voice'); ```` -#### Voice.startDictationSession(callback) +#### Voice.startDictationSession(callback, [enableConfirmation]) This function starts the dictation UI, and invokes the callback upon completion. The callback is invoked with an event with the following fields: * `status`: The [DictationSessionStatus](https://developer.getpebble.com/docs/c/Foundation/Dictation/#DictationSessionStatus) (or -1 if the platform is not supported). * `transcription`: The transcribed string +An optional second parameter - `enableConfirmation` - can be passed to the startDictationSession method to control the behaviour of the confirmation page. If `enableConfirmation` is to `false`, the confirmation page will be skipped, otherwise it will be included in the dictation flow. + ```js +// Start a diction session and skip confirmation Voice.startDictationSession(function(e) { if (e.status != 0) { // if there was an error @@ -501,7 +504,7 @@ Voice.startDictationSession(function(e) { // Log the result console.log('Success: ' + e.transcription); -}); +}, false); ``` ### Window From e439bf749c04ba5b3fd7a7c956e6eac0f4d2c14e Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 15:27:53 -0800 Subject: [PATCH 638/791] whitespace --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3411ead..9b40f050 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Pebble.js applications run on your phone. They have access to all the resources ## Getting Started - * In CloudPebble + * In CloudPebble The easiest way to use Pebble.js is in [CloudPebble](https://cloudpebble.net). Select the 'Pebble.js' project type when creating a new project. From 947348d7c59923a4a2106f71b4f9d1641dcab5c1 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 15:29:42 -0800 Subject: [PATCH 639/791] Removed CommandNumPackets --- src/js/ui/simply-pebble.js | 6 ------ src/simply/simply_msg_commands.h | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 8b68950d..640fcce4 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -765,11 +765,6 @@ var ElementAnimateDonePacket = new struct([ ['uint32', 'id'], ]); -var NumCommandsPacket = new struct([ - [Packet, 'packet'], - ['uint32', 'id'], -]); - var VoiceDictationStartPacket = new struct([ [Packet, 'packet'], ['bool', 'enableConfirmation'], @@ -832,7 +827,6 @@ var CommandPackets = [ ElementImagePacket, ElementAnimatePacket, ElementAnimateDonePacket, - NumCommandsPacket, VoiceDictationStartPacket, VoiceDictationDataPacket, ]; diff --git a/src/simply/simply_msg_commands.h b/src/simply/simply_msg_commands.h index 58e587ff..cd4fdafc 100644 --- a/src/simply/simply_msg_commands.h +++ b/src/simply/simply_msg_commands.h @@ -52,7 +52,7 @@ enum Command { CommandElementImage, CommandElementAnimate, CommandElementAnimateDone, - NumCommands, CommandVoiceStart, CommandVoiceData, + NumCommands, }; From 0f9eeffff602df546f2319744c7ab9f20333ddcb Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 15:31:35 -0800 Subject: [PATCH 640/791] inProgress -> in_progress --- src/simply/simply_voice.c | 8 ++++---- src/simply/simply_voice.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index 42a54f9a..2d5a8248 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -38,7 +38,7 @@ static bool send_voice_data(int status, char *transcription) { #ifndef PBL_SDK_2 // Define a callback for the dictation session static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) { - s_voice->inProgress = false; + s_voice->in_progress = false; // Send the result send_voice_data(status, transcription); @@ -57,14 +57,14 @@ static void handle_voice_start_packet(Simply *simply, Packet *data) { #else // Send an immediate response if there's already a dictation session in progress - if (s_voice->inProgress) { + if (s_voice->in_progress) { send_voice_data(-1, ""); return; } // Otherwise, start the timer as soon as possible // (we start a timer so we can return true as quickly as possible) - s_voice->inProgress = true; + s_voice->in_progress = true; VoiceStartPacket *packet = (VoiceStartPacket*) data; dictation_session_enable_confirmation(s_voice->session, packet->enableConfirmation); @@ -90,7 +90,7 @@ SimplyVoice *simply_voice_create(Simply *simply) { SimplyVoice *self = malloc(sizeof(*self)); *self = (SimplyVoice) { .simply = simply, - .inProgress = false, + .in_progress = false, }; #ifndef PBL_SDK_2 diff --git a/src/simply/simply_voice.h b/src/simply/simply_voice.h index 20dfcdfd..4ec600e7 100644 --- a/src/simply/simply_voice.h +++ b/src/simply/simply_voice.h @@ -20,7 +20,7 @@ struct SimplyVoice { DictationSession *session; AppTimer *timer; - bool inProgress; + bool in_progress; }; SimplyVoice *simply_voice_create(Simply *simply); From fe1c771e103aa5e3a9fbe42cf4aaf26e1a64c9f2 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 15:33:59 -0800 Subject: [PATCH 641/791] Moved compatibility defines to util/compat.h --- src/simply/simply_voice.h | 7 +------ src/util/compat.h | 5 +++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/simply/simply_voice.h b/src/simply/simply_voice.h index 4ec600e7..b6e734f5 100644 --- a/src/simply/simply_voice.h +++ b/src/simply/simply_voice.h @@ -2,17 +2,12 @@ #include "simply_msg.h" #include "simply.h" +#include "util/compat.h" #include #define SIMPLY_VOICE_BUFFER_LENGTH 512 -#ifdef PBL_SDK_2 -typedef struct DictationSession DictationSession; -typedef struct DictationSessionStatus DictationSessionStatus; -void dictation_session_start(DictationSession *session); -#endif - typedef struct SimplyVoice SimplyVoice; struct SimplyVoice { diff --git a/src/util/compat.h b/src/util/compat.h index c2a4e79d..0abd0761 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -136,6 +136,11 @@ typedef union GColor8 { ((prop_anim)->values.to.grect = *(value_ptr)) #endif +// Voice API +typedef struct DictationSession DictationSession; +typedef struct DictationSessionStatus DictationSessionStatus; +void dictation_session_start(DictationSession *session); + #endif // Legacy definitions for basalt on 3.0 From da2afba30dbbb6b59a2f303fafd31f86da640c6b Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 15:40:25 -0800 Subject: [PATCH 642/791] whitespace + line length --- src/simply/simply_voice.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index 2d5a8248..fcad6cd4 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -37,7 +37,8 @@ static bool send_voice_data(int status, char *transcription) { #ifndef PBL_SDK_2 // Define a callback for the dictation session -static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) { +static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, + char *transcription, void *context) { s_voice->in_progress = false; // Send the result @@ -83,7 +84,7 @@ bool simply_voice_handle_packet(Simply *simply, Packet *packet) { } SimplyVoice *simply_voice_create(Simply *simply) { - if(s_voice) { + if (s_voice) { return s_voice; } @@ -94,7 +95,7 @@ SimplyVoice *simply_voice_create(Simply *simply) { }; #ifndef PBL_SDK_2 - self->session = dictation_session_create(SIMPLY_VOICE_BUFFER_LENGTH, dictation_session_callback, NULL), + self->session = dictation_session_create(SIMPLY_VOICE_BUFFER_LENGTH, dictation_session_callback, NULL); #endif s_voice = self; From fa169a099a25214e4ae458ed11214aad9b0c371f Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 15:43:07 -0800 Subject: [PATCH 643/791] more whitespace --- src/js/ui/simply-pebble.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 640fcce4..c47a8acf 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1130,7 +1130,7 @@ SimplyPebble.accelConfig = function(def) { SimplyPebble.voiceDictationSession = function(callback, enableConfirmation) { // If there's a transcription in progress if (SimplyPebble.dictationCallback) { - callback( { 'status': -1, 'transcription': null } ); + callback({ 'status': -1, 'transcription': null }); return; } @@ -1148,7 +1148,7 @@ SimplyPebble.onVoiceData = function(packet) { console.log("No callback specified for dictation session"); } else { // invoke and clear the callback - SimplyPebble.dictationCallback( { 'status': packet.status(), 'transcription': packet.transcription() } ); + SimplyPebble.dictationCallback({ 'status': packet.status(), 'transcription': packet.transcription() }); SimplyPebble.dictationCallback = null; } From b6a487a581ef626a3cbd40c9e243d15394720c02 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 15:58:46 -0800 Subject: [PATCH 644/791] Variable length result --- src/simply/simply_voice.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index fcad6cd4..3e663c71 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -19,20 +19,26 @@ typedef struct VoiceDataPacket VoiceDataPacket; struct __attribute__((__packed__)) VoiceDataPacket { Packet packet; int8_t status; - char result[SIMPLY_VOICE_BUFFER_LENGTH]; + char result[]; }; static SimplyVoice *s_voice; static bool send_voice_data(int status, char *transcription) { - VoiceDataPacket packet = { + size_t transcription_length = strlen(transcription); + size_t packet_length = sizeof(VoiceDataPacket) + transcription_length; + + uint8_t buffer[packet_length]; + VoiceDataPacket *packet = (VoiceDataPacket *)buffer; + *packet = (VoiceDataPacket) { .packet.type = CommandVoiceData, - .packet.length = sizeof(packet), + .packet.length = packet_length, .status = (uint8_t) status, }; - snprintf(packet.result, sizeof(packet.result), "%s", transcription); - return simply_msg_send_packet(&packet.packet); + strncpy(packet->result, transcription, transcription_length); + + return simply_msg_send_packet(&packet->packet); } #ifndef PBL_SDK_2 From 533eeb07cabf1cecb213d15860120c0698da8e9d Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 16:21:44 -0800 Subject: [PATCH 645/791] Storing dictation window/callback in state now --- src/js/ui/simply-pebble.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index c47a8acf..0763971f 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1135,25 +1135,25 @@ SimplyPebble.voiceDictationSession = function(callback, enableConfirmation) { } // Grab the current window to re-show once we're done - SimplyPebble.window = WindowStack.top(); + state.dictationWindow = WindowStack.top(); // Set the callback and send the packet - SimplyPebble.dictationCallback = callback; + state.dictationCallback = callback; SimplyPebble.sendPacket(VoiceDictationStartPacket.enableConfirmation(enableConfirmation)); } SimplyPebble.onVoiceData = function(packet) { - if (!SimplyPebble.dictationCallback) { + if (!state.dictationCallback) { // Something bad happened console.log("No callback specified for dictation session"); } else { // invoke and clear the callback - SimplyPebble.dictationCallback({ 'status': packet.status(), 'transcription': packet.transcription() }); - SimplyPebble.dictationCallback = null; + state.dictationCallback({ 'status': packet.status(), 'transcription': packet.transcription() }); + state.dictationCallback = null; } // show the top window to re-register handlers, etc. - SimplyPebble.window.show(); + state.dictationWindow.show(); } SimplyPebble.menuClear = function() { From 0fad8e4f02867e6943e8d49136dc66ff67908361 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 16:47:47 -0800 Subject: [PATCH 646/791] Refactored dictation API and added stop command --- src/js/ui/simply-pebble.js | 22 +++++++++++++++++++++- src/js/ui/voice.js | 21 +++++++++++++++++---- src/simply/simply_msg_commands.h | 1 + src/simply/simply_voice.c | 12 ++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 0763971f..820fd497 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -770,6 +770,10 @@ var VoiceDictationStartPacket = new struct([ ['bool', 'enableConfirmation'], ]); +var VoiceDictationStopPacket = new struct([ + [Packet, 'packet'], +]); + var VoiceDictationDataPacket = new struct([ [Packet, 'packet'], ['int8', 'status'], @@ -828,6 +832,7 @@ var CommandPackets = [ ElementAnimatePacket, ElementAnimateDonePacket, VoiceDictationStartPacket, + VoiceDictationStopPacket, VoiceDictationDataPacket, ]; @@ -1127,7 +1132,7 @@ SimplyPebble.accelConfig = function(def) { SimplyPebble.sendPacket(AccelConfigPacket.prop(def)); }; -SimplyPebble.voiceDictationSession = function(callback, enableConfirmation) { +SimplyPebble.voiceDictationStart = function(callback, enableConfirmation) { // If there's a transcription in progress if (SimplyPebble.dictationCallback) { callback({ 'status': -1, 'transcription': null }); @@ -1142,6 +1147,20 @@ SimplyPebble.voiceDictationSession = function(callback, enableConfirmation) { SimplyPebble.sendPacket(VoiceDictationStartPacket.enableConfirmation(enableConfirmation)); } +SimplyPebble.voiceDictationStop = function() { + // Send the message + SimplyPebble.sendPacket(VoiceDictationStopPacket); + + // Clear the callback variable + state.dictationCallback = null; + + // If we have a window stored, show it then clear the varaible + if (state.dictationWindow) { + state.dictationWindow.show(); + state.dictationWindow = null; + } +} + SimplyPebble.onVoiceData = function(packet) { if (!state.dictationCallback) { // Something bad happened @@ -1154,6 +1173,7 @@ SimplyPebble.onVoiceData = function(packet) { // show the top window to re-register handlers, etc. state.dictationWindow.show(); + state.dictationWindow = null; } SimplyPebble.menuClear = function() { diff --git a/src/js/ui/voice.js b/src/js/ui/voice.js index d2fe0521..5b4d887d 100644 --- a/src/js/ui/voice.js +++ b/src/js/ui/voice.js @@ -2,11 +2,24 @@ var simply = require('ui/simply'); var Voice = {}; -Voice.startDictationSession = function(e, enableConfirmation) { - // default parameter value for enableDictation = true - enableConfirmation = typeof enableConfirmation !== 'undefined' ? enableConfirmation : true; +Voice.dictate = function(type, confirm, callback) { + type = type.toLowerCase(); + switch (type){ + case 'stop': + simply.impl.voiceDictationStop(); + break; + case 'start': + if (typeof callback === 'undefined') { + callback = confirm; + confirm = true; + } - simply.impl.voiceDictationSession(e, enableConfirmation); + simply.impl.voiceDictationStart(callback, confirm); + break; + default: + console.log('Unsupported type passed to Voice.dictate'); + } + }; module.exports = Voice; \ No newline at end of file diff --git a/src/simply/simply_msg_commands.h b/src/simply/simply_msg_commands.h index cd4fdafc..886b0213 100644 --- a/src/simply/simply_msg_commands.h +++ b/src/simply/simply_msg_commands.h @@ -53,6 +53,7 @@ enum Command { CommandElementAnimate, CommandElementAnimateDone, CommandVoiceStart, + CommandVoiceStop, CommandVoiceData, NumCommands, }; diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index 3e663c71..ddf4b65a 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -79,11 +79,23 @@ static void handle_voice_start_packet(Simply *simply, Packet *data) { #endif } +static void handle_voice_stop_packet(Simply *simply, Packet *data) { + // Do nothing for SDK 2 + #ifndef PBL_SDK_2 + // Stop the session and clear the in_progress flag + dictation_session_stop(s_voice->session); + s_voice->in_progress = false; + #endif +} + bool simply_voice_handle_packet(Simply *simply, Packet *packet) { switch (packet->type) { case CommandVoiceStart: handle_voice_start_packet(simply, packet); return true; + case CommandVoiceStop: + handle_voice_stop_packet(simply, packet); + return true; } return false; From a2ece17bd0bf593f26b384d68584642e1aab13cf Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 16:53:19 -0800 Subject: [PATCH 647/791] Updated docs with new voice interface --- README.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9b40f050..3fc7f253 100644 --- a/README.md +++ b/README.md @@ -484,27 +484,35 @@ The `Voice` module allows you to interact with Pebble's dictation API on support var Voice = require('ui/voice'); ```` -#### Voice.startDictationSession(callback, [enableConfirmation]) +#### Voice.dictate('state', [confirmDialog,] callback) This function starts the dictation UI, and invokes the callback upon completion. The callback is invoked with an event with the following fields: * `status`: The [DictationSessionStatus](https://developer.getpebble.com/docs/c/Foundation/Dictation/#DictationSessionStatus) (or -1 if the platform is not supported). * `transcription`: The transcribed string -An optional second parameter - `enableConfirmation` - can be passed to the startDictationSession method to control the behaviour of the confirmation page. If `enableConfirmation` is to `false`, the confirmation page will be skipped, otherwise it will be included in the dictation flow. +An optional second parameter - `confirmDialog` - can be passed to the `Voice.dictate` method to control the behaviour of the confirmation dialog. If `confirmDialog` is set to `false`, the confirmation page will be skipped, otherwise it will be included in the dictation flow. ```js // Start a diction session and skip confirmation -Voice.startDictationSession(function(e) { - if (e.status != 0) { - // if there was an error +Voice.dictate('start', false, function(e) { + if (e.status) { console.log('Error: ' + e.status); return; } - // Log the result - console.log('Success: ' + e.transcription); -}, false); + main.subtitle('Success: ' + e.transcription); +}); +``` + +**NOTE:** Only one dictation session can be active at any time. Trying to call `Voice.dicate('start', ...)` while another dictation session is in progress will result in the callback being called immediatly with status = -1. + +#### Voice.dictate('stop') + +This function stops a dictation session that is currently in progress and prevents the session's callback from being invoked. If no session is in progress this method has no effect. + +``` +Voice.dictate('stop'); ``` ### Window From 93ce3b622a136cb522ea4ae5bfe1ee467342aa7c Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 17:16:19 -0800 Subject: [PATCH 648/791] Updated Voice API to return string errors --- src/js/ui/simply-pebble.js | 20 ++++++++++++++++++-- src/simply/simply_voice.c | 6 ++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 820fd497..81643bfa 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -430,6 +430,21 @@ var LightTypes = [ var LightType = makeArrayType(LightTypes); +var DictationSessionStatus = [ + null, + 'transcriptionRejected', + 'transcriptionRejectedWithError', + 'systemAborted', + 'noSpeechDetected', + 'connectivityError', + 'disabled', + 'internalError', + 'recognizerError', +]; +// Custom Dictation Errors: +DictationSessionStatus[64] = "sessionAlreadyInProgress"; +DictationSessionStatus[65] = "noMicrophone"; + var Packet = new struct([ ['uint16', 'type'], ['uint16', 'length'], @@ -1135,7 +1150,8 @@ SimplyPebble.accelConfig = function(def) { SimplyPebble.voiceDictationStart = function(callback, enableConfirmation) { // If there's a transcription in progress if (SimplyPebble.dictationCallback) { - callback({ 'status': -1, 'transcription': null }); + // DictationSessionStatus[64] = dictationAlreadyInProgress + callback({ 'err': DictationSessionStatus[64], 'transcription': null }); return; } @@ -1167,7 +1183,7 @@ SimplyPebble.onVoiceData = function(packet) { console.log("No callback specified for dictation session"); } else { // invoke and clear the callback - state.dictationCallback({ 'status': packet.status(), 'transcription': packet.transcription() }); + state.dictationCallback({ 'err': DictationSessionStatus[packet.status()], 'transcription': packet.transcription() }); state.dictationCallback = null; } diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index ddf4b65a..356905dc 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -60,12 +60,14 @@ static void timer_callback_start_dictation(void *data) { static void handle_voice_start_packet(Simply *simply, Packet *data) { #ifdef PBL_SDK_2 // send an immediate reply if we don't support voice - send_voice_data(-1, ""); + // Status 65 = NoMicrophone + send_voice_data(65, ""); #else // Send an immediate response if there's already a dictation session in progress + // Status 64 = SessionAlreadyInProgress if (s_voice->in_progress) { - send_voice_data(-1, ""); + send_voice_data(64, ""); return; } From 9d191fe4f21a754aac0d7d7ff5cf531f1c26768e Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 17:17:47 -0800 Subject: [PATCH 649/791] Updated docs for new dictation callback --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3fc7f253..77247427 100644 --- a/README.md +++ b/README.md @@ -488,16 +488,16 @@ var Voice = require('ui/voice'); This function starts the dictation UI, and invokes the callback upon completion. The callback is invoked with an event with the following fields: -* `status`: The [DictationSessionStatus](https://developer.getpebble.com/docs/c/Foundation/Dictation/#DictationSessionStatus) (or -1 if the platform is not supported). -* `transcription`: The transcribed string +* `err`: A string describing the error, or `null` on success. +* `transcription`: The transcribed string. An optional second parameter - `confirmDialog` - can be passed to the `Voice.dictate` method to control the behaviour of the confirmation dialog. If `confirmDialog` is set to `false`, the confirmation page will be skipped, otherwise it will be included in the dictation flow. ```js // Start a diction session and skip confirmation Voice.dictate('start', false, function(e) { - if (e.status) { - console.log('Error: ' + e.status); + if (e.err) { + console.log('Error: ' + e.err); return; } From ffbf49ca8d5b1820929f14fc42cc18165c28cc44 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 17:21:38 -0800 Subject: [PATCH 650/791] =?UTF-8?q?=F0=9F=90=8Dcase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/simply/simply_voice.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index 356905dc..8235fbf9 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -10,7 +10,7 @@ typedef struct VoiceStartPacket VoiceStartPacket; struct __attribute__((__packed__)) VoiceStartPacket { Packet packet; - bool enableConfirmation; + bool enable_confirmation; }; @@ -76,7 +76,7 @@ static void handle_voice_start_packet(Simply *simply, Packet *data) { s_voice->in_progress = true; VoiceStartPacket *packet = (VoiceStartPacket*) data; - dictation_session_enable_confirmation(s_voice->session, packet->enableConfirmation); + dictation_session_enable_confirmation(s_voice->session, packet->enable_confirmation); s_voice->timer = app_timer_register(0, timer_callback_start_dictation, NULL); #endif } From 33faf29369c64869e61a27b5804d520ff2370353 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 18:23:40 -0800 Subject: [PATCH 651/791] Added failed key to Voice.dictate event --- src/js/ui/simply-pebble.js | 18 +++++++++++++++--- src/simply/simply_voice.c | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 81643bfa..2cb7b821 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1150,8 +1150,15 @@ SimplyPebble.accelConfig = function(def) { SimplyPebble.voiceDictationStart = function(callback, enableConfirmation) { // If there's a transcription in progress if (SimplyPebble.dictationCallback) { - // DictationSessionStatus[64] = dictationAlreadyInProgress - callback({ 'err': DictationSessionStatus[64], 'transcription': null }); + // Create the eror event + var e = { + 'err': DictationSessionStatus[64], // dictationAlreadyInProgress + 'failed': true, + 'transcription': null, + }; + + // Invoke the callback and return + callback(e); return; } @@ -1182,8 +1189,13 @@ SimplyPebble.onVoiceData = function(packet) { // Something bad happened console.log("No callback specified for dictation session"); } else { + var e = { + 'err': DictationSessionStatus[packet.status()], + 'failed': packet.status() != 0, + 'transcription': packet.transcription(), + }; // invoke and clear the callback - state.dictationCallback({ 'err': DictationSessionStatus[packet.status()], 'transcription': packet.transcription() }); + state.dictationCallback(e); state.dictationCallback = null; } diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index 8235fbf9..057eb0e8 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -25,7 +25,7 @@ struct __attribute__((__packed__)) VoiceDataPacket { static SimplyVoice *s_voice; static bool send_voice_data(int status, char *transcription) { - size_t transcription_length = strlen(transcription); + size_t transcription_length = strlen(transcription) + 1; size_t packet_length = sizeof(VoiceDataPacket) + transcription_length; uint8_t buffer[packet_length]; From 0bc9e805466ca55f9d8d3e23942a1cd4281c94bd Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 18:39:43 -0800 Subject: [PATCH 652/791] Reverted README --- README.md | 69 +++++++++++++++------------------------------------ src/js/app.js | 15 +++++++---- 2 files changed, 30 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 77247427..349f6f50 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Pebble.js ========= +[![Build Status](https://travis-ci.org/pebble/pebblejs.svg?branch=master)](https://travis-ci.org/pebble/pebblejs) + Pebble.js lets you write beautiful Pebble applications completely in JavaScript. Pebble.js applications run on your phone. They have access to all the resources of your phone (internet connectivity, GPS, almost unlimited memory, etc). Because they are written in JavaScript they are also perfect to make HTTP requests and connect your Pebble to the internet. @@ -146,6 +148,16 @@ wind.add(textfield); wind.show(); ```` +## Examples + +Coming Soon! + +## Acknowledgements + +Pebble.js started as [Simply.JS](http://simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! + +This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). + # API Reference ## Global namespace @@ -397,6 +409,14 @@ You can use the accelerometer in two different ways: var Accel = require('ui/accel'); ```` +#### Accel.init() + +Before you can use the accelerometer, you must call `Accel.init()`. + +````js +Accel.init(); +```` + #### Accel.config(accelConfig) This function configures the accelerometer `data` events to your liking. The `tap` event requires no configuration for use. Configuring the accelerometer is a very error prone process, so it is recommended to not configure the accelerometer and use `data` events with the default configuration without calling `Accel.config`. @@ -476,45 +496,6 @@ wind.on('accelData', function(e) { }); ```` -### Voice - -The `Voice` module allows you to interact with Pebble's dictation API on supported platforms (Basalt and Chalk). - -````js -var Voice = require('ui/voice'); -```` - -#### Voice.dictate('state', [confirmDialog,] callback) - -This function starts the dictation UI, and invokes the callback upon completion. The callback is invoked with an event with the following fields: - -* `err`: A string describing the error, or `null` on success. -* `transcription`: The transcribed string. - -An optional second parameter - `confirmDialog` - can be passed to the `Voice.dictate` method to control the behaviour of the confirmation dialog. If `confirmDialog` is set to `false`, the confirmation page will be skipped, otherwise it will be included in the dictation flow. - -```js -// Start a diction session and skip confirmation -Voice.dictate('start', false, function(e) { - if (e.err) { - console.log('Error: ' + e.err); - return; - } - - main.subtitle('Success: ' + e.transcription); -}); -``` - -**NOTE:** Only one dictation session can be active at any time. Trying to call `Voice.dicate('start', ...)` while another dictation session is in progress will result in the callback being called immediatly with status = -1. - -#### Voice.dictate('stop') - -This function stops a dictation session that is currently in progress and prevents the session's callback from being invoked. If no session is in progress this method has no effect. - -``` -Voice.dictate('stop'); -``` - ### Window `Window` is the basic building block in your Pebble.js application. All windows share some common properties and methods. @@ -1359,13 +1340,3 @@ For more information, see [Vector2 in the three.js reference documentation][thre [Text]: #text [TimeText]: #timetext [three.js Vector2]: http://threejs.org/docs/#Reference/Math/Vector2 - -## Examples - -Coming Soon! - -## Acknowledgements - -Pebble.js started as [Simply.JS](http://simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! - -This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). \ No newline at end of file diff --git a/src/js/app.js b/src/js/app.js index 1e3e9dc3..ba723efe 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -6,6 +6,7 @@ var UI = require('ui'); var Vector2 = require('vector2'); +var Voice = require('ui/voice'); var main = new UI.Card({ title: 'Pebble.js', @@ -54,9 +55,13 @@ main.on('click', 'select', function(e) { }); main.on('click', 'down', function(e) { - var card = new UI.Card(); - card.title('A Card'); - card.subtitle('Is a Window'); - card.body('The simplest window type in Pebble.js.'); - card.show(); + Voice.dictate('start', false, function(e) { + console.log(e.failed); + if (e.err) { + console.log('Error: ' + e.err); + return; + } + + main.subtitle('Success: ' + e.transcription); + }); }); From f97bea85bc005eec880bbc5ef0a53c7d2560f3ba Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 18:41:07 -0800 Subject: [PATCH 653/791] reverted app.js --- src/js/app.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index ba723efe..6edb445f 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -6,7 +6,6 @@ var UI = require('ui'); var Vector2 = require('vector2'); -var Voice = require('ui/voice'); var main = new UI.Card({ title: 'Pebble.js', @@ -55,13 +54,9 @@ main.on('click', 'select', function(e) { }); main.on('click', 'down', function(e) { - Voice.dictate('start', false, function(e) { - console.log(e.failed); - if (e.err) { - console.log('Error: ' + e.err); - return; - } - - main.subtitle('Success: ' + e.transcription); - }); -}); + var card = new UI.Card(); + card.title('A Card'); + card.subtitle('Is a Window'); + card.body('The simplest window type in Pebble.js.'); + card.show(); +}); \ No newline at end of file From 3b9db1debb9d025636f705e7b103560c2b04abf2 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Fri, 13 Nov 2015 19:12:31 -0800 Subject: [PATCH 654/791] Updated docs with Dictation API, also: - Moved examples and Acknowledgements to end of file - Removed accel.init() as it's invoked in the accel file --- README.md | 67 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 349f6f50..26143759 100644 --- a/README.md +++ b/README.md @@ -148,16 +148,6 @@ wind.add(textfield); wind.show(); ```` -## Examples - -Coming Soon! - -## Acknowledgements - -Pebble.js started as [Simply.JS](http://simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! - -This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). - # API Reference ## Global namespace @@ -409,14 +399,6 @@ You can use the accelerometer in two different ways: var Accel = require('ui/accel'); ```` -#### Accel.init() - -Before you can use the accelerometer, you must call `Accel.init()`. - -````js -Accel.init(); -```` - #### Accel.config(accelConfig) This function configures the accelerometer `data` events to your liking. The `tap` event requires no configuration for use. Configuring the accelerometer is a very error prone process, so it is recommended to not configure the accelerometer and use `data` events with the default configuration without calling `Accel.config`. @@ -496,6 +478,45 @@ wind.on('accelData', function(e) { }); ```` +### Voice + +The `Voice` module allows you to interact with Pebble's dictation API on supported platforms (Basalt and Chalk). + +````js +var Voice = require('ui/voice'); +```` + +#### Voice.dictate('state', [confirmDialog,] callback) + +This function starts the dictation UI, and invokes the callback upon completion. The callback is invoked with an event with the following fields: + +* `err`: A string describing the error, or `null` on success. +* `transcription`: The transcribed string. + +An optional second parameter - `confirmDialog` - can be passed to the `Voice.dictate` method to control the behaviour of the confirmation dialog. If `confirmDialog` is set to `false`, the confirmation page will be skipped, otherwise it will be included in the dictation flow. + +```js +// Start a diction session and skip confirmation +Voice.dictate('start', false, function(e) { + if (e.err) { + console.log('Error: ' + e.err); + return; + } + + main.subtitle('Success: ' + e.transcription); +}); +``` + +**NOTE:** Only one dictation session can be active at any time. Trying to call `Voice.dicate('start', ...)` while another dictation session is in progress will result in the callback being called immediatly with status = -1. + +#### Voice.dictate('stop') + +This function stops a dictation session that is currently in progress and prevents the session's callback from being invoked. If no session is in progress this method has no effect. + +``` +Voice.dictate('stop'); +``` + ### Window `Window` is the basic building block in your Pebble.js application. All windows share some common properties and methods. @@ -1340,3 +1361,13 @@ For more information, see [Vector2 in the three.js reference documentation][thre [Text]: #text [TimeText]: #timetext [three.js Vector2]: http://threejs.org/docs/#Reference/Math/Vector2 + +## Examples + +Coming Soon! + +## Acknowledgements + +Pebble.js started as [Simply.JS](http://simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! + +This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). \ No newline at end of file From 4d7a0cb316544290e0c9fc7fbb13537b264435fb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 13 Nov 2015 19:27:20 -0800 Subject: [PATCH 655/791] Add SDK 2.9 travis build --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index caa3236e..8de0fc7f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ python: - "2.7" env: + - PEBBLE_SDK_VERSION=2.9 - PEBBLE_SDK_VERSION=3.6 before_install: From ea682311096e42b9811b875dadb84b5d70d0946a Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Sat, 14 Nov 2015 15:28:39 -0800 Subject: [PATCH 656/791] Resolved issues where dictation dialog would immediatly be hidden --- src/js/ui/simply-pebble.js | 9 +++++---- src/simply/simply_voice.c | 4 ++++ src/simply/simply_voice.h | 2 ++ src/simply/simply_window.c | 5 +++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 2cb7b821..e30e107c 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1189,6 +1189,11 @@ SimplyPebble.onVoiceData = function(packet) { // Something bad happened console.log("No callback specified for dictation session"); } else { + + // show the top window to re-register handlers, etc. + state.dictationWindow.show(); + state.dictationWindow = null; + var e = { 'err': DictationSessionStatus[packet.status()], 'failed': packet.status() != 0, @@ -1198,10 +1203,6 @@ SimplyPebble.onVoiceData = function(packet) { state.dictationCallback(e); state.dictationCallback = null; } - - // show the top window to re-register handlers, etc. - state.dictationWindow.show(); - state.dictationWindow = null; } SimplyPebble.menuClear = function() { diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index 057eb0e8..677a15ff 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -130,3 +130,7 @@ void simply_voice_destroy(SimplyVoice *self) { free(self); s_voice = NULL; } + +bool simply_voice_dictation_in_progress() { + return s_voice->in_progress; +} diff --git a/src/simply/simply_voice.h b/src/simply/simply_voice.h index b6e734f5..fd361bb6 100644 --- a/src/simply/simply_voice.h +++ b/src/simply/simply_voice.h @@ -22,3 +22,5 @@ SimplyVoice *simply_voice_create(Simply *simply); void simply_voice_destroy(SimplyVoice *self); bool simply_voice_handle_packet(Simply *simply, Packet *packet); + +bool simply_voice_dictation_in_progress(); \ No newline at end of file diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 227b414f..3dc37f64 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -4,6 +4,7 @@ #include "simply_res.h" #include "simply_menu.h" #include "simply_window_stack.h" +#include "simply_voice.h" #include "simply.h" @@ -298,6 +299,10 @@ bool simply_window_disappear(SimplyWindow *self) { if (!self->id) { return false; } + // If the window is disappearing because of the dictation API + if(simply_voice_dictation_in_progress()) { + return false; + } if (simply_msg_has_communicated()) { simply_window_stack_send_hide(self->simply->window_stack, self); } From 6fc4451da2c9335cba02a32c9d4b366118103969 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Sat, 14 Nov 2015 16:27:56 -0800 Subject: [PATCH 657/791] Handling NULL result --- src/simply/simply_voice.c | 6 ++++++ src/util/compat.h | 1 + 2 files changed, 7 insertions(+) diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index 677a15ff..7670e871 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -25,6 +25,12 @@ struct __attribute__((__packed__)) VoiceDataPacket { static SimplyVoice *s_voice; static bool send_voice_data(int status, char *transcription) { + // Handle NULL Case + if (transcription == NULL) { + return send_voice_data(DictationSessionStatusFailureSystemAborted, ""); + } + + // Handle success case size_t transcription_length = strlen(transcription) + 1; size_t packet_length = sizeof(VoiceDataPacket) + transcription_length; diff --git a/src/util/compat.h b/src/util/compat.h index 0abd0761..db392119 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -140,6 +140,7 @@ typedef union GColor8 { typedef struct DictationSession DictationSession; typedef struct DictationSessionStatus DictationSessionStatus; void dictation_session_start(DictationSession *session); +#define DictationSessionStatusFailureSystemAborted 3 #endif From 61242290b23f3598af68335e0b03b236392d4940 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Sat, 14 Nov 2015 20:45:08 -0800 Subject: [PATCH 658/791] whitespace --- src/simply/simply_voice.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_voice.h b/src/simply/simply_voice.h index fd361bb6..890c590b 100644 --- a/src/simply/simply_voice.h +++ b/src/simply/simply_voice.h @@ -23,4 +23,4 @@ void simply_voice_destroy(SimplyVoice *self); bool simply_voice_handle_packet(Simply *simply, Packet *packet); -bool simply_voice_dictation_in_progress(); \ No newline at end of file +bool simply_voice_dictation_in_progress(); From 990db3dc7813387ea44d8c7cf2782c24fb05e2f7 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Sat, 14 Nov 2015 20:52:40 -0800 Subject: [PATCH 659/791] Don't need to store top window anymore --- src/js/ui/simply-pebble.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index e30e107c..128f5593 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1162,9 +1162,6 @@ SimplyPebble.voiceDictationStart = function(callback, enableConfirmation) { return; } - // Grab the current window to re-show once we're done - state.dictationWindow = WindowStack.top(); - // Set the callback and send the packet state.dictationCallback = callback; SimplyPebble.sendPacket(VoiceDictationStartPacket.enableConfirmation(enableConfirmation)); @@ -1176,12 +1173,6 @@ SimplyPebble.voiceDictationStop = function() { // Clear the callback variable state.dictationCallback = null; - - // If we have a window stored, show it then clear the varaible - if (state.dictationWindow) { - state.dictationWindow.show(); - state.dictationWindow = null; - } } SimplyPebble.onVoiceData = function(packet) { @@ -1189,11 +1180,6 @@ SimplyPebble.onVoiceData = function(packet) { // Something bad happened console.log("No callback specified for dictation session"); } else { - - // show the top window to re-register handlers, etc. - state.dictationWindow.show(); - state.dictationWindow = null; - var e = { 'err': DictationSessionStatus[packet.status()], 'failed': packet.status() != 0, From bf3ba07b0ff05bb1e260a6ad418ff8c04abbc11a Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Sun, 15 Nov 2015 07:56:13 -0800 Subject: [PATCH 660/791] whitespace --- src/simply/simply_window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 3dc37f64..4de686e0 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -300,7 +300,7 @@ bool simply_window_disappear(SimplyWindow *self) { return false; } // If the window is disappearing because of the dictation API - if(simply_voice_dictation_in_progress()) { + if (simply_voice_dictation_in_progress()) { return false; } if (simply_msg_has_communicated()) { From eca145c7549fde8c85f25273ee5a1716ea5cdca8 Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Mon, 16 Nov 2015 11:42:10 -0800 Subject: [PATCH 661/791] Fixed typo in dictation docs: state -> start --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 26143759..37a92281 100644 --- a/README.md +++ b/README.md @@ -486,7 +486,7 @@ The `Voice` module allows you to interact with Pebble's dictation API on support var Voice = require('ui/voice'); ```` -#### Voice.dictate('state', [confirmDialog,] callback) +#### Voice.dictate('start', [confirmDialog,] callback) This function starts the dictation UI, and invokes the callback upon completion. The callback is invoked with an event with the following fields: @@ -1370,4 +1370,4 @@ Coming Soon! Pebble.js started as [Simply.JS](http://simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com) with any questions! -This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). \ No newline at end of file +This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc). From 1230894092568e66b40218a5a21759c22579bfcf Mon Sep 17 00:00:00 2001 From: Cat Haines Date: Mon, 16 Nov 2015 11:42:55 -0800 Subject: [PATCH 662/791] Added js tag to Voice.dictate('stop') example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37a92281..03ae69a3 100644 --- a/README.md +++ b/README.md @@ -513,7 +513,7 @@ Voice.dictate('start', false, function(e) { This function stops a dictation session that is currently in progress and prevents the session's callback from being invoked. If no session is in progress this method has no effect. -``` +```js Voice.dictate('stop'); ``` From 7a7db5ffc6beae70a32769f63299469975cf4f1b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Nov 2015 17:32:25 -0800 Subject: [PATCH 663/791] Change the status bar layer on 3.0 to behave like a normal layer --- src/util/status_bar_layer.h | 50 +++++++++++++------------------------ 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/src/util/status_bar_layer.h b/src/util/status_bar_layer.h index 7ec02556..84eb8c74 100644 --- a/src/util/status_bar_layer.h +++ b/src/util/status_bar_layer.h @@ -9,6 +9,14 @@ typedef struct StatusBarLayer StatusBarLayer; struct StatusBarLayer; +//! Values that are used to indicate the different status bar separator modes. +typedef enum { + //! The default mode. No separator will be shown. + StatusBarLayerSeparatorModeNone = 0, + //! A dotted separator at the bottom of the status bar. + StatusBarLayerSeparatorModeDotted = 1, +} StatusBarLayerSeparatorMode; + static inline StatusBarLayer *status_bar_layer_create(void) { return NULL; } @@ -20,6 +28,14 @@ static inline Layer *status_bar_layer_get_layer(StatusBarLayer *status_bar_layer return (Layer *)status_bar_layer; } +static inline void status_bar_layer_set_colors(StatusBarLayer *status_bar_layer, GColor8 background, + GColor8 foreground) { +} + +static inline void status_bar_layer_set_separator_mode(StatusBarLayer *status_bar_layer, + StatusBarLayerSeparatorMode mode) { +} + static inline void status_bar_layer_add_to_window(Window *window, StatusBarLayer *status_bar_layer) { window_set_fullscreen(window, false); } @@ -31,44 +47,12 @@ static inline void status_bar_layer_remove_from_window(Window *window, StatusBar #else static inline void status_bar_layer_add_to_window(Window *window, StatusBarLayer *status_bar_layer) { - Layer *status_bar_base_layer = status_bar_layer_get_layer(status_bar_layer); - GRect status_frame = layer_get_frame(status_bar_base_layer); - status_frame.origin.y = -STATUS_BAR_LAYER_HEIGHT; - layer_set_frame(status_bar_base_layer, status_frame); - Layer *window_layer = window_get_root_layer(window); - layer_add_child(window_layer, status_bar_base_layer); - - GRect bounds = layer_get_bounds(window_layer); - if (bounds.origin.y == 0) { - bounds.origin.y = STATUS_BAR_LAYER_HEIGHT; - bounds.size.h -= STATUS_BAR_LAYER_HEIGHT; - - GRect frame = layer_get_frame(window_layer); - frame.size.h = bounds.size.h; - - layer_set_frame(window_layer, frame); - layer_set_bounds(window_layer, bounds); - layer_set_clips(window_layer, false); - } + layer_add_child(window_layer, status_bar_layer_get_layer(status_bar_layer)); } static inline void status_bar_layer_remove_from_window(Window *window, StatusBarLayer *status_bar_layer) { layer_remove_from_parent(status_bar_layer_get_layer(status_bar_layer)); - Layer *window_layer = window_get_root_layer(window); - - GRect bounds = layer_get_bounds(window_layer); - if (bounds.origin.y == STATUS_BAR_LAYER_HEIGHT) { - bounds.origin.y = 0; - bounds.size.h += STATUS_BAR_LAYER_HEIGHT; - - GRect frame = layer_get_frame(window_layer); - frame.size.h = bounds.size.h; - - layer_set_frame(window_layer, frame); - layer_set_bounds(window_layer, bounds); - layer_set_clips(window_layer, true); - } } #endif From fcac76af5978aff7bb1e4f086738ecf17a90bd09 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Nov 2015 17:32:48 -0800 Subject: [PATCH 664/791] Add gcolor_legible_over for aplite --- src/util/color.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/util/color.h b/src/util/color.h index ad367fa4..27c1e531 100644 --- a/src/util/color.h +++ b/src/util/color.h @@ -40,6 +40,12 @@ static inline bool gcolor8_equal_native(GColor8 color, GColor other) { return (color.argb == gcolor_get8(other).argb); } +static inline GColor8 gcolor_legible_over(GColor8 background_color) { + const int sum = background_color.r + background_color.g + background_color.b; + const int avg = sum / 3; + return (avg >= 2) ? GColor8Black : GColor8White; +} + #else static inline GColor gcolor8_get(GColor8 color) { From 3e3d6f3d0716dc30a60b647cdb48db0469d2bde5 Mon Sep 17 00:00:00 2001 From: Alexey Kotlyarov Date: Wed, 18 Nov 2015 20:53:46 +1100 Subject: [PATCH 665/791] Add support to set the selected item via UI.Menu.selection --- src/js/ui/menu.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 1e6244a5..d5c06d1b 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -29,8 +29,14 @@ util2.copy(Emitter.prototype, Menu.prototype); Menu.prototype._show = function() { Window.prototype._show.apply(this, arguments); - var select = this._selection; - simply.impl.menuSelection(select.sectionIndex, select.itemIndex); + this._select(); +}; + +Menu.prototype._select = function() { + if (this === WindowStack.top()) { + var select = this._selection; + simply.impl.menuSelection(select.sectionIndex, select.itemIndex); + } }; Menu.prototype._numPreloadItems = 50; @@ -299,9 +305,17 @@ Menu.prototype.item = function(sectionIndex, itemIndex, item) { return this; }; -Menu.prototype.selection = function(callback) { - this._selections.push(callback); - simply.impl.menuSelection(); +Menu.prototype.selection = function(callback_or_sectionIndex, itemIndex) { + if (typeof callback_or_sectionIndex === 'function') { + this._selections.push(callback); + simply.impl.menuSelection(); + } else { + this._selection = { + sectionIndex: callback_or_sectionIndex, + itemIndex: itemIndex + }; + this._select(); + } }; Menu.emit = Window.emit; From d3c283008df2f69f88988cd59bd299d6d48a3640 Mon Sep 17 00:00:00 2001 From: Alexey Kotlyarov Date: Thu, 19 Nov 2015 20:31:43 +1100 Subject: [PATCH 666/791] Document UI.Menu.selection --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 349f6f50..6a0a55fd 100644 --- a/README.md +++ b/README.md @@ -774,6 +774,31 @@ menu.item(0, 0, { title: 'A new item', subtitle: 'replacing the previous one' }) When called with no `item`, returns the item at the given `sectionIndex` and `itemIndex`. +#### Menu.selection(callback) + +Get the currently selected item and section. The callback function will be passed an event with the following fields: + +* `menu`: The menu object. +* `section`: The menu section object. +* `sectionIndex`: The section index of the section of the selected item. +* `item`: The menu item object. +* `itemIndex`: The item index of the selected item. + +````js +menu.selection(function(e) { + console.log('Currently selected item is #' + e.itemIndex + ' of section #' + e.sectionIndex); + console.log('The item is titled "' + e.item.title + '"'); +}); +```` + +#### Menu.selection(sectionIndex, itemIndex) + +Change the selected item and section. + +````js +menu.selection(0, 2); +```` + #### Menu.on('select', callback) [Menu.on('select', callback)]: #menu-on-select-callback From f696939f77e152a650224a45451ba6acba4dbe0c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 18 Nov 2015 17:33:25 -0800 Subject: [PATCH 667/791] Change 3.0 status bars to resize the main layer --- src/simply/simply_ui.c | 11 +++++--- src/simply/simply_window.c | 57 +++++++++++++++++++++++++------------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index ef0fbc5d..1cbc3116 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -134,8 +134,10 @@ void simply_ui_set_text_color(SimplyUi *self, SimplyUiTextfieldId textfield_id, static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyUi *self = *(void**) layer_get_data(layer); - GRect window_frame = layer_get_frame(window_get_root_layer(self->window.window)); - GRect frame = layer_get_frame(layer); + GRect window_frame = + { .size = layer_get_frame(scroll_layer_get_layer(self->window.scroll_layer)).size }; + GRect frame = window_frame; + layer_set_frame(layer, window_frame); const SimplyStyle *style = self->ui_layer.style; GFont title_font = fonts_get_system_font(style->title_font); @@ -249,7 +251,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { graphics_fill_rect(ctx, frame, 0, GCornerNone); graphics_context_set_fill_color(ctx, gcolor8_get_or(self->window.background_color, GColorWhite)); - graphics_fill_rect(ctx, frame, 4, GCornersAll); + const int radius = IF_SDK_2_ELSE(4, 0); + graphics_fill_rect(ctx, frame, radius, GCornersAll); if (title_icon) { GRect icon_frame = (GRect) { @@ -314,7 +317,7 @@ static void window_load(Window *window) { frame.origin = GPointZero; Layer *layer = layer_create_with_data(frame, sizeof(void*)); - self->window.layer = self->ui_layer.layer = layer; + self->ui_layer.layer = layer; *(void**) layer_get_data(layer) = self; layer_set_update_proc(layer, layer_update_callback); scroll_layer_add_child(self->window.scroll_layer, layer); diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 4de686e0..f2d09246 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -56,7 +56,7 @@ static GColor8 s_button_palette[] = { { GColorWhiteARGB8 }, { GColorClearARGB8 } static void click_config_provider(void *data); -static bool send_click(SimplyMsg *self, Command type, ButtonId button) { +static bool prv_send_click(SimplyMsg *self, Command type, ButtonId button) { ClickPacket packet = { .packet.type = type, .packet.length = sizeof(packet), @@ -65,15 +65,15 @@ static bool send_click(SimplyMsg *self, Command type, ButtonId button) { return simply_msg_send_packet(&packet.packet); } -static bool send_single_click(SimplyMsg *self, ButtonId button) { - return send_click(self, CommandClick, button); +static bool prv_send_single_click(SimplyMsg *self, ButtonId button) { + return prv_send_click(self, CommandClick, button); } -static bool send_long_click(SimplyMsg *self, ButtonId button) { - return send_click(self, CommandLongClick, button); +static bool prv_send_long_click(SimplyMsg *self, ButtonId button) { + return prv_send_click(self, CommandLongClick, button); } -static void set_scroll_layer_click_config(SimplyWindow *self) { +static void prv_set_scroll_layer_click_config(SimplyWindow *self) { if (!self->scroll_layer) { return; } @@ -89,7 +89,7 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { self->is_scrollable = is_scrollable; - set_scroll_layer_click_config(self); + prv_set_scroll_layer_click_config(self); if (!self->layer) { return; @@ -106,6 +106,22 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { layer_mark_dirty(self->layer); } +static void prv_refresh_main_layer_frame(SimplyWindow *self) { + Layer * const main_layer = self->layer ?: scroll_layer_get_layer(self->scroll_layer); + if (!main_layer) { + return; + } + GRect frame = { .size = layer_get_frame(window_get_root_layer(self->window)).size }; + IF_SDK_3_ELSE({ + if (layer_get_window(status_bar_layer_get_layer(self->status_bar_layer))) { + frame.origin.y = STATUS_BAR_LAYER_HEIGHT; + frame.size.h -= STATUS_BAR_LAYER_HEIGHT; + } + }, NONE); + layer_set_frame(main_layer, frame); +} + + void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { const bool was_status_bar = self->is_status_bar; self->is_status_bar = !is_fullscreen; @@ -119,13 +135,11 @@ void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { changed = true; } - if (!changed || !self->layer) { + if (!changed) { return; } - GRect frame = { GPointZero, layer_get_frame(window_get_root_layer(self->window)).size }; - scroll_layer_set_frame(self->scroll_layer, frame); - layer_set_frame(self->layer, frame); + prv_refresh_main_layer_frame(self); #ifdef PBL_SDK_2 if (!window_stack_contains_window(self->window)) { @@ -145,6 +159,8 @@ void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color) { self->background_color = background_color; + status_bar_layer_set_colors(self->status_bar_layer, background_color, + gcolor_legible_over(background_color)); } void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { @@ -156,7 +172,7 @@ void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { action_bar_layer_remove_from_window(self->action_bar_layer); - set_scroll_layer_click_config(self); + prv_set_scroll_layer_click_config(self); if (!is_action_bar) { return; @@ -227,7 +243,7 @@ void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *con } } if (is_enabled) { - send_single_click(self->simply->msg, button); + prv_send_single_click(self->simply->msg, button); } } @@ -236,7 +252,7 @@ static void long_click_handler(ClickRecognizerRef recognizer, void *context) { ButtonId button = click_recognizer_get_button_id(recognizer); bool is_enabled = (self->button_mask & (1 << button)); if (is_enabled) { - send_long_click(self->simply->msg, button); + prv_send_long_click(self->simply->msg, button); } } @@ -322,7 +338,7 @@ void simply_window_unload(SimplyWindow *self) { self->scroll_layer = NULL; } -static void handle_window_props_packet(Simply *simply, Packet *data) { +static void prv_handle_window_props_packet(Simply *simply, Packet *data) { WindowPropsPacket *packet = (WindowPropsPacket*) data; SimplyWindow *window = simply_window_stack_get_top_window(simply); if (!window) { @@ -334,7 +350,7 @@ static void handle_window_props_packet(Simply *simply, Packet *data) { simply_window_set_scrollable(window, packet->scrollable); } -static void handle_window_button_config_packet(Simply *simply, Packet *data) { +static void prv_handle_window_button_config_packet(Simply *simply, Packet *data) { WindowButtonConfigPacket *packet = (WindowButtonConfigPacket*) data; SimplyWindow *window = simply_window_stack_get_top_window(simply); if (!window) { @@ -343,7 +359,7 @@ static void handle_window_button_config_packet(Simply *simply, Packet *data) { window->button_mask = packet->button_mask; } -static void handle_window_action_bar_packet(Simply *simply, Packet *data) { +static void prv_handle_window_action_bar_packet(Simply *simply, Packet *data) { WindowActionBarPacket *packet = (WindowActionBarPacket*) data; SimplyWindow *window = simply_window_stack_get_top_window(simply); if (!window) { @@ -359,13 +375,13 @@ static void handle_window_action_bar_packet(Simply *simply, Packet *data) { bool simply_window_handle_packet(Simply *simply, Packet *packet) { switch (packet->type) { case CommandWindowProps: - handle_window_props_packet(simply, packet); + prv_handle_window_props_packet(simply, packet); return true; case CommandWindowButtonConfig: - handle_window_button_config_packet(simply, packet); + prv_handle_window_button_config_packet(simply, packet); return true; case CommandWindowActionBar: - handle_window_action_bar_packet(simply, packet); + prv_handle_window_action_bar_packet(simply, packet); return true; } return false; @@ -383,6 +399,7 @@ SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { simply_window_preload(self); self->status_bar_layer = status_bar_layer_create(); + status_bar_layer_set_separator_mode(self->status_bar_layer, StatusBarLayerSeparatorModeDotted); status_bar_layer_remove_from_window(self->window, self->status_bar_layer); self->is_status_bar = false; self->is_fullscreen = true; From 4363e1c03324ab054ac3f49b8e16b3109fcf3f39 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 19 Nov 2015 01:43:18 -0800 Subject: [PATCH 668/791] Update .travis.yml to use new container jobs --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 8de0fc7f..1f2f5529 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +sudo: false language: python python: - "2.7" From e597dad901bd8e5ff8d54942f7ced7958e9e3824 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 19 Nov 2015 03:37:06 -0800 Subject: [PATCH 669/791] Add 3.0 status bar and action bar behavior On 3.0, when both a status bar and action bar are present, the action bar takes up the top right instead of the status bar in the system apps. This changes simply window to exhibit the same behavior dynamically whenever status bars and action bars are toggled. --- src/simply/simply_stage.c | 11 +++++------ src/simply/simply_ui.c | 21 +++++++++++---------- src/simply/simply_window.c | 30 +++++++++++++++++++----------- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 31eee809..84e37919 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -429,16 +429,15 @@ SimplyAnimation *simply_stage_animate_element(SimplyStage *self, } static void window_load(Window *window) { - SimplyStage *self = window_get_user_data(window); + SimplyStage * const self = window_get_user_data(window); simply_window_load(&self->window); - Layer *window_layer = window_get_root_layer(window); - GRect frame = layer_get_frame(window_layer); - frame.origin = GPointZero; + Layer * const window_layer = window_get_root_layer(window); + const GRect frame = { .size = layer_get_frame(window_layer).size }; - Layer *layer = layer_create_with_data(frame, sizeof(void*)); - self->window.layer = self->stage_layer.layer = layer; + Layer * const layer = layer_create_with_data(frame, sizeof(void *)); + self->stage_layer.layer = layer; *(void**) layer_get_data(layer) = self; layer_set_update_proc(layer, layer_update_callback); scroll_layer_add_child(self->window.scroll_layer, layer); diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 1cbc3116..08a09dcd 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -134,10 +134,10 @@ void simply_ui_set_text_color(SimplyUi *self, SimplyUiTextfieldId textfield_id, static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyUi *self = *(void**) layer_get_data(layer); - GRect window_frame = - { .size = layer_get_frame(scroll_layer_get_layer(self->window.scroll_layer)).size }; + GRect window_frame = { + .size = layer_get_frame(scroll_layer_get_layer(self->window.scroll_layer)).size, + }; GRect frame = window_frame; - layer_set_frame(layer, window_frame); const SimplyStyle *style = self->ui_layer.style; GFont title_font = fonts_get_system_font(style->title_font); @@ -247,8 +247,10 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } } - graphics_context_set_fill_color(ctx, GColorBlack); - graphics_fill_rect(ctx, frame, 0, GCornerNone); + IF_SDK_2_ELSE(({ + graphics_context_set_fill_color(ctx, GColorBlack); + graphics_fill_rect(ctx, frame, 0, GCornerNone); + }), NONE); graphics_context_set_fill_color(ctx, gcolor8_get_or(self->window.background_color, GColorWhite)); const int radius = IF_SDK_2_ELSE(4, 0); @@ -308,15 +310,14 @@ static void show_welcome_text(SimplyUi *self) { } static void window_load(Window *window) { - SimplyUi *self = window_get_user_data(window); + SimplyUi * const self = window_get_user_data(window); simply_window_load(&self->window); - Layer *window_layer = window_get_root_layer(window); - GRect frame = layer_get_frame(window_layer); - frame.origin = GPointZero; + Layer * const window_layer = window_get_root_layer(window); + const GRect frame = { .size = layer_get_frame(window_layer).size }; - Layer *layer = layer_create_with_data(frame, sizeof(void*)); + Layer * const layer = layer_create_with_data(frame, sizeof(void *)); self->ui_layer.layer = layer; *(void**) layer_get_data(layer) = self; layer_set_update_proc(layer, layer_update_callback); diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index f2d09246..ad84f069 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -106,18 +106,27 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { layer_mark_dirty(self->layer); } -static void prv_refresh_main_layer_frame(SimplyWindow *self) { +static void prv_update_layer_placement(SimplyWindow *self) { Layer * const main_layer = self->layer ?: scroll_layer_get_layer(self->scroll_layer); if (!main_layer) { return; } GRect frame = { .size = layer_get_frame(window_get_root_layer(self->window)).size }; - IF_SDK_3_ELSE({ - if (layer_get_window(status_bar_layer_get_layer(self->status_bar_layer))) { + IF_SDK_3_ELSE(({ + Layer * const status_bar_base_layer = status_bar_layer_get_layer(self->status_bar_layer); + const bool has_status_bar = (layer_get_window(status_bar_base_layer) != NULL); + const bool has_action_bar = + (layer_get_window(action_bar_layer_get_layer(self->action_bar_layer)) != NULL); + if (has_status_bar) { + GRect status_frame = { .size = { frame.size.w, STATUS_BAR_LAYER_HEIGHT } }; frame.origin.y = STATUS_BAR_LAYER_HEIGHT; frame.size.h -= STATUS_BAR_LAYER_HEIGHT; + if (has_action_bar) { + status_frame.size.w -= ACTION_BAR_WIDTH; + } + layer_set_frame(status_bar_base_layer, status_frame); } - }, NONE); + }), NONE); layer_set_frame(main_layer, frame); } @@ -139,7 +148,7 @@ void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { return; } - prv_refresh_main_layer_frame(self); + prv_update_layer_placement(self); #ifdef PBL_SDK_2 if (!window_stack_contains_window(self->window)) { @@ -171,16 +180,15 @@ void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { } action_bar_layer_remove_from_window(self->action_bar_layer); - prv_set_scroll_layer_click_config(self); - if (!is_action_bar) { - return; + if (is_action_bar) { + action_bar_layer_set_context(self->action_bar_layer, self); + action_bar_layer_set_click_config_provider(self->action_bar_layer, click_config_provider); + action_bar_layer_add_to_window(self->action_bar_layer, self->window); } - action_bar_layer_set_context(self->action_bar_layer, self); - action_bar_layer_set_click_config_provider(self->action_bar_layer, click_config_provider); - action_bar_layer_add_to_window(self->action_bar_layer, self->window); + prv_update_layer_placement(self); } void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint32_t id) { From c096b3f8bf972e4c3a7aec082c463b5202e23e03 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 19 Nov 2015 03:49:12 -0800 Subject: [PATCH 670/791] Change inverter layer shim to a stub --- src/util/inverter_layer.h | 49 +++------------------------------------ 1 file changed, 3 insertions(+), 46 deletions(-) diff --git a/src/util/inverter_layer.h b/src/util/inverter_layer.h index b8807d03..ea9b47be 100644 --- a/src/util/inverter_layer.h +++ b/src/util/inverter_layer.h @@ -9,51 +9,8 @@ typedef struct InverterLayer InverterLayer; struct InverterLayer; -static void inverter_layer_update_proc(Layer *layer, GContext *ctx) { - GBitmap *frame_buffer = graphics_capture_frame_buffer(ctx); - const size_t bytes_per_row = gbitmap_get_bytes_per_row(frame_buffer); - GColor8 *data = (GColor8 *)gbitmap_get_data(frame_buffer); - - Layer *window_layer = window_get_root_layer(layer_get_window(layer)); - GRect window_bounds = layer_get_bounds(window_layer); - GRect window_frame = layer_get_frame(window_layer); - GRect drawing_box = { - .origin.x = window_frame.origin.x + window_bounds.origin.x, - .origin.y = window_frame.origin.y + window_bounds.origin.y, - .size = window_frame.size, - }; - - // This layer must not be nested in a layer not positioned at origin - GRect frame = layer_get_frame(layer); - - const int16_t min_x = MAX(0, drawing_box.origin.x + frame.origin.x); - const int16_t min_y = MAX(0, drawing_box.origin.y + frame.origin.y); - const int16_t max_x = MIN(drawing_box.size.w, min_x + frame.size.w); - const int16_t max_y = MIN(drawing_box.size.h, min_y + frame.size.h); - - data += bytes_per_row * min_y; - for (int16_t y = min_y; y < max_y; y++) { - for (int16_t x = min_x; x < max_x; x++) { - data[x] = (GColor8) { .argb = ~data[x].argb }; - } - data += bytes_per_row; - } - - graphics_release_frame_buffer(ctx, frame_buffer); -} - -static inline InverterLayer *inverter_layer_create(GRect bounds) { - Layer *layer = layer_create(bounds); - layer_set_update_proc(layer, inverter_layer_update_proc); - return (InverterLayer *)layer; -} - -static inline void inverter_layer_destroy(InverterLayer *inverter_layer) { - layer_destroy((Layer *)inverter_layer); -} - -static inline Layer *inverter_layer_get_layer(InverterLayer *inverter_layer) { - return (Layer *)inverter_layer; -} +InverterLayer *inverter_layer_create(GRect bounds); +void inverter_layer_destroy(InverterLayer *inverter_layer); +Layer *inverter_layer_get_layer(InverterLayer *inverter_layer); #endif From 1ac7569ccfa7cbb96f15bf2bfe903513449e77dc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 25 Nov 2015 08:02:57 -0800 Subject: [PATCH 671/791] Fix simply stage to destroy property animations on disappear --- src/simply/simply_stage.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 84e37919..f8cea9a1 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -152,6 +152,7 @@ static void destroy_element(SimplyStage *self, SimplyElementCommon *element) { static void destroy_animation(SimplyStage *self, SimplyAnimation *animation) { if (!animation) { return; } + property_animation_destroy(animation->animation); list1_remove(&self->stage_layer.animations, &animation->node); free(animation); } From 8fac33b538e8df29e6c5650bb4f0b3f1bd2a1c6f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 1 Dec 2015 03:01:23 -0800 Subject: [PATCH 672/791] Add simply window status bar object property with separator and color --- src/js/ui/card.js | 2 +- src/js/ui/menu.js | 2 +- src/js/ui/simply-pebble.js | 39 +++++++++++++++++- src/js/ui/window.js | 27 +++++------- src/simply/simply_msg_commands.h | 1 + src/simply/simply_ui.c | 2 +- src/simply/simply_window.c | 71 ++++++++++++++++++++++++-------- src/simply/simply_window.h | 7 ++-- 8 files changed, 109 insertions(+), 42 deletions(-) diff --git a/src/js/ui/card.js b/src/js/ui/card.js index b84180e5..fe7048c6 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -39,8 +39,8 @@ var accessorProps = textProps.concat(textColorProps).concat(imageProps).concat(c var clearableProps = textProps.concat(imageProps); var defaults = { + status: true, backgroundColor: 'white', - fullscreen: false, }; var Card = function(cardDef) { diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 1e6244a5..40bb5dbc 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -6,11 +6,11 @@ var Window = require('ui/window'); var simply = require('ui/simply'); var defaults = { + status: true, backgroundColor: 'white', textColor: 'black', highlightBackgroundColor: 'black', highlightTextColor: 'white', - fullscreen: false, }; var Menu = function(menuDef) { diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 128f5593..39ab095e 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -445,6 +445,13 @@ var DictationSessionStatus = [ DictationSessionStatus[64] = "sessionAlreadyInProgress"; DictationSessionStatus[65] = "noMicrophone"; +var StatusBarSeparatorModeTypes = [ + 'none', + 'dotted', +]; + +var StatusBarSeparatorModeType = makeArrayType(StatusBarSeparatorModeTypes); + var Packet = new struct([ ['uint16', 'type'], ['uint16', 'length'], @@ -517,7 +524,6 @@ var WindowPropsPacket = new struct([ [Packet, 'packet'], ['uint32', 'id'], ['uint8', 'backgroundColor', Color], - ['bool', 'fullscreen', BoolType], ['bool', 'scrollable', BoolType], ]); @@ -526,13 +532,21 @@ var WindowButtonConfigPacket = new struct([ ['uint8', 'buttonMask', ButtonFlagsType], ]); +var WindowStatusBarPacket = new struct([ + [Packet, 'packet'], + ['uint8', 'backgroundColor', Color], + ['uint8', 'color', Color], + ['uint8', 'separator', StatusBarSeparatorModeType], + ['uint8', 'status', BoolType], +]); + var WindowActionBarPacket = new struct([ [Packet, 'packet'], ['uint32', 'up', ImageType], ['uint32', 'select', ImageType], ['uint32', 'down', ImageType], - ['uint8', 'action', BoolType], ['uint8', 'backgroundColor', Color], + ['uint8', 'action', BoolType], ]); var ClickPacket = new struct([ @@ -810,6 +824,7 @@ var CommandPackets = [ WindowHideEventPacket, WindowPropsPacket, WindowButtonConfigPacket, + WindowStatusBarPacket, WindowActionBarPacket, ClickPacket, LongClickPacket, @@ -1046,6 +1061,23 @@ SimplyPebble.windowButtonConfig = function(def) { SimplyPebble.sendPacket(WindowButtonConfigPacket.buttonMask(def)); }; +var toStatusDef = function(statusDef) { + if (typeof statusDef === 'boolean') { + statusDef = { status: statusDef }; + } + return statusDef; +}; + +SimplyPebble.windowStatusBar = function(def) { + var statusDef = toStatusDef(def); + WindowStatusBarPacket + .separator(statusDef.separator || 'dotted') + .status(typeof def === 'boolean' ? def : true) + .color(statusDef.color || 'black') + .backgroundColor(statusDef.backgroundColor || 'white'); + SimplyPebble.sendPacket(WindowStatusBarPacket); +}; + var toActionDef = function(actionDef) { if (typeof actionDef === 'boolean') { actionDef = { action: actionDef }; @@ -1113,6 +1145,9 @@ SimplyPebble.card = function(def, clear, pushing) { SimplyPebble.cardClear(clear); } SimplyPebble.windowProps(def); + if (def.status !== undefined) { + SimplyPebble.windowStatusBar(def.status); + } if (def.action !== undefined) { SimplyPebble.windowActionBar(def.action); } diff --git a/src/js/ui/window.js b/src/js/ui/window.js index a9e862e6..47453269 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -14,21 +14,6 @@ var buttons = [ 'down', ]; -/** - * Enable fullscreen in the Pebble UI. - * Fullscreen removes the Pebble status bar, giving slightly more vertical display height. - * @memberOf simply - * @param {boolean} fullscreen - Whether to enable fullscreen mode. - */ - -/** - * Enable scrolling in the Pebble UI. - * When scrolling is enabled, up and down button presses are no longer forwarded to JavaScript handlers. - * Single select, long select, and accel tap events are still available to you however. - * @memberOf simply - * @param {boolean} scrollable - Whether to enable a scrollable view. - */ - var configProps = [ 'fullscreen', 'style', @@ -36,6 +21,12 @@ var configProps = [ 'backgroundColor', ]; +var statusProps = [ + 'separator', + 'color', + 'backgroundColor', +]; + var actionProps = [ 'up', 'select', @@ -46,8 +37,8 @@ var actionProps = [ var accessorProps = configProps; var defaults = { + status: false, backgroundColor: 'black', - fullscreen: false, scrollable: false, }; @@ -110,6 +101,10 @@ Window.prototype._remove = function() { } }; +Window.prototype._clearStatus = function() { + statusProps.forEach(Propable.unset.bind(this.state.status)); +}; + Window.prototype._clearAction = function() { actionProps.forEach(Propable.unset.bind(this.state.action)); }; diff --git a/src/simply/simply_msg_commands.h b/src/simply/simply_msg_commands.h index 886b0213..f321e2b0 100644 --- a/src/simply/simply_msg_commands.h +++ b/src/simply/simply_msg_commands.h @@ -16,6 +16,7 @@ enum Command { CommandWindowHideEvent, CommandWindowProps, CommandWindowButtonConfig, + CommandWindowStatusBar, CommandWindowActionBar, CommandClick, CommandLongClick, diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 08a09dcd..163ecdce 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -154,7 +154,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { text_frame.size.h += 1000; GPoint cursor = { margin_x, margin_y }; - if (self->window.is_action_bar) { + if (self->window.use_action_bar) { text_frame.size.w -= ACTION_BAR_WIDTH; window_frame.size.w -= ACTION_BAR_WIDTH; } diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index ad84f069..9d2791a9 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -21,7 +21,6 @@ struct __attribute__((__packed__)) WindowPropsPacket { Packet packet; uint32_t id; GColor8 background_color; - bool fullscreen; bool scrollable; }; @@ -32,13 +31,25 @@ struct __attribute__((__packed__)) WindowButtonConfigPacket { uint8_t button_mask; }; +typedef struct WindowStatusBarPacket WindowStatusBarPacket; + +struct __attribute__((__packed__)) WindowStatusBarPacket { + Packet packet; + GColor8 background_color; + GColor8 color; + StatusBarLayerSeparatorMode separator:8; + bool status; +}; + +typedef struct WindowButtonConfigPacket WindowButtonConfigPacket; + typedef struct WindowActionBarPacket WindowActionBarPacket; struct __attribute__((__packed__)) WindowActionBarPacket { Packet packet; uint32_t image[3]; - bool action; GColor8 background_color; + bool action; }; typedef struct ClickPacket ClickPacket; @@ -131,15 +142,15 @@ static void prv_update_layer_placement(SimplyWindow *self) { } -void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen) { - const bool was_status_bar = self->is_status_bar; - self->is_status_bar = !is_fullscreen; +void simply_window_set_status_bar(SimplyWindow *self, bool use_status_bar) { + const bool was_status_bar = self->use_status_bar; + self->use_status_bar = use_status_bar; bool changed = false; - if (is_fullscreen && was_status_bar) { + if (!use_status_bar && was_status_bar) { status_bar_layer_remove_from_window(self->window, self->status_bar_layer); changed = true; - } else if (!is_fullscreen && !was_status_bar) { + } else if (use_status_bar && !was_status_bar) { status_bar_layer_add_to_window(self->window, self->status_bar_layer); changed = true; } @@ -172,8 +183,22 @@ void simply_window_set_background_color(SimplyWindow *self, GColor8 background_c gcolor_legible_over(background_color)); } -void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { - self->is_action_bar = is_action_bar; +void simply_window_set_status_bar_colors(SimplyWindow *self, GColor8 background_color, + GColor8 foreground_color) { + if (self->status_bar_layer) { + status_bar_layer_set_colors(self->status_bar_layer, background_color, foreground_color); + } +} + +void simply_window_set_status_bar_separator_mode(SimplyWindow *self, + StatusBarLayerSeparatorMode separator) { + if (self->status_bar_layer) { + status_bar_layer_set_separator_mode(self->status_bar_layer, separator); + } +} + +void simply_window_set_action_bar(SimplyWindow *self, bool use_action_bar) { + self->use_action_bar = use_action_bar; if (!self->action_bar_layer) { return; @@ -182,7 +207,7 @@ void simply_window_set_action_bar(SimplyWindow *self, bool is_action_bar) { action_bar_layer_remove_from_window(self->action_bar_layer); prv_set_scroll_layer_click_config(self); - if (is_action_bar) { + if (use_action_bar) { action_bar_layer_set_context(self->action_bar_layer, self); action_bar_layer_set_click_config_provider(self->action_bar_layer, click_config_provider); action_bar_layer_add_to_window(self->action_bar_layer, self->window); @@ -306,7 +331,7 @@ void simply_window_load(SimplyWindow *self) { scroll_layer_set_shadow_hidden(scroll_layer, true); window_set_click_config_provider_with_context(window, click_config_provider, self); - simply_window_set_action_bar(self, self->is_action_bar); + simply_window_set_action_bar(self, self->use_action_bar); } bool simply_window_appear(SimplyWindow *self) { @@ -332,7 +357,7 @@ bool simply_window_disappear(SimplyWindow *self) { } #ifdef PBL_PLATFORM_BASALT - simply_window_set_fullscreen(self, true); + simply_window_set_status_bar(self, false); #endif return true; @@ -347,14 +372,13 @@ void simply_window_unload(SimplyWindow *self) { } static void prv_handle_window_props_packet(Simply *simply, Packet *data) { - WindowPropsPacket *packet = (WindowPropsPacket*) data; SimplyWindow *window = simply_window_stack_get_top_window(simply); if (!window) { return; } + WindowPropsPacket *packet = (WindowPropsPacket *)data; window->id = packet->id; simply_window_set_background_color(window, packet->background_color); - simply_window_set_fullscreen(window, packet->fullscreen); simply_window_set_scrollable(window, packet->scrollable); } @@ -367,12 +391,23 @@ static void prv_handle_window_button_config_packet(Simply *simply, Packet *data) window->button_mask = packet->button_mask; } +static void prv_handle_window_status_bar_packet(Simply *simply, Packet *data) { + SimplyWindow *window = simply_window_stack_get_top_window(simply); + if (!window) { + return; + } + WindowStatusBarPacket *packet = (WindowStatusBarPacket *)data; + simply_window_set_status_bar_colors(window, packet->background_color, packet->color); + simply_window_set_status_bar_separator_mode(window, packet->separator); + simply_window_set_status_bar(window, packet->status); +} + static void prv_handle_window_action_bar_packet(Simply *simply, Packet *data) { - WindowActionBarPacket *packet = (WindowActionBarPacket*) data; SimplyWindow *window = simply_window_stack_get_top_window(simply); if (!window) { return; } + WindowActionBarPacket *packet = (WindowActionBarPacket *)data; simply_window_set_action_bar_background_color(window, packet->background_color); for (unsigned int i = 0; i < ARRAY_LENGTH(packet->image); ++i) { simply_window_set_action_bar_icon(window, i + 1, packet->image[i]); @@ -388,6 +423,9 @@ bool simply_window_handle_packet(Simply *simply, Packet *packet) { case CommandWindowButtonConfig: prv_handle_window_button_config_packet(simply, packet); return true; + case CommandWindowStatusBar: + prv_handle_window_status_bar_packet(simply, packet); + return true; case CommandWindowActionBar: prv_handle_window_action_bar_packet(simply, packet); return true; @@ -409,8 +447,7 @@ SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { self->status_bar_layer = status_bar_layer_create(); status_bar_layer_set_separator_mode(self->status_bar_layer, StatusBarLayerSeparatorModeDotted); status_bar_layer_remove_from_window(self->window, self->status_bar_layer); - self->is_status_bar = false; - self->is_fullscreen = true; + self->use_status_bar = false; ActionBarLayer *action_bar_layer = self->action_bar_layer = action_bar_layer_create(); action_bar_layer_set_context(action_bar_layer, self); diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index f36c4a9c..6f080f1e 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -23,10 +23,9 @@ struct SimplyWindow { uint32_t id; ButtonId button_mask:4; GColor8 background_color; - bool is_fullscreen:1; bool is_scrollable:1; - bool is_action_bar:1; - bool is_status_bar:1; + bool use_status_bar:1; + bool use_action_bar:1; }; SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply); @@ -43,7 +42,7 @@ bool simply_window_disappear(SimplyWindow *self); void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *context); void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable); -void simply_window_set_fullscreen(SimplyWindow *self, bool is_fullscreen); +void simply_window_set_status_bar(SimplyWindow *self, bool use_status_bar); void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color); void simply_window_set_button(SimplyWindow *self, ButtonId button, bool enable); From 2c78d75e09a7ed31929fcede9cbc90e4b94425d4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 1 Dec 2015 03:01:48 -0800 Subject: [PATCH 673/791] Add simply window fullscreen property backwards compatibility --- src/js/ui/simply-pebble.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 39ab095e..4c9736ab 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1078,6 +1078,14 @@ SimplyPebble.windowStatusBar = function(def) { SimplyPebble.sendPacket(WindowStatusBarPacket); }; +SimplyPebble.windowStatusBarCompat = function(def) { + if (typeof def.fullscreen === 'boolean') { + SimplyPebble.windowStatusBar(!def.fullscreen); + } else if (def.status !== undefined) { + SimplyPebble.windowStatusBar(def.status); + } +}; + var toActionDef = function(actionDef) { if (typeof actionDef === 'boolean') { actionDef = { action: actionDef }; @@ -1145,9 +1153,7 @@ SimplyPebble.card = function(def, clear, pushing) { SimplyPebble.cardClear(clear); } SimplyPebble.windowProps(def); - if (def.status !== undefined) { - SimplyPebble.windowStatusBar(def.status); - } + SimplyPebble.windowStatusBarCompat(def); if (def.action !== undefined) { SimplyPebble.windowActionBar(def.action); } @@ -1278,6 +1284,7 @@ SimplyPebble.menu = function(def, clear, pushing) { SimplyPebble.menuClear(); } SimplyPebble.windowProps(def); + SimplyPebble.windowStatusBarCompat(def); SimplyPebble.menuProps(def); }; @@ -1366,6 +1373,7 @@ SimplyPebble.stage = function(def, clear, pushing) { SimplyPebble.windowShow({ type: 'window', pushing: pushing }); } SimplyPebble.windowProps(def); + SimplyPebble.windowStatusBarCompat(def); if (clear !== undefined) { SimplyPebble.stageClear(); } From 2135d83e95195b4fae352bfe8852d90da087943b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 2 Dec 2015 15:41:00 -0800 Subject: [PATCH 674/791] Add util platform for deciding based on platform This should be used sparingly, or as a building block to implement other feature based detection constructs. --- src/util/platform.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/util/platform.h diff --git a/src/util/platform.h b/src/util/platform.h new file mode 100644 index 00000000..764db647 --- /dev/null +++ b/src/util/platform.h @@ -0,0 +1,27 @@ +#pragma once + +#include "util/none.h" + +#if defined(PBL_PLATFORM_APLITE) || defined(PBL_SDK_2) +#define IF_APLITE_ELSE(aplite, other) aplite +#define APLITE_USAGE +#else +#define IF_APLITE_ELSE(aplite, other) other +#define APLITE_USAGE __attribute__((unused)) +#endif + +#if defined(PBL_PLATFORM_BASALT) +#define IF_BASALT_ELSE(basalt, other) basalt +#define BASALT_USAGE +#else +#define IF_BASALT_ELSE(basalt, other) other +#define BASALT_USAGE __attribute__((unused)) +#endif + +#if defined(PBL_PLATFORM_CHALK) +#define IF_CHALK_ELSE(chalk, other) chalk +#define CHALK_USAGE +#else +#define IF_CHALK_ELSE(chalk, other) other +#define CHALK_USAGE __attribute__((unused)) +#endif From 02ff0d8c85f04b7f1a902c6aff276edeefcf4a70 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 2 Dec 2015 15:42:02 -0800 Subject: [PATCH 675/791] Reduce the item cache size for aplite until a better memory model exists --- src/js/ui/menu.js | 3 ++- src/simply/simply_menu.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index d5c06d1b..1f8966a3 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -1,6 +1,7 @@ var util2 = require('util2'); var myutil = require('myutil'); var Emitter = require('emitter'); +var Platform = require('platform'); var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); var simply = require('ui/simply'); @@ -39,7 +40,7 @@ Menu.prototype._select = function() { } }; -Menu.prototype._numPreloadItems = 50; +Menu.prototype._numPreloadItems = (Platform.version() === 'aplite' ? 5 : 50); Menu.prototype._prop = function(state, clear, pushing) { if (this === WindowStack.top()) { diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index b5bcfb80..9b7e7e50 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -9,13 +9,14 @@ #include "util/color.h" #include "util/graphics.h" #include "util/menu_layer.h" +#include "util/platform.h" #include "util/string.h" #include #define MAX_CACHED_SECTIONS 10 -#define MAX_CACHED_ITEMS 51 +#define MAX_CACHED_ITEMS IF_APLITE_ELSE(6, 51) static const time_t SPINNER_MS = 66; From c18f034a55059c3a4e657e9c9f75dca4d3b86dd4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 9 Dec 2015 02:29:24 -0800 Subject: [PATCH 676/791] Tweak Voice dictate docs to use slightly different wording and the string typed error --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 03ae69a3..f542d6a0 100644 --- a/README.md +++ b/README.md @@ -488,7 +488,7 @@ var Voice = require('ui/voice'); #### Voice.dictate('start', [confirmDialog,] callback) -This function starts the dictation UI, and invokes the callback upon completion. The callback is invoked with an event with the following fields: +This function starts the dictation UI, and invokes the callback upon completion. The callback is passed an event with the following fields: * `err`: A string describing the error, or `null` on success. * `transcription`: The transcribed string. @@ -507,7 +507,7 @@ Voice.dictate('start', false, function(e) { }); ``` -**NOTE:** Only one dictation session can be active at any time. Trying to call `Voice.dicate('start', ...)` while another dictation session is in progress will result in the callback being called immediatly with status = -1. +**NOTE:** Only one dictation session can be active at any time. Trying to call `Voice.dicate('start', ...)` while another dictation session is in progress will result in the callback being called with an event having the error `"sessionAlreadyInProgress"`. #### Voice.dictate('stop') From 2a757cdc279efb24962480c555ac2dc21ad2c5f5 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 9 Dec 2015 02:37:40 -0800 Subject: [PATCH 677/791] Clarify what the confirmation dialog is, and explain the default. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f542d6a0..94d57ddd 100644 --- a/README.md +++ b/README.md @@ -493,7 +493,7 @@ This function starts the dictation UI, and invokes the callback upon completion. * `err`: A string describing the error, or `null` on success. * `transcription`: The transcribed string. -An optional second parameter - `confirmDialog` - can be passed to the `Voice.dictate` method to control the behaviour of the confirmation dialog. If `confirmDialog` is set to `false`, the confirmation page will be skipped, otherwise it will be included in the dictation flow. +An optional second parameter, `confirmDialog`, can be passed to the `Voice.dictate` method to control whether there should be a confirmation dialog displaying the transcription text after voice input. If `confirmDialog` is set to `false`, the confirmation dialog will be skipped. By default, there will be a confirmation dialog. ```js // Start a diction session and skip confirmation From a8bb4ca603ba761f583ed65ab6dac2f3e4ac6b9f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Fri, 11 Dec 2015 05:23:44 -0800 Subject: [PATCH 678/791] Add 3.x on aplite compile compatibility 3.x on aplite already has the GColor8 struct, so those constructs are no longer to be tested for existence using PBL_COLOR. The png format now refers to the output type rather than the input type, and thus images are changed to the bitmap type for minimum memory usage. --- appinfo.json | 6 +++--- src/util/color.h | 2 +- src/util/compat.h | 4 ++-- src/util/graphics.h | 2 +- src/util/inverter_layer.h | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/appinfo.json b/appinfo.json index dcfa9dfe..dc8624ea 100644 --- a/appinfo.json +++ b/appinfo.json @@ -16,17 +16,17 @@ "media": [ { "menuIcon": true, - "type": "png", + "type": "bitmap", "name": "IMAGE_MENU_ICON", "file": "images/menu_icon.png" }, { - "type": "png", + "type": "bitmap", "name": "IMAGE_LOGO_SPLASH", "file": "images/logo_splash.png" }, { - "type": "png", + "type": "bitmap", "name": "IMAGE_TILE_SPLASH", "file": "images/tile_splash.png" }, diff --git a/src/util/color.h b/src/util/color.h index ad367fa4..245b6e23 100644 --- a/src/util/color.h +++ b/src/util/color.h @@ -9,7 +9,7 @@ #define GColor8Clear (GColor8){.argb=GColorClearARGB8} #define GColor8ClearWhite (GColor8){.argb=0x3F} -#ifndef PBL_COLOR +#ifndef PBL_SDK_3 static inline GColor gcolor8_get(GColor8 color) { switch (color.argb) { diff --git a/src/util/compat.h b/src/util/compat.h index db392119..fec92703 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -32,8 +32,8 @@ static inline GBitmap *gbitmap_create_blank_with_format(GSize size, GBitmapForma #endif -// Compatibility definitions for aplite on all versions -#ifndef PBL_COLOR +// Compatibility definitions for aplite on 2.9 including those bundled in 3.x SDKs +#ifndef PBL_SDK_3 #define GBitmapFormat8Bit 1 #define GBitmapFormat1BitPalette 2 diff --git a/src/util/graphics.h b/src/util/graphics.h index ac577e82..4d055adc 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -5,7 +5,7 @@ #include -#ifndef PBL_COLOR +#ifndef PBL_SDK_3 #define GCompOpAlphaBlend GCompOpAnd diff --git a/src/util/inverter_layer.h b/src/util/inverter_layer.h index b8807d03..f14364fe 100644 --- a/src/util/inverter_layer.h +++ b/src/util/inverter_layer.h @@ -4,7 +4,7 @@ #include "util/math.h" -#ifdef PBL_COLOR +#ifdef PBL_SDK_3 typedef struct InverterLayer InverterLayer; struct InverterLayer; From 80d542fc25bda273e19cecd4a3e4308de57d5cb0 Mon Sep 17 00:00:00 2001 From: Stephen Niedzielski Date: Sun, 13 Dec 2015 13:53:06 -0700 Subject: [PATCH 679/791] Use app directory instead of file Make the project template more encouraging of multi-file projects by moving app.js to app/index.js and updating the require. --- README.md | 2 +- src/js/{app.js => app/index.js} | 0 src/js/main.js | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/js/{app.js => app/index.js} (100%) diff --git a/README.md b/README.md index cbb0e371..df134a46 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Pebble.js applications run on your phone. They have access to all the resources This option allows you to customize Pebble.js. Follow the [Pebble SDK installation instructions](https://developer.pebble.com/sdk/install/) to install the SDK on your computer and [fork this project](http://github.com/pebble/pebblejs) on Github. - The main entry point for your application is in the `src/js/app.js` file. + The main entry point for your application is in the `src/js/app/index.js` file. [Install the Pebble SDK on your computer >](http://developer.pebble.com/sdk/install/) diff --git a/src/js/app.js b/src/js/app/index.js similarity index 100% rename from src/js/app.js rename to src/js/app/index.js diff --git a/src/js/main.js b/src/js/main.js index 10301593..9ebc83a3 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -3,7 +3,7 @@ * you want to change the way PebbleJS starts, the script it runs or the libraries * it loads. * - * By default, this will run app.js + * By default, this will run app/index.js */ var safe = require('safe'); @@ -38,5 +38,5 @@ Pebble.addEventListener('ready', function(e) { window.moment = globalMoment; // Load local file - require('app.js'); + require('./app'); }); From f622255dc2c6695b785213c8d69cb57ae5d8a5e9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 13 Dec 2015 14:00:30 -0800 Subject: [PATCH 680/791] Add pebble sdk version for detecting sdk features --- waftools/pebble_sdk_version.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 waftools/pebble_sdk_version.py diff --git a/waftools/pebble_sdk_version.py b/waftools/pebble_sdk_version.py new file mode 100644 index 00000000..35a38431 --- /dev/null +++ b/waftools/pebble_sdk_version.py @@ -0,0 +1,16 @@ +from waflib.Configure import conf + + +@conf +def compare_sdk_version(ctx, platform, version): + target_env = ctx.all_envs[platform] if platform in ctx.all_envs else ctx.env + target_version = (int(target_env.SDK_VERSION_MAJOR or 0x5) * 0xff + + int(target_env.SDK_VERSION_MINOR or 0x19)) + other_version = int(version[0]) * 0xff + int(version[1]) + diff_version = target_version - other_version + return 0 if diff_version == 0 else diff_version / abs(diff_version) + + +@conf +def supports_bitmap_resource(ctx): + return (ctx.compare_sdk_version('aplite', [0x5, 0x48]) >= 0) From 25abc6eb0498e629160543d37d43a8dfa665397a Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 13 Dec 2015 14:00:50 -0800 Subject: [PATCH 681/791] Add configure appinfo waftool for transforming the appinfo --- waftools/configure_appinfo.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 waftools/configure_appinfo.py diff --git a/waftools/configure_appinfo.py b/waftools/configure_appinfo.py new file mode 100644 index 00000000..d14f0e46 --- /dev/null +++ b/waftools/configure_appinfo.py @@ -0,0 +1,15 @@ +import json + +from waflib.Configure import conf + + +@conf +def configure_appinfo(ctx, transforms): + with open('appinfo.json', 'r') as appinfo_file: + appinfo_json = json.load(appinfo_file) + + for transform in transforms: + transform(appinfo_json) + + with open('appinfo.json', 'w') as appinfo_file: + json.dump(appinfo_json, appinfo_file, indent=2, sort_keys=True, separators=(',', ': ')) From 7eeddff9a1648262f584334beebf20f7c830a74f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 13 Dec 2015 14:01:24 -0800 Subject: [PATCH 682/791] Convert bitmap resources to png for older SDKs --- waftools/aplite_legacy.py | 9 +++++++++ wscript | 6 ++++++ 2 files changed, 15 insertions(+) create mode 100644 waftools/aplite_legacy.py diff --git a/waftools/aplite_legacy.py b/waftools/aplite_legacy.py new file mode 100644 index 00000000..624f19fd --- /dev/null +++ b/waftools/aplite_legacy.py @@ -0,0 +1,9 @@ +from waflib.Configure import conf + + +@conf +def appinfo_bitmap_to_png(ctx, appinfo_json): + if not ctx.supports_bitmap_resource(): + for res in appinfo_json['resources']['media']: + if res['type'] == 'bitmap': + res['type'] = 'png' diff --git a/wscript b/wscript index 203f7943..830e2003 100644 --- a/wscript +++ b/wscript @@ -10,9 +10,15 @@ out = 'build' def options(ctx): ctx.load('pebble_sdk') + ctx.load('aplite_legacy', tooldir='waftools') + ctx.load('configure_appinfo', tooldir='waftools') + ctx.load('pebble_sdk_version', tooldir='waftools') + def configure(ctx): ctx.load('pebble_sdk') + ctx.configure_appinfo([ctx.appinfo_bitmap_to_png]) + if ctx.env.TARGET_PLATFORMS: for platform in ctx.env.TARGET_PLATFORMS: ctx.configure_platform(platform) From 11df22a3498b17c62fcb3092f32b1f4fe22cac6c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 13 Dec 2015 14:02:14 -0800 Subject: [PATCH 683/791] Ignore compiled python files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0f5add12..574622dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +*.pyc /.lock-* /build/ /out/ From fd2bc670ad176ecee9d9e0f93464890eb71e2234 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 13 Dec 2015 14:32:27 -0800 Subject: [PATCH 684/791] Reformat appinfo.json following the configure appinfo output --- appinfo.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/appinfo.json b/appinfo.json index dc8624ea..64029118 100644 --- a/appinfo.json +++ b/appinfo.json @@ -1,46 +1,46 @@ { - "uuid": "133215f0-cf20-4c05-997b-3c9be5a64e5b", - "shortName": "Pebble.js", - "longName": "Pebble.js", - "companyName": "Meiguro", - "versionCode": 1, - "versionLabel": "0.4", + "appKeys": {}, "capabilities": [ "configurable" ], - "watchapp": { - "watchface": false - }, - "appKeys": {}, + "companyName": "Meiguro", + "longName": "Pebble.js", "resources": { "media": [ { + "file": "images/menu_icon.png", "menuIcon": true, - "type": "bitmap", "name": "IMAGE_MENU_ICON", - "file": "images/menu_icon.png" + "type": "bitmap" }, { - "type": "bitmap", + "file": "images/logo_splash.png", "name": "IMAGE_LOGO_SPLASH", - "file": "images/logo_splash.png" + "type": "bitmap" }, { - "type": "bitmap", + "file": "images/tile_splash.png", "name": "IMAGE_TILE_SPLASH", - "file": "images/tile_splash.png" + "type": "bitmap" }, { - "type": "font", + "file": "fonts/UbuntuMono-Regular.ttf", "name": "MONO_FONT_14", - "file": "fonts/UbuntuMono-Regular.ttf" + "type": "font" } ] }, + "sdkVersion": "3", + "shortName": "Pebble.js", "targetPlatforms": [ "aplite", "basalt", "chalk" ], - "sdkVersion": "3" -} + "uuid": "133215f0-cf20-4c05-997b-3c9be5a64e5b", + "versionCode": 1, + "versionLabel": "0.4", + "watchapp": { + "watchface": false + } +} \ No newline at end of file From 828a97a2180a0013616374c644303ad6ace41409 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 13 Dec 2015 14:05:12 -0800 Subject: [PATCH 685/791] Fix wscript formatting to follow PEP8 --- wscript | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/wscript b/wscript index 830e2003..aad84847 100644 --- a/wscript +++ b/wscript @@ -7,6 +7,7 @@ from waflib.Configure import conf top = '.' out = 'build' + def options(ctx): ctx.load('pebble_sdk') @@ -14,6 +15,7 @@ def options(ctx): ctx.load('configure_appinfo', tooldir='waftools') ctx.load('pebble_sdk_version', tooldir='waftools') + def configure(ctx): ctx.load('pebble_sdk') @@ -25,6 +27,7 @@ def configure(ctx): else: ctx.configure_platform() + def build(ctx): ctx.load('pebble_sdk') @@ -46,13 +49,14 @@ def build(ctx): worker_elf=elfs['worker_elf'] if 'worker_elf' in elfs else None, js=js_target) + @conf def configure_platform(ctx, platform=None): if platform is not None: ctx.setenv(platform, ctx.all_envs[platform]) cflags = ctx.env.CFLAGS - cflags = [ x for x in cflags if not x.startswith('-std=') ] + cflags = [x for x in cflags if not x.startswith('-std=')] cflags.extend(['-std=c11', '-fms-extensions', '-Wno-address', @@ -61,6 +65,7 @@ def configure_platform(ctx, platform=None): ctx.env.CFLAGS = cflags + @conf def build_platform(ctx, platform=None, binaries=None): if platform is not None: @@ -68,18 +73,19 @@ def build_platform(ctx, platform=None, binaries=None): build_worker = os.path.exists('worker_src') - app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) + app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), target=app_elf) if build_worker: - worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) + worker_elf = '{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) binaries.append({'platform': platform, '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': platform, 'app_elf': app_elf}) + @conf def concat_javascript(ctx, js_path=None): js_nodes = (ctx.path.ant_glob(js_path + '/**/*.js') + @@ -98,9 +104,9 @@ def concat_javascript(ctx, js_path=None): def loader_translate(source, lineno): return LOADER_TEMPLATE.format( - relpath=json.dumps(source['relpath']), - lineno=lineno, - body=source['body']) + relpath=json.dumps(source['relpath']), + lineno=lineno, + body=source['body']) def coffeescript_compile(relpath, body): try: @@ -135,11 +141,11 @@ def concat_javascript(ctx, js_path=None): if relpath == LOADER_PATH: sources.insert(0, body) else: - sources.append({ 'relpath': relpath, 'body': body }) + sources.append({'relpath': relpath, 'body': body}) with open(APPINFO_PATH, 'r') as f: body = JSON_TEMPLATE.format(body=f.read()) - sources.append({ 'relpath': APPINFO_PATH, 'body': body }) + sources.append({'relpath': APPINFO_PATH, 'body': body}) sources.append('__loader.require("main");') From 4639936e299791b3e060e507313827d3950cd360 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 13 Dec 2015 17:08:09 -0800 Subject: [PATCH 686/791] Change travis to use the new pebble tool for installing the SDK --- .travis.yml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1f2f5529..dd9a6349 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,23 +5,26 @@ python: env: - PEBBLE_SDK_VERSION=2.9 - - PEBBLE_SDK_VERSION=3.6 + - PEBBLE_SDK_VERSION=3.7 + - PEBBLE_SDK_VERSION=3.8-beta10 before_install: - - wget https://sdk.getpebble.com/download/$PEBBLE_SDK_VERSION?source=travis -O PebbleSDK-$PEBBLE_SDK_VERSION.tar.gz - - wget http://assets.getpebble.com.s3-website-us-east-1.amazonaws.com/sdk/arm-cs-tools-ubuntu-universal.tar.gz - - mkdir -p ~/pebble-dev - - tar -zxf PebbleSDK-$PEBBLE_SDK_VERSION.tar.gz -C ~/pebble-dev - - tar -zxf arm-cs-tools-ubuntu-universal.tar.gz -C ~/pebble-dev/PebbleSDK-$PEBBLE_SDK_VERSION - - touch ~/pebble-dev/ENABLE_ANALYTICS + - wget https://github.com/pebble/pebble-tool/releases/download/v4.0-rc5/pebble-sdk-4.0-rc5-linux64.tar.bz2?source=travis + - mkdir -p ~/.pebble-sdk + - tar -jxf pebble-sdk-* -C ~/.pebble-sdk + - touch ~/.pebble-sdk/ENABLE_ANALYTICS + - export PEBBLE_SDK=~/.pebble-sdk/pebble-sdk-* + - export PEBBLE=$PEBBLE_SDK/bin/pebble install: - - pushd ~/pebble-dev/PebbleSDK-$PEBBLE_SDK_VERSION + - pushd $PEBBLE_SDK - virtualenv --no-site-packages .env - source .env/bin/activate - pip install -r requirements.txt - deactivate - popd + - $PEBBLE sdk set-channel beta + - yes | $PEBBLE sdk install $PEBBLE_SDK_VERSION script: - - ~/pebble-dev/PebbleSDK-$PEBBLE_SDK_VERSION/bin/pebble build + - $PEBBLE build From 5606ec2bad9cb326849832fdc982e839983614ea Mon Sep 17 00:00:00 2001 From: Stephen Niedzielski Date: Tue, 15 Dec 2015 00:02:01 -0700 Subject: [PATCH 687/791] Default to single file project --- README.md | 2 +- src/js/{app/index.js => app.js} | 0 src/js/main.js | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/js/{app/index.js => app.js} (100%) diff --git a/README.md b/README.md index df134a46..7c21ad6e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Pebble.js applications run on your phone. They have access to all the resources This option allows you to customize Pebble.js. Follow the [Pebble SDK installation instructions](https://developer.pebble.com/sdk/install/) to install the SDK on your computer and [fork this project](http://github.com/pebble/pebblejs) on Github. - The main entry point for your application is in the `src/js/app/index.js` file. + The main entry point for your application is in the `src/js/app.js` file. For projects with multiple files, you may use `src/app/index.js` instead and create new files under `src/app`. [Install the Pebble SDK on your computer >](http://developer.pebble.com/sdk/install/) diff --git a/src/js/app/index.js b/src/js/app.js similarity index 100% rename from src/js/app/index.js rename to src/js/app.js diff --git a/src/js/main.js b/src/js/main.js index 9ebc83a3..6e598de0 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -3,7 +3,7 @@ * you want to change the way PebbleJS starts, the script it runs or the libraries * it loads. * - * By default, this will run app/index.js + * By default, this will run app.js */ var safe = require('safe'); From 89f384cabc93b24ae638a7c4749e730c3eba3b20 Mon Sep 17 00:00:00 2001 From: Stephen Niedzielski Date: Tue, 15 Dec 2015 00:29:33 -0700 Subject: [PATCH 688/791] Fix path in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c21ad6e..485a8f3f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Pebble.js applications run on your phone. They have access to all the resources This option allows you to customize Pebble.js. Follow the [Pebble SDK installation instructions](https://developer.pebble.com/sdk/install/) to install the SDK on your computer and [fork this project](http://github.com/pebble/pebblejs) on Github. - The main entry point for your application is in the `src/js/app.js` file. For projects with multiple files, you may use `src/app/index.js` instead and create new files under `src/app`. + The main entry point for your application is in the `src/js/app.js` file. For projects with multiple files, you may use `src/js/app/index.js` instead and create new files under `src/js/app`. [Install the Pebble SDK on your computer >](http://developer.pebble.com/sdk/install/) From a10717b7fd87ff63e0b6593bac43415d85d8b9ac Mon Sep 17 00:00:00 2001 From: Stephen Niedzielski Date: Tue, 15 Dec 2015 00:31:33 -0700 Subject: [PATCH 689/791] Clarify that app.js xor app/ can exist --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 485a8f3f..2e949a77 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Pebble.js applications run on your phone. They have access to all the resources This option allows you to customize Pebble.js. Follow the [Pebble SDK installation instructions](https://developer.pebble.com/sdk/install/) to install the SDK on your computer and [fork this project](http://github.com/pebble/pebblejs) on Github. - The main entry point for your application is in the `src/js/app.js` file. For projects with multiple files, you may use `src/js/app/index.js` instead and create new files under `src/js/app`. + The main entry point for your application is in the `src/js/app.js` file. For projects with multiple files, you may move `src/js/app.js` to `src/js/app/index.js` instead and create new files under `src/js/app`. [Install the Pebble SDK on your computer >](http://developer.pebble.com/sdk/install/) From 054fbf06c47e0635f4c5e7a2fa419b244f21fc4b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 15 Dec 2015 03:42:16 -0800 Subject: [PATCH 690/791] Fix simply res to actually free the image --- src/simply/simply_res.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 9a7ece91..79ac5a77 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -20,6 +20,7 @@ static void destroy_image(SimplyRes *self, SimplyImage *image) { list1_remove(&self->images, &image->node); gbitmap_destroy(image->bitmap); free(image->palette); + free(image); } static void destroy_font(SimplyRes *self, SimplyFont *font) { From d527347b9116dfc377b6a9c6df0ffebb7b2037c7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 15 Dec 2015 03:42:59 -0800 Subject: [PATCH 691/791] Move all simply window layer create, destroy to load, unload 3.x aplite layers use more memory than their 2.x counterparts. There isn't enough memory to have any window layers preloaded, so change all simply window layers and pebble windows themselves to be allocated on demand. Also destroy when unused immediately on aplite only. --- src/simply/simply_window.c | 38 +++++++++++++++++++------------- src/simply/simply_window_stack.c | 3 ++- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 4de686e0..ad8f591e 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -199,6 +199,10 @@ void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor8 b } void simply_window_action_bar_clear(SimplyWindow *self) { + if (!self->action_bar_layer) { + return; + } + simply_window_set_action_bar(self, false); for (ButtonId button = BUTTON_ID_UP; button <= BUTTON_ID_DOWN; ++button) { @@ -274,12 +278,18 @@ void simply_window_load(SimplyWindow *self) { GRect frame = layer_get_frame(window_layer); frame.origin = GPointZero; - ScrollLayer *scroll_layer = self->scroll_layer = scroll_layer_create(frame); - Layer *scroll_base_layer = scroll_layer_get_layer(scroll_layer); + self->scroll_layer = scroll_layer_create(frame); + Layer *scroll_base_layer = scroll_layer_get_layer(self->scroll_layer); layer_add_child(window_layer, scroll_base_layer); - scroll_layer_set_context(scroll_layer, self); - scroll_layer_set_shadow_hidden(scroll_layer, true); + scroll_layer_set_context(self->scroll_layer, self); + scroll_layer_set_shadow_hidden(self->scroll_layer, true); + + self->status_bar_layer = status_bar_layer_create(); + status_bar_layer_remove_from_window(self->window, self->status_bar_layer); + + self->action_bar_layer = action_bar_layer_create(); + action_bar_layer_set_context(self->action_bar_layer, self); window_set_click_config_provider_with_context(window, click_config_provider, self); simply_window_set_action_bar(self, self->is_action_bar); @@ -320,6 +330,15 @@ void simply_window_unload(SimplyWindow *self) { scroll_layer_destroy(self->scroll_layer); self->scroll_layer = NULL; + + action_bar_layer_destroy(self->action_bar_layer); + self->action_bar_layer = NULL; + + status_bar_layer_destroy(self->status_bar_layer); + self->status_bar_layer = NULL; + + window_destroy(self->window); + self->window = NULL; } static void handle_window_props_packet(Simply *simply, Packet *data) { @@ -382,14 +401,9 @@ SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { simply_window_preload(self); - self->status_bar_layer = status_bar_layer_create(); - status_bar_layer_remove_from_window(self->window, self->status_bar_layer); self->is_status_bar = false; self->is_fullscreen = true; - ActionBarLayer *action_bar_layer = self->action_bar_layer = action_bar_layer_create(); - action_bar_layer_set_context(action_bar_layer, self); - return self; } @@ -398,12 +412,6 @@ void simply_window_deinit(SimplyWindow *self) { return; } - action_bar_layer_destroy(self->action_bar_layer); - self->action_bar_layer = NULL; - - status_bar_layer_destroy(self->status_bar_layer); - self->status_bar_layer = NULL; - window_destroy(self->window); self->window = NULL; } diff --git a/src/simply/simply_window_stack.c b/src/simply/simply_window_stack.c index ceb67297..0c3ca8aa 100644 --- a/src/simply/simply_window_stack.c +++ b/src/simply/simply_window_stack.c @@ -7,6 +7,7 @@ #include "util/math.h" #include "util/none.h" +#include "util/platform.h" #include "util/sdk.h" #include @@ -104,7 +105,7 @@ static void show_window_sdk_3(SimplyWindowStack *self, SimplyWindow *window, boo window_stack_push(window->window, animated); - if (animated) { + if (IF_APLITE_ELSE(true, animated)) { window_stack_remove(prev_window, animated); } } From a4d8eec177e9c724a45d55c279e10eb3a12d25da Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 15 Dec 2015 03:46:06 -0800 Subject: [PATCH 692/791] Reduce the aplite inbound buffer to 1024 bytes 3.x aplite is more memory constrained. Reduce the inbound buffer to recoup some of the losses. --- src/js/ui/simply-pebble.js | 3 ++- src/simply/simply_msg.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 128f5593..cf47454e 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1,6 +1,7 @@ var struct = require('struct'); var util2 = require('util2'); var myutil = require('myutil'); +var Platform = require('platform'); var Wakeup = require('wakeup'); var Timeline = require('timeline'); var Resource = require('ui/resource'); @@ -971,7 +972,7 @@ var PacketQueue = function() { this._send = this.send.bind(this); }; -PacketQueue.prototype._maxPayloadSize = 2044 - 32; +PacketQueue.prototype._maxPayloadSize = (Platform.version() === 'aplite' ? 1024 : 2044) - 32; PacketQueue.prototype.add = function(packet) { var byteArray = toByteArray(packet); diff --git a/src/simply/simply_msg.c b/src/simply/simply_msg.c index 4f7feabc..dcfc8065 100644 --- a/src/simply/simply_msg.c +++ b/src/simply/simply_msg.c @@ -15,14 +15,14 @@ #include "util/list1.h" #include "util/math.h" #include "util/memory.h" +#include "util/platform.h" #include "util/string.h" #include #define SEND_DELAY_MS 10 -static const size_t APP_MSG_SIZE_INBOUND = 2044; - +static const size_t APP_MSG_SIZE_INBOUND = IF_APLITE_ELSE(1024, 2044); static const size_t APP_MSG_SIZE_OUTBOUND = 1024; typedef enum VibeType VibeType; From 59fa17f214723f3e6f07ea7eb97d5e3ced8b0d2e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 15 Dec 2015 04:01:04 -0800 Subject: [PATCH 693/791] Implement the voice module on aplite using only JavaScript 3.x aplite is more memory constrained. There is enough information on the JavaScript side to predict the behavior of the voice module, so implement it entirely in JavaScript. The voice module on aplite only errors with 'noMicrophone'. This pushes the error logic from C to SimplyPebble, saving code space for aplite. --- src/js/ui/simply-pebble.js | 40 ++++++++++++++++++++------------------ src/js/ui/voice.js | 3 +-- src/simply/simply_voice.c | 18 +++-------------- src/simply/simply_voice.h | 13 +++++++++++++ 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index cf47454e..81415c37 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1149,32 +1149,34 @@ SimplyPebble.accelConfig = function(def) { }; SimplyPebble.voiceDictationStart = function(callback, enableConfirmation) { - // If there's a transcription in progress - if (SimplyPebble.dictationCallback) { - // Create the eror event - var e = { + if (Platform.version() === 'aplite') { + // If there is no microphone, call with an error event + callback({ + 'err': DictationSessionStatus[65], // noMicrophone + 'failed': true, + 'transcription': null, + }); + return; + } else if (state.dictationCallback) { + // If there's a transcription in progress, call with an error event + callback({ 'err': DictationSessionStatus[64], // dictationAlreadyInProgress 'failed': true, 'transcription': null, - }; - - // Invoke the callback and return - callback(e); + }); return; } // Set the callback and send the packet state.dictationCallback = callback; SimplyPebble.sendPacket(VoiceDictationStartPacket.enableConfirmation(enableConfirmation)); -} +}; SimplyPebble.voiceDictationStop = function() { - // Send the message + // Send the message and delete the callback SimplyPebble.sendPacket(VoiceDictationStopPacket); - - // Clear the callback variable - state.dictationCallback = null; -} + delete state.dictationCallback; +}; SimplyPebble.onVoiceData = function(packet) { if (!state.dictationCallback) { @@ -1182,15 +1184,15 @@ SimplyPebble.onVoiceData = function(packet) { console.log("No callback specified for dictation session"); } else { var e = { - 'err': DictationSessionStatus[packet.status()], - 'failed': packet.status() != 0, + 'err': DictationSessionStatus[packet.status()], + 'failed': packet.status() !== 0, 'transcription': packet.transcription(), }; - // invoke and clear the callback + // Invoke and delete the callback state.dictationCallback(e); - state.dictationCallback = null; + delete state.dictationCallback; } -} +}; SimplyPebble.menuClear = function() { SimplyPebble.sendPacket(MenuClearPacket); diff --git a/src/js/ui/voice.js b/src/js/ui/voice.js index 5b4d887d..609ae76f 100644 --- a/src/js/ui/voice.js +++ b/src/js/ui/voice.js @@ -19,7 +19,6 @@ Voice.dictate = function(type, confirm, callback) { default: console.log('Unsupported type passed to Voice.dictate'); } - }; -module.exports = Voice; \ No newline at end of file +module.exports = Voice; diff --git a/src/simply/simply_voice.c b/src/simply/simply_voice.c index 7670e871..d3f58ee8 100644 --- a/src/simply/simply_voice.c +++ b/src/simply/simply_voice.c @@ -6,6 +6,7 @@ #include +#if !defined(PBL_PLATFORM_APLITE) typedef struct VoiceStartPacket VoiceStartPacket; struct __attribute__((__packed__)) VoiceStartPacket { @@ -33,7 +34,7 @@ static bool send_voice_data(int status, char *transcription) { // Handle success case size_t transcription_length = strlen(transcription) + 1; size_t packet_length = sizeof(VoiceDataPacket) + transcription_length; - + uint8_t buffer[packet_length]; VoiceDataPacket *packet = (VoiceDataPacket *)buffer; *packet = (VoiceDataPacket) { @@ -47,7 +48,6 @@ static bool send_voice_data(int status, char *transcription) { return simply_msg_send_packet(&packet->packet); } -#ifndef PBL_SDK_2 // Define a callback for the dictation session static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) { @@ -56,7 +56,6 @@ static void dictation_session_callback(DictationSession *session, DictationSessi // Send the result send_voice_data(status, transcription); } -#endif static void timer_callback_start_dictation(void *data) { dictation_session_start(s_voice->session); @@ -64,12 +63,6 @@ static void timer_callback_start_dictation(void *data) { static void handle_voice_start_packet(Simply *simply, Packet *data) { - #ifdef PBL_SDK_2 - // send an immediate reply if we don't support voice - // Status 65 = NoMicrophone - send_voice_data(65, ""); - #else - // Send an immediate response if there's already a dictation session in progress // Status 64 = SessionAlreadyInProgress if (s_voice->in_progress) { @@ -84,16 +77,12 @@ static void handle_voice_start_packet(Simply *simply, Packet *data) { VoiceStartPacket *packet = (VoiceStartPacket*) data; dictation_session_enable_confirmation(s_voice->session, packet->enable_confirmation); s_voice->timer = app_timer_register(0, timer_callback_start_dictation, NULL); - #endif } static void handle_voice_stop_packet(Simply *simply, Packet *data) { - // Do nothing for SDK 2 - #ifndef PBL_SDK_2 // Stop the session and clear the in_progress flag dictation_session_stop(s_voice->session); s_voice->in_progress = false; - #endif } bool simply_voice_handle_packet(Simply *simply, Packet *packet) { @@ -120,9 +109,7 @@ SimplyVoice *simply_voice_create(Simply *simply) { .in_progress = false, }; - #ifndef PBL_SDK_2 self->session = dictation_session_create(SIMPLY_VOICE_BUFFER_LENGTH, dictation_session_callback, NULL); - #endif s_voice = self; return self; @@ -140,3 +127,4 @@ void simply_voice_destroy(SimplyVoice *self) { bool simply_voice_dictation_in_progress() { return s_voice->in_progress; } +#endif diff --git a/src/simply/simply_voice.h b/src/simply/simply_voice.h index 890c590b..b6aee7de 100644 --- a/src/simply/simply_voice.h +++ b/src/simply/simply_voice.h @@ -18,9 +18,22 @@ struct SimplyVoice { bool in_progress; }; +#if defined(PBL_PLATFORM_APLITE) + +#define simply_voice_create(simply) NULL +#define simply_voice_destroy(self) + +#define simply_voice_handle_packet(simply, packet) (false) + +#define simply_voice_dictation_in_progress() (false) + +#else + SimplyVoice *simply_voice_create(Simply *simply); void simply_voice_destroy(SimplyVoice *self); bool simply_voice_handle_packet(Simply *simply, Packet *packet); bool simply_voice_dictation_in_progress(); + +#endif From 37a1663542498b1601b8b02568027834abc6e207 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 26 Dec 2015 01:26:40 -0800 Subject: [PATCH 694/791] Fix simply window set background color to check for existence of status bar --- src/simply/simply_window.c | 89 ++++++++++++++----------------------- src/util/status_bar_layer.h | 16 ++++--- 2 files changed, 44 insertions(+), 61 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 02842f75..2bb47de7 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -85,26 +85,20 @@ static bool prv_send_long_click(SimplyMsg *self, ButtonId button) { } static void prv_set_scroll_layer_click_config(SimplyWindow *self) { - if (!self->scroll_layer) { - return; + if (self->scroll_layer) { + scroll_layer_set_click_config_provider_onto_window( + self->scroll_layer, click_config_provider, self->window, self); } - - scroll_layer_set_click_config_provider_onto_window( - self->scroll_layer, click_config_provider, self->window, self); } void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { - if (self->is_scrollable == is_scrollable) { - return; - } + if (self->is_scrollable == is_scrollable) { return; } self->is_scrollable = is_scrollable; prv_set_scroll_layer_click_config(self); - if (!self->layer) { - return; - } + if (!self->layer) { return; } if (!is_scrollable) { GRect bounds = { GPointZero, layer_get_bounds(window_get_root_layer(self->window)).size }; @@ -119,11 +113,11 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { static void prv_update_layer_placement(SimplyWindow *self) { Layer * const main_layer = self->layer ?: scroll_layer_get_layer(self->scroll_layer); - if (!main_layer) { - return; - } + if (!main_layer) { return; } + GRect frame = { .size = layer_get_frame(window_get_root_layer(self->window)).size }; - IF_SDK_3_ELSE(({ + + if (self->status_bar_layer) { Layer * const status_bar_base_layer = status_bar_layer_get_layer(self->status_bar_layer); const bool has_status_bar = (layer_get_window(status_bar_base_layer) != NULL); const bool has_action_bar = @@ -137,7 +131,8 @@ static void prv_update_layer_placement(SimplyWindow *self) { } layer_set_frame(status_bar_base_layer, status_frame); } - }), NONE); + } + layer_set_frame(main_layer, frame); } @@ -155,16 +150,12 @@ void simply_window_set_status_bar(SimplyWindow *self, bool use_status_bar) { changed = true; } - if (!changed) { - return; - } + if (!changed) { return; } prv_update_layer_placement(self); #ifdef PBL_SDK_2 - if (!window_stack_contains_window(self->window)) { - return; - } + if (!window_stack_contains_window(self->window)) { return; } // HACK: Refresh app chrome state uint32_t id = self->id; @@ -179,9 +170,11 @@ void simply_window_set_status_bar(SimplyWindow *self, bool use_status_bar) { void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color) { self->background_color = background_color; - status_bar_layer_set_colors(self->status_bar_layer, background_color, - gcolor_legible_over(background_color)); window_set_background_color(self->window, gcolor8_get_or(background_color, GColorBlack)); + if (self->status_bar_layer) { + status_bar_layer_set_colors(self->status_bar_layer, background_color, + gcolor_legible_over(background_color)); + } } void simply_window_set_status_bar_colors(SimplyWindow *self, GColor8 background_color, @@ -201,9 +194,7 @@ void simply_window_set_status_bar_separator_mode(SimplyWindow *self, void simply_window_set_action_bar(SimplyWindow *self, bool use_action_bar) { self->use_action_bar = use_action_bar; - if (!self->action_bar_layer) { - return; - } + if (!self->action_bar_layer) { return; } action_bar_layer_remove_from_window(self->action_bar_layer); prv_set_scroll_layer_click_config(self); @@ -218,9 +209,7 @@ void simply_window_set_action_bar(SimplyWindow *self, bool use_action_bar) { } void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint32_t id) { - if (!self->action_bar_layer) { - return; - } + if (!self->action_bar_layer) { return; } SimplyImage *icon = simply_res_auto_image(self->simply->res, id, true); @@ -238,9 +227,7 @@ void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint } void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor8 background_color) { - if (!self->action_bar_layer) { - return; - } + if (!self->action_bar_layer) { return; } s_button_palette[0] = gcolor8_equal(background_color, GColor8White) ? GColor8Black : GColor8White; @@ -249,9 +236,7 @@ void simply_window_set_action_bar_background_color(SimplyWindow *self, GColor8 b } void simply_window_action_bar_clear(SimplyWindow *self) { - if (!self->action_bar_layer) { - return; - } + if (!self->action_bar_layer) { return; } simply_window_set_action_bar(self, false); @@ -309,9 +294,7 @@ static void click_config_provider(void *context) { } void simply_window_preload(SimplyWindow *self) { - if (self->window) { - return; - } + if (self->window) { return; } Window *window = self->window = window_create(); window_set_background_color(window, GColorClear); @@ -395,9 +378,8 @@ void simply_window_unload(SimplyWindow *self) { static void prv_handle_window_props_packet(Simply *simply, Packet *data) { SimplyWindow *window = simply_window_stack_get_top_window(simply); - if (!window) { - return; - } + if (!window) { return; } + WindowPropsPacket *packet = (WindowPropsPacket *)data; window->id = packet->id; simply_window_set_background_color(window, packet->background_color); @@ -407,17 +389,15 @@ static void prv_handle_window_props_packet(Simply *simply, Packet *data) { static void prv_handle_window_button_config_packet(Simply *simply, Packet *data) { WindowButtonConfigPacket *packet = (WindowButtonConfigPacket*) data; SimplyWindow *window = simply_window_stack_get_top_window(simply); - if (!window) { - return; + if (window) { + window->button_mask = packet->button_mask; } - window->button_mask = packet->button_mask; } static void prv_handle_window_status_bar_packet(Simply *simply, Packet *data) { SimplyWindow *window = simply_window_stack_get_top_window(simply); - if (!window) { - return; - } + if (!window) { return; } + WindowStatusBarPacket *packet = (WindowStatusBarPacket *)data; simply_window_set_status_bar_colors(window, packet->background_color, packet->color); simply_window_set_status_bar_separator_mode(window, packet->separator); @@ -426,9 +406,8 @@ static void prv_handle_window_status_bar_packet(Simply *simply, Packet *data) { static void prv_handle_window_action_bar_packet(Simply *simply, Packet *data) { SimplyWindow *window = simply_window_stack_get_top_window(simply); - if (!window) { - return; - } + if (!window) { return; } + WindowActionBarPacket *packet = (WindowActionBarPacket *)data; simply_window_set_action_bar_background_color(window, packet->background_color); for (unsigned int i = 0; i < ARRAY_LENGTH(packet->image); ++i) { @@ -471,10 +450,8 @@ SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply) { } void simply_window_deinit(SimplyWindow *self) { - if (!self) { - return; + if (self) { + window_destroy(self->window); + self->window = NULL; } - - window_destroy(self->window); - self->window = NULL; } diff --git a/src/util/status_bar_layer.h b/src/util/status_bar_layer.h index 84eb8c74..804b4a74 100644 --- a/src/util/status_bar_layer.h +++ b/src/util/status_bar_layer.h @@ -46,13 +46,19 @@ static inline void status_bar_layer_remove_from_window(Window *window, StatusBar #else -static inline void status_bar_layer_add_to_window(Window *window, StatusBarLayer *status_bar_layer) { - Layer *window_layer = window_get_root_layer(window); - layer_add_child(window_layer, status_bar_layer_get_layer(status_bar_layer)); +static inline void status_bar_layer_add_to_window(Window *window, + StatusBarLayer *status_bar_layer) { + if (status_bar_layer) { + Layer *window_layer = window_get_root_layer(window); + layer_add_child(window_layer, status_bar_layer_get_layer(status_bar_layer)); + } } -static inline void status_bar_layer_remove_from_window(Window *window, StatusBarLayer *status_bar_layer) { - layer_remove_from_parent(status_bar_layer_get_layer(status_bar_layer)); +static inline void status_bar_layer_remove_from_window(Window *window, + StatusBarLayer *status_bar_layer) { + if (status_bar_layer) { + layer_remove_from_parent(status_bar_layer_get_layer(status_bar_layer)); + } } #endif From 128b1591c168b311c8efffa011ccd9cb9fe02004 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 26 Dec 2015 01:31:34 -0800 Subject: [PATCH 695/791] Change simply window status bar to be reset based instead of change --- src/simply/simply_window.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 2bb47de7..f54c7b9f 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -138,20 +138,14 @@ static void prv_update_layer_placement(SimplyWindow *self) { void simply_window_set_status_bar(SimplyWindow *self, bool use_status_bar) { - const bool was_status_bar = self->use_status_bar; self->use_status_bar = use_status_bar; - bool changed = false; - if (!use_status_bar && was_status_bar) { - status_bar_layer_remove_from_window(self->window, self->status_bar_layer); - changed = true; - } else if (use_status_bar && !was_status_bar) { + status_bar_layer_remove_from_window(self->window, self->status_bar_layer); + + if (use_status_bar) { status_bar_layer_add_to_window(self->window, self->status_bar_layer); - changed = true; } - if (!changed) { return; } - prv_update_layer_placement(self); #ifdef PBL_SDK_2 @@ -321,7 +315,7 @@ void simply_window_load(SimplyWindow *self) { self->status_bar_layer = status_bar_layer_create(); status_bar_layer_set_separator_mode(self->status_bar_layer, StatusBarLayerSeparatorModeDotted); - status_bar_layer_remove_from_window(self->window, self->status_bar_layer); + simply_window_set_status_bar(self, self->use_status_bar); self->action_bar_layer = action_bar_layer_create(); action_bar_layer_set_context(self->action_bar_layer, self); From dc4fcb2ae4b0ec469284a3373f1f65a17d9ede8d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 26 Dec 2015 00:47:26 -0800 Subject: [PATCH 696/791] Change simply menu to new coding style --- src/simply/simply_menu.c | 326 ++++++++++++++++++++------------------- 1 file changed, 168 insertions(+), 158 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 9b7e7e50..a3e3fe65 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -18,7 +18,9 @@ #define MAX_CACHED_ITEMS IF_APLITE_ELSE(6, 51) -static const time_t SPINNER_MS = 66; +#define EMPTY_TITLE "" + +#define SPINNER_MS 66 typedef Packet MenuClearPacket; @@ -86,8 +88,6 @@ struct __attribute__((__packed__)) MenuSelectionPacket { static GColor8 s_normal_palette[] = { { GColorBlackARGB8 }, { GColorClearARGB8 } }; static GColor8 s_inverted_palette[] = { { GColorWhiteARGB8 }, { GColorClearARGB8 } }; -static char EMPTY_TITLE[] = ""; - static void simply_menu_clear_section_items(SimplyMenu *self, int section_index); static void simply_menu_clear(SimplyMenu *self); @@ -102,14 +102,14 @@ static void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, Me static void refresh_spinner_timer(SimplyMenu *self); -static int64_t get_milliseconds(void) { +static int64_t prv_get_milliseconds(void) { time_t now_s; uint16_t now_ms_part; time_ms(&now_s, &now_ms_part); return ((int64_t) now_s) * 1000 + now_ms_part; } -static bool send_menu_item(Command type, uint16_t section, uint16_t item) { +static bool prv_send_menu_item(Command type, uint16_t section, uint16_t item) { MenuItemEventPacket packet = { .packet.type = type, .packet.length = sizeof(packet), @@ -119,46 +119,46 @@ static bool send_menu_item(Command type, uint16_t section, uint16_t item) { return simply_msg_send_packet(&packet.packet); } -static bool send_menu_get_section(uint16_t index) { - return send_menu_item(CommandMenuGetSection, index, 0); +static bool prv_send_menu_get_section(uint16_t index) { + return prv_send_menu_item(CommandMenuGetSection, index, 0); } -static bool send_menu_get_item(uint16_t section, uint16_t index) { - return send_menu_item(CommandMenuGetItem, section, index); +static bool prv_send_menu_get_item(uint16_t section, uint16_t index) { + return prv_send_menu_item(CommandMenuGetItem, section, index); } -static bool send_menu_select_click(uint16_t section, uint16_t index) { - return send_menu_item(CommandMenuSelect, section, index); +static bool prv_send_menu_select_click(uint16_t section, uint16_t index) { + return prv_send_menu_item(CommandMenuSelect, section, index); } -static bool send_menu_select_long_click(uint16_t section, uint16_t index) { - return send_menu_item(CommandMenuLongSelect, section, index); +static bool prv_send_menu_select_long_click(uint16_t section, uint16_t index) { + return prv_send_menu_item(CommandMenuLongSelect, section, index); } -static bool section_filter(List1Node *node, void *data) { - SimplyMenuCommon *section = (SimplyMenuCommon*) node; - uint16_t section_index = (uint16_t)(uintptr_t) data; +static bool prv_section_filter(List1Node *node, void *data) { + SimplyMenuCommon *section = (SimplyMenuCommon *)node; + const uint16_t section_index = (uint16_t)(uintptr_t) data; return (section->section == section_index); } -static bool item_filter(List1Node *node, void *data) { - SimplyMenuItem *item = (SimplyMenuItem*) node; - uint32_t cell_index = (uint32_t)(uintptr_t) data; - uint16_t section_index = cell_index; - uint16_t row = cell_index >> 16; +static bool prv_item_filter(List1Node *node, void *data) { + SimplyMenuItem *item = (SimplyMenuItem *)node; + const uint32_t cell_index = (uint32_t)(uintptr_t) data; + const uint16_t section_index = cell_index; + const uint16_t row = cell_index >> 16; return (item->section == section_index && item->item == row); } -static bool request_item_filter(List1Node *node, void *data) { - SimplyMenuItem *item = (SimplyMenuItem*) node; - return (item->title == NULL); +static bool prv_request_item_filter(List1Node *node, void *data) { + return (((SimplyMenuItem *)node)->title == NULL); } -static SimplyMenuSection *get_menu_section(SimplyMenu *self, int index) { - return (SimplyMenuSection*) list1_find(self->menu_layer.sections, section_filter, (void*)(uintptr_t) index); +static SimplyMenuSection *prv_get_menu_section(SimplyMenu *self, int index) { + return (SimplyMenuSection*) list1_find(self->menu_layer.sections, prv_section_filter, + (void*)(uintptr_t) index); } -static void destroy_section(SimplyMenu *self, SimplyMenuSection *section) { +static void prv_destroy_section(SimplyMenu *self, SimplyMenuSection *section) { if (!section) { return; } list1_remove(&self->menu_layer.sections, §ion->node); if (section->title && section->title != EMPTY_TITLE) { @@ -168,17 +168,20 @@ static void destroy_section(SimplyMenu *self, SimplyMenuSection *section) { free(section); } -static void destroy_section_by_index(SimplyMenu *self, int section) { - destroy_section(self, (SimplyMenuSection*) list1_find( - self->menu_layer.sections, section_filter, (void*)(uintptr_t) section)); +static void prv_destroy_section_by_index(SimplyMenu *self, int section) { + SimplyMenuSection *section_node = + (SimplyMenuSection *)list1_find(self->menu_layer.sections, prv_section_filter, + (void *)(uintptr_t)section); + prv_destroy_section(self, section_node); } -static SimplyMenuItem *get_menu_item(SimplyMenu *self, int section, int index) { - uint32_t cell_index = section | (index << 16); - return (SimplyMenuItem*) list1_find(self->menu_layer.items, item_filter, (void*)(uintptr_t) cell_index); +static SimplyMenuItem *prv_get_menu_item(SimplyMenu *self, int section, int index) { + const uint32_t cell_index = section | (index << 16); + return (SimplyMenuItem *) list1_find(self->menu_layer.items, prv_item_filter, + (void *)(uintptr_t) cell_index); } -static void destroy_item(SimplyMenu *self, SimplyMenuItem *item) { +static void prv_destroy_item(SimplyMenu *self, SimplyMenuItem *item) { if (!item) { return; } list1_remove(&self->menu_layer.items, &item->node); if (item->title) { @@ -192,62 +195,60 @@ static void destroy_item(SimplyMenu *self, SimplyMenuItem *item) { free(item); } -static void destroy_item_by_index(SimplyMenu *self, int section, int index) { - uint32_t cell_index = section | (index << 16); - destroy_item(self, (SimplyMenuItem*) list1_find( - self->menu_layer.items, item_filter, (void*)(uintptr_t) cell_index)); +static void prv_destroy_item_by_index(SimplyMenu *self, int section, int index) { + const uint32_t cell_index = section | (index << 16); + SimplyMenuItem *item = + (SimplyMenuItem *)list1_find(self->menu_layer.items, prv_item_filter, + (void *)(uintptr_t) cell_index); + prv_destroy_item(self, item); } -static void add_section(SimplyMenu *self, SimplyMenuSection *section) { +static void prv_add_section(SimplyMenu *self, SimplyMenuSection *section) { if (list1_size(self->menu_layer.sections) >= MAX_CACHED_SECTIONS) { - destroy_section(self, (SimplyMenuSection*) list1_last(self->menu_layer.sections)); + prv_destroy_section(self, (SimplyMenuSection *)list1_last(self->menu_layer.sections)); } - destroy_section_by_index(self, section->section); + prv_destroy_section_by_index(self, section->section); list1_prepend(&self->menu_layer.sections, §ion->node); } -static void add_item(SimplyMenu *self, SimplyMenuItem *item) { +static void prv_add_item(SimplyMenu *self, SimplyMenuItem *item) { if (list1_size(self->menu_layer.items) >= MAX_CACHED_ITEMS) { - destroy_item(self, (SimplyMenuItem*) list1_last(self->menu_layer.items)); + prv_destroy_item(self, (SimplyMenuItem*) list1_last(self->menu_layer.items)); } - destroy_item_by_index(self, item->section, item->item); + prv_destroy_item_by_index(self, item->section, item->item); list1_prepend(&self->menu_layer.items, &item->node); } -static void request_menu_section(SimplyMenu *self, uint16_t section_index) { - SimplyMenuSection *section = get_menu_section(self, section_index); - if (section) { - return; - } +static void prv_request_menu_section(SimplyMenu *self, uint16_t section_index) { + SimplyMenuSection *section = prv_get_menu_section(self, section_index); + if (section) { return; } section = malloc(sizeof(*section)); *section = (SimplyMenuSection) { .section = section_index, }; - add_section(self, section); - send_menu_get_section(section_index); + prv_add_section(self, section); + prv_send_menu_get_section(section_index); } -static void request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t item_index) { - SimplyMenuItem *item = get_menu_item(self, section_index, item_index); - if (item) { - return; - } +static void prv_request_menu_item(SimplyMenu *self, uint16_t section_index, uint16_t item_index) { + SimplyMenuItem *item = prv_get_menu_item(self, section_index, item_index); + if (item) { return; } item = malloc(sizeof(*item)); *item = (SimplyMenuItem) { .section = section_index, .item = item_index, }; - add_item(self, item); - send_menu_get_item(section_index, item_index); + prv_add_item(self, item); + prv_send_menu_get_item(section_index, item_index); } -static void mark_dirty(SimplyMenu *self) { +static void prv_mark_dirty(SimplyMenu *self) { if (self->menu_layer.menu_layer) { layer_mark_dirty(menu_layer_get_layer(self->menu_layer.menu_layer)); } } -static void reload_data(SimplyMenu *self) { +static void prv_reload_data(SimplyMenu *self) { if (self->menu_layer.menu_layer) { menu_layer_reload_data(self->menu_layer.menu_layer); } @@ -258,51 +259,52 @@ static void simply_menu_set_num_sections(SimplyMenu *self, uint16_t num_sections num_sections = 1; } self->menu_layer.num_sections = num_sections; - reload_data(self); + prv_reload_data(self); } static void simply_menu_add_section(SimplyMenu *self, SimplyMenuSection *section) { if (section->title == NULL) { section->title = EMPTY_TITLE; } - add_section(self, section); - reload_data(self); + prv_add_section(self, section); + prv_reload_data(self); } static void simply_menu_add_item(SimplyMenu *self, SimplyMenuItem *item) { if (item->title == NULL) { item->title = EMPTY_TITLE; } - add_item(self, item); - mark_dirty(self); + prv_add_item(self, item); + prv_mark_dirty(self); } static MenuIndex simply_menu_get_selection(SimplyMenu *self) { return menu_layer_get_selected_index(self->menu_layer.menu_layer); } -static void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, MenuRowAlign align, bool animated) { +static void simply_menu_set_selection(SimplyMenu *self, MenuIndex menu_index, MenuRowAlign align, + bool animated) { menu_layer_set_selected_index(self->menu_layer.menu_layer, menu_index, align, animated); } -static bool send_menu_selection(SimplyMenu *self) { +static bool prv_send_menu_selection(SimplyMenu *self) { MenuIndex menu_index = simply_menu_get_selection(self); - return send_menu_item(CommandMenuSelectionEvent, menu_index.section, menu_index.row); + return prv_send_menu_item(CommandMenuSelectionEvent, menu_index.section, menu_index.row); } static void spinner_timer_callback(void *data) { SimplyMenu *self = data; self->spinner_timer = NULL; - mark_dirty(self); + prv_mark_dirty(self); refresh_spinner_timer(self); } static SimplyMenuItem *get_first_request_item(SimplyMenu *self) { - return (SimplyMenuItem*) list1_find(self->menu_layer.items, request_item_filter, NULL); + return (SimplyMenuItem *)list1_find(self->menu_layer.items, prv_request_item_filter, NULL); } static SimplyMenuItem *get_last_request_item(SimplyMenu *self) { - return (SimplyMenuItem*) list1_find_last(self->menu_layer.items, request_item_filter, NULL); + return (SimplyMenuItem *)list1_find_last(self->menu_layer.items, prv_request_item_filter, NULL); } static void refresh_spinner_timer(SimplyMenu *self) { @@ -311,28 +313,32 @@ static void refresh_spinner_timer(SimplyMenu *self) { } } -static uint16_t menu_get_num_sections_callback(MenuLayer *menu_layer, void *data) { +static uint16_t prv_menu_get_num_sections_callback(MenuLayer *menu_layer, void *data) { SimplyMenu *self = data; return self->menu_layer.num_sections; } -static uint16_t menu_get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *data) { +static uint16_t prv_menu_get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, + void *data) { SimplyMenu *self = data; - SimplyMenuSection *section = get_menu_section(self, section_index); + SimplyMenuSection *section = prv_get_menu_section(self, section_index); return section ? section->num_items : 1; } -static int16_t menu_get_header_height_callback(MenuLayer *menu_layer, uint16_t section_index, void *data) { +static int16_t prv_menu_get_header_height_callback(MenuLayer *menu_layer, uint16_t section_index, + void *data) { SimplyMenu *self = data; - SimplyMenuSection *section = get_menu_section(self, section_index); - return section && section->title && section->title != EMPTY_TITLE ? MENU_CELL_BASIC_HEADER_HEIGHT : 0; + SimplyMenuSection *section = prv_get_menu_section(self, section_index); + return (section && section->title && + section->title != EMPTY_TITLE ? MENU_CELL_BASIC_HEADER_HEIGHT : 0); } -static void menu_draw_header_callback(GContext *ctx, const Layer *cell_layer, uint16_t section_index, void *data) { +static void prv_menu_draw_header_callback(GContext *ctx, const Layer *cell_layer, + uint16_t section_index, void *data) { SimplyMenu *self = data; - SimplyMenuSection *section = get_menu_section(self, section_index); + SimplyMenuSection *section = prv_get_menu_section(self, section_index); if (!section) { - request_menu_section(self, section_index); + prv_request_menu_section(self, section_index); return; } @@ -343,12 +349,14 @@ static void menu_draw_header_callback(GContext *ctx, const Layer *cell_layer, ui bounds.origin.x += 2; bounds.origin.y -= 1; - graphics_context_set_text_color(ctx, gcolor8_get_or(self->menu_layer.normal_foreground, GColorBlack)); + graphics_context_set_text_color(ctx, gcolor8_get_or(self->menu_layer.normal_foreground, + GColorBlack)); graphics_draw_text(ctx, section->title, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD), bounds, GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); } -static void simply_menu_draw_row_spinner(SimplyMenu *self, GContext *ctx, const Layer *cell_layer) { +static void simply_menu_draw_row_spinner(SimplyMenu *self, GContext *ctx, + const Layer *cell_layer) { GRect bounds = layer_get_bounds(cell_layer); GPoint center = grect_center_point(&bounds); @@ -357,13 +365,14 @@ static void simply_menu_draw_row_spinner(SimplyMenu *self, GContext *ctx, const const int16_t num_lines = 16; const int16_t num_drawn_lines = 3; - const int64_t now_ms = get_milliseconds(); + const int64_t now_ms = prv_get_milliseconds(); const uint32_t start_index = (now_ms / SPINNER_MS) % num_lines; graphics_context_set_antialiased(ctx, true); - GColor8 stroke_color = menu_cell_layer_is_highlighted(cell_layer) ? self->menu_layer.highlight_foreground - : self->menu_layer.normal_foreground; + GColor8 stroke_color = + menu_cell_layer_is_highlighted(cell_layer) ? self->menu_layer.highlight_foreground : + self->menu_layer.normal_foreground; graphics_context_set_stroke_color(ctx, gcolor8_get_or(stroke_color, GColorBlack)); for (int16_t i = 0; i < num_drawn_lines; i++) { @@ -374,17 +383,18 @@ static void simply_menu_draw_row_spinner(SimplyMenu *self, GContext *ctx, const } } -static void menu_draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *data) { +static void prv_menu_draw_row_callback(GContext *ctx, const Layer *cell_layer, + MenuIndex *cell_index, void *data) { SimplyMenu *self = data; - SimplyMenuSection *section = get_menu_section(self, cell_index->section); + SimplyMenuSection *section = prv_get_menu_section(self, cell_index->section); if (!section) { - request_menu_section(self, cell_index->section); + prv_request_menu_section(self, cell_index->section); return; } - SimplyMenuItem *item = get_menu_item(self, cell_index->section, cell_index->row); + SimplyMenuItem *item = prv_get_menu_item(self, cell_index->section, cell_index->row); if (!item) { - request_menu_item(self, cell_index->section, cell_index->row); + prv_request_menu_item(self, cell_index->section, cell_index->row); return; } @@ -406,7 +416,8 @@ static void menu_draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuI if (image && image->is_palette_black_and_white) { palette = gbitmap_get_palette(image->bitmap); const bool is_highlighted = menu_cell_layer_is_highlighted(cell_layer); - gbitmap_set_palette(image->bitmap, is_highlighted ? s_inverted_palette : s_normal_palette, false); + gbitmap_set_palette(image->bitmap, is_highlighted ? s_inverted_palette : s_normal_palette, + false); } graphics_context_set_alpha_blended(ctx, true); @@ -417,26 +428,28 @@ static void menu_draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuI } } -static void menu_select_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { - send_menu_select_click(cell_index->section, cell_index->row); +static void prv_menu_select_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, + void *data) { + prv_send_menu_select_click(cell_index->section, cell_index->row); } -static void menu_select_long_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { - send_menu_select_long_click(cell_index->section, cell_index->row); +static void prv_menu_select_long_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, + void *data) { + prv_send_menu_select_long_click(cell_index->section, cell_index->row); } -static void single_click_handler(ClickRecognizerRef recognizer, void *context) { +static void prv_single_click_handler(ClickRecognizerRef recognizer, void *context) { Window *base_window = layer_get_window(context); SimplyWindow *window = window_get_user_data(base_window); simply_window_single_click_handler(recognizer, window); } -static void click_config_provider(void *context) { - window_single_click_subscribe(BUTTON_ID_BACK, single_click_handler); +static void prv_click_config_provider(void *context) { + window_single_click_subscribe(BUTTON_ID_BACK, prv_single_click_handler); menu_layer_click_config(context); } -static void window_load(Window *window) { +static void prv_menu_window_load(Window *window) { SimplyMenu *self = window_get_user_data(window); simply_window_load(&self->window); @@ -451,24 +464,24 @@ static void window_load(Window *window) { layer_add_child(window_layer, menu_base_layer); menu_layer_set_callbacks(menu_layer, self, (MenuLayerCallbacks){ - .get_num_sections = menu_get_num_sections_callback, - .get_num_rows = menu_get_num_rows_callback, - .get_header_height = menu_get_header_height_callback, - .draw_header = menu_draw_header_callback, - .draw_row = menu_draw_row_callback, - .select_click = menu_select_click_callback, - .select_long_click = menu_select_long_click_callback, + .get_num_sections = prv_menu_get_num_sections_callback, + .get_num_rows = prv_menu_get_num_rows_callback, + .get_header_height = prv_menu_get_header_height_callback, + .draw_header = prv_menu_draw_header_callback, + .draw_row = prv_menu_draw_row_callback, + .select_click = prv_menu_select_click_callback, + .select_long_click = prv_menu_select_long_click_callback, }); - menu_layer_set_click_config_provider_onto_window(menu_layer, click_config_provider, window); + menu_layer_set_click_config_provider_onto_window(menu_layer, prv_click_config_provider, window); } -static void window_appear(Window *window) { +static void prv_menu_window_appear(Window *window) { SimplyMenu *self = window_get_user_data(window); simply_window_appear(&self->window); } -static void window_disappear(Window *window) { +static void prv_menu_window_disappear(Window *window) { SimplyMenu *self = window_get_user_data(window); if (simply_window_disappear(&self->window)) { simply_res_clear(self->window.simply->res); @@ -476,7 +489,7 @@ static void window_disappear(Window *window) { } } -static void window_unload(Window *window) { +static void prv_menu_window_unload(Window *window) { SimplyMenu *self = window_get_user_data(window); menu_layer_destroy(self->menu_layer.menu_layer); @@ -488,65 +501,62 @@ static void window_unload(Window *window) { static void simply_menu_clear_section_items(SimplyMenu *self, int section_index) { SimplyMenuItem *item = NULL; do { - item = (SimplyMenuItem*) list1_find(self->menu_layer.items, section_filter, (void*)(uintptr_t) section_index); - destroy_item(self, item); + item = (SimplyMenuItem *)list1_find(self->menu_layer.items, prv_section_filter, + (void *)(uintptr_t) section_index); + prv_destroy_item(self, item); } while (item); } static void simply_menu_clear(SimplyMenu *self) { while (self->menu_layer.sections) { - destroy_section(self, (SimplyMenuSection*) self->menu_layer.sections); + prv_destroy_section(self, (SimplyMenuSection *)self->menu_layer.sections); } while (self->menu_layer.items) { - destroy_item(self, (SimplyMenuItem*) self->menu_layer.items); + prv_destroy_item(self, (SimplyMenuItem *)self->menu_layer.items); } - reload_data(self); + prv_reload_data(self); } -static void handle_menu_clear_packet(Simply *simply, Packet *data) { +static void prv_handle_menu_clear_packet(Simply *simply, Packet *data) { simply_menu_clear(simply->menu); } -static void handle_menu_clear_section_packet(Simply *simply, Packet *data) { - MenuClearSectionPacket *packet = (MenuClearSectionPacket*) data; +static void prv_handle_menu_clear_section_packet(Simply *simply, Packet *data) { + MenuClearSectionPacket *packet = (MenuClearSectionPacket *)data; simply_menu_clear_section_items(simply->menu, packet->section); } -static void handle_menu_props_packet(Simply *simply, Packet *data) { - MenuPropsPacket *packet = (MenuPropsPacket*) data; +static void prv_handle_menu_props_packet(Simply *simply, Packet *data) { + MenuPropsPacket *packet = (MenuPropsPacket *)data; SimplyMenu *self = simply->menu; simply_menu_set_num_sections(self, packet->num_sections); - if (!self->window.window) { - return; - } - - window_set_background_color(self->window.window, gcolor8_get_or(packet->background_color, GColorWhite)); - - if (!self->menu_layer.menu_layer) { - return; - } + if (!self->window.window) { return; } - self->menu_layer.normal_background = packet->background_color; - self->menu_layer.normal_foreground = packet->text_color; + window_set_background_color(self->window.window, gcolor8_get_or(packet->background_color, + GColorWhite)); - self->menu_layer.highlight_background = packet->highlight_background_color; - self->menu_layer.highlight_foreground = packet->highlight_text_color; + SimplyMenuLayer *menu_layer = &self->menu_layer; + if (!menu_layer->menu_layer) { return; } - menu_layer_set_normal_colors(simply->menu->menu_layer.menu_layer, - gcolor8_get_or(self->menu_layer.normal_background, GColorWhite), - gcolor8_get_or(self->menu_layer.normal_foreground, GColorBlack)); + menu_layer->normal_background = packet->background_color; + menu_layer->normal_foreground = packet->text_color; + menu_layer->highlight_background = packet->highlight_background_color; + menu_layer->highlight_foreground = packet->highlight_text_color; - menu_layer_set_highlight_colors(simply->menu->menu_layer.menu_layer, - gcolor8_get_or(self->menu_layer.highlight_background, GColorBlack), - gcolor8_get_or(self->menu_layer.highlight_foreground, GColorWhite)); + menu_layer_set_normal_colors(menu_layer->menu_layer, + gcolor8_get_or(menu_layer->normal_background, GColorWhite), + gcolor8_get_or(menu_layer->normal_foreground, GColorBlack)); + menu_layer_set_highlight_colors(menu_layer->menu_layer, + gcolor8_get_or(menu_layer->highlight_background, GColorBlack), + gcolor8_get_or(menu_layer->highlight_foreground, GColorWhite)); } -static void handle_menu_section_packet(Simply *simply, Packet *data) { - MenuSectionPacket *packet = (MenuSectionPacket*) data; +static void prv_handle_menu_section_packet(Simply *simply, Packet *data) { + MenuSectionPacket *packet = (MenuSectionPacket *)data; SimplyMenuSection *section = malloc(sizeof(*section)); *section = (SimplyMenuSection) { .section = packet->section, @@ -556,8 +566,8 @@ static void handle_menu_section_packet(Simply *simply, Packet *data) { simply_menu_add_section(simply->menu, section); } -static void handle_menu_item_packet(Simply *simply, Packet *data) { - MenuItemPacket *packet = (MenuItemPacket*) data; +static void prv_handle_menu_item_packet(Simply *simply, Packet *data) { + MenuItemPacket *packet = (MenuItemPacket *)data; SimplyMenuItem *item = malloc(sizeof(*item)); *item = (SimplyMenuItem) { .section = packet->section, @@ -569,12 +579,12 @@ static void handle_menu_item_packet(Simply *simply, Packet *data) { simply_menu_add_item(simply->menu, item); } -static void handle_menu_get_selection_packet(Simply *simply, Packet *data) { - send_menu_selection(simply->menu); +static void prv_handle_menu_get_selection_packet(Simply *simply, Packet *data) { + prv_send_menu_selection(simply->menu); } -static void handle_menu_selection_packet(Simply *simply, Packet *data) { - MenuSelectionPacket *packet = (MenuSelectionPacket*) data; +static void prv_handle_menu_selection_packet(Simply *simply, Packet *data) { + MenuSelectionPacket *packet = (MenuSelectionPacket *)data; MenuIndex menu_index = { .section = packet->section, .row = packet->item, @@ -585,25 +595,25 @@ static void handle_menu_selection_packet(Simply *simply, Packet *data) { bool simply_menu_handle_packet(Simply *simply, Packet *packet) { switch (packet->type) { case CommandMenuClear: - handle_menu_clear_packet(simply, packet); + prv_handle_menu_clear_packet(simply, packet); return true; case CommandMenuClearSection: - handle_menu_clear_section_packet(simply, packet); + prv_handle_menu_clear_section_packet(simply, packet); return true; case CommandMenuProps: - handle_menu_props_packet(simply, packet); + prv_handle_menu_props_packet(simply, packet); return true; case CommandMenuSection: - handle_menu_section_packet(simply, packet); + prv_handle_menu_section_packet(simply, packet); return true; case CommandMenuItem: - handle_menu_item_packet(simply, packet); + prv_handle_menu_item_packet(simply, packet); return true; case CommandMenuSelection: - handle_menu_selection_packet(simply, packet); + prv_handle_menu_selection_packet(simply, packet); return true; case CommandMenuGetSelection: - handle_menu_get_selection_packet(simply, packet); + prv_handle_menu_get_selection_packet(simply, packet); return true; } return false; @@ -617,10 +627,10 @@ SimplyMenu *simply_menu_create(Simply *simply) { }; static const WindowHandlers s_window_handlers = { - .load = window_load, - .appear = window_appear, - .disappear = window_disappear, - .unload = window_unload, + .load = prv_menu_window_load, + .appear = prv_menu_window_appear, + .disappear = prv_menu_window_disappear, + .unload = prv_menu_window_unload, }; self->window.window_handlers = &s_window_handlers; From d71847499acfb2474ea7282728998f7ee0149bc4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 26 Dec 2015 01:36:54 -0800 Subject: [PATCH 697/791] Change simply menu to fit round --- src/simply/simply_menu.c | 15 +++++++++++++++ src/simply/simply_menu.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index a3e3fe65..c56318d8 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -333,6 +333,18 @@ static int16_t prv_menu_get_header_height_callback(MenuLayer *menu_layer, uint16 section->title != EMPTY_TITLE ? MENU_CELL_BASIC_HEADER_HEIGHT : 0); } +static int16_t prv_menu_get_cell_height_callback(MenuLayer *menu_layer, MenuIndex *cell_index, + void *context) { + if (PBL_IF_ROUND_ELSE(true, false)) { + const bool is_selected = menu_layer_is_index_selected(menu_layer, cell_index); + return is_selected ? MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT : + MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT; + } else { + return MENU_CELL_BASIC_CELL_HEIGHT; + } +} + + static void prv_menu_draw_header_callback(GContext *ctx, const Layer *cell_layer, uint16_t section_index, void *data) { SimplyMenu *self = data; @@ -467,6 +479,9 @@ static void prv_menu_window_load(Window *window) { .get_num_sections = prv_menu_get_num_sections_callback, .get_num_rows = prv_menu_get_num_rows_callback, .get_header_height = prv_menu_get_header_height_callback, +#if defined(PBL_ROUND) + .get_cell_height = prv_menu_get_cell_height_callback, +#endif .draw_header = prv_menu_draw_header_callback, .draw_row = prv_menu_draw_row_callback, .select_click = prv_menu_select_click_callback, diff --git a/src/simply/simply_menu.h b/src/simply/simply_menu.h index 0f59e134..22c230a2 100644 --- a/src/simply/simply_menu.h +++ b/src/simply/simply_menu.h @@ -10,6 +10,9 @@ #include +//! Default cell height in pixels +#define MENU_CELL_BASIC_CELL_HEIGHT ((const int16_t) 44) + typedef enum SimplyMenuType SimplyMenuType; enum SimplyMenuType { From d1229604c4270322bc6d7a1d1bc170e724a62cd0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 5 Jan 2016 15:55:22 -0800 Subject: [PATCH 698/791] Adjust inset for round --- src/simply/simply_menu.c | 1 + src/simply/simply_window.c | 3 ++- src/simply/simply_window.h | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index c56318d8..3c5f60b2 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -638,6 +638,7 @@ SimplyMenu *simply_menu_create(Simply *simply) { SimplyMenu *self = malloc(sizeof(*self)); *self = (SimplyMenu) { .window.simply = simply, + .window.status_bar_insets_bottom = true, .menu_layer.num_sections = 1, }; diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index f54c7b9f..11ad49e4 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -125,7 +125,8 @@ static void prv_update_layer_placement(SimplyWindow *self) { if (has_status_bar) { GRect status_frame = { .size = { frame.size.w, STATUS_BAR_LAYER_HEIGHT } }; frame.origin.y = STATUS_BAR_LAYER_HEIGHT; - frame.size.h -= STATUS_BAR_LAYER_HEIGHT; + frame.size.h -= self->status_bar_insets_bottom ? STATUS_BAR_LAYER_HEIGHT * 2 : + STATUS_BAR_LAYER_HEIGHT; if (has_action_bar) { status_frame.size.w -= ACTION_BAR_WIDTH; } diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index 6f080f1e..b29719f4 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -25,6 +25,7 @@ struct SimplyWindow { GColor8 background_color; bool is_scrollable:1; bool use_status_bar:1; + bool status_bar_insets_bottom:1; bool use_action_bar:1; }; From 4f755aaedaf71a16327e39a2ed4378560927f404 Mon Sep 17 00:00:00 2001 From: rdlee Date: Sun, 10 Jan 2016 15:23:40 -0800 Subject: [PATCH 699/791] permit menu section title colors Menu sections can set different foreground/background colors for their titles so that they are more easily distinguished from their items. Falls back to the foreground/background set for the whole menu if not specified. --- src/js/ui/menu.js | 4 ++++ src/js/ui/simply-pebble.js | 4 ++++ src/simply/simply_menu.c | 10 +++++++++- src/simply/simply_menu.h | 2 ++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 1f8966a3..4b386166 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -157,6 +157,10 @@ Menu.prototype._resolveMenu = function(clear, pushing) { Menu.prototype._resolveSection = function(e, clear) { var section = this._getSection(e); if (!section) { return; } + section = myutil.shadow({ + textColor: this.state.textColor, + backgroundColor: this.state.backgroundColor + }, section); section.items = this._getItems(e); if (this === WindowStack.top()) { simply.impl.menuSection.call(this, e.sectionIndex, section, clear); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 81415c37..880e10f6 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -641,6 +641,8 @@ var MenuSectionPacket = new struct([ [Packet, 'packet'], ['uint16', 'section'], ['uint16', 'items', EnumerableType], + ['uint8', 'backgroundColor', Color], + ['uint8', 'textColor', Color], ['uint16', 'titleLength', EnumerableType], ['cstring', 'title', StringType], ]); @@ -1213,6 +1215,8 @@ SimplyPebble.menuSection = function(section, def, clear) { MenuSectionPacket .section(section) .items(def.items) + .backgroundColor(def.backgroundColor) + .textColor(def.textColor) .titleLength(def.title) .title(def.title); SimplyPebble.sendPacket(MenuSectionPacket); diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 9b7e7e50..2099502d 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -46,6 +46,8 @@ struct __attribute__((__packed__)) MenuSectionPacket { Packet packet; uint16_t section; uint16_t num_items; + GColor8 background_color; + GColor8 text_color; uint16_t title_length; char title[]; }; @@ -340,10 +342,14 @@ static void menu_draw_header_callback(GContext *ctx, const Layer *cell_layer, ui list1_prepend(&self->menu_layer.sections, §ion->node); GRect bounds = layer_get_bounds(cell_layer); + + graphics_context_set_fill_color(ctx, gcolor8_get_or(section->title_background, GColorWhite)); + graphics_fill_rect(ctx, bounds, 0, GCornerNone); + bounds.origin.x += 2; bounds.origin.y -= 1; - graphics_context_set_text_color(ctx, gcolor8_get_or(self->menu_layer.normal_foreground, GColorBlack)); + graphics_context_set_text_color(ctx, gcolor8_get_or(section->title_foreground, GColorBlack)); graphics_draw_text(ctx, section->title, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD), bounds, GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); } @@ -551,6 +557,8 @@ static void handle_menu_section_packet(Simply *simply, Packet *data) { *section = (SimplyMenuSection) { .section = packet->section, .num_items = packet->num_items, + .title_foreground = packet->text_color, + .title_background = packet->background_color, .title = packet->title_length ? strdup2(packet->title) : NULL, }; simply_menu_add_section(simply->menu, section); diff --git a/src/simply/simply_menu.h b/src/simply/simply_menu.h index 0f59e134..0ea88f84 100644 --- a/src/simply/simply_menu.h +++ b/src/simply/simply_menu.h @@ -61,6 +61,8 @@ typedef struct SimplyMenuSection SimplyMenuSection; struct SimplyMenuSection { SimplyMenuCommonMember; uint16_t num_items; + GColor8 title_foreground; + GColor8 title_background; }; typedef struct SimplyMenuItem SimplyMenuItem; From 124c848de30aa7c123c457ae30535a945e30f156 Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 20 Jan 2016 10:55:37 +1300 Subject: [PATCH 700/791] Fix typo in docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e949a77..a40f3193 100644 --- a/README.md +++ b/README.md @@ -1178,7 +1178,7 @@ Note that this means you may have to move portions of your startup logic into th ## Wakeup -The Wakeup module allows you to schedule your app to wakeup at a specified time using Pebble's wakeup functionality. Whether the user is in a different watchface or app, your app while launch by the specified time. This allows you to write a custom alarm app, for example. If your app is already running, you may also subscribe to receive the wakeup event, which can be useful for more longer lived timers. With the Wakeup module, you can save data to be read on launch and configure your app to behave differently based on launch data. The Wakeup module, like the Settings module, is backed by localStorage. +The Wakeup module allows you to schedule your app to wakeup at a specified time using Pebble's wakeup functionality. Whether the user is in a different watchface or app, your app will launch at the specified time. This allows you to write a custom alarm app, for example. If your app is already running, you may also subscribe to receive the wakeup event, which can be useful for more longer lived timers. With the Wakeup module, you can save data to be read on launch and configure your app to behave differently based on launch data. The Wakeup module, like the Settings module, is backed by localStorage. ### Wakeup From 45522bc91cc8cb3f8527fdd97a347e530f3e364c Mon Sep 17 00:00:00 2001 From: Sebastian Rahlf Date: Mon, 25 Jan 2016 23:37:51 +0100 Subject: [PATCH 701/791] Fix ReferenceError when using callback. --- src/js/ui/menu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ui/menu.js b/src/js/ui/menu.js index 1f8966a3..4929265d 100644 --- a/src/js/ui/menu.js +++ b/src/js/ui/menu.js @@ -308,7 +308,7 @@ Menu.prototype.item = function(sectionIndex, itemIndex, item) { Menu.prototype.selection = function(callback_or_sectionIndex, itemIndex) { if (typeof callback_or_sectionIndex === 'function') { - this._selections.push(callback); + this._selections.push(callback_or_sectionIndex); simply.impl.menuSelection(); } else { this._selection = { From 52af61988d880ec998cc2e98a7629016b97c0287 Mon Sep 17 00:00:00 2001 From: bkbilly Date: Fri, 5 Feb 2016 02:11:02 +0200 Subject: [PATCH 702/791] Added feature to create an arc circle --- src/js/app.js | 23 +++++++------- src/js/ui/circlearc.js | 17 +++++++++++ src/js/ui/element.js | 1 + src/js/ui/index.js | 1 + src/js/ui/simply-pebble.js | 27 +++++++++++++++++ src/js/ui/stage.js | 1 + src/simply/simply_msg_commands.h | 2 ++ src/simply/simply_stage.c | 52 ++++++++++++++++++++++++++++++++ src/simply/simply_stage.h | 5 +++ 9 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 src/js/ui/circlearc.js diff --git a/src/js/app.js b/src/js/app.js index 6edb445f..60d0b601 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -39,18 +39,19 @@ main.on('click', 'up', function(e) { }); main.on('click', 'select', function(e) { - var wind = new UI.Window({ - fullscreen: true, - }); - var textfield = new UI.Text({ - position: new Vector2(0, 65), - size: new Vector2(144, 30), - font: 'gothic-24-bold', - text: 'Text Anywhere!', - textAlign: 'center' + var wind = new UI.Window({backgroundColor:'red'}); + + var circlearc = new UI.CircleArc({ + position: new Vector2(-30, -28), + size: new Vector2(205, 205), + anglestart: 0, + angleend: 132, + radius: 20, + backgroundColor: 'blue', }); - wind.add(textfield); - wind.show(); + + wind.add(circlearc); + wind.show(circlearc); }); main.on('click', 'down', function(e) { diff --git a/src/js/ui/circlearc.js b/src/js/ui/circlearc.js new file mode 100644 index 00000000..73e5018f --- /dev/null +++ b/src/js/ui/circlearc.js @@ -0,0 +1,17 @@ +var util2 = require('util2'); +var myutil = require('myutil'); +var StageElement = require('ui/element'); + +var defaults = { + backgroundColor: 'white', + borderColor: 'clear', +}; + +var CircleArc = function(elementDef) { + StageElement.call(this, myutil.shadow(defaults, elementDef || {})); + this.state.type = StageElement.CircleArcType; +}; + +util2.inherit(CircleArc, StageElement); + +module.exports = CircleArc; diff --git a/src/js/ui/element.js b/src/js/ui/element.js index b1a44b1e..e42e75da 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -30,6 +30,7 @@ var StageElement = function(elementDef) { StageElement.RectType = 1; StageElement.CircleType = 2; +StageElement.CircleArcType = 6; StageElement.TextType = 3; StageElement.ImageType = 4; StageElement.InverterType = 5; diff --git a/src/js/ui/index.js b/src/js/ui/index.js index fc76ae54..513504ec 100644 --- a/src/js/ui/index.js +++ b/src/js/ui/index.js @@ -6,6 +6,7 @@ UI.Card = require('ui/card'); UI.Menu = require('ui/menu'); UI.Rect = require('ui/rect'); UI.Circle = require('ui/circle'); +UI.CircleArc = require('ui/circlearc'); UI.Text = require('ui/text'); UI.TimeText = require('ui/timetext'); UI.Image = require('ui/image'); diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 81415c37..9221d2de 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -743,6 +743,18 @@ var ElementRadiusPacket = new struct([ ['uint16', 'radius', EnumerableType], ]); +var ElementAnglestartPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + ['uint16', 'anglestart', EnumerableType], +]); + +var ElementAngleendPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + ['uint16', 'angleend', EnumerableType], +]); + var ElementTextPacket = new struct([ [Packet, 'packet'], ['uint32', 'id'], @@ -842,6 +854,8 @@ var CommandPackets = [ ElementRemovePacket, ElementCommonPacket, ElementRadiusPacket, + ElementAnglestartPacket, + ElementAngleendPacket, ElementTextPacket, ElementTextStylePacket, ElementImagePacket, @@ -1270,6 +1284,14 @@ SimplyPebble.elementRadius = function(id, radius) { SimplyPebble.sendPacket(ElementRadiusPacket.id(id).radius(radius)); }; +SimplyPebble.elementAnglestart = function(id, anglestart) { + SimplyPebble.sendPacket(ElementAnglestartPacket.id(id).anglestart(anglestart)); +}; + +SimplyPebble.elementAngleend = function(id, angleend) { + SimplyPebble.sendPacket(ElementAngleendPacket.id(id).angleend(angleend)); +}; + SimplyPebble.elementText = function(id, text, timeUnits) { SimplyPebble.sendPacket(ElementTextPacket.id(id).updateTimeUnits(timeUnits).text(text)); }; @@ -1313,6 +1335,11 @@ SimplyPebble.stageElement = function(id, type, def, index) { case StageElement.CircleType: SimplyPebble.elementRadius(id, def.radius); break; + case StageElement.CircleArcType: + SimplyPebble.elementRadius(id, def.radius); + SimplyPebble.elementAnglestart(id, def.anglestart); + SimplyPebble.elementAngleend(id, def.angleend); + break; case StageElement.TextType: SimplyPebble.elementRadius(id, def.radius); SimplyPebble.elementTextStyle(id, def); diff --git a/src/js/ui/stage.js b/src/js/ui/stage.js index 7123a171..70ea49a7 100644 --- a/src/js/ui/stage.js +++ b/src/js/ui/stage.js @@ -10,6 +10,7 @@ var Stage = function(stageDef) { Stage.RectType = 1; Stage.CircleType = 2; +Stage.CircleArcType = 6; Stage.TextType = 3; Stage.ImageType = 4; Stage.InverterType = 5; diff --git a/src/simply/simply_msg_commands.h b/src/simply/simply_msg_commands.h index 886b0213..4d451c38 100644 --- a/src/simply/simply_msg_commands.h +++ b/src/simply/simply_msg_commands.h @@ -47,6 +47,8 @@ enum Command { CommandElementRemove, CommandElementCommon, CommandElementRadius, + CommandElementAnglestart, + CommandElementAngleend, CommandElementText, CommandElementTextStyle, CommandElementImage, diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 31eee809..64c2b150 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -53,6 +53,22 @@ struct __attribute__((__packed__)) ElementRadiusPacket { uint16_t radius; }; +typedef struct CommandElementAnglestartPacket CommandElementAnglestartPacket; + +struct __attribute__((__packed__)) CommandElementAnglestartPacket { + Packet packet; + uint32_t id; + uint16_t anglestart; +}; + +typedef struct CommandElementAngleendPacket CommandElementAngleendPacket; + +struct __attribute__((__packed__)) CommandElementAngleendPacket { + Packet packet; + uint32_t id; + uint16_t angleend; +}; + typedef struct ElementTextPacket ElementTextPacket; struct __attribute__((__packed__)) ElementTextPacket { @@ -200,6 +216,12 @@ static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementC } } +static void circlearc_element_draw(GContext *ctx, SimplyStage *self, SimplyElementCircleArc *element) { + GRect frame = grect_inset(element->frame, GEdgeInsets(30)); + graphics_context_set_fill_color(ctx, element->background_color); + graphics_fill_radial(ctx, frame, GOvalScaleModeFitCircle, element->radius, DEG_TO_TRIGANGLE(element->anglestart), DEG_TO_TRIGANGLE(element->angleend)); +} + static char *format_time(char *format) { time_t now = time(NULL); struct tm* tm = localtime(&now); @@ -264,6 +286,9 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { case SimplyElementTypeCircle: circle_element_draw(ctx, self, (SimplyElementCircle*) element); break; + case SimplyElementTypeCircleArc: + circlearc_element_draw(ctx, self, (SimplyElementCircleArc*) element); + break; case SimplyElementTypeText: text_element_draw(ctx, self, (SimplyElementText*) element); break; @@ -288,6 +313,7 @@ static SimplyElementCommon *alloc_element(SimplyElementType type) { case SimplyElementTypeNone: return NULL; case SimplyElementTypeRect: return malloc0(sizeof(SimplyElementRect)); case SimplyElementTypeCircle: return malloc0(sizeof(SimplyElementCircle)); + case SimplyElementTypeCircleArc: return malloc0(sizeof(SimplyElementCircleArc)); case SimplyElementTypeText: return malloc0(sizeof(SimplyElementText)); case SimplyElementTypeImage: return malloc0(sizeof(SimplyElementImage)); case SimplyElementTypeInverter: { @@ -542,6 +568,26 @@ static void handle_element_radius_packet(Simply *simply, Packet *data) { simply_stage_update(simply->stage); }; +static void handle_element_anglestart_packet(Simply *simply, Packet *data) { + CommandElementAnglestartPacket *packet = (CommandElementAnglestartPacket*) data; + SimplyElementRect *element = (SimplyElementRect*) simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + element->anglestart = packet->anglestart; + simply_stage_update(simply->stage); +}; + +static void handle_element_angleend_packet(Simply *simply, Packet *data) { + CommandElementAngleendPacket *packet = (CommandElementAngleendPacket*) data; + SimplyElementRect *element = (SimplyElementRect*) simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + element->angleend = packet->angleend; + simply_stage_update(simply->stage); +}; + static void handle_element_text_packet(Simply *simply, Packet *data) { ElementTextPacket *packet = (ElementTextPacket*) data; SimplyElementText *element = (SimplyElementText*) simply_stage_get_element(simply->stage, packet->id); @@ -618,6 +664,12 @@ bool simply_stage_handle_packet(Simply *simply, Packet *packet) { case CommandElementRadius: handle_element_radius_packet(simply, packet); return true; + case CommandElementAnglestart: + handle_element_anglestart_packet(simply, packet); + return true; + case CommandElementAngleend: + handle_element_angleend_packet(simply, packet); + return true; case CommandElementText: handle_element_text_packet(simply, packet); return true; diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index e28d0abd..287b0c79 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -26,6 +26,7 @@ enum SimplyElementType { SimplyElementTypeNone = 0, SimplyElementTypeRect = 1, SimplyElementTypeCircle = 2, + SimplyElementTypeCircleArc = 6, SimplyElementTypeText = 3, SimplyElementTypeImage = 4, SimplyElementTypeInverter = 5, @@ -66,10 +67,14 @@ typedef struct SimplyElementRect SimplyElementRect; struct SimplyElementRect { SimplyElementCommonMember; uint16_t radius; + uint16_t anglestart; + uint16_t angleend; }; typedef struct SimplyElementRect SimplyElementCircle; +typedef struct SimplyElementRect SimplyElementCircleArc; + typedef struct SimplyElementText SimplyElementText; struct SimplyElementText { From 68c8c107805305474822ec64ae99d1ef9ff9a677 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 7 Feb 2016 22:09:40 -0800 Subject: [PATCH 703/791] Add src/util/display.h for RECT_USAGE and ROUND_USAGE --- src/util/display.h | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/util/display.h diff --git a/src/util/display.h b/src/util/display.h new file mode 100644 index 00000000..a5620270 --- /dev/null +++ b/src/util/display.h @@ -0,0 +1,11 @@ +#pragma once + +#include "util/none.h" + +#if defined(PBL_RECT) +#define RECT_USAGE +#define ROUND_USAGE __attribute__((unused)) +#elif defined(PBL_ROUND) +#define RECT_USAGE __attribute__((unused)) +#define ROUND_USAGE +#endif From cfbad5ff2379e959ae641b27ccfe48207fc8595b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 7 Feb 2016 22:10:36 -0800 Subject: [PATCH 704/791] Add missing status and menu layer definitions for older aplite SDKs --- src/simply/simply_menu.c | 5 +++-- src/util/compat.h | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 3c5f60b2..391db719 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -7,6 +7,7 @@ #include "simply.h" #include "util/color.h" +#include "util/display.h" #include "util/graphics.h" #include "util/menu_layer.h" #include "util/platform.h" @@ -333,8 +334,8 @@ static int16_t prv_menu_get_header_height_callback(MenuLayer *menu_layer, uint16 section->title != EMPTY_TITLE ? MENU_CELL_BASIC_HEADER_HEIGHT : 0); } -static int16_t prv_menu_get_cell_height_callback(MenuLayer *menu_layer, MenuIndex *cell_index, - void *context) { +ROUND_USAGE static int16_t prv_menu_get_cell_height_callback(MenuLayer *menu_layer, MenuIndex *cell_index, + void *context) { if (PBL_IF_ROUND_ELSE(true, false)) { const bool is_selected = menu_layer_is_index_selected(menu_layer, cell_index); return is_selected ? MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT : diff --git a/src/util/compat.h b/src/util/compat.h index 9f0e3435..cd0beeb1 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -134,6 +134,14 @@ typedef TextLayout GTextAttributes; graphics_text_layout_get_content_size(text, font, box, overflow_mode, alignment) #endif +#ifndef MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT +#define MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT ((const int16_t) 84) +#endif + +#ifndef MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT +#define MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT ((const int16_t) 24) +#endif + #ifndef menu_layer_set_normal_colors #define menu_layer_set_normal_colors(menu_layer, background_color, text_color) #endif @@ -146,6 +154,13 @@ typedef TextLayout GTextAttributes; #define menu_cell_layer_is_highlighted(cell_layer) (false) #endif +#ifndef menu_layer_is_index_selected +static inline bool menu_layer_is_index_selected(MenuLayer *menu_layer, MenuIndex *cell_index) { + MenuIndex current_index = menu_layer_get_selected_index(menu_layer); + return (current_index.section == cell_index->section && current_index.row == cell_index->row); +} +#endif + //! Convenience macro to use SDK 3.0 function to set a `PropertyAnimation`'s //! `values.from.grect` field. #ifndef property_animation_set_from_grect @@ -170,6 +185,10 @@ void dictation_session_start(DictationSession *session); #define scroll_layer_set_paging(scroll_layer, paging_enabled) #endif +#ifndef STATUS_BAR_LAYER_HEIGHT +#define STATUS_BAR_LAYER_HEIGHT 16 +#endif + #endif // Legacy definitions for basalt on 3.0 From 6fb9c695172964a331645c1421f33bbfe4df7643 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 7 Feb 2016 22:11:15 -0800 Subject: [PATCH 705/791] Update .travis.yml to test only SDK 3.9 --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index dd9a6349..14b1cdd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,7 @@ python: - "2.7" env: - - PEBBLE_SDK_VERSION=2.9 - - PEBBLE_SDK_VERSION=3.7 - - PEBBLE_SDK_VERSION=3.8-beta10 + - PEBBLE_SDK_VERSION=3.9 before_install: - wget https://github.com/pebble/pebble-tool/releases/download/v4.0-rc5/pebble-sdk-4.0-rc5-linux64.tar.bz2?source=travis From 2e64f11e497763f4b012f66f662fe5c3d2bb156d Mon Sep 17 00:00:00 2001 From: bkbilly Date: Tue, 9 Feb 2016 18:54:37 +0200 Subject: [PATCH 706/791] Added borderWidth, Renamed CircleArc to Radial, renamed anglestart and angleend --- src/js/app.js | 16 +++--- src/js/ui/element.js | 2 +- src/js/ui/index.js | 2 +- src/js/ui/{circlearc.js => radial.js} | 12 ++-- src/js/ui/simply-pebble.js | 38 ++++++++----- src/js/ui/stage.js | 2 +- src/simply/simply_msg_commands.h | 5 +- src/simply/simply_stage.c | 81 +++++++++++++++++++-------- src/simply/simply_stage.h | 9 +-- 9 files changed, 110 insertions(+), 57 deletions(-) rename src/js/ui/{circlearc.js => radial.js} (54%) diff --git a/src/js/app.js b/src/js/app.js index 60d0b601..b91c377f 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -41,17 +41,19 @@ main.on('click', 'up', function(e) { main.on('click', 'select', function(e) { var wind = new UI.Window({backgroundColor:'red'}); - var circlearc = new UI.CircleArc({ - position: new Vector2(-30, -28), - size: new Vector2(205, 205), - anglestart: 0, - angleend: 132, + var radial = new UI.Radial({ + position: new Vector2(0, 0), + size: new Vector2(150, 150), + angleStart: 0, + angleEnd: 132, radius: 20, + borderColor: "red", + borderWidth: 5, backgroundColor: 'blue', }); - wind.add(circlearc); - wind.show(circlearc); + wind.add(radial); + wind.show(radial); }); main.on('click', 'down', function(e) { diff --git a/src/js/ui/element.js b/src/js/ui/element.js index e42e75da..fb469b71 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -30,7 +30,7 @@ var StageElement = function(elementDef) { StageElement.RectType = 1; StageElement.CircleType = 2; -StageElement.CircleArcType = 6; +StageElement.RadialType = 6; StageElement.TextType = 3; StageElement.ImageType = 4; StageElement.InverterType = 5; diff --git a/src/js/ui/index.js b/src/js/ui/index.js index 513504ec..32eedb31 100644 --- a/src/js/ui/index.js +++ b/src/js/ui/index.js @@ -6,7 +6,7 @@ UI.Card = require('ui/card'); UI.Menu = require('ui/menu'); UI.Rect = require('ui/rect'); UI.Circle = require('ui/circle'); -UI.CircleArc = require('ui/circlearc'); +UI.Radial = require('ui/radial'); UI.Text = require('ui/text'); UI.TimeText = require('ui/timetext'); UI.Image = require('ui/image'); diff --git a/src/js/ui/circlearc.js b/src/js/ui/radial.js similarity index 54% rename from src/js/ui/circlearc.js rename to src/js/ui/radial.js index 73e5018f..c815f894 100644 --- a/src/js/ui/circlearc.js +++ b/src/js/ui/radial.js @@ -4,14 +4,16 @@ var StageElement = require('ui/element'); var defaults = { backgroundColor: 'white', - borderColor: 'clear', + angleStart: 0, + angleEnd: 132, + borderWidth: 5, }; -var CircleArc = function(elementDef) { +var Radial = function(elementDef) { StageElement.call(this, myutil.shadow(defaults, elementDef || {})); - this.state.type = StageElement.CircleArcType; + this.state.type = StageElement.RadialType; }; -util2.inherit(CircleArc, StageElement); +util2.inherit(Radial, StageElement); -module.exports = CircleArc; +module.exports = Radial; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index fa5d9a5c..9e7ccb24 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -745,16 +745,22 @@ var ElementRadiusPacket = new struct([ ['uint16', 'radius', EnumerableType], ]); -var ElementAnglestartPacket = new struct([ +var ElementAngleStartPacket = new struct([ [Packet, 'packet'], ['uint32', 'id'], - ['uint16', 'anglestart', EnumerableType], + ['uint16', 'angleStart', EnumerableType], ]); -var ElementAngleendPacket = new struct([ +var ElementAngleEndPacket = new struct([ [Packet, 'packet'], ['uint32', 'id'], - ['uint16', 'angleend', EnumerableType], + ['uint16', 'angleEnd', EnumerableType], +]); + +var ElementBorderWidthPacket = new struct([ + [Packet, 'packet'], + ['uint32', 'id'], + ['uint16', 'borderWidth', EnumerableType], ]); var ElementTextPacket = new struct([ @@ -856,8 +862,9 @@ var CommandPackets = [ ElementRemovePacket, ElementCommonPacket, ElementRadiusPacket, - ElementAnglestartPacket, - ElementAngleendPacket, + ElementAngleStartPacket, + ElementAngleEndPacket, + ElementBorderWidthPacket, ElementTextPacket, ElementTextStylePacket, ElementImagePacket, @@ -1288,12 +1295,16 @@ SimplyPebble.elementRadius = function(id, radius) { SimplyPebble.sendPacket(ElementRadiusPacket.id(id).radius(radius)); }; -SimplyPebble.elementAnglestart = function(id, anglestart) { - SimplyPebble.sendPacket(ElementAnglestartPacket.id(id).anglestart(anglestart)); +SimplyPebble.elementAngleStart = function(id, angleStart) { + SimplyPebble.sendPacket(ElementAngleStartPacket.id(id).angleStart(angleStart)); +}; + +SimplyPebble.elementAngleEnd = function(id, angleEnd) { + SimplyPebble.sendPacket(ElementAngleEndPacket.id(id).angleEnd(angleEnd)); }; -SimplyPebble.elementAngleend = function(id, angleend) { - SimplyPebble.sendPacket(ElementAngleendPacket.id(id).angleend(angleend)); +SimplyPebble.elementBorderWidth = function(id, borderWidth) { + SimplyPebble.sendPacket(ElementBorderWidthPacket.id(id).borderWidth(borderWidth)); }; SimplyPebble.elementText = function(id, text, timeUnits) { @@ -1339,10 +1350,11 @@ SimplyPebble.stageElement = function(id, type, def, index) { case StageElement.CircleType: SimplyPebble.elementRadius(id, def.radius); break; - case StageElement.CircleArcType: + case StageElement.RadialType: SimplyPebble.elementRadius(id, def.radius); - SimplyPebble.elementAnglestart(id, def.anglestart); - SimplyPebble.elementAngleend(id, def.angleend); + SimplyPebble.elementAngleStart(id, def.angleStart); + SimplyPebble.elementAngleEnd(id, def.angleEnd); + SimplyPebble.elementBorderWidth(id, def.borderWidth); break; case StageElement.TextType: SimplyPebble.elementRadius(id, def.radius); diff --git a/src/js/ui/stage.js b/src/js/ui/stage.js index 70ea49a7..f8ec59d0 100644 --- a/src/js/ui/stage.js +++ b/src/js/ui/stage.js @@ -10,7 +10,7 @@ var Stage = function(stageDef) { Stage.RectType = 1; Stage.CircleType = 2; -Stage.CircleArcType = 6; +Stage.RadialType = 6; Stage.TextType = 3; Stage.ImageType = 4; Stage.InverterType = 5; diff --git a/src/simply/simply_msg_commands.h b/src/simply/simply_msg_commands.h index 4d451c38..41097537 100644 --- a/src/simply/simply_msg_commands.h +++ b/src/simply/simply_msg_commands.h @@ -47,8 +47,9 @@ enum Command { CommandElementRemove, CommandElementCommon, CommandElementRadius, - CommandElementAnglestart, - CommandElementAngleend, + CommandElementAngleStart, + CommandElementAngleEnd, + CommandElementBorderWidth, CommandElementText, CommandElementTextStyle, CommandElementImage, diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 64c2b150..6cf80d9a 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -53,20 +53,28 @@ struct __attribute__((__packed__)) ElementRadiusPacket { uint16_t radius; }; -typedef struct CommandElementAnglestartPacket CommandElementAnglestartPacket; +typedef struct CommandElementAngleStartPacket CommandElementAngleStartPacket; -struct __attribute__((__packed__)) CommandElementAnglestartPacket { +struct __attribute__((__packed__)) CommandElementAngleStartPacket { Packet packet; uint32_t id; - uint16_t anglestart; + uint16_t angle_start; }; -typedef struct CommandElementAngleendPacket CommandElementAngleendPacket; +typedef struct CommandElementAngleEndPacket CommandElementAngleEndPacket; -struct __attribute__((__packed__)) CommandElementAngleendPacket { +struct __attribute__((__packed__)) CommandElementAngleEndPacket { Packet packet; uint32_t id; - uint16_t angleend; + uint16_t angle_end; +}; + +typedef struct CommandElementBorderWidthPacket CommandElementBorderWidthPacket; + +struct __attribute__((__packed__)) CommandElementBorderWidthPacket { + Packet packet; + uint32_t id; + uint16_t border_width; }; typedef struct ElementTextPacket ElementTextPacket; @@ -216,10 +224,24 @@ static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementC } } -static void circlearc_element_draw(GContext *ctx, SimplyStage *self, SimplyElementCircleArc *element) { - GRect frame = grect_inset(element->frame, GEdgeInsets(30)); - graphics_context_set_fill_color(ctx, element->background_color); - graphics_fill_radial(ctx, frame, GOvalScaleModeFitCircle, element->radius, DEG_TO_TRIGANGLE(element->anglestart), DEG_TO_TRIGANGLE(element->angleend)); +static void radial_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRadial *element) { + // element->border_color + // element->border_width + if (element->border_color.a) { + printf("Draw Arc"); + GRect frame = grect_inset(element->frame, GEdgeInsets(0)); + graphics_context_set_fill_color(ctx, element->background_color); + graphics_draw_arc(ctx, frame, GOvalScaleModeFitCircle, + DEG_TO_TRIGANGLE(element->angle_start), + DEG_TO_TRIGANGLE(element->angle_end)); + } else { + printf("Draw Radial"); + GRect frame = grect_inset(element->frame, GEdgeInsets(0)); + graphics_context_set_fill_color(ctx, element->background_color); + graphics_fill_radial(ctx, frame, GOvalScaleModeFitCircle, element->radius, + DEG_TO_TRIGANGLE(element->angle_start), + DEG_TO_TRIGANGLE(element->angle_end)); + } } static char *format_time(char *format) { @@ -286,8 +308,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { case SimplyElementTypeCircle: circle_element_draw(ctx, self, (SimplyElementCircle*) element); break; - case SimplyElementTypeCircleArc: - circlearc_element_draw(ctx, self, (SimplyElementCircleArc*) element); + case SimplyElementTypeRadial: + radial_element_draw(ctx, self, (SimplyElementRadial*) element); break; case SimplyElementTypeText: text_element_draw(ctx, self, (SimplyElementText*) element); @@ -313,7 +335,7 @@ static SimplyElementCommon *alloc_element(SimplyElementType type) { case SimplyElementTypeNone: return NULL; case SimplyElementTypeRect: return malloc0(sizeof(SimplyElementRect)); case SimplyElementTypeCircle: return malloc0(sizeof(SimplyElementCircle)); - case SimplyElementTypeCircleArc: return malloc0(sizeof(SimplyElementCircleArc)); + case SimplyElementTypeRadial: return malloc0(sizeof(SimplyElementRadial)); case SimplyElementTypeText: return malloc0(sizeof(SimplyElementText)); case SimplyElementTypeImage: return malloc0(sizeof(SimplyElementImage)); case SimplyElementTypeInverter: { @@ -568,23 +590,33 @@ static void handle_element_radius_packet(Simply *simply, Packet *data) { simply_stage_update(simply->stage); }; -static void handle_element_anglestart_packet(Simply *simply, Packet *data) { - CommandElementAnglestartPacket *packet = (CommandElementAnglestartPacket*) data; +static void handle_element_angle_start_packet(Simply *simply, Packet *data) { + CommandElementAngleStartPacket *packet = (CommandElementAngleStartPacket*) data; SimplyElementRect *element = (SimplyElementRect*) simply_stage_get_element(simply->stage, packet->id); if (!element) { return; } - element->anglestart = packet->anglestart; + element->angle_start = packet->angle_start; simply_stage_update(simply->stage); }; -static void handle_element_angleend_packet(Simply *simply, Packet *data) { - CommandElementAngleendPacket *packet = (CommandElementAngleendPacket*) data; +static void handle_element_angle_end_packet(Simply *simply, Packet *data) { + CommandElementAngleEndPacket *packet = (CommandElementAngleEndPacket*) data; SimplyElementRect *element = (SimplyElementRect*) simply_stage_get_element(simply->stage, packet->id); if (!element) { return; } - element->angleend = packet->angleend; + element->angle_end = packet->angle_end; + simply_stage_update(simply->stage); +}; + +static void handle_element_border_width_packet(Simply *simply, Packet *data) { + CommandElementBorderWidthPacket *packet = (CommandElementBorderWidthPacket*) data; + SimplyElementRect *element = (SimplyElementRect*) simply_stage_get_element(simply->stage, packet->id); + if (!element) { + return; + } + element->border_width = packet->border_width; simply_stage_update(simply->stage); }; @@ -664,11 +696,14 @@ bool simply_stage_handle_packet(Simply *simply, Packet *packet) { case CommandElementRadius: handle_element_radius_packet(simply, packet); return true; - case CommandElementAnglestart: - handle_element_anglestart_packet(simply, packet); + case CommandElementAngleStart: + handle_element_angle_start_packet(simply, packet); + return true; + case CommandElementAngleEnd: + handle_element_angle_end_packet(simply, packet); return true; - case CommandElementAngleend: - handle_element_angleend_packet(simply, packet); + case CommandElementBorderWidth: + handle_element_border_width_packet(simply, packet); return true; case CommandElementText: handle_element_text_packet(simply, packet); diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index 287b0c79..1b4ee5af 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -26,7 +26,7 @@ enum SimplyElementType { SimplyElementTypeNone = 0, SimplyElementTypeRect = 1, SimplyElementTypeCircle = 2, - SimplyElementTypeCircleArc = 6, + SimplyElementTypeRadial = 6, SimplyElementTypeText = 3, SimplyElementTypeImage = 4, SimplyElementTypeInverter = 5, @@ -67,13 +67,14 @@ typedef struct SimplyElementRect SimplyElementRect; struct SimplyElementRect { SimplyElementCommonMember; uint16_t radius; - uint16_t anglestart; - uint16_t angleend; + uint16_t angle_start; + uint16_t angle_end; + uint16_t border_width; }; typedef struct SimplyElementRect SimplyElementCircle; -typedef struct SimplyElementRect SimplyElementCircleArc; +typedef struct SimplyElementRect SimplyElementRadial; typedef struct SimplyElementText SimplyElementText; From 3658e710707bb74ae030564f3077ebdd03a4f1da Mon Sep 17 00:00:00 2001 From: bkbilly Date: Wed, 10 Feb 2016 18:11:00 +0200 Subject: [PATCH 707/791] New struct SimplyElementRadial, Fixed the behavour of radial_element_draw --- src/js/app.js | 6 +++--- src/simply/simply_stage.c | 30 ++++++++++++++---------------- src/simply/simply_stage.h | 9 +++++++-- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index b91c377f..740a0cf0 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -39,16 +39,16 @@ main.on('click', 'up', function(e) { }); main.on('click', 'select', function(e) { - var wind = new UI.Window({backgroundColor:'red'}); + var wind = new UI.Window({backgroundColor:'white'}); var radial = new UI.Radial({ position: new Vector2(0, 0), size: new Vector2(150, 150), angleStart: 0, - angleEnd: 132, + angleEnd: 300, radius: 20, borderColor: "red", - borderWidth: 5, + borderWidth: 19, backgroundColor: 'blue', }); diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 6cf80d9a..6acf3a18 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -225,23 +225,21 @@ static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementC } static void radial_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRadial *element) { - // element->border_color - // element->border_width - if (element->border_color.a) { - printf("Draw Arc"); - GRect frame = grect_inset(element->frame, GEdgeInsets(0)); - graphics_context_set_fill_color(ctx, element->background_color); - graphics_draw_arc(ctx, frame, GOvalScaleModeFitCircle, - DEG_TO_TRIGANGLE(element->angle_start), - DEG_TO_TRIGANGLE(element->angle_end)); - } else { - printf("Draw Radial"); + if (element->background_color.a) { GRect frame = grect_inset(element->frame, GEdgeInsets(0)); graphics_context_set_fill_color(ctx, element->background_color); graphics_fill_radial(ctx, frame, GOvalScaleModeFitCircle, element->radius, - DEG_TO_TRIGANGLE(element->angle_start), - DEG_TO_TRIGANGLE(element->angle_end)); + DEG_TO_TRIGANGLE(element->angle_start), + DEG_TO_TRIGANGLE(element->angle_end)); } + if (element->border_color.a) { + GRect frame = grect_inset(element->frame, GEdgeInsets(10)); + graphics_context_set_stroke_color(ctx, element->border_color); + graphics_context_set_stroke_width(ctx, element->border_width); + graphics_draw_arc(ctx, frame, GOvalScaleModeFitCircle, + DEG_TO_TRIGANGLE(element->angle_start), + DEG_TO_TRIGANGLE(element->angle_end)); + } } static char *format_time(char *format) { @@ -592,7 +590,7 @@ static void handle_element_radius_packet(Simply *simply, Packet *data) { static void handle_element_angle_start_packet(Simply *simply, Packet *data) { CommandElementAngleStartPacket *packet = (CommandElementAngleStartPacket*) data; - SimplyElementRect *element = (SimplyElementRect*) simply_stage_get_element(simply->stage, packet->id); + SimplyElementRadial *element = (SimplyElementRadial*) simply_stage_get_element(simply->stage, packet->id); if (!element) { return; } @@ -602,7 +600,7 @@ static void handle_element_angle_start_packet(Simply *simply, Packet *data) { static void handle_element_angle_end_packet(Simply *simply, Packet *data) { CommandElementAngleEndPacket *packet = (CommandElementAngleEndPacket*) data; - SimplyElementRect *element = (SimplyElementRect*) simply_stage_get_element(simply->stage, packet->id); + SimplyElementRadial *element = (SimplyElementRadial*) simply_stage_get_element(simply->stage, packet->id); if (!element) { return; } @@ -612,7 +610,7 @@ static void handle_element_angle_end_packet(Simply *simply, Packet *data) { static void handle_element_border_width_packet(Simply *simply, Packet *data) { CommandElementBorderWidthPacket *packet = (CommandElementBorderWidthPacket*) data; - SimplyElementRect *element = (SimplyElementRect*) simply_stage_get_element(simply->stage, packet->id); + SimplyElementRadial *element = (SimplyElementRadial*) simply_stage_get_element(simply->stage, packet->id); if (!element) { return; } diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index 1b4ee5af..c00b3596 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -67,6 +67,13 @@ typedef struct SimplyElementRect SimplyElementRect; struct SimplyElementRect { SimplyElementCommonMember; uint16_t radius; +}; + +typedef struct SimplyElementRadial SimplyElementRadial; + +struct SimplyElementRadial { + SimplyElementCommonMember; + uint16_t radius; uint16_t angle_start; uint16_t angle_end; uint16_t border_width; @@ -74,8 +81,6 @@ struct SimplyElementRect { typedef struct SimplyElementRect SimplyElementCircle; -typedef struct SimplyElementRect SimplyElementRadial; - typedef struct SimplyElementText SimplyElementText; struct SimplyElementText { From f43d2e4e28e5a057ac6a9a9d9060c3ab77365909 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 13 Feb 2016 15:02:50 -0800 Subject: [PATCH 708/791] Change Radial defaults Sometimes the defaults are relied upon, so make sure they are defaults that are most likely to be used and consistent with the other elements. --- src/js/ui/radial.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/ui/radial.js b/src/js/ui/radial.js index c815f894..3d57e91d 100644 --- a/src/js/ui/radial.js +++ b/src/js/ui/radial.js @@ -4,9 +4,10 @@ var StageElement = require('ui/element'); var defaults = { backgroundColor: 'white', + borderColor: 'clear', + borderWidth: 1, angleStart: 0, - angleEnd: 132, - borderWidth: 5, + angleEnd: 360, }; var Radial = function(elementDef) { From 045e0f9d7557cb73af8c5be3a35d9197e2781a11 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 13 Feb 2016 15:04:14 -0800 Subject: [PATCH 709/791] Add Radial inner, start, and end borders --- src/simply/simply_stage.c | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index ff302a3b..cb62a74c 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -225,22 +225,33 @@ static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementC } } +static void prv_draw_line_polar(GContext *ctx, const GRect *outer_frame, const GRect *inner_frame, + GOvalScaleMode scale_mode, int32_t angle) { + const GPoint a = gpoint_from_polar(*outer_frame, scale_mode, angle); + const GPoint b = gpoint_from_polar(*inner_frame, scale_mode, angle); + graphics_draw_line(ctx, a, b); +} + static void radial_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRadial *element) { + const GOvalScaleMode scale_mode = GOvalScaleModeFitCircle; + const int32_t angle_start = DEG_TO_TRIGANGLE(element->angle_start); + const int32_t angle_end = DEG_TO_TRIGANGLE(element->angle_end); if (element->background_color.a) { - GRect frame = grect_inset(element->frame, GEdgeInsets(0)); graphics_context_set_fill_color(ctx, element->background_color); - graphics_fill_radial(ctx, frame, GOvalScaleModeFitCircle, element->radius, - DEG_TO_TRIGANGLE(element->angle_start), - DEG_TO_TRIGANGLE(element->angle_end)); + graphics_fill_radial(ctx, element->frame, scale_mode, element->radius, angle_start, angle_end); } - if (element->border_color.a) { - GRect frame = grect_inset(element->frame, GEdgeInsets(10)); + if (element->border_color.a && element->border_width) { graphics_context_set_stroke_color(ctx, element->border_color); graphics_context_set_stroke_width(ctx, element->border_width); - graphics_draw_arc(ctx, frame, GOvalScaleModeFitCircle, - DEG_TO_TRIGANGLE(element->angle_start), - DEG_TO_TRIGANGLE(element->angle_end)); - } + graphics_draw_arc(ctx, element->frame, scale_mode, angle_start, angle_end); + GRect inner_frame = grect_inset(element->frame, GEdgeInsets(element->radius)); + prv_draw_line_polar(ctx, &element->frame, &inner_frame, scale_mode, angle_start); + prv_draw_line_polar(ctx, &element->frame, &inner_frame, scale_mode, angle_end); + if (inner_frame.size.w) { + graphics_draw_arc(ctx, inner_frame, GOvalScaleModeFitCircle, + angle_start, angle_end); + } + } } static char *format_time(char *format) { From d0bc7bdd6120cb02cdb9b3f7a7f7cee0094bf873 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 13 Feb 2016 15:18:34 -0800 Subject: [PATCH 710/791] Update app.js dynamic window example --- src/js/app.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 740a0cf0..23a15048 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -39,21 +39,29 @@ main.on('click', 'up', function(e) { }); main.on('click', 'select', function(e) { - var wind = new UI.Window({backgroundColor:'white'}); - + var wind = new UI.Window({ + backgroundColor: 'black' + }); var radial = new UI.Radial({ - position: new Vector2(0, 0), - size: new Vector2(150, 150), + position: new Vector2(2, 14), + size: new Vector2(140, 140), angleStart: 0, angleEnd: 300, radius: 20, - borderColor: "red", - borderWidth: 19, - backgroundColor: 'blue', + backgroundColor: 'cyan', + borderColor: 'celeste', + borderWidth: 1, + }); + var textfield = new UI.Text({ + position: new Vector2(0, 57), + size: new Vector2(144, 60), + font: 'gothic-24-bold', + text: 'Dynamic\nWindow', + textAlign: 'center' }); - wind.add(radial); - wind.show(radial); + wind.add(textfield); + wind.show(); }); main.on('click', 'down', function(e) { @@ -62,4 +70,4 @@ main.on('click', 'down', function(e) { card.subtitle('Is a Window'); card.body('The simplest window type in Pebble.js.'); card.show(); -}); \ No newline at end of file +}); From fa8aa9d68084efdd602f15989eb090ca1a74e49f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 13 Feb 2016 15:28:48 -0800 Subject: [PATCH 711/791] Reset the border width for each element This is a temporary solution until all elements gain the border width property. Previously, stroke width was not a graphics context variable, thus elements did not begin with a border width. --- src/simply/simply_stage.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index cb62a74c..b6d94e11 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -305,6 +305,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyElementCommon *element = (SimplyElementCommon*) self->stage_layer.elements; while (element) { + // TODO: change border_width to a common element member + graphics_context_set_stroke_width(ctx, 1); int16_t max_y = element->frame.origin.y + element->frame.size.h; if (max_y > frame.size.h) { frame.size.h = max_y; From 7b670016edf2c8f789e9687ff4273b177b562230 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 13 Feb 2016 21:00:07 -0800 Subject: [PATCH 712/791] Fix simply_res_add_custom_font to set the id (Addresses #137) --- src/simply/simply_res.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_res.c b/src/simply/simply_res.c index 79ac5a77..e60c1529 100644 --- a/src/simply/simply_res.c +++ b/src/simply/simply_res.c @@ -172,7 +172,7 @@ SimplyImage *simply_res_auto_image(SimplyRes *self, uint32_t id, bool is_placeho } GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id) { - SimplyFont *font = malloc(sizeof(*font)); + SimplyFont *font = malloc0(sizeof(*font)); if (!font) { return NULL; } @@ -188,6 +188,7 @@ GFont simply_res_add_custom_font(SimplyRes *self, uint32_t id) { return NULL; } + font->id = id; font->font = custom_font; list1_prepend(&self->fonts, &font->node); From 47dcc1dc50198385a975ef8d1fa35b3268dff8bd Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 13 Feb 2016 21:08:05 -0800 Subject: [PATCH 713/791] Fix SimplyMenu to free title only if it is not the empty title (Addresses #132) --- src/simply/simply_menu.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index 09276f00..a8a05d07 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -161,13 +161,17 @@ static SimplyMenuSection *prv_get_menu_section(SimplyMenu *self, int index) { (void*)(uintptr_t) index); } +static void prv_free_title(char **title) { + if (*title && *title != EMPTY_TITLE) { + free(*title); + *title = NULL; + } +} + static void prv_destroy_section(SimplyMenu *self, SimplyMenuSection *section) { if (!section) { return; } list1_remove(&self->menu_layer.sections, §ion->node); - if (section->title && section->title != EMPTY_TITLE) { - free(section->title); - section->title = NULL; - } + prv_free_title(§ion->title); free(section); } @@ -187,14 +191,8 @@ static SimplyMenuItem *prv_get_menu_item(SimplyMenu *self, int section, int inde static void prv_destroy_item(SimplyMenu *self, SimplyMenuItem *item) { if (!item) { return; } list1_remove(&self->menu_layer.items, &item->node); - if (item->title) { - free(item->title); - item->title = NULL; - } - if (item->subtitle) { - free(item->subtitle); - item->subtitle = NULL; - } + prv_free_title(&item->title); + prv_free_title(&item->subtitle); free(item); } From 6996a55583d2f8668a066580b60e047f223337e1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 13 Feb 2016 21:18:36 -0800 Subject: [PATCH 714/791] Fix ajax synchronous requests to only call the callback once (Addresses #131) --- src/js/lib/ajax.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/lib/ajax.js b/src/js/lib/ajax.js index 92d90432..89b2f981 100644 --- a/src/js/lib/ajax.js +++ b/src/js/lib/ajax.js @@ -95,8 +95,10 @@ var ajax = function(opt, success, failure) { } } + var ready = false; req.onreadystatechange = function(e) { - if (req.readyState === 4) { + if (req.readyState === 4 && !ready) { + ready = true; var body = req.responseText; var okay = req.status >= 200 && req.status < 300 || req.status === 304; From 5d8b9bd775523f3366509a9659746250647d5a75 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 13 Feb 2016 22:03:22 -0800 Subject: [PATCH 715/791] Fix SimplyWindow to have status_bar_insets_bottom only for round (Addresses #139) --- src/js/app.js | 4 ++++ src/simply/simply_menu.c | 2 ++ src/simply/simply_window.c | 2 ++ src/simply/simply_window.h | 4 +++- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/js/app.js b/src/js/app.js index 23a15048..6ea8975d 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -28,6 +28,10 @@ main.on('click', 'up', function(e) { }, { title: 'Second Item', subtitle: 'Subtitle Text' + }, { + title: 'Third Item', + }, { + title: 'Fourth Item', }] }] }); diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index a8a05d07..be14aa89 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -644,7 +644,9 @@ SimplyMenu *simply_menu_create(Simply *simply) { SimplyMenu *self = malloc(sizeof(*self)); *self = (SimplyMenu) { .window.simply = simply, +#if defined(PBL_ROUND) .window.status_bar_insets_bottom = true, +#endif .menu_layer.num_sections = 1, }; diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 11ad49e4..2c49a243 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -125,8 +125,10 @@ static void prv_update_layer_placement(SimplyWindow *self) { if (has_status_bar) { GRect status_frame = { .size = { frame.size.w, STATUS_BAR_LAYER_HEIGHT } }; frame.origin.y = STATUS_BAR_LAYER_HEIGHT; +#if defined(PBL_ROUND) frame.size.h -= self->status_bar_insets_bottom ? STATUS_BAR_LAYER_HEIGHT * 2 : STATUS_BAR_LAYER_HEIGHT; +#endif if (has_action_bar) { status_frame.size.w -= ACTION_BAR_WIDTH; } diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index b29719f4..f8010f53 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -25,8 +25,10 @@ struct SimplyWindow { GColor8 background_color; bool is_scrollable:1; bool use_status_bar:1; - bool status_bar_insets_bottom:1; bool use_action_bar:1; +#if defined(PBL_ROUND) + bool status_bar_insets_bottom:1; +#endif }; SimplyWindow *simply_window_init(SimplyWindow *self, Simply *simply); From 96f9bdb38f04a5efcaffc84cdf476acc35c9d9e7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 13 Feb 2016 22:45:56 -0800 Subject: [PATCH 716/791] Fix simply_ui text flow and paging to account for the status bar (Addresses #141) --- src/simply/simply_ui.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 48eee9d8..3ab14ea8 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -158,8 +158,13 @@ void simply_ui_set_text_color(SimplyUi *self, SimplyUiTextfieldId textfield_id, static void enable_text_flow_and_paging(SimplyUi *self, GTextAttributes *text_attributes, const GRect *box) { graphics_text_attributes_enable_screen_text_flow(text_attributes, TEXT_FLOW_INSET); - graphics_text_attributes_enable_paging( - text_attributes, box->origin, layer_get_bounds((Layer *)self->window.scroll_layer)); + Layer *base_layer = (Layer *)self->window.scroll_layer; + const GPoint origin_on_screen = + layer_convert_point_to_screen(base_layer, box->origin); + const GRect paging_on_screen = + layer_convert_rect_to_screen(base_layer, + (GRect) { .size = layer_get_bounds(base_layer).size }); + graphics_text_attributes_enable_paging(text_attributes, origin_on_screen, paging_on_screen); } static void layer_update_callback(Layer *layer, GContext *ctx) { From 35fa7a26e4c6bd176c12ac9a7ba8dce813f31e21 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 13 Feb 2016 23:12:35 -0800 Subject: [PATCH 717/791] Add src/util/graphics_text.h with graphics_text_attributes_enable_paging_on_layer --- src/util/graphics_text.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/util/graphics_text.h diff --git a/src/util/graphics_text.h b/src/util/graphics_text.h new file mode 100644 index 00000000..0f8fde54 --- /dev/null +++ b/src/util/graphics_text.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#define TEXT_FLOW_DEFAULT_INSET 8 + +static inline void graphics_text_attributes_enable_paging_on_layer( + GTextAttributes *text_attributes, const Layer *layer, const GRect *box, const int inset) { + graphics_text_attributes_enable_screen_text_flow(text_attributes, inset); + const GPoint origin_on_screen = layer_convert_point_to_screen(layer, box->origin); + const GRect paging_on_screen = + layer_convert_rect_to_screen(layer, (GRect) { .size = layer_get_bounds(layer).size }); + graphics_text_attributes_enable_paging(text_attributes, origin_on_screen, paging_on_screen); +} + From 05d4f5cf831cc616677da148579675f6db9f10d6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 13 Feb 2016 23:13:28 -0800 Subject: [PATCH 718/791] Add round text flow and centering to section title (Addresses #140) --- src/simply/simply_menu.c | 15 ++++++++++++--- src/simply/simply_ui.c | 15 ++++----------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/simply/simply_menu.c b/src/simply/simply_menu.c index be14aa89..b2ecd564 100644 --- a/src/simply/simply_menu.c +++ b/src/simply/simply_menu.c @@ -9,7 +9,9 @@ #include "util/color.h" #include "util/display.h" #include "util/graphics.h" +#include "util/graphics_text.h" #include "util/menu_layer.h" +#include "util/noop.h" #include "util/platform.h" #include "util/string.h" @@ -50,7 +52,7 @@ struct __attribute__((__packed__)) MenuSectionPacket { uint16_t section; uint16_t num_items; GColor8 background_color; - GColor8 text_color; + GColor8 text_color; uint16_t title_length; char title[]; }; @@ -345,7 +347,6 @@ ROUND_USAGE static int16_t prv_menu_get_cell_height_callback(MenuLayer *menu_lay } } - static void prv_menu_draw_header_callback(GContext *ctx, const Layer *cell_layer, uint16_t section_index, void *data) { SimplyMenu *self = data; @@ -367,8 +368,16 @@ static void prv_menu_draw_header_callback(GContext *ctx, const Layer *cell_layer bounds.origin.y -= 1; graphics_context_set_text_color(ctx, gcolor8_get_or(section->title_foreground, GColorBlack)); + + GTextAttributes *title_attributes = graphics_text_attributes_create(); + PBL_IF_ROUND_ELSE( + graphics_text_attributes_enable_paging_on_layer( + title_attributes, (Layer *)menu_layer_get_scroll_layer(self->menu_layer.menu_layer), + &bounds, TEXT_FLOW_DEFAULT_INSET), NOOP); + const GTextAlignment align = PBL_IF_ROUND_ELSE(GTextAlignmentCenter, GTextAlignmentLeft); graphics_draw_text(ctx, section->title, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD), - bounds, GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); + bounds, GTextOverflowModeTrailingEllipsis, align, title_attributes); + graphics_text_attributes_destroy(title_attributes); } static void simply_menu_draw_row_spinner(SimplyMenu *self, GContext *ctx, diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 3ab14ea8..11736453 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -9,6 +9,7 @@ #include "util/compat.h" #include "util/color.h" #include "util/graphics.h" +#include "util/graphics_text.h" #include "util/math.h" #include "util/noop.h" #include "util/string.h" @@ -16,8 +17,6 @@ #include -#define TEXT_FLOW_INSET 8 - struct __attribute__((__packed__)) SimplyStyle { const char *title_font; const char *subtitle_font; @@ -157,14 +156,8 @@ void simply_ui_set_text_color(SimplyUi *self, SimplyUiTextfieldId textfield_id, static void enable_text_flow_and_paging(SimplyUi *self, GTextAttributes *text_attributes, const GRect *box) { - graphics_text_attributes_enable_screen_text_flow(text_attributes, TEXT_FLOW_INSET); - Layer *base_layer = (Layer *)self->window.scroll_layer; - const GPoint origin_on_screen = - layer_convert_point_to_screen(base_layer, box->origin); - const GRect paging_on_screen = - layer_convert_rect_to_screen(base_layer, - (GRect) { .size = layer_get_bounds(base_layer).size }); - graphics_text_attributes_enable_paging(text_attributes, origin_on_screen, paging_on_screen); + graphics_text_attributes_enable_paging_on_layer( + text_attributes, (Layer *)self->window.scroll_layer, box, TEXT_FLOW_DEFAULT_INSET); } static void layer_update_callback(Layer *layer, GContext *ctx) { @@ -195,7 +188,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { GPoint cursor = { margin_x, margin_y }; if (self->window.use_action_bar) { - text_frame.size.w -= ACTION_BAR_WIDTH + PBL_IF_ROUND_ELSE(TEXT_FLOW_INSET, 0); + text_frame.size.w -= ACTION_BAR_WIDTH + PBL_IF_ROUND_ELSE(TEXT_FLOW_DEFAULT_INSET, 0); window_frame.size.w -= ACTION_BAR_WIDTH; } From 72de08780157a240b8e5362563ddc8500da664a8 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 14 Feb 2016 14:29:39 -0800 Subject: [PATCH 719/791] Fix simply_ui to adjust the body draw box by the content height only (Addresses #142) --- src/simply/simply_ui.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 11736453..1d0dba12 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -287,6 +287,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { enable_text_flow_and_paging(self, body_attributes, &body_rect), NOOP); GSize body_size = graphics_text_layout_get_content_size_with_attributes( body->text, body_font, body_rect, GTextOverflowModeWordWrap, text_align, body_attributes); + body_size.w = body_rect.size.w; if (self->window.is_scrollable) { body_rect.size = body_size; int16_t new_height = cursor.y + 2 * margin_y + body_size.h; From 1aa59c825e2dd0606903a52c81fd81ba695be3ff Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 14 Feb 2016 21:23:35 -0800 Subject: [PATCH 720/791] Fix simply_window to compensate for the status bar on rect (Addresses #143) --- src/simply/simply_window.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 2c49a243..9d9d9b70 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -65,6 +65,7 @@ typedef ClickPacket LongClickPacket; static GColor8 s_button_palette[] = { { GColorWhiteARGB8 }, { GColorClearARGB8 } }; +static GRect prv_update_layer_placement(SimplyWindow *self); static void click_config_provider(void *data); static bool prv_send_click(SimplyMsg *self, Command type, ButtonId button) { @@ -101,19 +102,18 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { if (!self->layer) { return; } if (!is_scrollable) { - GRect bounds = { GPointZero, layer_get_bounds(window_get_root_layer(self->window)).size }; - layer_set_bounds(self->layer, bounds); + GRect frame = prv_update_layer_placement(self); const bool animated = true; scroll_layer_set_content_offset(self->scroll_layer, GPointZero, animated); - scroll_layer_set_content_size(self->scroll_layer, bounds.size); + scroll_layer_set_content_size(self->scroll_layer, frame.size); } layer_mark_dirty(self->layer); } -static void prv_update_layer_placement(SimplyWindow *self) { +static GRect prv_update_layer_placement(SimplyWindow *self) { Layer * const main_layer = self->layer ?: scroll_layer_get_layer(self->scroll_layer); - if (!main_layer) { return; } + if (!main_layer) { return GRectZero; } GRect frame = { .size = layer_get_frame(window_get_root_layer(self->window)).size }; @@ -125,10 +125,9 @@ static void prv_update_layer_placement(SimplyWindow *self) { if (has_status_bar) { GRect status_frame = { .size = { frame.size.w, STATUS_BAR_LAYER_HEIGHT } }; frame.origin.y = STATUS_BAR_LAYER_HEIGHT; -#if defined(PBL_ROUND) - frame.size.h -= self->status_bar_insets_bottom ? STATUS_BAR_LAYER_HEIGHT * 2 : - STATUS_BAR_LAYER_HEIGHT; -#endif + frame.size.h -= + PBL_IF_ROUND_ELSE(self->status_bar_insets_bottom, false) ? STATUS_BAR_LAYER_HEIGHT * 2 : + STATUS_BAR_LAYER_HEIGHT; if (has_action_bar) { status_frame.size.w -= ACTION_BAR_WIDTH; } @@ -137,6 +136,7 @@ static void prv_update_layer_placement(SimplyWindow *self) { } layer_set_frame(main_layer, frame); + return frame; } From df25de3e5af907d831f71fc22b533f187f174003 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 14 Feb 2016 21:28:31 -0800 Subject: [PATCH 721/791] Change simply_ui to render the last line of body with margin --- src/simply/simply_ui.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 1d0dba12..da236194 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -179,13 +179,14 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { self->ui_layer.custom_body_font : fonts_get_system_font(style->body_font); const int16_t margin_x = 5; - const int16_t margin_y = 2; + const int16_t margin_top = 2; + const int16_t margin_bottom = self->window.is_scrollable ? 10 : margin_top; const int16_t image_offset_y = 3; GRect text_frame = frame; text_frame.size.w -= 2 * margin_x; - text_frame.size.h += 1000; - GPoint cursor = { margin_x, margin_y }; + text_frame.size.h += INT16_MAX / 2; + GPoint cursor = { margin_x, margin_top }; if (self->window.use_action_bar) { text_frame.size.w -= ACTION_BAR_WIDTH + PBL_IF_ROUND_ELSE(TEXT_FLOW_DEFAULT_INSET, 0); @@ -272,32 +273,32 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { if (body_image) { body_image_bounds = gbitmap_get_bounds(body_image->bitmap); + image_pos = cursor; + cursor.y += body_image_bounds.size.h; } if (has_body) { body_rect = (GRect) { cursor, (self->window.is_scrollable ? text_frame.size : frame.size) }; body_rect.origin = cursor; body_rect.size.w = text_frame.size.w; - body_rect.size.h -= 2 * margin_y + cursor.y; - if (body_image) { - image_pos = body_rect.origin; - body_rect.origin.y += body_image_bounds.size.h; - } + body_rect.size.h -= cursor.y + margin_bottom; PBL_IF_ROUND_ELSE( enable_text_flow_and_paging(self, body_attributes, &body_rect), NOOP); GSize body_size = graphics_text_layout_get_content_size_with_attributes( body->text, body_font, body_rect, GTextOverflowModeWordWrap, text_align, body_attributes); body_size.w = body_rect.size.w; + cursor.y = body_rect.origin.y + body_size.h; if (self->window.is_scrollable) { body_rect.size = body_size; - int16_t new_height = cursor.y + 2 * margin_y + body_size.h; - frame.size.h = window_frame.size.h > new_height ? window_frame.size.h : new_height; + const int new_height = cursor.y + margin_bottom; + frame.size.h = MAX(window_frame.size.h, new_height); layer_set_frame(layer, frame); scroll_layer_set_content_size(self->window.scroll_layer, frame.size); } else if (!self->ui_layer.custom_body_font && body_size.h > body_rect.size.h) { body_font = fonts_get_system_font(FONT_KEY_GOTHIC_18); } - cursor.y = body_rect.origin.y + body_size.h; + // For rendering text descenders + body_rect.size.h += margin_bottom; } IF_SDK_2_ELSE(({ From b9bcd8faf5d693bb6925f6845bd8c60221ef61ff Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 16 Feb 2016 10:56:19 +1300 Subject: [PATCH 722/791] Add the option to not send the current options in the url hash When using data URLs, sending the options in the URL hash will break the URL --- src/js/settings/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index a47bbc47..b03ec734 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -165,7 +165,7 @@ Settings.onOpenConfig = function(e) { return; } var hash = encodeURIComponent(JSON.stringify(options)); - Pebble.openURL(url + '#' + hash); + Pebble.openURL(url + (listener.params.noHash ? '' : '#' + hash)); }; Settings.onCloseConfig = function(e) { From 2022b6c8e44486b1512b34f6153e395c341e0202 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 19 Feb 2016 17:12:22 -0800 Subject: [PATCH 723/791] Add windowStack.length --- src/js/ui/windowstack.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/js/ui/windowstack.js b/src/js/ui/windowstack.js index ab16f258..2d0c61b6 100644 --- a/src/js/ui/windowstack.js +++ b/src/js/ui/windowstack.js @@ -110,6 +110,10 @@ WindowStack.prototype.each = function(callback) { } }; +WindowStack.prototype.length = function() { + return this._items.length; +}; + WindowStack.prototype.emitHide = function(windowId) { var wind = this.get(windowId); if (wind !== this.top()) { return; } From 0fd64936eb2ecf93d8fc461efba1a69205636ae2 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 28 Feb 2016 09:32:23 +1300 Subject: [PATCH 724/791] Change noHash to hash: false --- src/js/settings/settings.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/settings/settings.js b/src/js/settings/settings.js index b03ec734..1dff6bec 100644 --- a/src/js/settings/settings.js +++ b/src/js/settings/settings.js @@ -164,8 +164,9 @@ Settings.onOpenConfig = function(e) { options = Settings.getBaseOptions(); return; } - var hash = encodeURIComponent(JSON.stringify(options)); - Pebble.openURL(url + (listener.params.noHash ? '' : '#' + hash)); + if (listener.params.hash !== false) + url += '#' + encodeURIComponent(JSON.stringify(options)); + Pebble.openURL(url); }; Settings.onCloseConfig = function(e) { From dc4849f6c892faca1fdb34f633b84ddb1e64ff51 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 27 Feb 2016 12:34:51 -0800 Subject: [PATCH 725/791] Fix to reset scroll position for distinct cards (Addresses #128) --- src/simply/simply_window.c | 39 +++++++++++++++++++++----------------- src/simply/simply_window.h | 3 ++- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 9d9d9b70..f1c12128 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -41,8 +41,6 @@ struct __attribute__((__packed__)) WindowStatusBarPacket { bool status; }; -typedef struct WindowButtonConfigPacket WindowButtonConfigPacket; - typedef struct WindowActionBarPacket WindowActionBarPacket; struct __attribute__((__packed__)) WindowActionBarPacket { @@ -65,7 +63,7 @@ typedef ClickPacket LongClickPacket; static GColor8 s_button_palette[] = { { GColorWhiteARGB8 }, { GColorClearARGB8 } }; -static GRect prv_update_layer_placement(SimplyWindow *self); +static void prv_update_layer_placement(SimplyWindow *self, GRect *frame_out); static void click_config_provider(void *data); static bool prv_send_click(SimplyMsg *self, Command type, ButtonId button) { @@ -92,28 +90,32 @@ static void prv_set_scroll_layer_click_config(SimplyWindow *self) { } } -void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable) { - if (self->is_scrollable == is_scrollable) { return; } +void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable, bool animated, + bool reset) { + if (self->is_scrollable == is_scrollable && !reset) { return; } self->is_scrollable = is_scrollable; prv_set_scroll_layer_click_config(self); - if (!self->layer) { return; } - - if (!is_scrollable) { - GRect frame = prv_update_layer_placement(self); + if (!is_scrollable || reset) { + GRect frame = GRectZero; + prv_update_layer_placement(self, &frame); + // TODO: Remove `animated = true` once set content offset always unschedules the animation const bool animated = true; scroll_layer_set_content_offset(self->scroll_layer, GPointZero, animated); scroll_layer_set_content_size(self->scroll_layer, frame.size); } - layer_mark_dirty(self->layer); + + if (self->layer) { + layer_mark_dirty(self->layer); + } } -static GRect prv_update_layer_placement(SimplyWindow *self) { +static void prv_update_layer_placement(SimplyWindow *self, GRect *frame_out) { Layer * const main_layer = self->layer ?: scroll_layer_get_layer(self->scroll_layer); - if (!main_layer) { return GRectZero; } + if (!main_layer) { return; } GRect frame = { .size = layer_get_frame(window_get_root_layer(self->window)).size }; @@ -136,7 +138,9 @@ static GRect prv_update_layer_placement(SimplyWindow *self) { } layer_set_frame(main_layer, frame); - return frame; + if (frame_out) { + *frame_out = frame; + } } @@ -149,7 +153,7 @@ void simply_window_set_status_bar(SimplyWindow *self, bool use_status_bar) { status_bar_layer_add_to_window(self->window, self->status_bar_layer); } - prv_update_layer_placement(self); + prv_update_layer_placement(self, NULL); #ifdef PBL_SDK_2 if (!window_stack_contains_window(self->window)) { return; } @@ -202,7 +206,7 @@ void simply_window_set_action_bar(SimplyWindow *self, bool use_action_bar) { action_bar_layer_add_to_window(self->action_bar_layer, self->window); } - prv_update_layer_placement(self); + prv_update_layer_placement(self, NULL); } void simply_window_set_action_bar_icon(SimplyWindow *self, ButtonId button, uint32_t id) { @@ -378,9 +382,10 @@ static void prv_handle_window_props_packet(Simply *simply, Packet *data) { if (!window) { return; } WindowPropsPacket *packet = (WindowPropsPacket *)data; - window->id = packet->id; simply_window_set_background_color(window, packet->background_color); - simply_window_set_scrollable(window, packet->scrollable); + const bool is_same_window = (window->id == packet->id); + simply_window_set_scrollable(window, packet->scrollable, is_same_window, !is_same_window); + window->id = packet->id; } static void prv_handle_window_button_config_packet(Simply *simply, Packet *data) { diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index f8010f53..ccc5c8e5 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -44,7 +44,8 @@ bool simply_window_disappear(SimplyWindow *self); void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *context); -void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable); +void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable, bool animated, + bool reset); void simply_window_set_status_bar(SimplyWindow *self, bool use_status_bar); void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color); From 2ad75b8dc3549eda8109608afc652704b43f4388 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 27 Feb 2016 13:17:40 -0800 Subject: [PATCH 726/791] Add strset_truncated which sets as much of the string as possible --- src/simply/simply_ui.c | 2 +- src/util/string.h | 24 ++++++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index da236194..ef92c632 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -144,7 +144,7 @@ void simply_ui_set_style(SimplyUi *self, int style_index) { void simply_ui_set_text(SimplyUi *self, SimplyUiTextfieldId textfield_id, const char *str) { SimplyUiTextfield *textfield = &self->ui_layer.textfields[textfield_id]; char **str_field = &textfield->text; - strset(str_field, str); + strset_truncated(str_field, str); mark_dirty(self); } diff --git a/src/util/string.h b/src/util/string.h index 36b120fe..f606b68c 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -8,29 +8,41 @@ static inline bool is_string(const char *str) { return str && str[0]; } -static inline char *strdup2(const char *str) { +static inline char *strndup2(const char *str, size_t n) { if (!str) { return NULL; } - size_t n = strlen(str); char *buffer = malloc(n + 1); if (!buffer) { return NULL; } strncpy(buffer, str, n + 1); + buffer[n] = '\0'; return buffer; } -static inline void strset(char **str_field, const char *str) { +static inline char *strdup2(const char *str) { + return strndup2(str, strlen(str)); +} + +static inline bool strnset(char **str_field, const char *str, size_t n) { free(*str_field); + *str_field = NULL; if (!is_string(str)) { - *str_field = NULL; - return; + return true; } - *str_field = strdup2(str); + return (*str_field = strndup2(str, n)); } +static inline bool strset(char **str_field, const char *str) { + return strnset(str_field, str, strlen(str)); +} + +static inline void strset_truncated(char **str_field, const char *str) { + size_t n = strlen(str); + for (; !strnset(str_field, str, n) && n > 1; n /= 2) {} +} From c3c6b9cd6149dff0d7ad6b4207e6bd0ed16184e3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 27 Feb 2016 13:57:48 -0800 Subject: [PATCH 727/791] Don't reset the scroll layer if it is not being used SimplyMenu does not use the scroll layer, therefore resetting the scroll layer will break SimplyMenu scrolling due to the scroll layer taking over the click config. Add a SimplyWindow boolean property that explicitly specifies whether the scroll layer is used in order to avoid resetting and applying the scroll layer click config if it is not used. --- src/simply/simply_stage.c | 1 + src/simply/simply_ui.c | 1 + src/simply/simply_window.c | 2 +- src/simply/simply_window.h | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index b6d94e11..2e1f2738 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -501,6 +501,7 @@ static void window_load(Window *window) { *(void**) layer_get_data(layer) = self; layer_set_update_proc(layer, layer_update_callback); scroll_layer_add_child(self->window.scroll_layer, layer); + self->window.use_scroll_layer = true; } static void window_appear(Window *window) { diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index ef92c632..5793a413 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -381,6 +381,7 @@ static void window_load(Window *window) { *(void**) layer_get_data(layer) = self; layer_set_update_proc(layer, layer_update_callback); scroll_layer_add_child(self->window.scroll_layer, layer); + self->window.use_scroll_layer = true; simply_ui_set_style(self, 1); } diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index f1c12128..7569051c 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -92,7 +92,7 @@ static void prv_set_scroll_layer_click_config(SimplyWindow *self) { void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable, bool animated, bool reset) { - if (self->is_scrollable == is_scrollable && !reset) { return; } + if (!self->use_scroll_layer || (self->is_scrollable == is_scrollable && !reset)) { return; } self->is_scrollable = is_scrollable; diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index ccc5c8e5..4f50db4f 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -24,6 +24,7 @@ struct SimplyWindow { ButtonId button_mask:4; GColor8 background_color; bool is_scrollable:1; + bool use_scroll_layer:1; bool use_status_bar:1; bool use_action_bar:1; #if defined(PBL_ROUND) From 2b9328164156405621206b354e9a90193daa4153 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 27 Feb 2016 17:33:03 -0800 Subject: [PATCH 728/791] Fix Element animate to properly queue subsequent animations (Addresses #123) --- src/js/ui/element.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/js/ui/element.js b/src/js/ui/element.js index fb469b71..95195ad5 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -80,14 +80,12 @@ StageElement.prototype.animate = function(field, value, duration) { duration = value; } var animateDef = myutil.toObject(field, value); - function animate() { + this.queue(function() { this._animate(animateDef, duration); util2.copy(animateDef, this.state); - } - if (this._queue.length === 0) { - animate.call(this); - } else { - this.queue(animate); + }); + if (!this.state.animating) { + this.dequeue(); } return this; }; @@ -98,8 +96,12 @@ StageElement.prototype.queue = function(callback) { StageElement.prototype.dequeue = function() { var callback = this._queue.shift(); - if (!callback) { return; } - callback.call(this, this.dequeue.bind(this)); + if (callback) { + this.state.animating = true; + callback.call(this, this.dequeue.bind(this)); + } else { + this.state.animating = false; + } }; StageElement.emitAnimateDone = function(id) { From 6f33ce12326be542820380a5dcd78afb7beac999 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 27 Feb 2016 13:46:29 -0800 Subject: [PATCH 729/791] Change SimplyElement to inherit structs without inlining --- src/simply/simply_stage.c | 51 +++++++++++++++++++++------------------ src/simply/simply_stage.h | 40 ++++++++++-------------------- 2 files changed, 40 insertions(+), 51 deletions(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 2e1f2738..c353996e 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -195,17 +195,18 @@ void simply_stage_clear(SimplyStage *self) { simply_stage_update_ticker(self); } -static void rect_element_draw_background(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { - if (element->background_color.a) { - graphics_context_set_fill_color(ctx, gcolor8_get(element->background_color)); - graphics_fill_rect(ctx, element->frame, element->radius, GCornersAll); +static void rect_element_draw_background(GContext *ctx, SimplyStage *self, + SimplyElementRect *element) { + if (element->common.background_color.a) { + graphics_context_set_fill_color(ctx, gcolor8_get(element->common.background_color)); + graphics_fill_rect(ctx, element->common.frame, element->radius, GCornersAll); } } static void rect_element_draw_border(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { - if (element->border_color.a) { - graphics_context_set_stroke_color(ctx, gcolor8_get(element->border_color)); - graphics_draw_round_rect(ctx, element->frame, element->radius); + if (element->common.border_color.a) { + graphics_context_set_stroke_color(ctx, gcolor8_get(element->common.border_color)); + graphics_draw_round_rect(ctx, element->common.frame, element->radius); } } @@ -215,13 +216,13 @@ static void rect_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRec } static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementCircle *element) { - if (element->background_color.a) { - graphics_context_set_fill_color(ctx, gcolor8_get(element->background_color)); - graphics_fill_circle(ctx, element->frame.origin, element->radius); + if (element->common.background_color.a) { + graphics_context_set_fill_color(ctx, gcolor8_get(element->common.background_color)); + graphics_fill_circle(ctx, element->common.frame.origin, element->radius); } - if (element->border_color.a) { - graphics_context_set_stroke_color(ctx, gcolor8_get(element->border_color)); - graphics_draw_circle(ctx, element->frame.origin, element->radius); + if (element->common.border_color.a) { + graphics_context_set_stroke_color(ctx, gcolor8_get(element->common.border_color)); + graphics_draw_circle(ctx, element->common.frame.origin, element->radius); } } @@ -236,17 +237,18 @@ static void radial_element_draw(GContext *ctx, SimplyStage *self, SimplyElementR const GOvalScaleMode scale_mode = GOvalScaleModeFitCircle; const int32_t angle_start = DEG_TO_TRIGANGLE(element->angle_start); const int32_t angle_end = DEG_TO_TRIGANGLE(element->angle_end); - if (element->background_color.a) { - graphics_context_set_fill_color(ctx, element->background_color); - graphics_fill_radial(ctx, element->frame, scale_mode, element->radius, angle_start, angle_end); + if (element->common.background_color.a) { + graphics_context_set_fill_color(ctx, element->common.background_color); + graphics_fill_radial(ctx, element->common.frame, scale_mode, element->radius, + angle_start, angle_end); } - if (element->border_color.a && element->border_width) { - graphics_context_set_stroke_color(ctx, element->border_color); + if (element->common.border_color.a && element->border_width) { + graphics_context_set_stroke_color(ctx, element->common.border_color); graphics_context_set_stroke_width(ctx, element->border_width); - graphics_draw_arc(ctx, element->frame, scale_mode, angle_start, angle_end); - GRect inner_frame = grect_inset(element->frame, GEdgeInsets(element->radius)); - prv_draw_line_polar(ctx, &element->frame, &inner_frame, scale_mode, angle_start); - prv_draw_line_polar(ctx, &element->frame, &inner_frame, scale_mode, angle_end); + graphics_draw_arc(ctx, element->common.frame, scale_mode, angle_start, angle_end); + GRect inner_frame = grect_inset(element->common.frame, GEdgeInsets(element->radius)); + prv_draw_line_polar(ctx, &element->common.frame, &inner_frame, scale_mode, angle_start); + prv_draw_line_polar(ctx, &element->common.frame, &inner_frame, scale_mode, angle_end); if (inner_frame.size.w) { graphics_draw_arc(ctx, inner_frame, GOvalScaleModeFitCircle, angle_start, angle_end); @@ -271,7 +273,8 @@ static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementTex } GFont font = element->font ? element->font : fonts_get_system_font(FONT_KEY_GOTHIC_14); graphics_context_set_text_color(ctx, gcolor8_get(element->text_color)); - graphics_draw_text(ctx, text, font, element->frame, element->overflow_mode, element->alignment, NULL); + graphics_draw_text(ctx, text, font, element->rect.common.frame, element->overflow_mode, + element->alignment, NULL); } } @@ -280,7 +283,7 @@ static void image_element_draw(GContext *ctx, SimplyStage *self, SimplyElementIm rect_element_draw_background(ctx, self, (SimplyElementRect*) element); SimplyImage *image = simply_res_get_image(self->window.simply->res, element->image); if (image && image->bitmap) { - GRect frame = element->frame; + GRect frame = element->rect.common.frame; if (frame.size.w == 0 && frame.size.h == 0) { frame = gbitmap_get_bounds(image->bitmap); } diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index c00b3596..65b6bc7b 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -45,34 +45,26 @@ struct SimplyStage { typedef struct SimplyElementCommon SimplyElementCommon; -#define SimplyElementCommonDef { \ - List1Node node; \ - uint32_t id; \ - SimplyElementType type; \ - GRect frame; \ - GColor8 background_color; \ - GColor8 border_color; \ -} - -struct SimplyElementCommon SimplyElementCommonDef; - -#define SimplyElementCommonMember \ - union { \ - struct SimplyElementCommon common; \ - struct SimplyElementCommonDef; \ - } +struct SimplyElementCommon { + List1Node node; + uint32_t id; + SimplyElementType type; + GRect frame; + GColor8 background_color; + GColor8 border_color; +}; typedef struct SimplyElementRect SimplyElementRect; struct SimplyElementRect { - SimplyElementCommonMember; + SimplyElementCommon common; uint16_t radius; }; typedef struct SimplyElementRadial SimplyElementRadial; struct SimplyElementRadial { - SimplyElementCommonMember; + SimplyElementCommon common; uint16_t radius; uint16_t angle_start; uint16_t angle_end; @@ -84,10 +76,7 @@ typedef struct SimplyElementRect SimplyElementCircle; typedef struct SimplyElementText SimplyElementText; struct SimplyElementText { - union { - struct SimplyElementRect common; - struct SimplyElementCommonDef; - }; + SimplyElementRect rect; char *text; GFont font; TimeUnits time_units:8; @@ -99,10 +88,7 @@ struct SimplyElementText { typedef struct SimplyElementImage SimplyElementImage; struct SimplyElementImage { - union { - struct SimplyElementRect common; - struct SimplyElementCommonDef; - }; + SimplyElementRect rect; uint32_t image; GCompOp compositing; }; @@ -110,7 +96,7 @@ struct SimplyElementImage { typedef struct SimplyElementInverter SimplyElementInverter; struct SimplyElementInverter { - SimplyElementCommonMember; + SimplyElementCommon common; InverterLayer *inverter_layer; }; From 0f4b7f042af774445a43dd91157fb49fbdcf236b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 27 Feb 2016 13:48:17 -0800 Subject: [PATCH 730/791] Change SimplyElementType to be automatically enumerated --- src/js/ui/element.js | 19 +++++++++++++------ src/simply/simply_stage.h | 12 ++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/js/ui/element.js b/src/js/ui/element.js index 95195ad5..110bf020 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -28,12 +28,19 @@ var StageElement = function(elementDef) { this._queue = []; }; -StageElement.RectType = 1; -StageElement.CircleType = 2; -StageElement.RadialType = 6; -StageElement.TextType = 3; -StageElement.ImageType = 4; -StageElement.InverterType = 5; +var Types = [ + 'NoneType', + 'RectType', + 'CircleType', + 'RadialType', + 'TextType', + 'ImageType', + 'InverterType', +]; + +Types.forEach(function(name, index) { + StageElement[name] = index; +}); util2.copy(Propable.prototype, StageElement.prototype); diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index 65b6bc7b..7b2aa17c 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -24,12 +24,12 @@ typedef enum SimplyElementType SimplyElementType; enum SimplyElementType { SimplyElementTypeNone = 0, - SimplyElementTypeRect = 1, - SimplyElementTypeCircle = 2, - SimplyElementTypeRadial = 6, - SimplyElementTypeText = 3, - SimplyElementTypeImage = 4, - SimplyElementTypeInverter = 5, + SimplyElementTypeRect, + SimplyElementTypeCircle, + SimplyElementTypeRadial, + SimplyElementTypeText, + SimplyElementTypeImage, + SimplyElementTypeInverter, }; struct SimplyStageLayer { From 44778d5d84e53149561bf0538ba84d8320ee7f1c Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 27 Feb 2016 13:53:47 -0800 Subject: [PATCH 731/791] Add borderWidth to all stage elements --- src/js/ui/circle.js | 1 + src/js/ui/element.js | 3 +- src/js/ui/image.js | 1 + src/js/ui/rect.js | 1 + src/js/ui/simply-pebble.js | 13 +------ src/js/ui/text.js | 1 + src/simply/simply_msg_commands.h | 1 - src/simply/simply_stage.c | 60 +++++++++++--------------------- src/simply/simply_stage.h | 5 ++- 9 files changed, 29 insertions(+), 57 deletions(-) diff --git a/src/js/ui/circle.js b/src/js/ui/circle.js index 03332d41..391a6964 100644 --- a/src/js/ui/circle.js +++ b/src/js/ui/circle.js @@ -5,6 +5,7 @@ var StageElement = require('ui/element'); var defaults = { backgroundColor: 'white', borderColor: 'clear', + borderWidth: 1, }; var Circle = function(elementDef) { diff --git a/src/js/ui/element.js b/src/js/ui/element.js index 110bf020..7d1fbf25 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -8,8 +8,9 @@ var simply = require('ui/simply'); var elementProps = [ 'position', 'size', - 'borderColor', 'backgroundColor', + 'borderColor', + 'borderWidth', ]; var accessorProps = elementProps; diff --git a/src/js/ui/image.js b/src/js/ui/image.js index c97b2888..ef45bbd9 100644 --- a/src/js/ui/image.js +++ b/src/js/ui/image.js @@ -11,6 +11,7 @@ var imageProps = [ var defaults = { backgroundColor: 'clear', borderColor: 'clear', + borderWidth: 1, }; var ImageElement = function(elementDef) { diff --git a/src/js/ui/rect.js b/src/js/ui/rect.js index d705c222..6632dc22 100644 --- a/src/js/ui/rect.js +++ b/src/js/ui/rect.js @@ -5,6 +5,7 @@ var StageElement = require('ui/element'); var defaults = { backgroundColor: 'white', borderColor: 'clear', + borderWidth: 1, }; var Rect = function(elementDef) { diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 8163b6f9..46232c48 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -751,6 +751,7 @@ var ElementCommonPacket = new struct([ ['uint32', 'id'], [GPoint, 'position', PositionType], [GSize, 'size', SizeType], + ['uint16', 'borderWidth', EnumerableType], ['uint8', 'backgroundColor', Color], ['uint8', 'borderColor', Color], ]); @@ -773,12 +774,6 @@ var ElementAngleEndPacket = new struct([ ['uint16', 'angleEnd', EnumerableType], ]); -var ElementBorderWidthPacket = new struct([ - [Packet, 'packet'], - ['uint32', 'id'], - ['uint16', 'borderWidth', EnumerableType], -]); - var ElementTextPacket = new struct([ [Packet, 'packet'], ['uint32', 'id'], @@ -881,7 +876,6 @@ var CommandPackets = [ ElementRadiusPacket, ElementAngleStartPacket, ElementAngleEndPacket, - ElementBorderWidthPacket, ElementTextPacket, ElementTextStylePacket, ElementImagePacket, @@ -1347,10 +1341,6 @@ SimplyPebble.elementAngleEnd = function(id, angleEnd) { SimplyPebble.sendPacket(ElementAngleEndPacket.id(id).angleEnd(angleEnd)); }; -SimplyPebble.elementBorderWidth = function(id, borderWidth) { - SimplyPebble.sendPacket(ElementBorderWidthPacket.id(id).borderWidth(borderWidth)); -}; - SimplyPebble.elementText = function(id, text, timeUnits) { SimplyPebble.sendPacket(ElementTextPacket.id(id).updateTimeUnits(timeUnits).text(text)); }; @@ -1398,7 +1388,6 @@ SimplyPebble.stageElement = function(id, type, def, index) { SimplyPebble.elementRadius(id, def.radius); SimplyPebble.elementAngleStart(id, def.angleStart); SimplyPebble.elementAngleEnd(id, def.angleEnd); - SimplyPebble.elementBorderWidth(id, def.borderWidth); break; case StageElement.TextType: SimplyPebble.elementRadius(id, def.radius); diff --git a/src/js/ui/text.js b/src/js/ui/text.js index 5b7864f8..eecd4571 100644 --- a/src/js/ui/text.js +++ b/src/js/ui/text.js @@ -15,6 +15,7 @@ var textProps = [ var defaults = { backgroundColor: 'clear', borderColor: 'clear', + borderWidth: 1, color: 'white', font: 'gothic-24', }; diff --git a/src/simply/simply_msg_commands.h b/src/simply/simply_msg_commands.h index 9b53113e..57820043 100644 --- a/src/simply/simply_msg_commands.h +++ b/src/simply/simply_msg_commands.h @@ -50,7 +50,6 @@ enum Command { CommandElementRadius, CommandElementAngleStart, CommandElementAngleEnd, - CommandElementBorderWidth, CommandElementText, CommandElementTextStyle, CommandElementImage, diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index c353996e..a21d5426 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -41,6 +41,7 @@ struct __attribute__((__packed__)) ElementCommonPacket { Packet packet; uint32_t id; GRect frame; + uint16_t border_width; GColor8 background_color; GColor8 border_color; }; @@ -69,14 +70,6 @@ struct __attribute__((__packed__)) CommandElementAngleEndPacket { uint16_t angle_end; }; -typedef struct CommandElementBorderWidthPacket CommandElementBorderWidthPacket; - -struct __attribute__((__packed__)) CommandElementBorderWidthPacket { - Packet packet; - uint32_t id; - uint16_t border_width; -}; - typedef struct ElementTextPacket ElementTextPacket; struct __attribute__((__packed__)) ElementTextPacket { @@ -195,17 +188,22 @@ void simply_stage_clear(SimplyStage *self) { simply_stage_update_ticker(self); } +static void element_set_graphics_context(GContext *ctx, SimplyStage *self, + SimplyElementCommon *element) { + graphics_context_set_fill_color(ctx, element->background_color); + graphics_context_set_stroke_color(ctx, element->border_color); + graphics_context_set_stroke_width(ctx, element->border_width); +} + static void rect_element_draw_background(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { if (element->common.background_color.a) { - graphics_context_set_fill_color(ctx, gcolor8_get(element->common.background_color)); graphics_fill_rect(ctx, element->common.frame, element->radius, GCornersAll); } } static void rect_element_draw_border(GContext *ctx, SimplyStage *self, SimplyElementRect *element) { if (element->common.border_color.a) { - graphics_context_set_stroke_color(ctx, gcolor8_get(element->common.border_color)); graphics_draw_round_rect(ctx, element->common.frame, element->radius); } } @@ -217,11 +215,9 @@ static void rect_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRec static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementCircle *element) { if (element->common.background_color.a) { - graphics_context_set_fill_color(ctx, gcolor8_get(element->common.background_color)); graphics_fill_circle(ctx, element->common.frame.origin, element->radius); } if (element->common.border_color.a) { - graphics_context_set_stroke_color(ctx, gcolor8_get(element->common.border_color)); graphics_draw_circle(ctx, element->common.frame.origin, element->radius); } } @@ -237,18 +233,15 @@ static void radial_element_draw(GContext *ctx, SimplyStage *self, SimplyElementR const GOvalScaleMode scale_mode = GOvalScaleModeFitCircle; const int32_t angle_start = DEG_TO_TRIGANGLE(element->angle_start); const int32_t angle_end = DEG_TO_TRIGANGLE(element->angle_end); - if (element->common.background_color.a) { - graphics_context_set_fill_color(ctx, element->common.background_color); - graphics_fill_radial(ctx, element->common.frame, scale_mode, element->radius, + if (element->rect.common.background_color.a) { + graphics_fill_radial(ctx, element->rect.common.frame, scale_mode, element->rect.radius, angle_start, angle_end); } - if (element->common.border_color.a && element->border_width) { - graphics_context_set_stroke_color(ctx, element->common.border_color); - graphics_context_set_stroke_width(ctx, element->border_width); - graphics_draw_arc(ctx, element->common.frame, scale_mode, angle_start, angle_end); - GRect inner_frame = grect_inset(element->common.frame, GEdgeInsets(element->radius)); - prv_draw_line_polar(ctx, &element->common.frame, &inner_frame, scale_mode, angle_start); - prv_draw_line_polar(ctx, &element->common.frame, &inner_frame, scale_mode, angle_end); + if (element->rect.common.border_color.a && element->rect.common.border_width) { + graphics_draw_arc(ctx, element->rect.common.frame, scale_mode, angle_start, angle_end); + GRect inner_frame = grect_inset(element->rect.common.frame, GEdgeInsets(element->rect.radius)); + prv_draw_line_polar(ctx, &element->rect.common.frame, &inner_frame, scale_mode, angle_start); + prv_draw_line_polar(ctx, &element->rect.common.frame, &inner_frame, scale_mode, angle_end); if (inner_frame.size.w) { graphics_draw_arc(ctx, inner_frame, GOvalScaleModeFitCircle, angle_start, angle_end); @@ -265,7 +258,7 @@ static char *format_time(char *format) { } static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementText *element) { - rect_element_draw(ctx, self, (SimplyElementRect*) element); + rect_element_draw(ctx, self, &element->rect); char *text = element->text; if (element->text_color.a && is_string(text)) { if (element->time_units) { @@ -280,7 +273,7 @@ static void text_element_draw(GContext *ctx, SimplyStage *self, SimplyElementTex static void image_element_draw(GContext *ctx, SimplyStage *self, SimplyElementImage *element) { graphics_context_set_compositing_mode(ctx, element->compositing); - rect_element_draw_background(ctx, self, (SimplyElementRect*) element); + rect_element_draw_background(ctx, self, &element->rect); SimplyImage *image = simply_res_get_image(self->window.simply->res, element->image); if (image && image->bitmap) { GRect frame = element->rect.common.frame; @@ -289,7 +282,7 @@ static void image_element_draw(GContext *ctx, SimplyStage *self, SimplyElementIm } graphics_draw_bitmap_centered(ctx, image->bitmap, frame); } - rect_element_draw_border(ctx, self, (SimplyElementRect*) element); + rect_element_draw_border(ctx, self, &element->rect); graphics_context_set_compositing_mode(ctx, GCompOpAssign); } @@ -308,8 +301,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { SimplyElementCommon *element = (SimplyElementCommon*) self->stage_layer.elements; while (element) { - // TODO: change border_width to a common element member - graphics_context_set_stroke_width(ctx, 1); + element_set_graphics_context(ctx, self, element); int16_t max_y = element->frame.origin.y + element->frame.size.h; if (max_y > frame.size.h) { frame.size.h = max_y; @@ -592,6 +584,7 @@ static void handle_element_common_packet(Simply *simply, Packet *data) { simply_stage_set_element_frame(simply->stage, element, packet->frame); element->background_color = packet->background_color; element->border_color = packet->border_color; + element->border_width = packet->border_width; simply_stage_update(simply->stage); } @@ -625,16 +618,6 @@ static void handle_element_angle_end_packet(Simply *simply, Packet *data) { simply_stage_update(simply->stage); }; -static void handle_element_border_width_packet(Simply *simply, Packet *data) { - CommandElementBorderWidthPacket *packet = (CommandElementBorderWidthPacket*) data; - SimplyElementRadial *element = (SimplyElementRadial*) simply_stage_get_element(simply->stage, packet->id); - if (!element) { - return; - } - element->border_width = packet->border_width; - simply_stage_update(simply->stage); -}; - static void handle_element_text_packet(Simply *simply, Packet *data) { ElementTextPacket *packet = (ElementTextPacket*) data; SimplyElementText *element = (SimplyElementText*) simply_stage_get_element(simply->stage, packet->id); @@ -717,9 +700,6 @@ bool simply_stage_handle_packet(Simply *simply, Packet *packet) { case CommandElementAngleEnd: handle_element_angle_end_packet(simply, packet); return true; - case CommandElementBorderWidth: - handle_element_border_width_packet(simply, packet); - return true; case CommandElementText: handle_element_text_packet(simply, packet); return true; diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index 7b2aa17c..c93a3b4e 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -50,6 +50,7 @@ struct SimplyElementCommon { uint32_t id; SimplyElementType type; GRect frame; + uint16_t border_width; GColor8 background_color; GColor8 border_color; }; @@ -64,11 +65,9 @@ struct SimplyElementRect { typedef struct SimplyElementRadial SimplyElementRadial; struct SimplyElementRadial { - SimplyElementCommon common; - uint16_t radius; + SimplyElementRect rect; uint16_t angle_start; uint16_t angle_end; - uint16_t border_width; }; typedef struct SimplyElementRect SimplyElementCircle; From f60439b62fa7504bcbbb92b0e171a0e55b32f9d3 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 27 Feb 2016 14:51:13 -0800 Subject: [PATCH 732/791] Add Line StageElement --- src/js/ui/element.js | 1 + src/js/ui/index.js | 1 + src/js/ui/line.js | 26 +++++++++++++++ src/js/ui/simply-pebble.js | 24 +++++++++++--- src/simply/simply_stage.c | 65 ++++++++++++++++++++++++++------------ src/simply/simply_stage.h | 3 ++ 6 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 src/js/ui/line.js diff --git a/src/js/ui/element.js b/src/js/ui/element.js index 7d1fbf25..c4759f29 100644 --- a/src/js/ui/element.js +++ b/src/js/ui/element.js @@ -32,6 +32,7 @@ var StageElement = function(elementDef) { var Types = [ 'NoneType', 'RectType', + 'LineType', 'CircleType', 'RadialType', 'TextType', diff --git a/src/js/ui/index.js b/src/js/ui/index.js index 32eedb31..8ba16893 100644 --- a/src/js/ui/index.js +++ b/src/js/ui/index.js @@ -5,6 +5,7 @@ UI.Window = require('ui/window'); UI.Card = require('ui/card'); UI.Menu = require('ui/menu'); UI.Rect = require('ui/rect'); +UI.Line = require('ui/line'); UI.Circle = require('ui/circle'); UI.Radial = require('ui/radial'); UI.Text = require('ui/text'); diff --git a/src/js/ui/line.js b/src/js/ui/line.js new file mode 100644 index 00000000..deb8bdae --- /dev/null +++ b/src/js/ui/line.js @@ -0,0 +1,26 @@ +var util2 = require('util2'); +var myutil = require('myutil'); +var Propable = require('ui/propable'); +var StageElement = require('ui/element'); + +var accessorProps = [ + 'strokeColor', + 'strokeWidth', + 'position2', +]; + +var defaults = { + strokeColor: 'white', + strokeWidth: 1, +}; + +var Line = function(elementDef) { + StageElement.call(this, myutil.shadow(defaults, elementDef || {})); + this.state.type = StageElement.LineType; +}; + +util2.inherit(Line, StageElement); + +Propable.makeAccessors(accessorProps, Line.prototype); + +module.exports = Line; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 46232c48..8dfdc5d5 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -12,6 +12,7 @@ var WindowStack = require('ui/windowstack'); var Window = require('ui/window'); var Menu = require('ui/menu'); var StageElement = require('ui/element'); +var Vector2 = require('vector2'); var simply = require('ui/simply'); @@ -1320,11 +1321,27 @@ SimplyPebble.elementRemove = function(id) { SimplyPebble.sendPacket(ElementRemovePacket.id(id)); }; +SimplyPebble.elementFrame = function(packet, def, altDef) { + var position = def.position || (altDef ? altDef.position : undefined); + var position2 = def.position2 || (altDef ? altDef.position2 : undefined); + var size = def.size || (altDef ? altDef.size : undefined); + if (position && position2) { + size = position2.clone().subSelf(position); + } + packet.position(position); + packet.size(size); +}; + SimplyPebble.elementCommon = function(id, def) { + if ('strokeColor' in def) { + ElementCommonPacket.borderColor(def.strokeColor); + } + if ('strokeWidth' in def) { + ElementCommonPacket.borderWidth(def.strokeWidth); + } + SimplyPebble.elementFrame(ElementCommonPacket, def); ElementCommonPacket .id(id) - .position(def.position) - .size(def.size) .prop(def); SimplyPebble.sendPacket(ElementCommonPacket); }; @@ -1361,10 +1378,9 @@ SimplyPebble.elementImage = function(id, image, compositing) { }; SimplyPebble.elementAnimate = function(id, def, animateDef, duration, easing) { + SimplyPebble.elementFrame(ElementAnimatePacket, animateDef, def); ElementAnimatePacket .id(id) - .position(animateDef.position || def.position) - .size(animateDef.size || def.size) .duration(duration) .easing(easing); SimplyPebble.sendPacket(ElementAnimatePacket); diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index a21d5426..6bd555ca 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -213,6 +213,14 @@ static void rect_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRec rect_element_draw_border(ctx, self, element); } +static void line_element_draw(GContext *ctx, SimplyStage *self, SimplyElementLine *element) { + if (element->border_color.a) { + const GPoint end = { element->frame.origin.x + element->frame.size.w, + element->frame.origin.y + element->frame.size.h }; + graphics_draw_line(ctx, element->frame.origin, end); + } +} + static void circle_element_draw(GContext *ctx, SimplyStage *self, SimplyElementCircle *element) { if (element->common.background_color.a) { graphics_fill_circle(ctx, element->common.frame.origin, element->radius); @@ -299,7 +307,7 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, gcolor8_get(self->window.background_color)); graphics_fill_rect(ctx, frame, 0, GCornerNone); - SimplyElementCommon *element = (SimplyElementCommon*) self->stage_layer.elements; + SimplyElementCommon *element = (SimplyElementCommon *)self->stage_layer.elements; while (element) { element_set_graphics_context(ctx, self, element); int16_t max_y = element->frame.origin.y + element->frame.size.h; @@ -310,19 +318,22 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { case SimplyElementTypeNone: break; case SimplyElementTypeRect: - rect_element_draw(ctx, self, (SimplyElementRect*) element); + rect_element_draw(ctx, self, (SimplyElementRect *)element); + break; + case SimplyElementTypeLine: + line_element_draw(ctx, self, (SimplyElementLine *)element); break; case SimplyElementTypeCircle: - circle_element_draw(ctx, self, (SimplyElementCircle*) element); + circle_element_draw(ctx, self, (SimplyElementCircle *)element); break; case SimplyElementTypeRadial: - radial_element_draw(ctx, self, (SimplyElementRadial*) element); + radial_element_draw(ctx, self, (SimplyElementRadial *)element); break; case SimplyElementTypeText: - text_element_draw(ctx, self, (SimplyElementText*) element); + text_element_draw(ctx, self, (SimplyElementText *)element); break; case SimplyElementTypeImage: - image_element_draw(ctx, self, (SimplyElementImage*) element); + image_element_draw(ctx, self, (SimplyElementImage *)element); break; case SimplyElementTypeInverter: break; @@ -337,24 +348,34 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { } } -static SimplyElementCommon *alloc_element(SimplyElementType type) { +static size_t prv_get_element_size(SimplyElementType type) { + switch (type) { + case SimplyElementTypeNone: return 0; + case SimplyElementTypeLine: return sizeof(SimplyElementLine); + case SimplyElementTypeRect: return sizeof(SimplyElementRect); + case SimplyElementTypeCircle: return sizeof(SimplyElementCircle); + case SimplyElementTypeRadial: return sizeof(SimplyElementRadial); + case SimplyElementTypeText: return sizeof(SimplyElementText); + case SimplyElementTypeImage: return sizeof(SimplyElementImage); + case SimplyElementTypeInverter: return sizeof(SimplyElementInverter); + } + return 0; +} + +static SimplyElementCommon *prv_create_element(SimplyElementType type) { + SimplyElementCommon *common = malloc0(prv_get_element_size(type)); + if (!common) { + return NULL; + } switch (type) { - case SimplyElementTypeNone: return NULL; - case SimplyElementTypeRect: return malloc0(sizeof(SimplyElementRect)); - case SimplyElementTypeCircle: return malloc0(sizeof(SimplyElementCircle)); - case SimplyElementTypeRadial: return malloc0(sizeof(SimplyElementRadial)); - case SimplyElementTypeText: return malloc0(sizeof(SimplyElementText)); - case SimplyElementTypeImage: return malloc0(sizeof(SimplyElementImage)); + default: return common; case SimplyElementTypeInverter: { - SimplyElementInverter *element = malloc0(sizeof(SimplyElementInverter)); - if (!element) { - return NULL; - } + SimplyElementInverter *element = (SimplyElementInverter *)common; element->inverter_layer = inverter_layer_create(GRect(0, 0, 0, 0)); - return &element->common; + return common; } } - return NULL; + return common; } SimplyElementCommon *simply_stage_auto_element(SimplyStage *self, uint32_t id, SimplyElementType type) { @@ -366,7 +387,7 @@ SimplyElementCommon *simply_stage_auto_element(SimplyStage *self, uint32_t id, S if (element) { return element; } - while (!(element = alloc_element(type))) { + while (!(element = prv_create_element(type))) { if (!simply_res_evict_image(self->window.simply->res)) { return NULL; } @@ -399,7 +420,9 @@ SimplyElementCommon *simply_stage_remove_element(SimplyStage *self, SimplyElemen } void simply_stage_set_element_frame(SimplyStage *self, SimplyElementCommon *element, GRect frame) { - grect_standardize(&frame); + if (element->type != SimplyElementTypeLine) { + grect_standardize(&frame); + } element->frame = frame; switch (element->type) { default: break; diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index c93a3b4e..2946dd0e 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -25,6 +25,7 @@ typedef enum SimplyElementType SimplyElementType; enum SimplyElementType { SimplyElementTypeNone = 0, SimplyElementTypeRect, + SimplyElementTypeLine, SimplyElementTypeCircle, SimplyElementTypeRadial, SimplyElementTypeText, @@ -55,6 +56,8 @@ struct SimplyElementCommon { GColor8 border_color; }; +typedef struct SimplyElementCommon SimplyElementLine; + typedef struct SimplyElementRect SimplyElementRect; struct SimplyElementRect { From 0da98c1692866e8c43f05fb03307cb4d05f3c403 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 27 Feb 2016 16:06:50 -0800 Subject: [PATCH 733/791] Add Radial angle accessors --- src/js/ui/radial.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/js/ui/radial.js b/src/js/ui/radial.js index 3d57e91d..971869cd 100644 --- a/src/js/ui/radial.js +++ b/src/js/ui/radial.js @@ -1,11 +1,19 @@ var util2 = require('util2'); var myutil = require('myutil'); +var Propable = require('ui/propable'); var StageElement = require('ui/element'); +var accessorProps = [ + 'radius', + 'angleStart', + 'angleEnd', +]; + var defaults = { backgroundColor: 'white', borderColor: 'clear', borderWidth: 1, + radius: 0, angleStart: 0, angleEnd: 360, }; @@ -17,4 +25,6 @@ var Radial = function(elementDef) { util2.inherit(Radial, StageElement); +Propable.makeAccessors(accessorProps, Radial.prototype); + module.exports = Radial; From 4db6cd0e4f7d2036c0f199ab2cc0ecdf1ea36423 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 27 Feb 2016 16:24:52 -0800 Subject: [PATCH 734/791] Modify Radial to be reducible to an Arc --- src/simply/simply_stage.c | 19 ++++++++++--------- src/simply/simply_stage.h | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 6bd555ca..2460b6d1 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -241,18 +241,19 @@ static void radial_element_draw(GContext *ctx, SimplyStage *self, SimplyElementR const GOvalScaleMode scale_mode = GOvalScaleModeFitCircle; const int32_t angle_start = DEG_TO_TRIGANGLE(element->angle_start); const int32_t angle_end = DEG_TO_TRIGANGLE(element->angle_end); + const GRect *frame = &element->rect.common.frame; if (element->rect.common.background_color.a) { - graphics_fill_radial(ctx, element->rect.common.frame, scale_mode, element->rect.radius, - angle_start, angle_end); + graphics_fill_radial(ctx, *frame, scale_mode, element->rect.radius, angle_start, angle_end); } if (element->rect.common.border_color.a && element->rect.common.border_width) { - graphics_draw_arc(ctx, element->rect.common.frame, scale_mode, angle_start, angle_end); - GRect inner_frame = grect_inset(element->rect.common.frame, GEdgeInsets(element->rect.radius)); - prv_draw_line_polar(ctx, &element->rect.common.frame, &inner_frame, scale_mode, angle_start); - prv_draw_line_polar(ctx, &element->rect.common.frame, &inner_frame, scale_mode, angle_end); - if (inner_frame.size.w) { - graphics_draw_arc(ctx, inner_frame, GOvalScaleModeFitCircle, - angle_start, angle_end); + graphics_draw_arc(ctx, *frame, scale_mode, angle_start, angle_end); + if (element->rect.radius) { + GRect inner_frame = grect_inset(*frame, GEdgeInsets(element->rect.radius)); + if (inner_frame.size.w) { + prv_draw_line_polar(ctx, frame, &inner_frame, scale_mode, angle_start); + prv_draw_line_polar(ctx, frame, &inner_frame, scale_mode, angle_end); + graphics_draw_arc(ctx, inner_frame, GOvalScaleModeFitCircle, angle_start, angle_end); + } } } } diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index 2946dd0e..6c2a6f37 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -65,6 +65,8 @@ struct SimplyElementRect { uint16_t radius; }; +typedef struct SimplyElementRect SimplyElementCircle; + typedef struct SimplyElementRadial SimplyElementRadial; struct SimplyElementRadial { @@ -73,8 +75,6 @@ struct SimplyElementRadial { uint16_t angle_end; }; -typedef struct SimplyElementRect SimplyElementCircle; - typedef struct SimplyElementText SimplyElementText; struct SimplyElementText { From 327dc6e5bf985b73097c8a4e7ca3e973422c8f3b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 27 Feb 2016 16:45:01 -0800 Subject: [PATCH 735/791] Change angleStart, angleEnd to angle, angle2 to match Line's position, position2 --- src/js/app.js | 4 +-- src/js/lib/safe.js | 2 +- src/js/main.js | 2 +- src/js/ui/radial.js | 28 ++++++++++++++--- src/js/ui/simply-pebble.js | 36 ++++++++++----------- src/simply/simply_msg_commands.h | 4 +-- src/simply/simply_stage.c | 54 +++++++++++++++----------------- src/simply/simply_stage.h | 4 +-- 8 files changed, 75 insertions(+), 59 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 6ea8975d..9d9d2461 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -49,8 +49,8 @@ main.on('click', 'select', function(e) { var radial = new UI.Radial({ position: new Vector2(2, 14), size: new Vector2(140, 140), - angleStart: 0, - angleEnd: 300, + angle: 0, + angle2: 300, radius: 20, backgroundColor: 'cyan', borderColor: 'celeste', diff --git a/src/js/lib/safe.js b/src/js/lib/safe.js index 58b1404f..2184199e 100644 --- a/src/js/lib/safe.js +++ b/src/js/lib/safe.js @@ -140,7 +140,7 @@ safe.dumpError = function(err, intro, level) { safe.warn = function(message, level, name) { var err = new Error(message); err.name = name || 'Warning'; - safe.dumpError(err, 'Warning:', 1); + safe.dumpError(err, 'Warning:', level); }; /* Takes a function and return a new function with a call to it wrapped in a try/catch statement */ diff --git a/src/js/main.js b/src/js/main.js index 6e598de0..166bc892 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -21,7 +21,7 @@ Pebble.addEventListener('ready', function(e) { return function() { if (safe.warnGlobalMoment !== false) { safe.warn("You've accessed moment globally. Pleae use `var moment = require('moment')` instead.\n\t" + - 'moment will not be automatically loaded as a global in future versions.', 5); + 'moment will not be automatically loaded as a global in future versions.', 1); safe.warnGlobalMoment = false; } return (methodName ? moment[methodName] : moment).apply(this, arguments); diff --git a/src/js/ui/radial.js b/src/js/ui/radial.js index 971869cd..65fd5b68 100644 --- a/src/js/ui/radial.js +++ b/src/js/ui/radial.js @@ -1,12 +1,13 @@ var util2 = require('util2'); var myutil = require('myutil'); +var safe = require('safe'); var Propable = require('ui/propable'); var StageElement = require('ui/element'); var accessorProps = [ 'radius', - 'angleStart', - 'angleEnd', + 'angle', + 'angle2', ]; var defaults = { @@ -14,11 +15,25 @@ var defaults = { borderColor: 'clear', borderWidth: 1, radius: 0, - angleStart: 0, - angleEnd: 360, + angle: 0, + angle2: 360, +}; + +var checkProps = function(def) { + if ('angleStart' in def && safe.warnAngleStart !== false) { + safe.warn('`angleStart` has been deprecated in favor of `angle` in order to match\n\t' + + "Line's `position` and `position2`. Please use `angle` intead.", 2); + safe.warnAngleStart = false; + } + if ('angleEnd' in def && safe.warnAngleEnd !== false) { + safe.warn('`angleEnd` has been deprecated in favor of `angle2` in order to match\n\t' + + "Line's `position` and `position2`. Please use `angle2` intead.", 2); + safe.warnAngleEnd = false; + } }; var Radial = function(elementDef) { + checkProps(elementDef); StageElement.call(this, myutil.shadow(defaults, elementDef || {})); this.state.type = StageElement.RadialType; }; @@ -27,4 +42,9 @@ util2.inherit(Radial, StageElement); Propable.makeAccessors(accessorProps, Radial.prototype); +Radial.prototype._prop = function(def) { + checkProps(def); + StageElement.prototype._prop.call(this, def); +}; + module.exports = Radial; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 8dfdc5d5..e5efca64 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -763,16 +763,16 @@ var ElementRadiusPacket = new struct([ ['uint16', 'radius', EnumerableType], ]); -var ElementAngleStartPacket = new struct([ +var ElementAnglePacket = new struct([ [Packet, 'packet'], ['uint32', 'id'], - ['uint16', 'angleStart', EnumerableType], + ['uint16', 'angle', EnumerableType], ]); -var ElementAngleEndPacket = new struct([ +var ElementAngle2Packet = new struct([ [Packet, 'packet'], ['uint32', 'id'], - ['uint16', 'angleEnd', EnumerableType], + ['uint16', 'angle2', EnumerableType], ]); var ElementTextPacket = new struct([ @@ -875,8 +875,8 @@ var CommandPackets = [ ElementRemovePacket, ElementCommonPacket, ElementRadiusPacket, - ElementAngleStartPacket, - ElementAngleEndPacket, + ElementAnglePacket, + ElementAngle2Packet, ElementTextPacket, ElementTextStylePacket, ElementImagePacket, @@ -1346,16 +1346,16 @@ SimplyPebble.elementCommon = function(id, def) { SimplyPebble.sendPacket(ElementCommonPacket); }; -SimplyPebble.elementRadius = function(id, radius) { - SimplyPebble.sendPacket(ElementRadiusPacket.id(id).radius(radius)); +SimplyPebble.elementRadius = function(id, def) { + SimplyPebble.sendPacket(ElementRadiusPacket.id(id).radius(def.radius)); }; -SimplyPebble.elementAngleStart = function(id, angleStart) { - SimplyPebble.sendPacket(ElementAngleStartPacket.id(id).angleStart(angleStart)); +SimplyPebble.elementAngle = function(id, def) { + SimplyPebble.sendPacket(ElementAnglePacket.id(id).angle(def.angleStart || def.angle)); }; -SimplyPebble.elementAngleEnd = function(id, angleEnd) { - SimplyPebble.sendPacket(ElementAngleEndPacket.id(id).angleEnd(angleEnd)); +SimplyPebble.elementAngle2 = function(id, def) { + SimplyPebble.sendPacket(ElementAngle2Packet.id(id).angle2(def.angleEnd || def.angle2)); }; SimplyPebble.elementText = function(id, text, timeUnits) { @@ -1398,20 +1398,20 @@ SimplyPebble.stageElement = function(id, type, def, index) { switch (type) { case StageElement.RectType: case StageElement.CircleType: - SimplyPebble.elementRadius(id, def.radius); + SimplyPebble.elementRadius(id, def); break; case StageElement.RadialType: - SimplyPebble.elementRadius(id, def.radius); - SimplyPebble.elementAngleStart(id, def.angleStart); - SimplyPebble.elementAngleEnd(id, def.angleEnd); + SimplyPebble.elementRadius(id, def); + SimplyPebble.elementAngle(id, def); + SimplyPebble.elementAngle2(id, def); break; case StageElement.TextType: - SimplyPebble.elementRadius(id, def.radius); + SimplyPebble.elementRadius(id, def); SimplyPebble.elementTextStyle(id, def); SimplyPebble.elementText(id, def.text, def.updateTimeUnits); break; case StageElement.ImageType: - SimplyPebble.elementRadius(id, def.radius); + SimplyPebble.elementRadius(id, def); SimplyPebble.elementImage(id, def.image, def.compositing); break; } diff --git a/src/simply/simply_msg_commands.h b/src/simply/simply_msg_commands.h index 57820043..c04f7aaa 100644 --- a/src/simply/simply_msg_commands.h +++ b/src/simply/simply_msg_commands.h @@ -48,8 +48,8 @@ enum Command { CommandElementRemove, CommandElementCommon, CommandElementRadius, - CommandElementAngleStart, - CommandElementAngleEnd, + CommandElementAngle, + CommandElementAngle2, CommandElementText, CommandElementTextStyle, CommandElementImage, diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 2460b6d1..a5c0e98a 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -54,21 +54,15 @@ struct __attribute__((__packed__)) ElementRadiusPacket { uint16_t radius; }; -typedef struct CommandElementAngleStartPacket CommandElementAngleStartPacket; +typedef struct ElementAnglePacket ElementAnglePacket; -struct __attribute__((__packed__)) CommandElementAngleStartPacket { +struct __attribute__((__packed__)) ElementAnglePacket { Packet packet; uint32_t id; - uint16_t angle_start; + uint16_t angle; }; -typedef struct CommandElementAngleEndPacket CommandElementAngleEndPacket; - -struct __attribute__((__packed__)) CommandElementAngleEndPacket { - Packet packet; - uint32_t id; - uint16_t angle_end; -}; +typedef struct ElementAnglePacket ElementAngle2Packet; typedef struct ElementTextPacket ElementTextPacket; @@ -239,20 +233,20 @@ static void prv_draw_line_polar(GContext *ctx, const GRect *outer_frame, const G static void radial_element_draw(GContext *ctx, SimplyStage *self, SimplyElementRadial *element) { const GOvalScaleMode scale_mode = GOvalScaleModeFitCircle; - const int32_t angle_start = DEG_TO_TRIGANGLE(element->angle_start); - const int32_t angle_end = DEG_TO_TRIGANGLE(element->angle_end); + const int32_t angle = DEG_TO_TRIGANGLE(element->angle); + const int32_t angle2 = DEG_TO_TRIGANGLE(element->angle2); const GRect *frame = &element->rect.common.frame; if (element->rect.common.background_color.a) { - graphics_fill_radial(ctx, *frame, scale_mode, element->rect.radius, angle_start, angle_end); + graphics_fill_radial(ctx, *frame, scale_mode, element->rect.radius, angle, angle2); } if (element->rect.common.border_color.a && element->rect.common.border_width) { - graphics_draw_arc(ctx, *frame, scale_mode, angle_start, angle_end); + graphics_draw_arc(ctx, *frame, scale_mode, angle, angle2); if (element->rect.radius) { GRect inner_frame = grect_inset(*frame, GEdgeInsets(element->rect.radius)); if (inner_frame.size.w) { - prv_draw_line_polar(ctx, frame, &inner_frame, scale_mode, angle_start); - prv_draw_line_polar(ctx, frame, &inner_frame, scale_mode, angle_end); - graphics_draw_arc(ctx, inner_frame, GOvalScaleModeFitCircle, angle_start, angle_end); + prv_draw_line_polar(ctx, frame, &inner_frame, scale_mode, angle); + prv_draw_line_polar(ctx, frame, &inner_frame, scale_mode, angle2); + graphics_draw_arc(ctx, inner_frame, GOvalScaleModeFitCircle, angle, angle2); } } } @@ -622,23 +616,25 @@ static void handle_element_radius_packet(Simply *simply, Packet *data) { simply_stage_update(simply->stage); }; -static void handle_element_angle_start_packet(Simply *simply, Packet *data) { - CommandElementAngleStartPacket *packet = (CommandElementAngleStartPacket*) data; - SimplyElementRadial *element = (SimplyElementRadial*) simply_stage_get_element(simply->stage, packet->id); +static void handle_element_angle_packet(Simply *simply, Packet *data) { + ElementAnglePacket *packet = (ElementAnglePacket *)data; + SimplyElementRadial *element = + (SimplyElementRadial *)simply_stage_get_element(simply->stage, packet->id); if (!element) { return; } - element->angle_start = packet->angle_start; + element->angle = packet->angle; simply_stage_update(simply->stage); }; -static void handle_element_angle_end_packet(Simply *simply, Packet *data) { - CommandElementAngleEndPacket *packet = (CommandElementAngleEndPacket*) data; - SimplyElementRadial *element = (SimplyElementRadial*) simply_stage_get_element(simply->stage, packet->id); +static void handle_element_angle2_packet(Simply *simply, Packet *data) { + ElementAngle2Packet *packet = (ElementAngle2Packet *)data; + SimplyElementRadial *element = + (SimplyElementRadial *)simply_stage_get_element(simply->stage, packet->id); if (!element) { return; } - element->angle_end = packet->angle_end; + element->angle2 = packet->angle; simply_stage_update(simply->stage); }; @@ -718,11 +714,11 @@ bool simply_stage_handle_packet(Simply *simply, Packet *packet) { case CommandElementRadius: handle_element_radius_packet(simply, packet); return true; - case CommandElementAngleStart: - handle_element_angle_start_packet(simply, packet); + case CommandElementAngle: + handle_element_angle_packet(simply, packet); return true; - case CommandElementAngleEnd: - handle_element_angle_end_packet(simply, packet); + case CommandElementAngle2: + handle_element_angle2_packet(simply, packet); return true; case CommandElementText: handle_element_text_packet(simply, packet); diff --git a/src/simply/simply_stage.h b/src/simply/simply_stage.h index 6c2a6f37..e9714f51 100644 --- a/src/simply/simply_stage.h +++ b/src/simply/simply_stage.h @@ -71,8 +71,8 @@ typedef struct SimplyElementRadial SimplyElementRadial; struct SimplyElementRadial { SimplyElementRect rect; - uint16_t angle_start; - uint16_t angle_end; + uint16_t angle; + uint16_t angle2; }; typedef struct SimplyElementText SimplyElementText; From 369f024e53ea26f73904ecf5478ea80b60eb9db4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 6 Mar 2016 21:17:21 -0800 Subject: [PATCH 736/791] Fix strset, strdup2 functions to be NULL safe by using a new NULL safe strlen2 (Addresses #152) --- src/util/string.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/util/string.h b/src/util/string.h index f606b68c..fddb0fd8 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -8,6 +8,10 @@ static inline bool is_string(const char *str) { return str && str[0]; } +static inline int strlen2(const char *str) { + return is_string(str) ? strlen(str) : 0; +} + static inline char *strndup2(const char *str, size_t n) { if (!str) { return NULL; @@ -24,7 +28,7 @@ static inline char *strndup2(const char *str, size_t n) { } static inline char *strdup2(const char *str) { - return strndup2(str, strlen(str)); + return strndup2(str, strlen2(str)); } static inline bool strnset(char **str_field, const char *str, size_t n) { @@ -39,10 +43,10 @@ static inline bool strnset(char **str_field, const char *str, size_t n) { } static inline bool strset(char **str_field, const char *str) { - return strnset(str_field, str, strlen(str)); + return strnset(str_field, str, strlen2(str)); } static inline void strset_truncated(char **str_field, const char *str) { - size_t n = strlen(str); + size_t n = strlen2(str); for (; !strnset(str_field, str, n) && n > 1; n /= 2) {} } From 7b3aad5804d6704d721f991f2e4b40aacd7ea592 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 6 Mar 2016 21:24:53 -0800 Subject: [PATCH 737/791] Change strlen2 to return a size_t --- src/util/string.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/string.h b/src/util/string.h index fddb0fd8..c76fbdb4 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -8,7 +8,7 @@ static inline bool is_string(const char *str) { return str && str[0]; } -static inline int strlen2(const char *str) { +static inline size_t strlen2(const char *str) { return is_string(str) ? strlen(str) : 0; } From 66f8f4f3cce4aa005ca70452d5d9602497638ed7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 12 Mar 2016 12:05:41 -0800 Subject: [PATCH 738/791] Fix struct.js to interpret C strings as UTF-8 strings (Addresses #150) --- src/js/lib/struct.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/lib/struct.js b/src/js/lib/struct.js index f3e472ec..9989b161 100644 --- a/src/js/lib/struct.js +++ b/src/js/lib/struct.js @@ -77,7 +77,7 @@ struct.types.cstring.get = function(offset) { chars[j] = String.fromCharCode(buffer.getUint8(i)); } this._advance = chars.length + 1; - return chars.join(''); + return decodeURIComponent(escape(chars.join(''))); }; struct.types.cstring.set = function(offset, value) { From e8fec272c5f755659f1d285d7c7f65b1a9157039 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 12 Mar 2016 13:27:08 -0800 Subject: [PATCH 739/791] Add missing Propable.unset method --- src/js/ui/propable.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/js/ui/propable.js b/src/js/ui/propable.js index c0244bdf..a279c7f5 100644 --- a/src/js/ui/propable.js +++ b/src/js/ui/propable.js @@ -5,6 +5,10 @@ var Propable = function(def) { this.state = def || {}; }; +Propable.unset = function(k) { + delete this[k]; +}; + Propable.makeAccessor = function(k) { return function(value) { if (arguments.length === 0) { From ca0ff89b83820745f7a9020af308961f3c80c308 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 12 Mar 2016 13:28:42 -0800 Subject: [PATCH 740/791] Add Propable makeNestedAccessor and makeNestedAccessors --- src/js/ui/propable.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/js/ui/propable.js b/src/js/ui/propable.js index a279c7f5..1616e842 100644 --- a/src/js/ui/propable.js +++ b/src/js/ui/propable.js @@ -20,6 +20,40 @@ Propable.makeAccessor = function(k) { }; }; +Propable.makeNestedAccessor = function(k) { + var _k = '_' + k; + return function(field, value, clear) { + var nest = this.state[k]; + if (arguments.length === 0) { + return nest; + } + if (arguments.length === 1 && typeof field === 'string') { + return typeof nest === 'object' ? nest[field] : undefined; + } + if (typeof field === 'boolean') { + value = field; + field = k; + } + if (typeof field === 'object') { + clear = value; + value = undefined; + } + if (clear) { + this._clear(k); + } + if (field !== undefined && typeof nest !== 'object') { + nest = this.state[k] = {}; + } + if (field !== undefined && typeof nest === 'object') { + util2.copy(myutil.toObject(field, value), nest); + } + if (this[_k]) { + this[_k](nest); + } + return this; + }; +}; + Propable.makeAccessors = function(props, proto) { proto = proto || {}; props.forEach(function(k) { @@ -28,6 +62,14 @@ Propable.makeAccessors = function(props, proto) { return proto; }; +Propable.makeNestedAccessors = function(props, proto) { + proto = proto || {}; + props.forEach(function(k) { + proto[k] = Propable.makeNestedAccessor(k); + }); + return proto; +}; + Propable.prototype.unset = function(k) { delete this.state[k]; }; From feb25269f8655d8448fe3ae3baaf960323227b2f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 12 Mar 2016 13:29:03 -0800 Subject: [PATCH 741/791] Change Propable.prototype._clear to support nested accessors --- src/js/ui/propable.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/js/ui/propable.js b/src/js/ui/propable.js index 1616e842..14af23e1 100644 --- a/src/js/ui/propable.js +++ b/src/js/ui/propable.js @@ -74,8 +74,12 @@ Propable.prototype.unset = function(k) { delete this.state[k]; }; -Propable.prototype._clear = function() { - this.state = {}; +Propable.prototype._clear = function(k) { + if (k === undefined || k === true) { + this.state = {}; + } else if (k !== false) { + this.state[k] = {}; + } }; Propable.prototype._prop = function(def) { From 4f10252a60dcdbe805217643d0b93605b8568b1f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 12 Mar 2016 13:31:50 -0800 Subject: [PATCH 742/791] Add Window status nested accessor --- src/js/ui/window.js | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 47453269..6038e339 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -36,6 +36,11 @@ var actionProps = [ var accessorProps = configProps; +var nestedProps = [ + 'action', + 'status', +]; + var defaults = { status: false, backgroundColor: 'black', @@ -62,6 +67,8 @@ util2.copy(Stage.prototype, Window.prototype); Propable.makeAccessors(accessorProps, Window.prototype); +Propable.makeNestedAccessors(nestedProps, Window.prototype); + Window.prototype._id = function() { return this.state.id; }; @@ -141,28 +148,10 @@ Window.prototype._action = function(actionDef) { } }; -Window.prototype.action = function(field, value, clear) { - var action = this.state.action; - if (!action) { - action = this.state.action = {}; - } - if (arguments.length === 0) { - return action; - } - if (arguments.length === 1 && typeof field === 'string') { - return action[field]; - } - if (typeof field !== 'string') { - clear = value; - } - if (clear) { - this._clear('action'); - } - if (typeof field !== 'boolean') { - util2.copy(myutil.toObject(field, value), this.state.action); +Window.prototype._status = function(statusDef) { + if (this === WindowStack.top()) { + simply.impl.windowStatusBar(statusDef); } - this._action(field); - return this; }; var isBackEvent = function(type, subtype) { From c664cdaf9cb0658521beda294d7e26cd6ca94810 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 12 Mar 2016 13:32:11 -0800 Subject: [PATCH 743/791] Add Window status clearing with nested property retention --- src/js/ui/card.js | 10 ++++------ src/js/ui/simply-pebble.js | 4 ++-- src/js/ui/window.js | 27 ++++++++------------------- 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/js/ui/card.js b/src/js/ui/card.js index fe7048c6..c7a7fe4e 100644 --- a/src/js/ui/card.js +++ b/src/js/ui/card.js @@ -62,14 +62,12 @@ Card.prototype._prop = function() { } }; -Card.prototype._clear = function(flags) { - flags = myutil.toFlags(flags); +Card.prototype._clear = function(flags_) { + var flags = myutil.toFlags(flags_); if (flags === true) { - clearableProps.forEach(Propable.unset.bind(this)); - } - if (myutil.flag(flags, 'action')) { - this._clearAction(); + clearableProps.forEach(Propable.unset.bind(this.state)); } + Window.prototype._clear.call(this, flags_); }; module.exports = Card; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index e5efca64..04d79c99 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1093,7 +1093,7 @@ SimplyPebble.windowStatusBar = function(def) { var statusDef = toStatusDef(def); WindowStatusBarPacket .separator(statusDef.separator || 'dotted') - .status(typeof def === 'boolean' ? def : true) + .status(typeof def === 'boolean' ? def : def.status !== false) .color(statusDef.color || 'black') .backgroundColor(statusDef.backgroundColor || 'white'); SimplyPebble.sendPacket(WindowStatusBarPacket); @@ -1120,7 +1120,7 @@ SimplyPebble.windowActionBar = function(def) { .up(actionDef.up) .select(actionDef.select) .down(actionDef.down) - .action(typeof def === 'boolean' ? def : true) + .action(typeof def === 'boolean' ? def : def.action !== false) .backgroundColor(actionDef.backgroundColor || 'black'); SimplyPebble.sendPacket(WindowActionBarPacket); }; diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 6038e339..ecccbd86 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -22,12 +22,14 @@ var configProps = [ ]; var statusProps = [ + 'status', 'separator', 'color', 'backgroundColor', ]; var actionProps = [ + 'action', 'up', 'select', 'back', @@ -116,30 +118,17 @@ Window.prototype._clearAction = function() { actionProps.forEach(Propable.unset.bind(this.state.action)); }; -Window.prototype._clear = function(flags) { - flags = myutil.toFlags(flags); +Window.prototype._clear = function(flags_) { + var flags = myutil.toFlags(flags_); if (myutil.flag(flags, 'action')) { this._clearAction(); } -}; - -Window.prototype.prop = function(field, value, clear) { - if (arguments.length === 0) { - return util2.copy(this.state); + if (myutil.flag(flags, 'status')) { + this._clearStatus(); } - if (arguments.length === 1 && typeof field !== 'object') { - return this.state[field]; + if (flags_ === true || flags_ === undefined) { + Propable.prototype._clear.call(this); } - if (typeof field === 'object') { - clear = value; - } - if (clear) { - this._clear(true); - } - var windowDef = myutil.toObject(field, value); - util2.copy(windowDef, this.state); - this._prop(windowDef); - return this; }; Window.prototype._action = function(actionDef) { From 4158b8b49c1de4f387a4dd01f8a9221c27859619 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 12 Mar 2016 13:54:17 -0800 Subject: [PATCH 744/791] Fix Card to reset the style upon switching to a different card --- src/simply/simply_ui.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index 5793a413..c9511581 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -31,6 +31,7 @@ enum ClearIndex { ClearIndex_Action = 0, ClearIndex_Text, ClearIndex_Image, + ClearIndex_Style, }; enum StyleIndex { @@ -42,6 +43,8 @@ enum StyleIndex { StyleIndexCount, }; +#define StyleIndex_Default StyleIndex_ClassicLarge + static const SimplyStyle STYLES[StyleIndexCount] = { [StyleIndex_ClassicSmall] = { .title_font = FONT_KEY_GOTHIC_18_BOLD, @@ -126,6 +129,9 @@ void simply_ui_clear(SimplyUi *self, uint32_t clear_mask) { if (clear_mask & (1 << ClearIndex_Image)) { memset(self->ui_layer.imagefields, 0, sizeof(self->ui_layer.imagefields)); } + if (clear_mask & (1 << ClearIndex_Style)) { + simply_ui_set_style(self, StyleIndex_Default); + } } void simply_ui_set_style(SimplyUi *self, int style_index) { @@ -383,7 +389,7 @@ static void window_load(Window *window) { scroll_layer_add_child(self->window.scroll_layer, layer); self->window.use_scroll_layer = true; - simply_ui_set_style(self, 1); + simply_ui_set_style(self, StyleIndex_Default); } static void window_appear(Window *window) { From 1617e7d8edf07ed71bd47bc2164a3af2b11f9f33 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 12 Mar 2016 14:02:23 -0800 Subject: [PATCH 745/791] Add Window fullscreen deprecation notice --- src/js/ui/radial.js | 1 + src/js/ui/window.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/js/ui/radial.js b/src/js/ui/radial.js index 65fd5b68..55b31d14 100644 --- a/src/js/ui/radial.js +++ b/src/js/ui/radial.js @@ -20,6 +20,7 @@ var defaults = { }; var checkProps = function(def) { + if (!def) return; if ('angleStart' in def && safe.warnAngleStart !== false) { safe.warn('`angleStart` has been deprecated in favor of `angle` in order to match\n\t' + "Line's `position` and `position2`. Please use `angle` intead.", 2); diff --git a/src/js/ui/window.js b/src/js/ui/window.js index ecccbd86..4fcd4190 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -1,5 +1,6 @@ var util2 = require('util2'); var myutil = require('myutil'); +var safe = require('safe'); var Emitter = require('emitter'); var Accel = require('ui/accel'); var WindowStack = require('ui/windowstack'); @@ -51,7 +52,18 @@ var defaults = { var nextId = 1; +var checkProps = function(def) { + if (!def) return; + if ('fullscreen' in def && safe.warnFullscreen !== false) { + safe.warn('`fullscreen` has been deprecated by `status` which allows settings\n\t' + + 'its color and separator in a similar manner to the `action` property.\n\t' + + 'Remove usages of `fullscreen` to enable usage of `status`.', 2); + safe.warnFullscreen = false; + } +}; + var Window = function(windowDef) { + checkProps(windowDef); this.state = myutil.shadow(defaults, windowDef || {}); this.state.id = nextId++; this._buttonInit(); @@ -75,6 +87,11 @@ Window.prototype._id = function() { return this.state.id; }; +Window.prototype._prop = function(def, clear, pushing) { + checkProps(def); + Stage.prototype._prop.call(this, def, clear, pushing); +}; + Window.prototype._hide = function(broadcast) { if (broadcast === false) { return; } simply.impl.windowHide(this._id()); From f200af743952e4fe698d92e93a64a44020bbc397 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 12 Mar 2016 14:50:55 -0800 Subject: [PATCH 746/791] Fix status bar to be centered on round when there is an action bar --- src/simply/simply_window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 7569051c..1f9e90ff 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -130,7 +130,7 @@ static void prv_update_layer_placement(SimplyWindow *self, GRect *frame_out) { frame.size.h -= PBL_IF_ROUND_ELSE(self->status_bar_insets_bottom, false) ? STATUS_BAR_LAYER_HEIGHT * 2 : STATUS_BAR_LAYER_HEIGHT; - if (has_action_bar) { + if (PBL_IF_RECT_ELSE(has_action_bar, false)) { status_frame.size.w -= ACTION_BAR_WIDTH; } layer_set_frame(status_bar_base_layer, status_frame); From 8ac71ba74a06ea15752abb6f5907b042db3006e0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 26 Mar 2016 17:00:03 -0700 Subject: [PATCH 747/791] Add Platform Feature module --- src/js/platform/feature.js | 50 ++++++++++++++++++++++++++++++++++++++ src/js/ui/imageservice.js | 4 +-- 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/js/platform/feature.js diff --git a/src/js/platform/feature.js b/src/js/platform/feature.js new file mode 100644 index 00000000..be103c16 --- /dev/null +++ b/src/js/platform/feature.js @@ -0,0 +1,50 @@ +var Platform = require('platform'); + +var Feature = module.exports; + +Feature.platform = function(map, yes, no) { + var v = map[Platform.version()]; + var rv; + if (v && yes !== undefined) { + rv = typeof yes === 'function' ? yes(v) : yes; + } else if (!v && no !== undefined) { + rv = typeof no === 'function' ? no(v) : no; + } + return rv !== undefined ? rv : v; +}; + +Feature.makePlatformTest = function(map) { + return function(yes, no) { + return Feature.platform(map, yes, no); + }; +}; + +Feature.blackAndWhite = Feature.makePlatformTest({ + aplite: true, + basalt: false, + chalk: false, +}); + +Feature.color = Feature.makePlatformTest({ + aplite: false, + basalt: true, + chalk: true, +}); + +Feature.rectangle = Feature.makePlatformTest({ + aplite: true, + basalt: true, + chalk: false, +}); + +Feature.round = Feature.makePlatformTest({ + aplite: false, + basalt: false, + chalk: true, +}); + +Feature.microphone = Feature.makePlatformTest({ + aplite: false, + basalt: true, + chalk: true, +}); diff --git a/src/js/ui/imageservice.js b/src/js/ui/imageservice.js index 67fa5b48..57bb3420 100644 --- a/src/js/ui/imageservice.js +++ b/src/js/ui/imageservice.js @@ -1,6 +1,6 @@ var imagelib = require('lib/image'); var myutil = require('myutil'); -var Platform = require('platform'); +var Feature = require('platform/feature'); var Resource = require('ui/resource'); var simply = require('ui/simply'); @@ -100,7 +100,7 @@ ImageService.load = function(opt, reset, callback) { } }; if (fetch) { - var bitdepth = Platform.version() === 'basalt' ? 8 : 1; + var bitdepth = Feature.color(8, 1); imagelib.load(image, bitdepth, onLoad); } else { onLoad(); From 19a0cf153b473d65524d1f4217538fcc65650624 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 26 Mar 2016 17:10:38 -0700 Subject: [PATCH 748/791] Add Feature.resolution --- src/js/platform/feature.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/js/platform/feature.js b/src/js/platform/feature.js index be103c16..970f395b 100644 --- a/src/js/platform/feature.js +++ b/src/js/platform/feature.js @@ -3,7 +3,7 @@ var Platform = require('platform'); var Feature = module.exports; Feature.platform = function(map, yes, no) { - var v = map[Platform.version()]; + var v = map[Platform.version()] || map.unknown; var rv; if (v && yes !== undefined) { rv = typeof yes === 'function' ? yes(v) : yes; @@ -48,3 +48,9 @@ Feature.microphone = Feature.makePlatformTest({ basalt: true, chalk: true, }); + +Feature.resolution = Feature.makePlatformTest({ + aplite: { width: 144, height: 168 }, + basalt: { width: 144, height: 168 }, + chalk: { width: 180, height: 180 }, +}); From 8410af7d4fb5afb1fc03b2af3b0888930dedc136 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 26 Mar 2016 20:57:15 -0700 Subject: [PATCH 749/791] Add Window.size for obtaining the window size --- src/js/platform/feature.js | 15 ++++++++++++--- src/js/ui/window.js | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/js/platform/feature.js b/src/js/platform/feature.js index 970f395b..a8b98b93 100644 --- a/src/js/platform/feature.js +++ b/src/js/platform/feature.js @@ -1,3 +1,4 @@ +var Vector2 = require('vector2'); var Platform = require('platform'); var Feature = module.exports; @@ -50,7 +51,15 @@ Feature.microphone = Feature.makePlatformTest({ }); Feature.resolution = Feature.makePlatformTest({ - aplite: { width: 144, height: 168 }, - basalt: { width: 144, height: 168 }, - chalk: { width: 180, height: 180 }, + aplite: new Vector2(144, 168), + basalt: new Vector2(144, 168), + chalk: new Vector2(180, 180), }); + +Feature.actionBarWidth = function() { + return Feature.rectangle(30, 40); +}; + +Feature.statusBarHeight = function() { + return 16; +}; diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 4fcd4190..96c43c09 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -2,6 +2,8 @@ var util2 = require('util2'); var myutil = require('myutil'); var safe = require('safe'); var Emitter = require('emitter'); +var Vector2 = require('vector2'); +var Feature = require('platform/feature'); var Accel = require('ui/accel'); var WindowStack = require('ui/windowstack'); var Propable = require('ui/propable'); @@ -69,6 +71,8 @@ var Window = function(windowDef) { this._buttonInit(); this._items = []; this._dynamic = true; + this._size = new Vector2(); + this.size(); // calculate and set the size }; Window._codeName = 'window'; @@ -251,6 +255,20 @@ Window.prototype._buttonAutoConfig = function() { } }; +Window.prototype.size = function() { + var state = this.state; + var size = this._size.copy(Feature.resolution()); + if ('status' in state && state.status !== false) { + size.y -= Feature.statusBarHeight(); + } else if ('fullscreen' in state && state.fullscreen === false) { + size.y -= Feature.statusBarHeight(); + } + if ('action' in state && state.action !== false) { + size.x -= Feature.actionBarWidth(); + } + return size; +}; + Window.prototype._toString = function() { return '[' + this.constructor._codeName + ' ' + this._id() + ']'; }; From 9753483cba6a8e3b54f3b6fa435671a215ee7dc0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 26 Mar 2016 21:01:58 -0700 Subject: [PATCH 750/791] Center the demo radial and textfield in the window --- src/js/app.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 9d9d2461..9255c293 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -47,7 +47,6 @@ main.on('click', 'select', function(e) { backgroundColor: 'black' }); var radial = new UI.Radial({ - position: new Vector2(2, 14), size: new Vector2(140, 140), angle: 0, angle2: 300, @@ -57,12 +56,24 @@ main.on('click', 'select', function(e) { borderWidth: 1, }); var textfield = new UI.Text({ - position: new Vector2(0, 57), - size: new Vector2(144, 60), + size: new Vector2(140, 60), font: 'gothic-24-bold', text: 'Dynamic\nWindow', textAlign: 'center' }); + var windSize = wind.size(); + // Center the radial in the window + var radialPos = radial.position() + .addSelf(windSize) + .subSelf(radial.size()) + .multiplyScalar(0.5); + radial.position(radialPos); + // Center the textfield in the window + var textfieldPos = textfield.position() + .addSelf(windSize) + .subSelf(textfield.size()) + .multiplyScalar(0.5); + textfield.position(textfieldPos); wind.add(radial); wind.add(textfield); wind.show(); From c297342a1fbf2f35e0f40b7100dbcf3a9b44d1cc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 28 Mar 2016 07:41:08 -0700 Subject: [PATCH 751/791] Remove window and status background color linking logic --- src/simply/simply_window.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 1f9e90ff..839491f5 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -172,10 +172,6 @@ void simply_window_set_status_bar(SimplyWindow *self, bool use_status_bar) { void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color) { self->background_color = background_color; window_set_background_color(self->window, gcolor8_get_or(background_color, GColorBlack)); - if (self->status_bar_layer) { - status_bar_layer_set_colors(self->status_bar_layer, background_color, - gcolor_legible_over(background_color)); - } } void simply_window_set_status_bar_colors(SimplyWindow *self, GColor8 background_color, From 70852a76e3669d7821eb7a2f88a6e195140bcf09 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Thu, 7 Apr 2016 05:29:01 -0700 Subject: [PATCH 752/791] Fix Card scrolling issue scroll_layer_set_content_size now cancels the current scroll, so set it only if the frame size has actually changed. --- src/simply/simply_ui.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index c9511581..ac12df60 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -298,8 +298,11 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { body_rect.size = body_size; const int new_height = cursor.y + margin_bottom; frame.size.h = MAX(window_frame.size.h, new_height); - layer_set_frame(layer, frame); - scroll_layer_set_content_size(self->window.scroll_layer, frame.size); + GRect original_frame = layer_get_frame(layer); + if (!grect_equal(&frame, &original_frame)) { + layer_set_frame(layer, frame); + scroll_layer_set_content_size(self->window.scroll_layer, frame.size); + } } else if (!self->ui_layer.custom_body_font && body_size.h > body_rect.size.h) { body_font = fonts_get_system_font(FONT_KEY_GOTHIC_18); } From 760f0f3702b159be429998f3b86fdc7843dd53bf Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 10 Apr 2016 23:40:27 -0700 Subject: [PATCH 753/791] Add color.js lib which converts hex-string and number formats to ARGB32 and ARGB8 --- src/js/lib/color.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/js/lib/color.js diff --git a/src/js/lib/color.js b/src/js/lib/color.js new file mode 100644 index 00000000..b23a1bf3 --- /dev/null +++ b/src/js/lib/color.js @@ -0,0 +1,45 @@ +var Color = {}; + +Color.normalizeString = function(color) { + if (typeof color === 'string') { + if (color.substr(0, 2) === '0x') { + return color.substr(2); + } else if (color[0] === '#') { + return color.substr(1); + } + } + return color; +}; + +Color.rgbUint12To24 = function(color) { + return ((color & 0xf00) << 12) | ((color & 0xf0) << 8) | ((color & 0xf) << 4); +}; + +Color.toArgbUint32 = function(color) { + var argb = color; + if (typeof color !== 'number') { + color = Color.normalizeString(color.toString()); + argb = parseInt(color, 16); + } + if (typeof color === 'string') { + var alpha = 0xff000000; + if (color.length === 3) { + argb = alpha | Color.rgbUint12To14(argb); + } else if (color.length === 6) { + argb = alpha | argb; + } + } + return argb; +}; + +Color.toRgbUint24 = function(color) { + return Color.toArgbUint32(color) & 0xffffff; +}; + +Color.toArgbUint8 = function(color) { + var argb = Color.toArgbUint32(color); + return (((argb >> 24) & 0xc0) | ((argb >> 18) & 0x30) | + ((argb >> 12) & 0xc) | ((argb >> 6) & 0x3)); +}; + +module.exports = Color; From 31b70c7b94031460327e955cccd3af673b034df9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 10 Apr 2016 23:51:08 -0700 Subject: [PATCH 754/791] Add support for colors specified as number values, e.g. 0xaabbcc --- src/js/lib/color.js | 4 ++ src/js/ui/simply-pebble.js | 142 ++++++------------------------------- 2 files changed, 27 insertions(+), 119 deletions(-) diff --git a/src/js/lib/color.js b/src/js/lib/color.js index b23a1bf3..11b05efe 100644 --- a/src/js/lib/color.js +++ b/src/js/lib/color.js @@ -42,4 +42,8 @@ Color.toArgbUint8 = function(color) { ((argb >> 12) & 0xc) | ((argb >> 6) & 0x3)); }; +Color.toRgbUint8 = function(color) { + return Color.toArgbUint8(color) & 0x3f; +}; + module.exports = Color; diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 04d79c99..9c7229da 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -1,3 +1,4 @@ +var Color = require('color'); var struct = require('struct'); var util2 = require('util2'); var myutil = require('myutil'); @@ -73,73 +74,6 @@ var SizeType = function(x) { this.sizeH(x.y); }; -var hexColorMap = { - '#000000': 0xC0, - '#000055': 0xC1, - '#0000AA': 0xC2, - '#0000FF': 0xC3, - '#005500': 0xC4, - '#005555': 0xC5, - '#0055AA': 0xC6, - '#0055FF': 0xC7, - '#00AA00': 0xC8, - '#00AA55': 0xC9, - '#00AAAA': 0xCA, - '#00AAFF': 0xCB, - '#00FF00': 0xCC, - '#00FF55': 0xCD, - '#00FFAA': 0xCE, - '#00FFFF': 0xCF, - '#550000': 0xD0, - '#550055': 0xD1, - '#5500AA': 0xD2, - '#5500FF': 0xD3, - '#555500': 0xD4, - '#555555': 0xD5, - '#5555AA': 0xD6, - '#5555FF': 0xD7, - '#55AA00': 0xD8, - '#55AA55': 0xD9, - '#55AAAA': 0xDA, - '#55AAFF': 0xDB, - '#55FF00': 0xDC, - '#55FF55': 0xDD, - '#55FFAA': 0xDE, - '#55FFFF': 0xDF, - '#AA0000': 0xE0, - '#AA0055': 0xE1, - '#AA00AA': 0xE2, - '#AA00FF': 0xE3, - '#AA5500': 0xE4, - '#AA5555': 0xE5, - '#AA55AA': 0xE6, - '#AA55FF': 0xE7, - '#AAAA00': 0xE8, - '#AAAA55': 0xE9, - '#AAAAAA': 0xEA, - '#AAAAFF': 0xEB, - '#AAFF00': 0xEC, - '#AAFF55': 0xED, - '#AAFFAA': 0xEE, - '#AAFFFF': 0xEF, - '#FF0000': 0xF0, - '#FF0055': 0xF1, - '#FF00AA': 0xF2, - '#FF00FF': 0xF3, - '#FF5500': 0xF4, - '#FF5555': 0xF5, - '#FF55AA': 0xF6, - '#FF55FF': 0xF7, - '#FFAA00': 0xF8, - '#FFAA55': 0xF9, - '#FFAAAA': 0xFA, - '#FFAAFF': 0xFB, - '#FFFF00': 0xFC, - '#FFFF55': 0xFD, - '#FFFFAA': 0xFE, - '#FFFFFF': 0xFF, -}; - var namedColorMap = { 'clear': 0x00, 'black': 0xC0, @@ -209,45 +143,15 @@ var namedColorMap = { 'clearWhite': 0x3F, }; -var Color = function(color) { - if (color.charAt(0) === '#') { - // Convert shorthand hex to full length for rounding - if (color.length === 4) { - var r = color.charAt(1); - var g = color.charAt(2); - var b = color.charAt(3); - color = '#'+r+r+g+g+b+b; - } - // Ensure upper case - color = color.toUpperCase(); - return hexColorMap[roundColor(color)]; +var ColorType = function(color) { + if (color in namedColorMap) { + return namedColorMap[color]; } - return namedColorMap[color] ? namedColorMap[color] : namedColorMap.clear; -}; - -var pebbleColors = ['00', '55', 'AA', 'FF']; - -var roundColor = function (color) { - var rHex = color.substr(1, 2); - var gHex = color.substr(3, 2); - var bHex = color.substr(5, 2); - var r = findClosestColor(rHex, pebbleColors); - var g = findClosestColor(gHex, pebbleColors); - var b = findClosestColor(bHex, pebbleColors); - return '#'+r+g+b; -}; - -var findClosestColor = function(color, colors) { - var nearestDist = Infinity; - var result = color; - colors.forEach(function(col) { - var dist = Math.abs(parseInt(color, 16) - parseInt(col, 16)); - if (dist < nearestDist) { - nearestDist = dist; - result = col; - } - }); - return result; + var argb = Color.toArgbUint8(color); + if ((argb & 0xc0) === 0 && argb !== 0) { + argb = argb | 0xc0; + } + return argb; }; var Font = function(x) { @@ -527,7 +431,7 @@ var WindowHideEventPacket = new struct([ var WindowPropsPacket = new struct([ [Packet, 'packet'], ['uint32', 'id'], - ['uint8', 'backgroundColor', Color], + ['uint8', 'backgroundColor', ColorType], ['bool', 'scrollable', BoolType], ]); @@ -538,8 +442,8 @@ var WindowButtonConfigPacket = new struct([ var WindowStatusBarPacket = new struct([ [Packet, 'packet'], - ['uint8', 'backgroundColor', Color], - ['uint8', 'color', Color], + ['uint8', 'backgroundColor', ColorType], + ['uint8', 'color', ColorType], ['uint8', 'separator', StatusBarSeparatorModeType], ['uint8', 'status', BoolType], ]); @@ -549,7 +453,7 @@ var WindowActionBarPacket = new struct([ ['uint32', 'up', ImageType], ['uint32', 'select', ImageType], ['uint32', 'down', ImageType], - ['uint8', 'backgroundColor', Color], + ['uint8', 'backgroundColor', ColorType], ['uint8', 'action', BoolType], ]); @@ -580,7 +484,7 @@ var CardClearPacket = new struct([ var CardTextPacket = new struct([ [Packet, 'packet'], ['uint8', 'index', CardTextType], - ['uint8', 'color', Color], + ['uint8', 'color', ColorType], ['cstring', 'text'], ]); @@ -648,18 +552,18 @@ var MenuClearSectionPacket = new struct([ var MenuPropsPacket = new struct([ [Packet, 'packet'], ['uint16', 'sections', EnumerableType], - ['uint8', 'backgroundColor', Color], - ['uint8', 'textColor', Color], - ['uint8', 'highlightBackgroundColor', Color], - ['uint8', 'highlightTextColor', Color], + ['uint8', 'backgroundColor', ColorType], + ['uint8', 'textColor', ColorType], + ['uint8', 'highlightBackgroundColor', ColorType], + ['uint8', 'highlightTextColor', ColorType], ]); var MenuSectionPacket = new struct([ [Packet, 'packet'], ['uint16', 'section'], ['uint16', 'items', EnumerableType], - ['uint8', 'backgroundColor', Color], - ['uint8', 'textColor', Color], + ['uint8', 'backgroundColor', ColorType], + ['uint8', 'textColor', ColorType], ['uint16', 'titleLength', EnumerableType], ['cstring', 'title', StringType], ]); @@ -753,8 +657,8 @@ var ElementCommonPacket = new struct([ [GPoint, 'position', PositionType], [GSize, 'size', SizeType], ['uint16', 'borderWidth', EnumerableType], - ['uint8', 'backgroundColor', Color], - ['uint8', 'borderColor', Color], + ['uint8', 'backgroundColor', ColorType], + ['uint8', 'borderColor', ColorType], ]); var ElementRadiusPacket = new struct([ @@ -785,7 +689,7 @@ var ElementTextPacket = new struct([ var ElementTextStylePacket = new struct([ [Packet, 'packet'], ['uint32', 'id'], - ['uint8', 'color', Color], + ['uint8', 'color', ColorType], ['uint8', 'textOverflow', TextOverflowMode], ['uint8', 'textAlign', TextAlignment], ['uint32', 'customFont'], From 7bf9c37530bbcd55f8aec7a9bd0ffe44f78756a2 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 10 Apr 2016 23:54:52 -0700 Subject: [PATCH 755/791] Add support for more named color styles, e.g. 'sunset-orange', 'SUNSET_ORANGE' --- src/js/ui/simply-pebble.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 9c7229da..a753cbf2 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -143,9 +143,21 @@ var namedColorMap = { 'clearWhite': 0x3F, }; +var namedColorMapUpper = (function() { + var map = {}; + for (var k in namedColorMap) { + map[k.toUpperCase()] = namedColorMap[k]; + } + return map; +})(); + var ColorType = function(color) { - if (color in namedColorMap) { - return namedColorMap[color]; + if (typeof color === 'string') { + var name = myutil.toCConstantName(color); + name = name.replace(/_+/g, ''); + if (name in namedColorMapUpper) { + return namedColorMapUpper[name]; + } } var argb = Color.toArgbUint8(color); if ((argb & 0xc0) === 0 && argb !== 0) { From 4b6697bbf3ac7ffccd7fd493a7a1e824059f770d Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 Apr 2016 00:06:07 -0700 Subject: [PATCH 756/791] Add Card color docs --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a40f3193..63cd98f5 100644 --- a/README.md +++ b/README.md @@ -545,7 +545,7 @@ A `Window` action bar can be displayed by setting its Window `action` property t | `up` | Image | None | An image to display in the action bar, next to the up button. | | `select` | Image | None | An image to display in the action bar, next to the select button. | | `down` | Image | None | An image to display in the action bar, next to the down button. | -| `backgroundColor` | Image | 'black' | The background color of the action bar. You can set this to 'white' for windows with black backgrounds | +| `backgroundColor` | Color | 'black' | The background color of the action bar. You can set this to 'white' for windows with black backgrounds | ````js var card = new UI.Card({ @@ -709,14 +709,19 @@ The properties available on a [Card] are: | Name | Type | Default | Description | | ---- | :-------: | --------- | ------------- | -| `title` | string | "" | Text to display in the title field at the top of the screen | -| `subtitle` | string | "" | Text to display below the title | -| `body` | string | "" | Text to display in the body field. | -| `icon` | Image | null | An image to display before the title text. Refer to [Using Images] for instructions on how to include images in your app. | -| `subicon` | Image | null | An image to display before the subtitle text. Refer to [Using Images] for instructions on how to include images in your app. | -| `banner` | Image | null | An image to display in the center of the screen. Refer to [Using Images] for instructions on how to include images in your app. | +| `title` | string | '' | Text to display in the title field at the top of the screen | +| `titleColor` | Color | 'black' | Text color of the title field | +| `subtitle` | string | '' | Text to display below the title | +| `subtitleColor` | Color | 'black' | Text color of the subtitle field | +| `body` | string | '' | Text to display in the body field | +| `bodyColor` | Color | 'black' | Text color of the body field | +| `icon` | Image | null | An image to display before the title text. Refer to [Using Images] for instructions on how to include images in your app. | +| `subicon` | Image | null | An image to display before the subtitle text. Refer to [Using Images] for instructions on how to include images in your app. | +| `banner` | Image | null | An image to display in the center of the screen. Refer to [Using Images] for instructions on how to include images in your app. | | `scrollable` | boolean | false | Whether the user can scroll this card with the up and down button. When this is enabled, single and long click events on the up and down button will not be transmitted to your app. | -| `style` | string | "small" | Selects the font used to display the body. This can be 'small', 'large' or 'mono' | +| `style` | string | 'small' | Selects the font used to display the body. This can be 'small', 'large' or 'mono' | + +A [Card] is also a [Window] and thus also has Window properties. The small and large styles correspond to the system notification styles. Mono sets a monospace font for the body textfield, enabling more complex text UIs or ASCII art. From 3613f6b3382c10cdb48c7193a29cc1dbcfeb8197 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 Apr 2016 00:13:57 -0700 Subject: [PATCH 757/791] Add classic Card style docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 63cd98f5..c55e3601 100644 --- a/README.md +++ b/README.md @@ -723,9 +723,9 @@ The properties available on a [Card] are: A [Card] is also a [Window] and thus also has Window properties. -The small and large styles correspond to the system notification styles. Mono sets a monospace font for the body textfield, enabling more complex text UIs or ASCII art. +The `'small'` and `'large`' styles correspond to the system notification styles. `'mono'` sets a monospace font for the body textfield, enabling more complex text UIs or ASCII art. The `'small'` and `'large'` styles were updated to match the Pebble firmware 3.x design during the 3.11 release. In order to use the older 2.x styles, you may specify `'classic-small'` and `'classic-large'`, however it is encouraged to use the newer styles. -Note that all fields will automatically span multiple lines if needed and that you can '\n' to insert line breaks. +Note that all text fields will automatically span multiple lines if needed and that you can use '\n' to insert line breaks. ### Menu From 41dfb440a933780f977e6b61906f6944a0aef60e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 Apr 2016 00:22:02 -0700 Subject: [PATCH 758/791] Add Menu section docs --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c55e3601..5075cd63 100644 --- a/README.md +++ b/README.md @@ -743,7 +743,18 @@ The properties available on a [Menu] are: | `highlightBackgroundColor` | Color | `black` | The background color of a selected menu item. | | `highlightTextColor` | Color | `white` | The text color of a selected menu item. | -A menu contains one or more sections. Each section has a title and contains zero or more items. An item must have a title. It can also have a subtitle and an icon. +A menu contains one or more sections. + +The properties available on a section are: + +| Name | Type | Default | Description | +| ---- |:-------:|---------|-------------| +| `items` | Array | `[]` | A list of all the items to display. | +| `title` | string | '' | Title text of the section header. | +| `backgroundColor` | Color | `white` | The background color of the section header. | +| `textColor` | Color | `black` | The text color of the section header. | + +Each section has a title and contains zero or more items. An item must have a title. Items can also optionally have a subtitle and an icon. ````js var menu = new UI.Menu({ From b01cb90369efeb5505a3773529432cf90005b4d0 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 Apr 2016 00:27:40 -0700 Subject: [PATCH 759/791] Add Element borderWidth doc --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5075cd63..ae492b92 100644 --- a/README.md +++ b/README.md @@ -871,15 +871,19 @@ They all share some common properties: | Name | Type | Default | Description | | ------------ | :-------: | --------- | ------------- | | `position` | Vector2 | | Position of this element in the window. | -| `size` | Vector2 | | Size of this element in this window. Note that [Circle] uses `radius` instead. | -| `borderColor` | string | '' | Color of the border of this element ('clear', 'black',or 'white'). | -| `backgroundColor` | string | '' | Background color of this element ('clear', 'black' or 'white'). | +| `size` | Vector2 | | Size of this element in this window. [Circle] uses `radius` instead. | +| `borderWidth` | number | 0 | Width of the border of this element. | +| `borderColor` | Color | 'clear' | Color of the border of this element. | +| `backgroundColor` | Color | 'white' | Background color of this element. | -All properties can be initialized by passing an object when creating the Element, and changed with accessors functions who have the name of the properties. Calling an accessor without a parameter will return the current value. +All properties can be initialized by passing an object when creating the Element, and changed with accessors functions that have the same name as the properties. Calling an accessor without a parameter will return the current value. ````js var Vector2 = require('vector2'); -var element = new Text({ position: new Vector2(0, 0), size: new Vector2(144, 168) }); +var element = new Text({ + position: new Vector2(0, 0), + size: new Vector2(144, 168), +}); element.borderColor('white'); console.log('This element background color is: ' + element.backgroundColor()); ```` @@ -957,6 +961,10 @@ Accessor to the `position` property. See [Element]. Accessor to the `size` property. See [Element]. +#### Element.borderWidth(width) + +Accessor to the `borderWidth` property. See [Element]. + #### Element.borderColor(color) Accessor to the `borderColor` property. See [Element]. From 3410eb609f74dc092768fff6f01e57342dd3583b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 11 Apr 2016 00:39:53 -0700 Subject: [PATCH 760/791] Add Line element docs --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ae492b92..ca729ac8 100644 --- a/README.md +++ b/README.md @@ -866,15 +866,15 @@ Similar to the select callback, except for long select presses. See [Menu.on('se There are four types of [Element] that can be instantiated at the moment: [Circle], [Image], [Rect] and [Text]. -They all share some common properties: +Most elements share these common properties: | Name | Type | Default | Description | | ------------ | :-------: | --------- | ------------- | | `position` | Vector2 | | Position of this element in the window. | | `size` | Vector2 | | Size of this element in this window. [Circle] uses `radius` instead. | -| `borderWidth` | number | 0 | Width of the border of this element. | -| `borderColor` | Color | 'clear' | Color of the border of this element. | -| `backgroundColor` | Color | 'white' | Background color of this element. | +| `borderWidth` | number | 0 | Width of the border of this element. [Line] uses `strokeWidth` instead. | +| `borderColor` | Color | 'clear' | Color of the border of this element. [Line] uses `strokeColor` instead. | +| `backgroundColor` | Color | 'white' | Background color of this element. [Line] has no background. | All properties can be initialized by passing an object when creating the Element, and changed with accessors functions that have the same name as the properties. Calling an accessor without a parameter will return the current value. @@ -973,14 +973,48 @@ Accessor to the `borderColor` property. See [Element]. Accessor to the `backgroundColor` property. See [Element]. -### Circle +### Line -An [Element] that displays a circle on the screen. +An [Element] that displays a line on the screen. + +[Line] also has these additional properties: + +| Name | Type | Default | Description | +| ------------ | :-------: | --------- | ------------- | +| `position2` | Vector2 | | Ending position of the line where `position` is the starting position. | +| `strokeWidth` | number | 0 | Width of the line. | +| `strokeColor` | Color | 'clear' | Color of the line. | + +For clarity, [Line] has `strokeWidth` and `strokeColor` instead of `borderWidth` and `borderColor`. + +````js +var wind = new UI.Window(); + +var line = new UI.Line({ + position: new Vector2(10, 10), + position2: new Vector2(72, 84), + strokeColor: 'white', +}); -Default properties value: +wind.add(line); +wind.show(); +``` + +#### Line.position2(position) + +Accessor to the `position2` ending position property. See [Line]. + +#### Line.strokeWidth(width) + +Accessor to the `strokeWidth` property. See [Line]. - * `backgroundColor`: 'white' - * `borderColor`: 'clear' +#### Line.strokeColor(color) + +Accessor to the `strokeColor` property. See [Line]. + +### Circle + +An [Element] that displays a circle on the screen. [Circle] also has the additional property `radius` which it uses for size rather than `size`. [Circle] is also different in that it positions its origin at the position, rather than anchoring by its top left. These differences are to keep the graphics operation characteristics that it is built upon. @@ -994,7 +1028,7 @@ var circle = new UI.Circle({ }); wind.add(circle); -wind.show(circle); +wind.show(); ```` #### Circle.radius(radius) From 633b25d5388eaf20427fd42e5fab487ac7c094bf Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 16 Apr 2016 20:15:26 -0700 Subject: [PATCH 761/791] Add Radial element docs --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index ca729ac8..488bf328 100644 --- a/README.md +++ b/README.md @@ -1035,6 +1035,30 @@ wind.show(); Accessor to the `radius` property. See [Circle] +### Radial + +An [Element] that can display as an arc, ring, sector of a circle depending on its properties are set. + +[Radial] has these additional properties: + +| Name | Type | Default | Description | +| ------------ | :-------: | --------- | ------------- | +| `radius` | number | 0 | Radius of the radial starting from its outer edge. A sufficiently large radius results in a sector or circle instead of an arc or ring. | +| `angle` | number | 0 | Starting angle in degrees. An arc or sector will be drawn from `angle` to `angle2`. | +| `angle2` | number | 360 | Ending angle in degrees. An ending angle that is 360 beyond the starting angle will result in a ring or circle. | + +#### Radial.radius(radius) + +Accessor to the `radius` property. See [Radial] + +#### Radial.angle(angle) + +Accessor to the `angle` starting angle property. See [Radial] + +#### Radial.angle2(angle) + +Accessor to the `angle2` ending angle property. See [Radial] + ### Rect An [Element] that displays a rectangle on the screen. From 63e9ef383ac3e2044fb05c3145cbf82a1e603784 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 16 Apr 2016 20:57:01 -0700 Subject: [PATCH 762/791] Add Platform module docs --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 488bf328..7be42ac3 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,27 @@ Wakeup.schedule( ) ```` +## Platform + +The Platform module allows you to determine the current platform runtime on the watch through its `Platform.version` method. This is to be used when the [Feature] module does not give enough ability to discern whether a feature exists or not. + +````js +var Platform = require('platform'); +console.log('Current platform is ' + Platform.version()); +```` + +### Platform.version() + +`Platform.version` returns the current platform version name as a lowercase string. This can be `'aplite'`, `'basalt'`, or `'chalk'`. Use the following table to determine the platform that `Platform.version` will return. + +| Watch Model | Platform | +| ---- | :----: | +| Pebble Classic | `'aplite'` | +| Pebble Steel Classic | `'aplite'` | +| Pebble Time | `'basalt'` | +| Pebble Time Steel | `'basalt'` | +| Pebble Time Round | `'chalk'` | + ## Settings The Settings module allows you to add a configurable web view to your application and share options with it. Settings also provides two data accessors `Settings.option` and `Settings.data` which are backed by localStorage. Data stored in `Settings.option` is automatically shared with the configurable web view. From b1326873dd7cad7ba5291f88e3ba5ca8569cd2a4 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 16 Apr 2016 20:57:09 -0700 Subject: [PATCH 763/791] Add Feature module docs --- README.md | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/README.md b/README.md index 7be42ac3..3042ab3d 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,116 @@ console.log('Current platform is ' + Platform.version()); | Pebble Time Steel | `'basalt'` | | Pebble Time Round | `'chalk'` | +### Feature + +The Feature module under Platform allows you to perform feature detection, adjusting aspects of your app to the capabilities of the current watch model it is current running on. This allows you to consider the functionality of your app based on the current set of available capabilities or features. The Feature module also provides information about features that exist on all watch models such as `Feature.resolution` which returns the resolution of the current watch model. + +````js +var Feature = require('platform/feature'); + +console.log('Color is ' + Feature.color('avaiable', 'not available')); +console.log('Display width is ' + Feature.resolution().x); +```` + +#### Feature.color([yes, no]) + +`Feature.color` will return the `yes` parameter if color is supported and `no` if it is not. This is the opposite of `Feature.blackAndWhite`. When given no parameters, it will return true or false respectively. + +````js +var textColor = Feature.color('oxford-blue', 'black'); + +if (Feature.color()) { + // Perform color-only operation + console.log('Color supported'); +} +```` + +#### Feature.blackAndWhite([yes, no]) + +`Feature.blackAndWhite` will return the `yes` parameter if only black and white is supported and `no` if it is not. This is the opposite of `Feature.color`. When given no parameters, it will return true or false respectively. + +````js +var backgroundColor = Feature.blackAndWhite('white', 'clear'); + +if (Feature.blackAndWhite()) { + // Perform black-and-white-only operation + console.log('Black and white only'); +} +```` + +#### Feature.rectangle([yes, no]) + +`Feature.rectangle` will return the `yes` parameter if the watch screen is rectangular and `no` if it is not. This is the opposite of `Feature.round`. When given no parameters, it will return true or false respectively. + +````js +var margin = Feature.rectangle(10, 20); + +if (Feature.rectangle()) { + // Perform rectangular display only operation + console.log('Rectangular display'); +} +```` + +#### Feature.round([yes, no]) + +`Feature.round` will return the `yes` parameter if the watch screen is round and `no` if it is not. This is the opposite of `Feature.rectangle`. When given no parameters, it will return true or false respectively. + +````js +var textAlign = Feature.round('center', 'left'); + +if (Feature.round()) { + // Perform round display only operation + console.log('Round display'); +} +```` + +#### Feature.microphone([yes, no]) + +`Feature.microphone` will return the `yes` parameter if the watch has a microphone and `no` if it does not. When given no parameters, it will return true or false respectively. Useful for determining whether the `Voice` module will allow transcription or not and changing the UI accordingly. + +````js +var text = Feature.microphone('Say your command.', + 'Select your command.'); + +if (Feature.microphone()) { + // Perform microphone only operation + console.log('Microphone available'); +} +```` + +#### Feature.resolution() + +`Feature.resolution` returns a [Vector2] containing the display width as the `x` component and the display height as the `y` component. Use the following table to determine the resolution that `Feature.resolution` will return on a given platform. + +| Platform | Width | Height | Note | +| ---- | :---: | :----: | ------ | +| aplite | 144 | 168 | | +| basalt | 144 | 168 | This is a rounded rectangle, therefore there is small set of pixels at each corner not available. | +| chalk | 180 | 180 | This is a circular display, therefore not all pixels in a 180 by 180 square are available. | + +````js +var res = Feature.resolution(); +console.log('Current display is ' + res.x + 'x' + res.y); +```` + +#### Feature.actionBarWidth() + +`Feature.actionBarWidth` returns the action bar width based on the platform. This is `30` for rectangular displays and `40` for round displays. Useful for determining the remaining screen real estate in a dynamic [Window] with an action bar visible. + +````js +var rightMargin = Feature.actionBarWidth() + 5; +var elementWidth = Feature.resolution().x - rightMargin; +```` + +#### Feature.statusBarHeight() + +`Feature.statusBarHeight` returns the status bar height. This is `16` and can change accordingly if the Pebble Firmware theme ever changes. Useful for determining the remaining screen real estate in a dynamic [Window] with a status bar visible. + +````js +var topMargin = Feature.statusBarHeight() + 5; +var elementHeight = Feature.resolution().y - topMargin; +```` + ## Settings The Settings module allows you to add a configurable web view to your application and share options with it. Settings also provides two data accessors `Settings.option` and `Settings.data` which are backed by localStorage. Data stored in `Settings.option` is automatically shared with the configurable web view. From 79a4af70c68d7c91870964f57fd4a0f48172aef9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 16 Apr 2016 21:09:04 -0700 Subject: [PATCH 764/791] Move doc anchors to their definition site and add missing anchors --- README.md | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3042ab3d..feb93e7b 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ You can do much more with Pebble.js: Keep reading for the full [API Reference]. ## Using Images +[Using Images]: #using-images You can use images in your Pebble.js application. Currently all images must be embedded in your applications. They will be resized and converted to black and white when you build your project. @@ -131,6 +132,7 @@ wind.show(); ```` ## Using Fonts +[Using Fonts]: #using-fonts You can use any of the Pebble system fonts in your Pebble.js applications. Please refer to [this Pebble Developer's blog post](https://developer.pebble.com/blog/2013/07/24/Using-Pebble-System-Fonts/) for a list of all the Pebble system fonts. When referring to a font, using lowercase with dashes is recommended. For example, `GOTHIC_18_BOLD` becomes `gothic-18-bold`. @@ -149,6 +151,7 @@ wind.show(); ```` # API Reference +[API Reference]: #api-reference ## Global namespace @@ -179,6 +182,7 @@ More specifically: If in doubt, please contact [devsupport@getpebble.com](mailto:devsupport@getpebble.com). ## Clock +[Clock]: #clock The Clock module makes working with the Wakeup module with time utility functions. @@ -219,6 +223,7 @@ Wakeup.schedule( ```` ## Platform +[Platform]: #platform The Platform module allows you to determine the current platform runtime on the watch through its `Platform.version` method. This is to be used when the [Feature] module does not give enough ability to discern whether a feature exists or not. @@ -240,6 +245,7 @@ console.log('Current platform is ' + Platform.version()); | Pebble Time Round | `'chalk'` | ### Feature +[Feature]: #feature The Feature module under Platform allows you to perform feature detection, adjusting aspects of your app to the capabilities of the current watch model it is current running on. This allows you to consider the functionality of your app based on the current set of available capabilities or features. The Feature module also provides information about features that exist on all watch models such as `Feature.resolution` which returns the resolution of the current watch model. @@ -350,6 +356,7 @@ var elementHeight = Feature.resolution().y - topMargin; ```` ## Settings +[Settings]: #settings The Settings module allows you to add a configurable web view to your application and share options with it. Settings also provides two data accessors `Settings.option` and `Settings.data` which are backed by localStorage. Data stored in `Settings.option` is automatically shared with the configurable web view. @@ -514,10 +521,12 @@ console.log(JSON.stringify(data)); ```` ## UI +[UI]: #ui The UI framework contains all the classes needed to build the user interface of your Pebble applications and interact with the user. ### Accel +[Accel]: #accel The `Accel` module allows you to get events from the accelerometer on Pebble. @@ -610,6 +619,7 @@ wind.on('accelData', function(e) { ```` ### Voice +[Voice]: #voice The `Voice` module allows you to interact with Pebble's dictation API on supported platforms (Basalt and Chalk). @@ -649,6 +659,7 @@ Voice.dictate('stop'); ``` ### Window +[Window]: #window `Window` is the basic building block in your Pebble.js application. All windows share some common properties and methods. @@ -824,6 +835,7 @@ wind.each(function(element) { ```` ### Card +[Card]: #card A Card is a type of [Window] that allows you to display a title, a subtitle, an image and a body on the screen of Pebble. @@ -859,6 +871,7 @@ The `'small'` and `'large`' styles correspond to the system notification styles. Note that all text fields will automatically span multiple lines if needed and that you can use '\n' to insert line breaks. ### Menu +[Menu]: #menu A menu is a type of [Window] that displays a standard Pebble menu on the screen of Pebble. @@ -994,6 +1007,7 @@ menu.on('select', function(e) { Similar to the select callback, except for long select presses. See [Menu.on('select', callback)]. ### Element +[Element]: #element There are four types of [Element] that can be instantiated at the moment: [Circle], [Image], [Rect] and [Text]. @@ -1105,6 +1119,7 @@ Accessor to the `borderColor` property. See [Element]. Accessor to the `backgroundColor` property. See [Element]. ### Line +[Line]: #line An [Element] that displays a line on the screen. @@ -1144,6 +1159,7 @@ Accessor to the `strokeWidth` property. See [Line]. Accessor to the `strokeColor` property. See [Line]. ### Circle +[Circle]: #circle An [Element] that displays a circle on the screen. @@ -1167,6 +1183,7 @@ wind.show(); Accessor to the `radius` property. See [Circle] ### Radial +[Radial]: #radial An [Element] that can display as an arc, ring, sector of a circle depending on its properties are set. @@ -1191,6 +1208,7 @@ Accessor to the `angle` starting angle property. See [Radial] Accessor to the `angle2` ending angle property. See [Radial] ### Rect +[Rect]: #rect An [Element] that displays a rectangle on the screen. @@ -1202,6 +1220,7 @@ The [Rect] element has the following properties. Just like any other [Element] y | `borderColor` | string | "clear" | Color of the border of this element ('clear', 'black',or 'white'). | ### Text +[Text]: #text An [Element] that displays text on the screen. @@ -1218,6 +1237,7 @@ The [Text] element has the following properties. Just like any other [Element] y | `backgroundColor` | string | 'clear' | Background color of this element ('clear', 'black' or 'white'). | ### TimeText +[TimeText]: #timetext A [Text] element that displays time formatted text on the screen. @@ -1285,6 +1305,7 @@ Sets the borderColor property. See [Text]. Sets the backgroundColor property. See [Text]. ### Image +[Image]: #image An [Element] that displays an image on the screen. @@ -1316,6 +1337,7 @@ Sets the compositing operation to be used when rendering. Specify the compositin | `"set"` | The image's black pixels are painted as white, and the rest are clear. | ### Vibe +[Vibe]: #vibe `Vibe` allows you to trigger vibration on the user wrist. @@ -1333,6 +1355,7 @@ Vibe.vibrate('long'); | `type` | string | optional | `short` | The duration of the vibration. `short`, `long` or `double`. | ### Light +[Light]: #light `Light` allows you to control the Pebble's backlight. ````js @@ -1346,12 +1369,13 @@ Light.on('long'); Turn on the light indefinitely. #### Light.auto() -Restore the normal behaviour. +Restore the normal behavior. #### Light.trigger() Trigger the backlight to turn on momentarily, just like if the user shook their wrist. ## Timeline +[Timeline]: #timeline The Timeline module allows your app to handle a launch via a timeline action. This allows you to write a custom handler to manage launch events outside of the app menu. With the Timeline module, you can preform a specific set of actions based on the action which launched the app. @@ -1390,6 +1414,7 @@ The `callback` will be called with a timeline launch event. The event has the fo Note that this means you may have to move portions of your startup logic into the `Timeline.launch` callback or a function called by the callback. This can also add a very small delay to startup behavior because the underlying implementation must query the watch for the launch information. ## Wakeup +[Wakeup]: #wakeup The Wakeup module allows you to schedule your app to wakeup at a specified time using Pebble's wakeup functionality. Whether the user is in a different watchface or app, your app will launch at the specified time. This allows you to write a custom alarm app, for example. If your app is already running, you may also subscribe to receive the wakeup event, which can be useful for more longer lived timers. With the Wakeup module, you can save data to be read on launch and configure your app to behave differently based on launch data. The Wakeup module, like the Settings module, is backed by localStorage. @@ -1581,6 +1606,7 @@ Cancels all wakeup events scheduled by your app. You can check what wakeup event Pebble.js includes several libraries to help you write applications. ### ajax +[ajax]: #ajax This module gives you a very simple and easy way to make HTTP requests. @@ -1620,6 +1646,7 @@ The `success` callback will be called if the HTTP request is successful (when th The `failure` callback is called when an error occurred. The parameters are the same as `success`. ### Vector2 +[Vector2]: #vector2 A 2 dimensional vector. The constructor takes two parameters for the x and y values. @@ -1630,22 +1657,6 @@ var vec = new Vector2(144, 168); ```` For more information, see [Vector2 in the three.js reference documentation][three.js Vector2]. - - -[API Reference]: #api-reference -[Using Images]: #using-images -[Using Fonts]: #using-fonts - -[Clock]: #clock -[Window]: #window -[Card]: #card -[Menu]: #menu -[Element]: #element -[Circle]: #circle -[Image]: #image -[Rect]: #rect -[Text]: #text -[TimeText]: #timetext [three.js Vector2]: http://threejs.org/docs/#Reference/Math/Vector2 ## Examples From a5c38178ea38ed393afafccab2e582c203d32079 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 16 Apr 2016 21:24:52 -0700 Subject: [PATCH 765/791] Add top level Platform doc --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index feb93e7b..c29cd220 100644 --- a/README.md +++ b/README.md @@ -225,14 +225,17 @@ Wakeup.schedule( ## Platform [Platform]: #platform -The Platform module allows you to determine the current platform runtime on the watch through its `Platform.version` method. This is to be used when the [Feature] module does not give enough ability to discern whether a feature exists or not. +`Platform` provides a module of the same name `Platform` and a feature detection module [Feature]. ````js var Platform = require('platform'); -console.log('Current platform is ' + Platform.version()); ```` -### Platform.version() +### Platform + +The Platform module allows you to determine the current platform runtime on the watch through its `Platform.version` method. This is to be used when the [Feature] module does not give enough ability to discern whether a feature exists or not. + +#### Platform.version() `Platform.version` returns the current platform version name as a lowercase string. This can be `'aplite'`, `'basalt'`, or `'chalk'`. Use the following table to determine the platform that `Platform.version` will return. @@ -244,6 +247,10 @@ console.log('Current platform is ' + Platform.version()); | Pebble Time Steel | `'basalt'` | | Pebble Time Round | `'chalk'` | +````js +console.log('Current platform is ' + Platform.version()); +```` + ### Feature [Feature]: #feature From b7ad5d572aafa55991636339b04affba7eaa8f2f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 17 Apr 2016 01:23:16 -0700 Subject: [PATCH 766/791] Add Window status nested accessor docs --- README.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c29cd220..8df60bbb 100644 --- a/README.md +++ b/README.md @@ -676,6 +676,8 @@ Pebble.js provides three types of Windows: * [Menu]: Displays a menu on the Pebble screen. This is similar to the standard system menu in Pebble. * [Window]: The Window by itself is the most flexible. It allows you to add different [Element]s ([Circle], [Image], [Rect], [Text], [TimeText]) and to specify a position and size for each of them. You can also animate them. +A `Window` can have the following properties: + | Name | Type | Default | Description | | ---- | :-------: | --------- | ------------- | | `clear` | boolean | | | @@ -687,26 +689,77 @@ Pebble.js provides three types of Windows: #### Window actionDef [Window actionDef]: #window-actiondef -A `Window` action bar can be displayed by setting its Window `action` property to an `actionDef`: +A `Window` action bar can be displayed by setting its Window `action` property to an `actionDef`. + +An `actionDef` has the following properties: | Name | Type | Default | Description | | ---- | :-------: | --------- | ------------- | | `up` | Image | None | An image to display in the action bar, next to the up button. | | `select` | Image | None | An image to display in the action bar, next to the select button. | | `down` | Image | None | An image to display in the action bar, next to the down button. | -| `backgroundColor` | Color | 'black' | The background color of the action bar. You can set this to 'white' for windows with black backgrounds | +| `backgroundColor` | Color | 'black' | The background color of the action bar. You can set this to 'white' for windows with black backgrounds. | ````js +// Set action properties during initialization var card = new UI.Card({ action: { up: 'images/action_icon_plus.png', down: 'images/action_icon_minus.png' } }); + +// Set action properties after initialization +card.action({ + up: 'images/action_icon_plus.png', + down: 'images/action_icon_minus.png' +}); + +// Set a single action property +card.action('select', 'images/action_icon_checkmark.png'); + +// Disable the action bar +card.action(false); ```` You will need to add images to your project according to the [Using Images] guide in order to display action bar icons. + +#### Window statusDef +[Window statusDef]: #window-statusdef + +A `Window` status bar can be displayed by setting its Window `status` property to a `statusDef`: + +A `statusDef` has the following properties: + +| Name | Type | Default | Description | +| ---- | :-------: | --------- | ------------- | +| `separator` | string | 'dotted' | The separate between the status bar and the content of the window. Can be `'dotted'` or `'none'`. | +| `color` | Color | 'black' | The foreground color of the status bar used to display the separator and time text. | +| `backgroundColor` | Color | 'white' | The background color of the status bar. You can set this to 'black' for windows with white backgrounds. | + +````js +// Set status properties during initialization +var card = new UI.Card({ + status: { + color: 'white', + backgroundColor: 'black' + } +}); + +// Set status properties after initialization +card.status({ + color: 'white', + backgroundColor: 'black' +}); + +// Set a single status property +card.status('separator', 'none'); + +// Disable the status bar +card.status(false); +```` + #### Window.show() This will push the window to the screen and display it. If user press the 'back' button, they will navigate to the previous screen. @@ -779,7 +832,7 @@ wind.on('hide', function() { #### Window.action(actionDef) -This is a special nested accessor to the `action` property which takes an `actionDef`. It can be used to set a new `actionDef`. See [Window actionDef]. +Nested accessor to the `action` property which takes an `actionDef`. Used to configure the action bar with a new `actionDef`. See [Window actionDef]. ````js card.action({ @@ -788,17 +841,39 @@ card.action({ }); ```` +To disable the action bar after enabling it, `false` can be passed in place of an `actionDef`. + +````js +// Disable the action bar +card.action(false); +```` + #### Window.action(field, value) -You may also call `Window.action` with two arguments, `field` and `value`, to set specific fields of the window's `action` propery. `field` is a string refering to the [actionDef] property to change. `value` is the new property value to set. +`Window.action` can also be called with two arguments, `field` and `value`, to set specific fields of the window's `action` property. `field` is the name of a [Window actionDef] property as a string and `value` is the new property value. ````js -card.action('up', 'images/action_icon_plus.png'); +card.action('select', 'images/action_icon_checkmark.png'); ```` -#### Window.fullscreen(fullscreen) +#### Window.status(statusDef) -Accessor to the `fullscreen` property. See [Window]. +Nested accessor to the `status` property which takes a `statusDef`. Used to configure the status bar with a new `statusDef`. See [Window statusDef]. + +````js +card.status({ + color: 'white', + backgroundColor: 'black' +}); +```` + +#### Window.status(field, value) + +`Window.status` can also be called with two arguments, `field` and `value`, to set specific fields of the window's `status` property. `field` is the name of a [Window statusDef] property as a string and `value` is the new property value. + +````js +card.status('separator', 'none'); +```` ### Window (dynamic) @@ -1151,7 +1226,7 @@ var line = new UI.Line({ wind.add(line); wind.show(); -``` +```` #### Line.position2(position) From 85d8619e6d9d336e55d5e570a074e6f605d3544b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 17 Apr 2016 01:30:33 -0700 Subject: [PATCH 767/791] Update the list of elements in the docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8df60bbb..858d6169 100644 --- a/README.md +++ b/README.md @@ -674,7 +674,7 @@ Pebble.js provides three types of Windows: * [Card]: Displays a title, a subtitle, a banner image and text on a screen. The position of the elements are fixed and cannot be changed. * [Menu]: Displays a menu on the Pebble screen. This is similar to the standard system menu in Pebble. - * [Window]: The Window by itself is the most flexible. It allows you to add different [Element]s ([Circle], [Image], [Rect], [Text], [TimeText]) and to specify a position and size for each of them. You can also animate them. + * [Window]: The `Window` by itself is the most flexible. It allows you to add different [Element]s ([Circle], [Image], [Line], [Radial], [Rect], [Text], [TimeText]) and to specify a position and size for each of them. [Element]s can also be animated. A `Window` can have the following properties: @@ -1091,7 +1091,7 @@ Similar to the select callback, except for long select presses. See [Menu.on('se ### Element [Element]: #element -There are four types of [Element] that can be instantiated at the moment: [Circle], [Image], [Rect] and [Text]. +There are seven types of [Element] that can be instantiated at the moment: [Circle], [Image], [Line], [Radial], [Rect], [Text], [TimeText]. Most elements share these common properties: From a56f1f5f0e8fe8ed3a1a2754606af885936dbe05 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 17 Apr 2016 01:51:08 -0700 Subject: [PATCH 768/791] Add Window status boolean as possible statusDef doc --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 858d6169..f140f452 100644 --- a/README.md +++ b/README.md @@ -867,6 +867,20 @@ card.status({ }); ```` +To disable the status bar after enabling it, `false` can be passed in place of `statusDef`. + +````js +// Disable the status bar +card.status(false); +```` + +Similarly, `true` can be used as a [Window statusDef] to represent a `statusDef` with all default properties. + +````js +var card = new UI.Card({ status: true }); +card.show(); +```` + #### Window.status(field, value) `Window.status` can also be called with two arguments, `field` and `value`, to set specific fields of the window's `status` property. `field` is the name of a [Window statusDef] property as a string and `value` is the new property value. From 00e2b3f9a2603da5a896534e731bb554541ffda1 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 17 Apr 2016 01:51:40 -0700 Subject: [PATCH 769/791] Add Window size method docs --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index f140f452..873eca88 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,7 @@ if (Feature.microphone()) { ```` #### Feature.resolution() +[Feature.resolution()]: #feature-resolution `Feature.resolution` returns a [Vector2] containing the display width as the `x` component and the display height as the `y` component. Use the following table to determine the resolution that `Feature.resolution` will return on a given platform. @@ -339,6 +340,8 @@ if (Feature.microphone()) { | basalt | 144 | 168 | This is a rounded rectangle, therefore there is small set of pixels at each corner not available. | | chalk | 180 | 180 | This is a circular display, therefore not all pixels in a 180 by 180 square are available. | +**NOTE:** [Window]s also have a [Window.size()] method which returns its size as a [Vector2]. Use [Window.size()] when possible. + ````js var res = Feature.resolution(); console.log('Current display is ' + res.x + 'x' + res.y); @@ -353,6 +356,8 @@ var rightMargin = Feature.actionBarWidth() + 5; var elementWidth = Feature.resolution().x - rightMargin; ```` +**NOTE:** [Window.size()] already takes the action bar into consideration, so use it instead when possible. + #### Feature.statusBarHeight() `Feature.statusBarHeight` returns the status bar height. This is `16` and can change accordingly if the Pebble Firmware theme ever changes. Useful for determining the remaining screen real estate in a dynamic [Window] with a status bar visible. @@ -362,6 +367,8 @@ var topMargin = Feature.statusBarHeight() + 5; var elementHeight = Feature.resolution().y - topMargin; ```` +**NOTE:** [Window.size()] already takes the status bar into consideration, so use it instead when possible. + ## Settings [Settings]: #settings @@ -889,6 +896,23 @@ card.show(); card.status('separator', 'none'); ```` +#### Window.size() +[Window.size()]: #window-size + +`Window.size` returns the size of the max viewable content size of the window as a [Vector2] taking into account whether there is an action bar and status bar. A [Window] will return a size that is shorter than a [Window] without for example. + +If the automatic consideration of the action bar and status bar does not satisfy your use case, you can use [Feature.resolution()] to obtain the Pebble's screen resolution as a [Vector2]. + +````js +var wind = new UI.Window({ status: true }); + +var size = wind.size(); +var rect = new UI.Rect({ size: new Vector2(size.x / 4, size.y / 4) }); +wind.add(rect); + +wind.show(); +```` + ### Window (dynamic) A [Window] instantiated directly is a dynamic window that can display a completely customizable user interface on the screen. Dynamic windows are initialized empty and will need [Element]s added to it. [Card] and [Menu] will not display elements added to them in this way. From aab0553414678ac490a0969604a3c0f706be029b Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 17 Apr 2016 02:05:58 -0700 Subject: [PATCH 770/791] Add Settings config `hash` parameter doc --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 873eca88..f845fd1d 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,7 @@ var Settings = require('settings'); | ---- | :----: | :--------: | --------- | ------------- | | `url` | string | | | The URL to the configurable. e.g. 'http://www.example.com?name=value' | | `autoSave` | boolean | (optional) | true | Whether to automatically save the web view response to options | +| `hash` | boolean | (optional) | true | Whether to automatically concatenate the URI encoded json `Settings` options to the URL as the hash component. | `open` is an optional callback used to perform any tasks before the webview is open, such as managing the options that will be passed to the web view. From c72d6bdfd2b833ec4ce5cf0ec1844706fe30bea9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 17 Apr 2016 02:06:19 -0700 Subject: [PATCH 771/791] Fixup Settings option and data docs --- README.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f845fd1d..f2f5e766 100644 --- a/README.md +++ b/README.md @@ -445,13 +445,13 @@ document.location = 'pebblejs://close#' + encodeURIComponent(JSON.stringify(opti #### Settings.option(field, value) -Saves `value` to `field`. It is recommended that `value` be either a primitive, or an object whose data is retained after going through `JSON.stringify` and `JSON.parse`. +Saves `value` to `field`. It is recommended that `value` be either a primitive or an object whose data is retained after going through `JSON.stringify` and `JSON.parse`. ````js Settings.option('color', 'red'); ```` -If value is undefined or null, the field will be deleted. +If `value` is undefined or null, the field will be deleted. ````js Settings.option('color', null); @@ -479,11 +479,16 @@ Settings.option({ #### Settings.option() -Returns all options. The options can be modified, but you must call `Settings.option` in a way that sets to save. +Returns all options. The returned options can be modified, but if you want the modifications to be saved, you must call `Settings.option` as a setter. ````js var options = Settings.option(); console.log(JSON.stringify(options)); + +options.counter = (options.counter || 0) + 1; + +// Modifications are not saved until `Settings.option` is called as a setter +Settings.option(options); ```` #### Settings.data @@ -494,13 +499,13 @@ While localStorage is still accessible, it is recommended to use `Settings.data` #### Settings.data(field, value) -Saves `value` to `field`. It is recommended that `value` be either a primitive, or an object whose data is retained after going through `JSON.stringify` and `JSON.parse`. +Saves `value` to `field`. It is recommended that `value` be either a primitive or an object whose data is retained after going through `JSON.stringify` and `JSON.parse`. ````js Settings.data('player', { id: 1, x: 10, y: 10 }); ```` -If value is undefined or null, the field will be deleted. +If `value` is undefined or null, the field will be deleted. ````js Settings.data('player', null); @@ -528,11 +533,16 @@ Settings.data({ #### Settings.data() -Returns all data. The data can be modified, but you must call `Settings.data` in a way that sets to save. +Returns all data. The returned data can be modified, but if you want the modifications to be saved, you must call `Settings.data` as a setter. ````js var data = Settings.data(); console.log(JSON.stringify(data)); + +data.counter = (data.counter || 0) + 1; + +// Modifications are not saved until `Settings.data` is called as a setter +Settings.data(data); ```` ## UI From 46ff9db7a12b8d788e99fba6a39009f8aa6befdb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sun, 17 Apr 2016 03:47:13 -0700 Subject: [PATCH 772/791] Add Using Color guide --- README.md | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/README.md b/README.md index f2f5e766..e641a6e9 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,148 @@ wind.add(textfield); wind.show(); ```` +## Using Color +[Using Color]: #using-color + +You can use Color in your Pebble.js applications by specifying them in the supported [Color Formats]. Use the [Pebble Color Picker](https://developer.pebble.com/guides/tools-and-resources/color-picker/) to find colors to use. Be sure to maintain [Readability and Contrast] when developing your application. + +### Color Formats +[Color Formats]: #color-formats + +Color can be specified in various ways in your Pebble.js application. The formats are named string, hexidecimal string, and hexidecimal number. Each format has different benefits. + +The following table includes examples of all the supported formats in Pebble.js: + +| Color Format | Examples | +| ------------ | :------: | +| Named String | `'green', 'sunset-orange'` | +| Hexidecimal String | `'#00ff00', '#ff5555'` | +| Hexidecimal String (with alpha) | `'#ff00ff00', '#ffff5555'` | +| Hexidecimal Number | `0x00ff00, 0xff5555` | +| Hexidecimal Number (with alpha) | `0xff00ff00, 0xffff5555` | + +**Named strings** are convenient to remember and read more naturally. They however cannot have the alpha channel be specified with the exception of the named string color `'clear'`. All other named colors are at max opacity. Named colors can also be specified in other casing styles, such as C constant `'SUNSET_ORANGE'`, Pascal `'SunsetOrange'`, or camel case `'sunsetOrange'`. Use the casing most convenient for you, but do so consistently across your own codebase. + +**Hexidecimal strings** can be used for specifying the exact color desired as Pebble.js will automatically round the color to the supported color of the current platform. Two hexidecimal digits are used to represent the three color channels red, green, blue in that order. + +**Hexidecimal strings (with alpha)** specified with eight digits are parsed as having an alpha channel specified in the first two digits where `00` is clear and `ff` is full opacity. + +**Hexidecimal numbers** can be manipulated directly with the arithmetic and bitwise operators. This is also the format which the configurable framework Clay uses. + +**Hexidecimal numbers (with alpha)** also have an alpha channel specified, but it is recommended to use hexidecimal strings instead for two reasons. The first reason is that `00` also represents full opacity since they are equivalent to six digit hexidecimal numbers which are implicitly at full opacity. The second is that when explicitly representing full opacity as `ff`, some integer logic can cause a signed overflow, resulting in negative color values. Intermediate alpha channels such as `55` or `aa` have no such caveats. + +Various parts of the Pebble.js API support color. Parameters of the type Color can take any of the color formats mentioned in the above table. + +````js +var UI = require('ui'); + +var card = new UI.Card({ + title: 'Using Color', + titleColor: 'sunset-orange', // Named string + subtitle: 'Color', + subtitleColor: '#00dd00', // 6-digit Hexidecimal string + body: 'Format', + bodyColor: 0x9a0036 // 6-digit Hexidecimal number +}); + +card.show(); +```` + +### Readability and Contrast +[Readability and Contrast]: #readability-and-contrast + +When using color or not, be mindful that your users may not have a Pebble supporting color or the reverse. Black and white Pebbles will display colors with medium luminance as a gray checkered pattern which makes text of any color difficult to read. In Pebble.js, you can use [Feature.color()] to use a different value depending on whether color is supported. + +````js +var UI = require('ui'); +var Feature = require('platform/feature'); + +var card = new UI.Card({ + title: 'Using Color', + titleColor: Feature.color('sunset-orange', 'black'), + subtitle: 'Readability', + subtitleColor: Feature.color('#00dd00', 'black'), + body: 'Contrast', + bodyColor: Feature.color(0x9a0036, 'black'), + backgroundColor: Feature.color('light-gray', 'white'), +}); + +card.show(); +```` + +Whether you have a color Pebble or not, you will want to test your app in all platforms. You can see how your app looks in multiple platforms with the following local SDK command or by changing the current platform in CloudPebble. + +> `pebble build && pebble install --emulator=aplite && pebble install --emulator=basalt && pebble install --emulator=chalk` + +Using too much color such as in the previous example can be overwhelming however. Just using one color that stands out in a single place can have a more defined effect and remain readable. + +````js +var card = new UI.Card({ + status: { + color: 'white', + backgroundColor: Feature.color('eletric-ultramarine', 'black'), + separator: 'none', + }, + title: 'Using Color', + subtitle: 'Readability', + body: 'Contrast', +}); +```` + +Likewise, if introducing an action bar, you can remove all color from the status bar and instead apply color to the action bar. + +````js +var card = new UI.Card({ + status: true, + action: { + backgroundColor: Feature.color('jazzberry-jam', 'black'), + }, + title: 'Dialog', + subtitle: 'Action', + body: 'Button', +}); +```` + +When changing the background color, note that the status bar also needs its background color changed too if you would like it to match. + +````js +var backgroundColor = Feature.color('light-gray', 'black'); +var card = new UI.Card({ + status: { + backgroundColor: backgroundColor, + separator: Feature.round('none', 'dotted'), + }, + action: { + backgroundColor: 'black', + }, + title: 'Music', + titleColor: Feature.color('orange', 'black'), + subtitle: 'Playing', + body: 'Current Track', + backgroundColor: backgroundColor, +}); +```` + +For a menu, following this style of coloring, you would only set the `highlightBackgroundColor`. + +````js +var menu = new UI.Menu({ + status: { + separator: Feature.round('none', 'dotted'), + }, + highlightBackgroundColor: Feature.color('vivid-violet', 'black'), + sections: [{ + items: [{ title: 'One', subtitle: 'Using Color' }, + { title: 'Color', subtitle: 'Color Formats' }, + { title: 'Hightlight', subtitle: 'Readability' }], + }], +}); + +menu.show(); +```` + +In the examples above, mostly black text on white or light gray is used which has the most contrast. Try to maintain this amount of contrast with text. Using dark gray on light gray for example can be unreadable at certain angles in the sunlight or in darkly lit areas. + # API Reference [API Reference]: #api-reference @@ -264,6 +406,7 @@ console.log('Display width is ' + Feature.resolution().x); ```` #### Feature.color([yes, no]) +[Feature.color()]: #feature-color `Feature.color` will return the `yes` parameter if color is supported and `no` if it is not. This is the opposite of `Feature.blackAndWhite`. When given no parameters, it will return true or false respectively. From a178f0557e2c555fd8bfb2acfb3cecaef2c8d09e Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 20 Apr 2016 17:49:55 -0700 Subject: [PATCH 773/791] Add Feature Detection guide --- README.md | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e641a6e9..828d4cfa 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,104 @@ menu.show(); In the examples above, mostly black text on white or light gray is used which has the most contrast. Try to maintain this amount of contrast with text. Using dark gray on light gray for example can be unreadable at certain angles in the sunlight or in darkly lit areas. +## Feature Detection +[Feature Detection]: #feature-detection + +Pebble.js provides the [Feature] module so that you may perform feature detection. This allows you change the presentation or behavior of your application based on the capabilities or characteristics of the current Pebble watch that the user is running your application with. + +### Using Feature +[Using Feature]: #feature-vs-platform + +During the development of your Pebble.js application, you will want to test your application on all platforms. You can use the following local SDK command or change the current platform in CloudPebble. + +> `pebble build && pebble install --emulator=aplite && pebble install --emulator=basalt && pebble install --emulator=chalk` + +You'll notice that there are a few differing capabilities across platforms, such as having `color` support or having a `round` screen. You can use [Feature.color()] and [Feature.round()] respectively in order to test for these capabilities. Most capability functions also have a direct opposite, such as [Feature.blackAndWhite()] and [Feature.rectangle()] respectively. + +The most common way to use [Feature] capability functions is to pass two parameters. + +````js +var UI = require('ui'); +var Feature = require('platform/feature'); + +// Use 'red' if round, otherwise use 'blue' +var color = Feature.round('red', 'blue'); + +var card = new UI.Card({ + title: 'Color', + titleColor: color, +}); + +card.show(); +```` + +You can also call the [Feature] capability functions with no arguments. In these cases, the function will return either `true` or `false` based on whether the capability exists. + +````js +if (Feature.round()) { + // Perform round-only logic + console.log('This is a round device.'); +} +```` + +Among all Pebble platforms, there are characteristics that exist on all platforms, such as the device `resolution` and the height of the status bar. [Feature] also provides methods which gives additional information about these characteristics, such as [Feature.resolution()] and [Feature.statusBarHeight()]. + +````js +var res = Feature.resolution(); +console.log('Current display height is ' + res.y); +```` + +Check out the [Feature] API Reference for all the capabilities it detects and characteristics it offers. + +### Feature vs Platform +[Feature vs Platform]: #feature-vs-platform + +When do you use [Feature] detection instead of just changing the logic based on the current [Platform]? Using feature detection allows you minimize the concerns of your logic, allowing it to be a single unit that does not rely on anything else unrelated. + +Consider the following [Platform] detection logic: + +````js +var UI = require('ui'); +var Platform = require('platform'); + +var isAplite = (Platform.version() === 'aplite'); +var isChalk = (Platform.version() === 'chalk'); +var card = new UI.Card({ + title: 'Example', + titleColor: isAplite ? 'black' : 'dark-green', + subtitle: isChalk ? 'Hello World!' : 'Hello!', + body: isAplite ? 'Press up or down' : 'Speak to me', +}); + +card.show(); +```` + +The first issue has to do with future proofing. It is checking if the current Pebble has a round screen by seeing if it is on Chalk, however there may be future platforms that have round screens. It can instead use [Feature.round()] which will update to include newer platforms as they are introduced. + +The second issue is unintentional entanglement of different concerns. `isAplite` is being used to both determine whether the Pebble is black and white and whether there is a microphone. It is harmless in this small example, but when the code grows, it could potentially change such that a function both sets up the color and interaction based on a single boolean `isAplite`. This mixes color presentation logic with interaction logic. + +Consider the same example using [Feature] detection instead: + +````js +var UI = require('ui'); +var Feature = require('platform/feature'); + +var card = new UI.Card({ + title: 'Example', + titleColor: Feature.color('dark-green', 'black'), + subtitle: Feature.round( 'Hello World!', 'Hello!'), + body: Feature.microphone('Speak to me', 'Press up or down'), +}); + +card.show(); +```` + +Now, if it is necessary to separate the different logic in setting up the card, the individual units can be implemented in separate functions without anything unintentionally mixing the logic together. [Feature] is provided as a module, so it is always available where you decide to move your logic. + +The two examples consist of logic confined into one line, but if each line was instead large blocks of logic with the `isAplite` boolean used throughout, the issue is more apparent, hence the recommendation to use [Feature] detection. Of course, for capabilities or characteristics that [Feature] is unable to allow you to discern, use [Platform]. + +**NOTE:** [Feature] underneath the hood does not actually perform feature detection. The benefit of using [Feature] however is that the detection logic is abstracted out of your application, so if any platform is changed or introduced, Pebble.js will update and your application will automatically adapt in the case that no new capabilities or characteristics are introduced. + # API Reference [API Reference]: #api-reference @@ -396,7 +494,7 @@ console.log('Current platform is ' + Platform.version()); ### Feature [Feature]: #feature -The Feature module under Platform allows you to perform feature detection, adjusting aspects of your app to the capabilities of the current watch model it is current running on. This allows you to consider the functionality of your app based on the current set of available capabilities or features. The Feature module also provides information about features that exist on all watch models such as `Feature.resolution` which returns the resolution of the current watch model. +The Feature module under Platform allows you to perform feature detection, adjusting aspects of your application to the capabilities of the current watch model it is current running on. This allows you to consider the functionality of your application based on the current set of available capabilities or features. The Feature module also provides information about features that exist on all watch models such as `Feature.resolution` which returns the resolution of the current watch model. ````js var Feature = require('platform/feature'); @@ -405,10 +503,11 @@ console.log('Color is ' + Feature.color('avaiable', 'not available')); console.log('Display width is ' + Feature.resolution().x); ```` + #### Feature.color([yes, no]) [Feature.color()]: #feature-color -`Feature.color` will return the `yes` parameter if color is supported and `no` if it is not. This is the opposite of `Feature.blackAndWhite`. When given no parameters, it will return true or false respectively. +`Feature.color` will return the `yes` parameter if color is supported and `no` if it is not. This is the opposite of [Feature.blackAndWhite()]. When given no parameters, it will return true or false respectively. ````js var textColor = Feature.color('oxford-blue', 'black'); @@ -419,9 +518,11 @@ if (Feature.color()) { } ```` + #### Feature.blackAndWhite([yes, no]) +[Feature.blackAndWhite()]: #feature-blackAndWhite -`Feature.blackAndWhite` will return the `yes` parameter if only black and white is supported and `no` if it is not. This is the opposite of `Feature.color`. When given no parameters, it will return true or false respectively. +`Feature.blackAndWhite` will return the `yes` parameter if only black and white is supported and `no` if it is not. This is the opposite of [Feature.color()]. When given no parameters, it will return true or false respectively. ````js var backgroundColor = Feature.blackAndWhite('white', 'clear'); @@ -432,9 +533,11 @@ if (Feature.blackAndWhite()) { } ```` + #### Feature.rectangle([yes, no]) +[Feature.rectangle()]: #feature-rectangle -`Feature.rectangle` will return the `yes` parameter if the watch screen is rectangular and `no` if it is not. This is the opposite of `Feature.round`. When given no parameters, it will return true or false respectively. +`Feature.rectangle` will return the `yes` parameter if the watch screen is rectangular and `no` if it is not. This is the opposite of [Feature.round()]. When given no parameters, it will return true or false respectively. ````js var margin = Feature.rectangle(10, 20); @@ -445,9 +548,11 @@ if (Feature.rectangle()) { } ```` + #### Feature.round([yes, no]) +[Feature.round()]: #feature-round -`Feature.round` will return the `yes` parameter if the watch screen is round and `no` if it is not. This is the opposite of `Feature.rectangle`. When given no parameters, it will return true or false respectively. +`Feature.round` will return the `yes` parameter if the watch screen is round and `no` if it is not. This is the opposite of [Feature.rectangle()]. When given no parameters, it will return true or false respectively. ````js var textAlign = Feature.round('center', 'left'); @@ -472,6 +577,7 @@ if (Feature.microphone()) { } ```` + #### Feature.resolution() [Feature.resolution()]: #feature-resolution @@ -501,7 +607,9 @@ var elementWidth = Feature.resolution().x - rightMargin; **NOTE:** [Window.size()] already takes the action bar into consideration, so use it instead when possible. + #### Feature.statusBarHeight() +[Feature.statusBarHeight()]: #feature-statusBarHeight `Feature.statusBarHeight` returns the status bar height. This is `16` and can change accordingly if the Pebble Firmware theme ever changes. Useful for determining the remaining screen real estate in a dynamic [Window] with a status bar visible. @@ -1050,6 +1158,7 @@ card.show(); card.status('separator', 'none'); ```` + #### Window.size() [Window.size()]: #window-size From b1fba03b2ae66d356d7b7eb9190e9b8f52996915 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 20 Apr 2016 17:52:14 -0700 Subject: [PATCH 774/791] Clarify that Pebble is usable in the absence of equivalents --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 828d4cfa..e05ce268 100644 --- a/README.md +++ b/README.md @@ -408,7 +408,7 @@ Exporting is possible by modifying or setting `module.exports` within the requir ### Pebble -The `Pebble` object from [PebbleKit JavaScript](https://developer.pebble.com/guides/pebble-apps/pebblekit-js/) is available as a global variable. Its usage is discouraged in Pebble.js, instead you should use the objects documented below who provide a cleaner object interface to the same functionalities. +The `Pebble` object from [PebbleKit JavaScript](https://developer.pebble.com/guides/pebble-apps/pebblekit-js/) is available as a global variable. Its usage is discouraged in Pebble.js where there are equivalents. You should use the objects documented below that provide a cleaner object interface to the same functionalities. Use the `Pebble` object when there is no Pebble.js alternative. ### window -- browser From d60d3a3fad64c5f7debc6194261df42831bbf0d7 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 20 Apr 2016 17:53:30 -0700 Subject: [PATCH 775/791] Fix Clock module description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e05ce268..a15c3266 100644 --- a/README.md +++ b/README.md @@ -424,7 +424,7 @@ If in doubt, please contact [devsupport@getpebble.com](mailto:devsupport@getpebb ## Clock [Clock]: #clock -The Clock module makes working with the Wakeup module with time utility functions. +The Clock module makes working with the [Wakeup] module simpler with its provided time utility functions. ### Clock From 0f1a098259f6fd4da43423c0b7d060ff6bf6f495 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 20 Apr 2016 17:55:54 -0700 Subject: [PATCH 776/791] Move Platform require example to the nested Platform --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a15c3266..0dbcb501 100644 --- a/README.md +++ b/README.md @@ -467,14 +467,15 @@ Wakeup.schedule( `Platform` provides a module of the same name `Platform` and a feature detection module [Feature]. -````js -var Platform = require('platform'); -```` ### Platform The Platform module allows you to determine the current platform runtime on the watch through its `Platform.version` method. This is to be used when the [Feature] module does not give enough ability to discern whether a feature exists or not. +````js +var Platform = require('platform'); +```` + #### Platform.version() `Platform.version` returns the current platform version name as a lowercase string. This can be `'aplite'`, `'basalt'`, or `'chalk'`. Use the following table to determine the platform that `Platform.version` will return. From eaff84ffbc8f2ef058971d71109a175e5e2fde34 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 23 Apr 2016 11:11:12 -0700 Subject: [PATCH 777/791] Fixup Using Feature markdown reference --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0dbcb501..592236ac 100644 --- a/README.md +++ b/README.md @@ -298,7 +298,7 @@ In the examples above, mostly black text on white or light gray is used which ha Pebble.js provides the [Feature] module so that you may perform feature detection. This allows you change the presentation or behavior of your application based on the capabilities or characteristics of the current Pebble watch that the user is running your application with. ### Using Feature -[Using Feature]: #feature-vs-platform +[Using Feature]: #using-feature During the development of your Pebble.js application, you will want to test your application on all platforms. You can use the following local SDK command or change the current platform in CloudPebble. From cf9221630afe5bbbaed518a3306ed5ce751e1846 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 23 Apr 2016 11:14:55 -0700 Subject: [PATCH 778/791] Remove extraneous note about Feature's implementation --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 592236ac..2a299e93 100644 --- a/README.md +++ b/README.md @@ -388,8 +388,6 @@ Now, if it is necessary to separate the different logic in setting up the card, The two examples consist of logic confined into one line, but if each line was instead large blocks of logic with the `isAplite` boolean used throughout, the issue is more apparent, hence the recommendation to use [Feature] detection. Of course, for capabilities or characteristics that [Feature] is unable to allow you to discern, use [Platform]. -**NOTE:** [Feature] underneath the hood does not actually perform feature detection. The benefit of using [Feature] however is that the detection logic is abstracted out of your application, so if any platform is changed or introduced, Pebble.js will update and your application will automatically adapt in the case that no new capabilities or characteristics are introduced. - # API Reference [API Reference]: #api-reference From e1da221f6456eec0dc5018eddc8db06666d3ce13 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 23 Apr 2016 12:38:46 -0700 Subject: [PATCH 779/791] Add Pebble.js equivalent docs --- README.md | 66 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2a299e93..0009c96f 100644 --- a/README.md +++ b/README.md @@ -295,7 +295,7 @@ In the examples above, mostly black text on white or light gray is used which ha ## Feature Detection [Feature Detection]: #feature-detection -Pebble.js provides the [Feature] module so that you may perform feature detection. This allows you change the presentation or behavior of your application based on the capabilities or characteristics of the current Pebble watch that the user is running your application with. +Pebble.js provides the [Feature] module so that you may perform feature detection. This allows you to change the presentation or behavior of your application based on the capabilities or characteristics of the current Pebble watch that the user is running your application with. ### Using Feature [Using Feature]: #using-feature @@ -406,7 +406,54 @@ Exporting is possible by modifying or setting `module.exports` within the requir ### Pebble -The `Pebble` object from [PebbleKit JavaScript](https://developer.pebble.com/guides/pebble-apps/pebblekit-js/) is available as a global variable. Its usage is discouraged in Pebble.js where there are equivalents. You should use the objects documented below that provide a cleaner object interface to the same functionalities. Use the `Pebble` object when there is no Pebble.js alternative. +The `Pebble` object from [PebbleKit JavaScript](https://developer.pebble.com/guides/pebble-apps/pebblekit-js/) is available as a global variable. Some of the methods it provides have Pebble.js equivalents. When available, it is recommended to use the Pebble.js equivalents as they have more documented features and cleaner interfaces. + +This table lists the current Pebble.js equivalents: + +| Pebble API | Pebble.js Equivalent | +| ------------ | :------: | +| `Pebble.addEventListener('ready', ...)` | Your application automatically starts after it is ready. | +| `Pebble.addEventListener('showConfiguration', ...)` | [Settings.config()] | +| `Pebble.addEventListener('webviewclosed', ...)` | [Settings.config()] with close handler. | + +Use `Pebble` when there is no Pebble.js alternative. Currently, these are the `Pebble` methods that have no direct Pebble.js alternative: + +| Pebble API without Equivalents | Note | +| ------------ | :---: | +| `Pebble.getAccountToken()` | | +| `Pebble.getActiveWatchInfo()` | Use [Platform.version()] if only querying for the platform version. | +| `Pebble.getTimelineToken()` | | +| `Pebble.getWatchToken()` | | +| `Pebble.showSimpleNotificationOnPebble()` | Consider presenting a [Card] or using Pebble Timeline instead. | +| `Pebble.timelineSubscribe()` | | +| `Pebble.timelineSubscriptions()` | | +| `Pebble.timelineUnsubscribe()` |   | + +### localStorage + +`localStorage` is [available for your use](https://developer.pebble.com/guides/communication/using-pebblekit-js/#using-localstorage), but consider using the [Settings] module instead which provides an alternative interface that can save and load JavaScript objects for you. + +````js +var Settings = require('settings'); + +Settings.data('playerInfo', { id: 1, name: 'Gordon Freeman' }); +var playerInfo = Settings.data('playerInfo'); +console.log("Player's name is " + playerInfo.name); +```` + +### XMLHttpRequest + +`XMLHttpRequest` is [available for your use](https://developer.pebble.com/guides/communication/using-pebblekit-js/#using-xmlhttprequest), but consider using the [ajax] module instead which provides a jQuery-like ajax alternative to performing asynchronous and synchronous HTTP requests, with built in support for forms and headers. + +````js +var ajax = require('ajax'); + +ajax({ url: 'http://api.theysaidso.com/qod.json', type: 'json' }, + function(data, status, req) { + console.log('Quote of the day is: ' + data.contents.quote); + } +); +```` ### window -- browser @@ -474,7 +521,9 @@ The Platform module allows you to determine the current platform runtime on the var Platform = require('platform'); ```` + #### Platform.version() +[Platform.version()]: #platform-version `Platform.version` returns the current platform version name as a lowercase string. This can be `'aplite'`, `'basalt'`, or `'chalk'`. Use the following table to determine the platform that `Platform.version` will return. @@ -632,7 +681,9 @@ The Settings module allows you to add a configurable web view to your applicatio var Settings = require('settings'); ```` + #### Settings.config(options, [open,] close) +[Settings.config()]: #settings-config `Settings.config` registers your configurable for use along with `open` and `close` handlers. @@ -1995,16 +2046,9 @@ This module gives you a very simple and easy way to make HTTP requests. ````js var ajax = require('ajax'); -ajax( - { - url: 'http://api.theysaidso.com/qod.json', - type: 'json' - }, - function(data, status, request) { +ajax({ url: 'http://api.theysaidso.com/qod.json', type: 'json' }, + function(data) { console.log('Quote of the day is: ' + data.contents.quote); - }, - function(error, status, request) { - console.log('The ajax request failed: ' + error); } ); ```` From 96614acdb0b57670eab08d2884827e6b66a5953f Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 23 Apr 2016 13:44:50 -0700 Subject: [PATCH 780/791] Fixup Using Color guide copy --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 0009c96f..227f4a26 100644 --- a/README.md +++ b/README.md @@ -153,32 +153,32 @@ wind.show(); ## Using Color [Using Color]: #using-color -You can use Color in your Pebble.js applications by specifying them in the supported [Color Formats]. Use the [Pebble Color Picker](https://developer.pebble.com/guides/tools-and-resources/color-picker/) to find colors to use. Be sure to maintain [Readability and Contrast] when developing your application. +You can use color in your Pebble.js applications by specifying them in the supported [Color Formats]. Use the [Pebble Color Picker](https://developer.pebble.com/guides/tools-and-resources/color-picker/) to find colors to use. Be sure to maintain [Readability and Contrast] when developing your application. ### Color Formats [Color Formats]: #color-formats -Color can be specified in various ways in your Pebble.js application. The formats are named string, hexidecimal string, and hexidecimal number. Each format has different benefits. +Color can be specified in various ways in your Pebble.js application. The formats are named string, hexadecimal string, and hexadecimal number. Each format has different benefits. The following table includes examples of all the supported formats in Pebble.js: | Color Format | Examples | | ------------ | :------: | | Named String | `'green', 'sunset-orange'` | -| Hexidecimal String | `'#00ff00', '#ff5555'` | -| Hexidecimal String (with alpha) | `'#ff00ff00', '#ffff5555'` | -| Hexidecimal Number | `0x00ff00, 0xff5555` | -| Hexidecimal Number (with alpha) | `0xff00ff00, 0xffff5555` | +| Hexadecimal String | `'#00ff00', '#ff5555'` | +| Hexadecimal String (with alpha) | `'#ff00ff00', '#ffff5555'` | +| Hexadecimal Number | `0x00ff00, 0xff5555` | +| Hexadecimal Number (with alpha) | `0xff00ff00, 0xffff5555` | -**Named strings** are convenient to remember and read more naturally. They however cannot have the alpha channel be specified with the exception of the named string color `'clear'`. All other named colors are at max opacity. Named colors can also be specified in other casing styles, such as C constant `'SUNSET_ORANGE'`, Pascal `'SunsetOrange'`, or camel case `'sunsetOrange'`. Use the casing most convenient for you, but do so consistently across your own codebase. +**Named strings** are convenient to remember and read more naturally. They however cannot have the alpha channel be specified with the exception of the named string color `'clear'`. All other named colors are at max opacity. Named colors can also be specified in multiple casing styles, such as hyphenated lowercase `'sunset-orange'`, C constant `'SUNSET_ORANGE'`, Pascal `'SunsetOrange'`, or camel case `'sunsetOrange'`. Use the casing most convenient for you, but do so consistently across your own codebase. -**Hexidecimal strings** can be used for specifying the exact color desired as Pebble.js will automatically round the color to the supported color of the current platform. Two hexidecimal digits are used to represent the three color channels red, green, blue in that order. +**Hexadecimal strings** can be used for specifying the exact color desired as Pebble.js will automatically round the color to the supported color of the current platform. Two hexadecimal digits are used to represent the three color channels red, green, blue in that order. -**Hexidecimal strings (with alpha)** specified with eight digits are parsed as having an alpha channel specified in the first two digits where `00` is clear and `ff` is full opacity. +**Hexadecimal strings (with alpha)** specified with eight digits are parsed as having an alpha channel specified in the first two digits where `00` is clear and `ff` is full opacity. -**Hexidecimal numbers** can be manipulated directly with the arithmetic and bitwise operators. This is also the format which the configurable framework Clay uses. +**Hexadecimal numbers** can be manipulated directly with the arithmetic and bitwise operators. This is also the format which the configurable framework Clay uses. -**Hexidecimal numbers (with alpha)** also have an alpha channel specified, but it is recommended to use hexidecimal strings instead for two reasons. The first reason is that `00` also represents full opacity since they are equivalent to six digit hexidecimal numbers which are implicitly at full opacity. The second is that when explicitly representing full opacity as `ff`, some integer logic can cause a signed overflow, resulting in negative color values. Intermediate alpha channels such as `55` or `aa` have no such caveats. +**Hexadecimal numbers (with alpha)** also have an alpha channel specified, but it is recommended to use hexadecimal strings instead for two reasons. The first reason is that `00` also represents full opacity since they are equivalent to six digit hexadecimal numbers which are implicitly at full opacity. The second is that when explicitly representing full opacity as `ff`, some integer logic can cause a signed overflow, resulting in negative color values. Intermediate alpha channels such as `55` or `aa` have no such caveats. Various parts of the Pebble.js API support color. Parameters of the type Color can take any of the color formats mentioned in the above table. @@ -189,9 +189,9 @@ var card = new UI.Card({ title: 'Using Color', titleColor: 'sunset-orange', // Named string subtitle: 'Color', - subtitleColor: '#00dd00', // 6-digit Hexidecimal string + subtitleColor: '#00dd00', // 6-digit Hexadecimal string body: 'Format', - bodyColor: 0x9a0036 // 6-digit Hexidecimal number + bodyColor: 0x9a0036 // 6-digit Hexadecimal number }); card.show(); @@ -229,7 +229,7 @@ Using too much color such as in the previous example can be overwhelming however var card = new UI.Card({ status: { color: 'white', - backgroundColor: Feature.color('eletric-ultramarine', 'black'), + backgroundColor: Feature.color('electric-ultramarine', 'black'), separator: 'none', }, title: 'Using Color', From a9fc73c224990809327248f352bea041f7f5b2bb Mon Sep 17 00:00:00 2001 From: Meiguro Date: Sat, 23 Apr 2016 13:45:34 -0700 Subject: [PATCH 781/791] Fixup Feature Detection guide copy --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 227f4a26..2ef0b054 100644 --- a/README.md +++ b/README.md @@ -304,7 +304,7 @@ During the development of your Pebble.js application, you will want to test your > `pebble build && pebble install --emulator=aplite && pebble install --emulator=basalt && pebble install --emulator=chalk` -You'll notice that there are a few differing capabilities across platforms, such as having `color` support or having a `round` screen. You can use [Feature.color()] and [Feature.round()] respectively in order to test for these capabilities. Most capability functions also have a direct opposite, such as [Feature.blackAndWhite()] and [Feature.rectangle()] respectively. +You'll notice that there are a few differing capabilities across platforms, such as having color support or having a round screen. You can use [Feature.color()] and [Feature.round()] respectively in order to test for these capabilities. Most capability functions also have a direct opposite, such as [Feature.blackAndWhite()] and [Feature.rectangle()] respectively. The most common way to use [Feature] capability functions is to pass two parameters. @@ -332,7 +332,7 @@ if (Feature.round()) { } ```` -Among all Pebble platforms, there are characteristics that exist on all platforms, such as the device `resolution` and the height of the status bar. [Feature] also provides methods which gives additional information about these characteristics, such as [Feature.resolution()] and [Feature.statusBarHeight()]. +Among all Pebble platforms, there are characteristics that exist on all platforms, such as the device resolution and the height of the status bar. [Feature] also provides methods which gives additional information about these characteristics, such as [Feature.resolution()] and [Feature.statusBarHeight()]. ````js var res = Feature.resolution(); @@ -344,7 +344,7 @@ Check out the [Feature] API Reference for all the capabilities it detects and ch ### Feature vs Platform [Feature vs Platform]: #feature-vs-platform -When do you use [Feature] detection instead of just changing the logic based on the current [Platform]? Using feature detection allows you minimize the concerns of your logic, allowing it to be a single unit that does not rely on anything else unrelated. +Pebble.js offers both [Feature] detection and [Platform] detection which are different. When do you use [Feature] detection instead of just changing the logic based on the current [Platform]? Using feature detection allows you to minimize the concerns of your logic, allowing each section of logic to be a single unit that does not rely on anything else unrelated. Consider the following [Platform] detection logic: @@ -366,7 +366,7 @@ card.show(); The first issue has to do with future proofing. It is checking if the current Pebble has a round screen by seeing if it is on Chalk, however there may be future platforms that have round screens. It can instead use [Feature.round()] which will update to include newer platforms as they are introduced. -The second issue is unintentional entanglement of different concerns. `isAplite` is being used to both determine whether the Pebble is black and white and whether there is a microphone. It is harmless in this small example, but when the code grows, it could potentially change such that a function both sets up the color and interaction based on a single boolean `isAplite`. This mixes color presentation logic with interaction logic. +The second issue is unintentional entanglement of different concerns. In the example above, `isAplite` is being used to both determine whether the Pebble is black and white and whether there is a microphone. It is harmless in this small example, but when the code grows, it could potentially change such that a function both sets up the color and interaction based on a single boolean `isAplite`. This mixes color presentation logic with interaction logic. Consider the same example using [Feature] detection instead: @@ -386,7 +386,7 @@ card.show(); Now, if it is necessary to separate the different logic in setting up the card, the individual units can be implemented in separate functions without anything unintentionally mixing the logic together. [Feature] is provided as a module, so it is always available where you decide to move your logic. -The two examples consist of logic confined into one line, but if each line was instead large blocks of logic with the `isAplite` boolean used throughout, the issue is more apparent, hence the recommendation to use [Feature] detection. Of course, for capabilities or characteristics that [Feature] is unable to allow you to discern, use [Platform]. +The two examples consist of units of logic that consist of one liners, but if each line was instead large blocks of logic with the `isAplite` boolean used throughout, the entanglement issue would be more difficult to remove from your codebase, hence the recommendation to use [Feature] detection. Of course, for capabilities or characteristics that [Feature] is unable to allow you to discern, use [Platform]. # API Reference [API Reference]: #api-reference From 1135dbc3cfc2936a7ae1383bece4da33a2e90ea6 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 27 Apr 2016 10:30:31 -0700 Subject: [PATCH 782/791] Fix structs string length members to use a StringLengthType transform (Addresses #159) structs were using EnumerableType which was overloaded to handle strings. However, when a number type is used in place of where a string is expected, StringType and EnumerableType don't cooperate. StringType will convert the number correctly, but EnumerableType will just return the number value itself. The fix is to move the string length logic out of EnumerableType into a new type StringLengthType. StringType was also modified to treat undefined as the empty string, since undefined would then show up for unspecified text fields. --- src/js/ui/simply-pebble.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index a753cbf2..956e8ea9 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -34,7 +34,7 @@ var BoolType = function(x) { }; var StringType = function(x) { - return '' + x; + return (x === undefined) ? '' : '' + x; }; var UTF8ByteLength = function(x) { @@ -42,14 +42,16 @@ var UTF8ByteLength = function(x) { }; var EnumerableType = function(x) { - if (typeof x === 'string') { - return UTF8ByteLength(x); - } else if (x && x.hasOwnProperty('length')) { + if (x && x.hasOwnProperty('length')) { return x.length; } return x ? Number(x) : 0; }; +var StringLengthType = function(x) { + return UTF8ByteLength(StringType(x)); +}; + var TimeType = function(x) { if (x instanceof Date) { x = x.getTime() / 1000; @@ -576,7 +578,7 @@ var MenuSectionPacket = new struct([ ['uint16', 'items', EnumerableType], ['uint8', 'backgroundColor', ColorType], ['uint8', 'textColor', ColorType], - ['uint16', 'titleLength', EnumerableType], + ['uint16', 'titleLength', StringLengthType], ['cstring', 'title', StringType], ]); @@ -590,8 +592,8 @@ var MenuItemPacket = new struct([ ['uint16', 'section'], ['uint16', 'item'], ['uint32', 'icon', ImageType], - ['uint16', 'titleLength', EnumerableType], - ['uint16', 'subtitleLength', EnumerableType], + ['uint16', 'titleLength', StringLengthType], + ['uint16', 'subtitleLength', StringLengthType], ['cstring', 'title', StringType], ['cstring', 'subtitle', StringType], ]); From 11d6e486a51994f3c6f04200176c51297f914abc Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 2 May 2016 15:50:14 -0700 Subject: [PATCH 783/791] Fix Card to compare against the scroll content size instead of the window frame Also change to not animate the scroll when changing the window virtually --- src/simply/simply_ui.c | 4 ++-- src/simply/simply_window.c | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/simply/simply_ui.c b/src/simply/simply_ui.c index ac12df60..abb9b6a1 100644 --- a/src/simply/simply_ui.c +++ b/src/simply/simply_ui.c @@ -298,8 +298,8 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { body_rect.size = body_size; const int new_height = cursor.y + margin_bottom; frame.size.h = MAX(window_frame.size.h, new_height); - GRect original_frame = layer_get_frame(layer); - if (!grect_equal(&frame, &original_frame)) { + const GSize content_size = scroll_layer_get_content_size(self->window.scroll_layer); + if (!gsize_equal(&frame.size, &content_size)) { layer_set_frame(layer, frame); scroll_layer_set_content_size(self->window.scroll_layer, frame.size); } diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 839491f5..546125f9 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -101,8 +101,7 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable, bool a if (!is_scrollable || reset) { GRect frame = GRectZero; prv_update_layer_placement(self, &frame); - // TODO: Remove `animated = true` once set content offset always unschedules the animation - const bool animated = true; + const bool animated = false; scroll_layer_set_content_offset(self->scroll_layer, GPointZero, animated); scroll_layer_set_content_size(self->scroll_layer, frame.size); } From 382c116308ed0ef84d042b341b06df178e98ce13 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 17 May 2016 14:41:26 -0700 Subject: [PATCH 784/791] Fix Stage to scrolling issue (Addresses #163) Stage was using the same content size resizing logic as Card which requires the same fix. Don't update the content size unless necessary since updating the content size cancels the scroll animation on certain firmware versions. --- src/simply/simply_stage.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index a5c0e98a..4d08f650 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -339,7 +339,10 @@ static void layer_update_callback(Layer *layer, GContext *ctx) { if (self->window.is_scrollable) { frame.origin = GPointZero; layer_set_frame(layer, frame); - scroll_layer_set_content_size(self->window.scroll_layer, frame.size); + const GSize content_size = scroll_layer_get_content_size(self->window.scroll_layer); + if (!gsize_equal(&frame.size, &content_size)) { + scroll_layer_set_content_size(self->window.scroll_layer, frame.size); + } } } From 06025aedca5c4ab19b481f1a6da7fc4321d43c12 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 23 May 2016 22:36:27 -0700 Subject: [PATCH 785/791] Add missing Circle radius accessor (Addresses #164) --- src/js/ui/circle.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/js/ui/circle.js b/src/js/ui/circle.js index 391a6964..00fc2307 100644 --- a/src/js/ui/circle.js +++ b/src/js/ui/circle.js @@ -1,7 +1,12 @@ var util2 = require('util2'); var myutil = require('myutil'); +var Propable = require('ui/propable'); var StageElement = require('ui/element'); +var accessorProps = [ + 'radius', +]; + var defaults = { backgroundColor: 'white', borderColor: 'clear', @@ -15,4 +20,6 @@ var Circle = function(elementDef) { util2.inherit(Circle, StageElement); +Propable.makeAccessors(accessorProps, Circle.prototype); + module.exports = Circle; From a41cc389a7912b8977021a619ea2b14144c9f1b0 Mon Sep 17 00:00:00 2001 From: Jon Barlow Date: Mon, 6 Jun 2016 23:03:13 +0100 Subject: [PATCH 786/791] API has changed --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2ef0b054..44e64fde 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,8 @@ Making HTTP connections is very easy with the included `ajax` library. var ajax = require('ajax'); ajax({ url: 'http://api.theysaidso.com/qod.json', type: 'json' }, function(data) { - card.body(data.contents.quote); - card.title(data.contents.author); + card.body(data.contents.quotes[0].quote); + card.title(data.contents.quotes[0].author); } ); ```` @@ -450,7 +450,7 @@ var ajax = require('ajax'); ajax({ url: 'http://api.theysaidso.com/qod.json', type: 'json' }, function(data, status, req) { - console.log('Quote of the day is: ' + data.contents.quote); + console.log('Quote of the day is: ' + data.contents.quotes[0].quote); } ); ```` @@ -2048,7 +2048,7 @@ var ajax = require('ajax'); ajax({ url: 'http://api.theysaidso.com/qod.json', type: 'json' }, function(data) { - console.log('Quote of the day is: ' + data.contents.quote); + console.log('Quote of the day is: ' + data.contents.quotes[0].quote); } ); ```` From 5730dea399958864e26769665962a0fcc62223a9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 7 Jun 2016 21:15:53 -0700 Subject: [PATCH 787/791] Add paging window property Stage text elements do not yet perform text flow, but paging was forced for all round windows. Add a paging property for windows to allow developers to disable paging for text that spans more than one screen. Once text flow is added to stage text elements, it will be recommended not to disable paging. Other use cases that relied on stages paging are unaffected. --- src/js/ui/simply-pebble.js | 1 + src/js/ui/window.js | 1 + src/simply/simply_stage.c | 3 +++ src/simply/simply_window.c | 16 ++++++++++------ src/simply/simply_window.h | 5 +++-- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/js/ui/simply-pebble.js b/src/js/ui/simply-pebble.js index 956e8ea9..648dc445 100644 --- a/src/js/ui/simply-pebble.js +++ b/src/js/ui/simply-pebble.js @@ -447,6 +447,7 @@ var WindowPropsPacket = new struct([ ['uint32', 'id'], ['uint8', 'backgroundColor', ColorType], ['bool', 'scrollable', BoolType], + ['bool', 'paging', BoolType], ]); var WindowButtonConfigPacket = new struct([ diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 96c43c09..4e0e741f 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -50,6 +50,7 @@ var defaults = { status: false, backgroundColor: 'black', scrollable: false, + paging: Feature.round(true, false), }; var nextId = 1; diff --git a/src/simply/simply_stage.c b/src/simply/simply_stage.c index 4d08f650..1b5106ad 100644 --- a/src/simply/simply_stage.c +++ b/src/simply/simply_stage.c @@ -509,6 +509,9 @@ static void window_load(Window *window) { simply_window_load(&self->window); + // Stage does not yet support text flow + scroll_layer_set_paging(self->window.scroll_layer, false); + Layer * const window_layer = window_get_root_layer(window); const GRect frame = { .size = layer_get_frame(window_layer).size }; diff --git a/src/simply/simply_window.c b/src/simply/simply_window.c index 546125f9..e63353e0 100644 --- a/src/simply/simply_window.c +++ b/src/simply/simply_window.c @@ -22,6 +22,7 @@ struct __attribute__((__packed__)) WindowPropsPacket { uint32_t id; GColor8 background_color; bool scrollable; + bool paging; }; typedef struct WindowButtonConfigPacket WindowButtonConfigPacket; @@ -90,11 +91,15 @@ static void prv_set_scroll_layer_click_config(SimplyWindow *self) { } } -void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable, bool animated, - bool reset) { - if (!self->use_scroll_layer || (self->is_scrollable == is_scrollable && !reset)) { return; } +void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable, bool is_paging, + bool animated, bool reset) { + const bool is_state_same = (self->is_scrollable == is_scrollable && + self->is_paging == is_paging); + if (!self->use_scroll_layer || (is_state_same && !reset)) { return; } self->is_scrollable = is_scrollable; + self->is_paging = is_paging; + scroll_layer_set_paging(self->scroll_layer, is_paging); prv_set_scroll_layer_click_config(self); @@ -106,7 +111,6 @@ void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable, bool a scroll_layer_set_content_size(self->scroll_layer, frame.size); } - if (self->layer) { layer_mark_dirty(self->layer); } @@ -313,7 +317,6 @@ void simply_window_load(SimplyWindow *self) { scroll_layer_set_context(self->scroll_layer, self); scroll_layer_set_shadow_hidden(self->scroll_layer, true); - scroll_layer_set_paging(self->scroll_layer, PBL_IF_ROUND_ELSE(true, false)); // TODO: Expose this to JS self->status_bar_layer = status_bar_layer_create(); status_bar_layer_set_separator_mode(self->status_bar_layer, StatusBarLayerSeparatorModeDotted); @@ -379,7 +382,8 @@ static void prv_handle_window_props_packet(Simply *simply, Packet *data) { WindowPropsPacket *packet = (WindowPropsPacket *)data; simply_window_set_background_color(window, packet->background_color); const bool is_same_window = (window->id == packet->id); - simply_window_set_scrollable(window, packet->scrollable, is_same_window, !is_same_window); + simply_window_set_scrollable(window, packet->scrollable, packet->paging, is_same_window, + !is_same_window); window->id = packet->id; } diff --git a/src/simply/simply_window.h b/src/simply/simply_window.h index 4f50db4f..048bb8d6 100644 --- a/src/simply/simply_window.h +++ b/src/simply/simply_window.h @@ -24,6 +24,7 @@ struct SimplyWindow { ButtonId button_mask:4; GColor8 background_color; bool is_scrollable:1; + bool is_paging:1; bool use_scroll_layer:1; bool use_status_bar:1; bool use_action_bar:1; @@ -45,8 +46,8 @@ bool simply_window_disappear(SimplyWindow *self); void simply_window_single_click_handler(ClickRecognizerRef recognizer, void *context); -void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable, bool animated, - bool reset); +void simply_window_set_scrollable(SimplyWindow *self, bool is_scrollable, bool is_paging, + bool animated, bool reset); void simply_window_set_status_bar(SimplyWindow *self, bool use_status_bar); void simply_window_set_background_color(SimplyWindow *self, GColor8 background_color); From 0a5871ca591dd6df71974b7c0a076728fca95414 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Tue, 7 Jun 2016 21:41:34 -0700 Subject: [PATCH 788/791] Add window paging accessor --- src/js/ui/window.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/ui/window.js b/src/js/ui/window.js index 4e0e741f..b26112a3 100644 --- a/src/js/ui/window.js +++ b/src/js/ui/window.js @@ -21,6 +21,7 @@ var configProps = [ 'fullscreen', 'style', 'scrollable', + 'paging', 'backgroundColor', ]; From 8769602bc86cf67122d87254d9667685326ce9e2 Mon Sep 17 00:00:00 2001 From: Matej Hrincar Date: Thu, 8 Sep 2016 17:18:53 +0200 Subject: [PATCH 789/791] Simple support for diorite platform. --- src/js/platform/feature.js | 6 ++++++ src/util/compat.h | 2 +- src/util/platform.h | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/js/platform/feature.js b/src/js/platform/feature.js index a8b98b93..661cf734 100644 --- a/src/js/platform/feature.js +++ b/src/js/platform/feature.js @@ -24,36 +24,42 @@ Feature.blackAndWhite = Feature.makePlatformTest({ aplite: true, basalt: false, chalk: false, + diorite: true, }); Feature.color = Feature.makePlatformTest({ aplite: false, basalt: true, chalk: true, + diorite: false, }); Feature.rectangle = Feature.makePlatformTest({ aplite: true, basalt: true, chalk: false, + diorite: true, }); Feature.round = Feature.makePlatformTest({ aplite: false, basalt: false, chalk: true, + diorite: false, }); Feature.microphone = Feature.makePlatformTest({ aplite: false, basalt: true, chalk: true, + diorite: true, }); Feature.resolution = Feature.makePlatformTest({ aplite: new Vector2(144, 168), basalt: new Vector2(144, 168), chalk: new Vector2(180, 180), + diorite: new Vector2(144, 168), }); Feature.actionBarWidth = function() { diff --git a/src/util/compat.h b/src/util/compat.h index cd0beeb1..706fb046 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -9,7 +9,7 @@ */ // Compatibility definitions for aplite on 2.9 -#if !defined(PBL_PLATFORM_APLITE) && !defined(PBL_PLATFORM_BASALT) && !defined(PBL_PLATFORM_CHALK) +#if !defined(PBL_PLATFORM_APLITE) && !defined(PBL_PLATFORM_BASALT) && !defined(PBL_PLATFORM_CHALK) && !defined(PBL_PLATFORM_DIORITE) #define PBL_SDK_2 diff --git a/src/util/platform.h b/src/util/platform.h index 764db647..24a2e669 100644 --- a/src/util/platform.h +++ b/src/util/platform.h @@ -25,3 +25,11 @@ #define IF_CHALK_ELSE(chalk, other) other #define CHALK_USAGE __attribute__((unused)) #endif + +#if defined(PBL_PLATFORM_DIORITE) +#define IF_DIORITE_ELSE(diorite, other) diorite +#define DIORITE_USAGE +#else +#define IF_DIORITE_ELSE(diorite, other) other +#define DIORITE_USAGE __attribute__((unused)) +#endif From aa7e1e8d9f61f0e8c5eebbae53a6516b2f9208fa Mon Sep 17 00:00:00 2001 From: Matej Hrincar Date: Thu, 6 Oct 2016 08:59:16 +0200 Subject: [PATCH 790/791] Fixing menu icon size for successful build with SDK v 4.1.4 --- resources/images/menu_icon.png | Bin 15463 -> 160 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/images/menu_icon.png b/resources/images/menu_icon.png index a9ed50333781bac396a4ac094e9bf0ad40b4978b..7e44dc19d499f4171cac3b8e51ac580e893c7ccd 100644 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~;3?%=liz@&rwg8_H*Z=?j1DTbP(1_g&i9jVH;8KCU#F1+;*{)78&qol`;+ E0L}Y0pa1{> literal 15463 zcmeI3Pl)4G9LL{+GCH#O7nEUHLl?mX)BI`DOk&&F+3Bn^iyeoVE_U|fz9z5JFl~}e zGSklBVO7>Y;9*gOMfd1U5bqwugLo9Y=ye4TiXeCp1>O3R^iSrseU9sjEWAM5{NC?< zf4{uXd+(R@khj*?o;yA}HwyqbzPeK0z`rM=&oTT3F7AD&JZrh7gps>*Opt*xWu8y{Crldu7S^^W+B7HUBaDWW-&_0 z)hg@UlItT*&8oa9$`YsPSxHrNNqUBpMJXppq9E(MBpHfkNO`V*WJW21bo zpAK8aOfw8ULl8QhPPUWFy8gBx>AEh6vLMSm?!gE9P6)fa6P!wd4C0hgVEVQf+OESz zabexv4U3sflxTSL$JO?RiJYL%4pS7m&=aJrC`=Mr=1}A9`mOlVEK@)&)J9GiV4XBk z=QZ8X4VvzhltcH_#CT3Bm0|9Q*V}GSOdW*Fdl*3<=|p8v-S?2NfdY5eH_`GQ9?Gdn zqYv%IXmSokiab-V9d+#~vdELj;-!$xPyvtCSn?4JUBBwOtx~ee)&~f=r6um^3N&pe zLVCI&ko`xKJqm@&FhnK17!_XBcsW;e+v0w;~6eZm^6~{7-hU>RsShCx28wsAXT@Xf%sh-0nWh}WZ*T*%tE%D&7VpTMJ0PU5HJ({ps1$f0ehBvjmMdXT_vgdOOk z#cjMM{&u4$ArEWF*aab~`z`xFn1ulD{ku7^8b~yAYMnP_S>Y8CLVg};d0x|XQ<5ZI zoNt(eIlhfKzy&F9sH!3AgM#!w&B18>noa0zBda6~hBauoo2Kn(wePmK&iH-gZlAVf z4<0Usg9<)gIt$UmgMrH~E&(tucTzzZYa1ajVln@KBvD98T?gyA28v64Ex?cB+e(TMJ3R3a8jLI9zg3>Tdb z;bpi8Aas-AqVplV3>N`}ZZceSK7^OyB7o3MhKtUJ@G@Kk5W2~5(fJTwhKm3~HyJKE zAHvIU5kTlB!$s#qco{AN2;F43=zIt-!$kn0n+zA758-9F2q1Km;iB^)ybKosgl;li zbUuWa;Ua+0O@@okhww671Q5E(aMAe?UWSVRLN^&MIv>Kza1lW0Cc{PNLwFf30tnq? zxafQcFT+Iup_>dBoe$w Date: Wed, 19 Oct 2016 07:00:14 +0200 Subject: [PATCH 791/791] Simple support for emery platform. --- appinfo.json | 4 +++- src/js/platform/feature.js | 6 ++++++ src/util/compat.h | 2 +- src/util/platform.h | 8 ++++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/appinfo.json b/appinfo.json index 64029118..1c2c0c8d 100644 --- a/appinfo.json +++ b/appinfo.json @@ -35,7 +35,9 @@ "targetPlatforms": [ "aplite", "basalt", - "chalk" + "chalk", + "diorite", + "emery" ], "uuid": "133215f0-cf20-4c05-997b-3c9be5a64e5b", "versionCode": 1, diff --git a/src/js/platform/feature.js b/src/js/platform/feature.js index 661cf734..da56a699 100644 --- a/src/js/platform/feature.js +++ b/src/js/platform/feature.js @@ -25,6 +25,7 @@ Feature.blackAndWhite = Feature.makePlatformTest({ basalt: false, chalk: false, diorite: true, + emery: false, }); Feature.color = Feature.makePlatformTest({ @@ -32,6 +33,7 @@ Feature.color = Feature.makePlatformTest({ basalt: true, chalk: true, diorite: false, + emery: true, }); Feature.rectangle = Feature.makePlatformTest({ @@ -39,6 +41,7 @@ Feature.rectangle = Feature.makePlatformTest({ basalt: true, chalk: false, diorite: true, + emery: true, }); Feature.round = Feature.makePlatformTest({ @@ -46,6 +49,7 @@ Feature.round = Feature.makePlatformTest({ basalt: false, chalk: true, diorite: false, + emery: false, }); Feature.microphone = Feature.makePlatformTest({ @@ -53,6 +57,7 @@ Feature.microphone = Feature.makePlatformTest({ basalt: true, chalk: true, diorite: true, + emery: true, }); Feature.resolution = Feature.makePlatformTest({ @@ -60,6 +65,7 @@ Feature.resolution = Feature.makePlatformTest({ basalt: new Vector2(144, 168), chalk: new Vector2(180, 180), diorite: new Vector2(144, 168), + emery: new Vector2(200, 228), }); Feature.actionBarWidth = function() { diff --git a/src/util/compat.h b/src/util/compat.h index 706fb046..e6c6ba21 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -9,7 +9,7 @@ */ // Compatibility definitions for aplite on 2.9 -#if !defined(PBL_PLATFORM_APLITE) && !defined(PBL_PLATFORM_BASALT) && !defined(PBL_PLATFORM_CHALK) && !defined(PBL_PLATFORM_DIORITE) +#if !defined(PBL_PLATFORM_APLITE) && !defined(PBL_PLATFORM_BASALT) && !defined(PBL_PLATFORM_CHALK) && !defined(PBL_PLATFORM_DIORITE) && !defined(PBL_PLATFORM_EMERY) #define PBL_SDK_2 diff --git a/src/util/platform.h b/src/util/platform.h index 24a2e669..b096c208 100644 --- a/src/util/platform.h +++ b/src/util/platform.h @@ -33,3 +33,11 @@ #define IF_DIORITE_ELSE(diorite, other) other #define DIORITE_USAGE __attribute__((unused)) #endif + +#if defined(PBL_PLATFORM_EMERY) +#define IF_EMERY_ELSE(emery, other) emery +#define EMERY_USAGE +#else +#define IF_EMERY_ELSE(emery, other) other +#define EMERY_USAGE __attribute__((unused)) +#endif \ No newline at end of file