#include #include #include "pebcom.h" #include "object.h" #include "pebbleobject.h" #include "calc.h" // Custom layers that users can build Window *s_custom_window; TextLayer *s_heading_text_layer; char header[30] = ""; // Code display Window *s_code_window; TextLayer *s_input_text_layer; TextLayer *s_result_text_layer; // Script selection Window *s_script_menu_window; MenuLayer *s_script_select_layer; int current_script_num; // PebbLisp environment static struct Environment env; // Live code text char current_code_text[SMAX_LENGTH] = ""; // The actual displayed code text char displayed_code[SMAX_LENGTH] = ""; // The result of execution #define RESULT_PREFIX "R:" char result_text[RESULT_LENGTH] = RESULT_PREFIX; const char *tokens[] = { " ", "(", ")", "+ ", "- ", "* ", "/ ", "1","2","3", "4","5","6", "7","8","9", "0", "x", "a", "b", "c", "d", "e", "= ", "< ", "> ", "\"", "s", "cat ", "map ", "fil ", "fn ", "def ", "if ", "\n", "...", "END" }; // Currently selected button, starts on '(' static int8_t selected_token = 1; const char *func_tokens[] = { "spent ", "window ", "max ", "min ", "sq ", "cube ", "exp ", "sc ", "cw ", "pw ", "rw ", "atl ", "utl ", "sec ", "mnt ", "hr ", "hrt ", "vibe ", "sub " }; bool using_func_tokens = false; // Size of messages to/from the phone app const uint32_t inbox_size = 1024; const uint32_t outbox_size = 1024; /** Text Editing **/ // Get the number of tokens in the current list static inline int8_t tokenCount() { return using_func_tokens? sizeof(func_tokens) / sizeof(func_tokens[0]) : sizeof(tokens) / sizeof(tokens[0]); } // Get current token from tokens[] or func_tokens[], as appropriate static inline const char* getToken(int8_t n) { int8_t t = n % tokenCount(); return using_func_tokens? func_tokens[t] : tokens[t]; } // Update the current code text with the contents of `current_code_text`, // and add the current selected_token to the end static void updateText() { const char *token = getToken(selected_token); strcpy(displayed_code, current_code_text); strcat(displayed_code, token[0] == ' ' ? "_": // Display space as underscore token[0] == '\n'? "\\n": // Display newline as \n token // Display others literally ); text_layer_set_text(s_input_text_layer, displayed_code); } // Cycle through the current list of tokens static void cycle_tokens(ClickRecognizerRef recognizer, void *context) { // Change current token if(click_recognizer_get_button_id(recognizer) == BUTTON_ID_DOWN) { selected_token++; } else { selected_token--; } // If selected token is outside of range, wrap around if(selected_token < 0) { selected_token = tokenCount() - 1; } else if(selected_token >= tokenCount()) { selected_token = 0; } updateText(); } static GFont tiny_font; static const char *fonts[] = { FONT_KEY_GOTHIC_28_BOLD, FONT_KEY_GOTHIC_24_BOLD, FONT_KEY_GOTHIC_18_BOLD, FONT_KEY_GOTHIC_14_BOLD }; static const int smallestFont = 3; static void adjustFont() { int i = 0; int size = 0; while(displayed_code[i++]) { if(displayed_code[i] == '\n') size += 10; size++; } // Use various system fonts for most text if(size <= 100) { int f = size < 50 ? smallestFont - 3 : size < 80 ? smallestFont - 2 : size < 100 ? smallestFont - 1 : smallestFont; text_layer_set_font(s_input_text_layer, fonts_get_system_font(fonts[f])); } else { // Use custom extra small font for lots of text text_layer_set_font(s_input_text_layer, tiny_font); } } static void custom_unload(Window *window) { text_layer_destroy(s_heading_text_layer); window_destroy(window); s_custom_window = NULL; } /** * In normal token list, backspace if possible, otherwise return to script list * In function token list, return to normal token list */ static void click_backspace(ClickRecognizerRef recognizer, void *context) { if(s_custom_window) { window_stack_remove(s_custom_window, true); return; } if(!using_func_tokens) { if(current_code_text[0] == '\0') { window_stack_remove(window_stack_get_top_window(), true); } else { current_code_text[strlen(current_code_text) - 1] = '\0'; updateText(); } } else { using_func_tokens = false; updateText(); } adjustFont(); } // Adds the current token string to the main code string static void add_token() { strcat(current_code_text, getToken(selected_token)); selected_token = 0; if(using_func_tokens) { using_func_tokens = false; } updateText(); adjustFont(); } /** Code running and saving **/ // Calculate result, display it and reset static void calculate() { Object obj = parseEval(current_code_text, &env); char temp[RESULT_LENGTH + 2] = ""; stringNObj(temp, &obj, RESULT_LENGTH); if(obj.type == TYPE_ERROR) { text_layer_set_font(s_result_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD)); } else { text_layer_set_font(s_result_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); } snprintf(result_text, RESULT_LENGTH, RESULT_PREFIX "%s", temp); text_layer_set_text(s_result_text_layer, result_text); } // Button press handler static void click_select(ClickRecognizerRef recognizer, void *context) { if(!using_func_tokens && selected_token == tokenCount() - 1) { calculate(); } else if(!using_func_tokens && selected_token == tokenCount() - 2) { using_func_tokens = true; selected_token = 0; updateText(); } else { add_token(); } } // Saves text in editor to persistent storage static void click_save(ClickRecognizerRef recognizer, void *context) { int8_t i = strlen(displayed_code); unsigned token_len = strlen(getToken(selected_token)); printf("%s", displayed_code); for(unsigned j = 0; j < token_len; j++) { displayed_code[i-(1 + j)] = '\0'; printf("%s", displayed_code); } persist_write_string(current_script_num, displayed_code); window_stack_pop(true); } // Sets the code_window click functions static void code_click_subscribe(void *context) { window_single_repeating_click_subscribe(BUTTON_ID_UP, 100, cycle_tokens); window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 100, cycle_tokens); window_single_click_subscribe(BUTTON_ID_SELECT, click_select); window_long_click_subscribe(BUTTON_ID_SELECT, 500, click_save, NULL); window_single_click_subscribe(BUTTON_ID_BACK, click_backspace); } #define FORCE_TEXT \ "(def time (fn () ((def m (mnt)) (utl tt \n"\ " (cat \"Hey, it's \"\n"\ " (hrt) \":\" (if (< m 10) \"0\" \"\") m)\n"\ "))))\n" \ "(def ww (cw))\n" \ "(def tt (atl ww \":\"))\n" \ "(pw ww)\n" \ "(sub time 2)" // Remove to re-enable #undef FORCE_TEXT static void code_window_load(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); // Register click config provider window_set_click_config_provider(s_code_window, code_click_subscribe); // Input text layer setup s_input_text_layer = text_layer_create(bounds); text_layer_set_text(s_input_text_layer, getToken(1)); text_layer_set_font(s_input_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD)); layer_add_child(window_get_root_layer(s_code_window), text_layer_get_layer(s_input_text_layer)); // Result text layer setup GRect result_bounds = GRect(6, 148, 132, 132); s_result_text_layer = text_layer_create(result_bounds); text_layer_set_text(s_result_text_layer, result_text); text_layer_set_font(s_result_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); text_layer_set_text_alignment(s_result_text_layer, GTextAlignmentRight); layer_add_child(window_get_root_layer(s_code_window), text_layer_get_layer(s_result_text_layer)); // Push the window, setting the window animation to 'true' window_stack_push(s_code_window, true); // If possible, load the previous code text #ifdef FORCE_TEXT strncpy(current_code_text, FORCE_TEXT, SMAX_LENGTH); updateText(); adjustFont(); #else if(persist_exists(current_script_num)) { persist_read_string(current_script_num, current_code_text, SMAX_LENGTH); updateText(); adjustFont(); } #endif } void debug_result(const char* d) { snprintf(result_text, RESULT_LENGTH, "%s", d); text_layer_set_text(s_result_text_layer, result_text); psleep(300); } static Object run_script(Object script_num, Object obj, struct Environment *e) { int n = script_num.number - 1; if(persist_exists(n)) { char *code = malloc(sizeof(char) * SMAX_LENGTH); persist_read_string(n, code, SMAX_LENGTH); Object o = parseEval(code, e); free(code); return o; } return errorObject(SCRIPT_NOT_FOUND); } static void code_window_unload(Window *window) { // Save the current code text persist_write_string(current_script_num, displayed_code); text_layer_destroy(s_result_text_layer); text_layer_destroy(s_input_text_layer); window_destroy(window); s_code_window = NULL; } void code_window_push() { if(!s_code_window) { s_code_window = window_create(); WindowHandlers wh = { .load = code_window_load, .unload = code_window_unload }; window_set_window_handlers(s_code_window, wh); } window_stack_push(s_code_window, true); } /** Menu Window **/ static void select_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { current_script_num = cell_index->row; code_window_push(); } static uint16_t get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *context) { return SCRIPT_COUNT; } static void draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { static char s_buff[16]; snprintf(s_buff, sizeof(s_buff), "Script %d", 1 + (int)cell_index->row); // Draw this row's index menu_cell_basic_draw(ctx, cell_layer, s_buff, NULL, NULL); } static int16_t get_cell_height_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { return CELL_HEIGHT; } static void script_menu_load(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); s_script_select_layer = menu_layer_create(bounds); menu_layer_set_click_config_onto_window(s_script_select_layer, window); #if defined(PBL_COLOR) menu_layer_set_normal_colors(s_script_select_layer, GColorBlack, GColorWhite); menu_layer_set_highlight_colors(s_script_select_layer, GColorDukeBlue, GColorWhite); #endif menu_layer_set_callbacks(s_script_select_layer, NULL, (MenuLayerCallbacks) { .get_num_rows = get_num_rows_callback, .draw_row = draw_row_callback, .get_cell_height = get_cell_height_callback, .select_click = select_callback, }); layer_add_child(window_layer, menu_layer_get_layer(s_script_select_layer)); } static void script_menu_unload(Window *window) { menu_layer_destroy(s_script_select_layer); } /** Custom Window **/ static void custom_load(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); // Register click config provider window_set_click_config_provider(s_custom_window, code_click_subscribe); // Header text layer setup s_heading_text_layer = text_layer_create(bounds); text_layer_set_text(s_heading_text_layer, header); text_layer_set_font(s_heading_text_layer, fonts_get_system_font(FONT_KEY_BITHAM_30_BLACK)); layer_add_child(window_get_root_layer(s_custom_window), text_layer_get_layer(s_heading_text_layer)); // Push the window with animations enabled window_stack_push(s_custom_window, true); } Object add_window(Object obj1, Object _, struct Environment *env) { printf("ADD_WINDOW\n"); stringObj(header, &obj1); if(!s_custom_window) { s_custom_window = window_create(); WindowHandlers wh = { .load = custom_load, .unload = custom_unload }; window_set_window_handlers(s_custom_window, wh); } window_stack_push(s_custom_window, true); return numberObject(1); } static void inbox_received_callback(DictionaryIterator *iter, void *context) { Tuple *script_tuple = dict_find(iter, MESSAGE_KEY_ScriptText); if(script_tuple) { if(!s_code_window) { current_script_num = 0; code_window_push(); } char *script_text = script_tuple->value->cstring; snprintf(displayed_code, sizeof(displayed_code), "%s", script_text); snprintf(current_code_text, sizeof(current_code_text), "%s", script_text); updateText(); } } /** General **/ static struct Environment pebbleEnv() { struct Environment e = defaultEnv(); addFunc("window", &add_window, &e); addFunc("sc", &run_script, &e); addFunc("cw", &createWindow, &e); addFunc("pw", &pushWindow, &e); addFunc("rw", &deleteWindow, &e); addFunc("atl", &addTextLayer, &e); addFunc("utl", &updateTextLayer, &e); addFunc("sec", &getSeconds, &e); addFunc("mnt", &getMinutes, &e); addFunc("hr", &getHours, &e); addFunc("hrt", &getTwelveHours, &e); addFunc("vibe", &doVibe, &e); addFunc("sub", &subscribe, &e); return e; } static void init(void) { env = pebbleEnv(); s_script_menu_window = window_create(); window_set_window_handlers(s_script_menu_window, (WindowHandlers) { .load = script_menu_load, .unload = script_menu_unload }); window_stack_push(s_script_menu_window, true); app_message_open(inbox_size, outbox_size); app_message_register_inbox_received(inbox_received_callback); tiny_font = fonts_load_custom_font( resource_get_handle(RESOURCE_ID_FONT_TINY_11)); } static void deinit(void) { deleteEnv(&env); text_layer_destroy(s_input_text_layer); window_destroy(s_script_menu_window); } int main(void) { init(); app_event_loop(); deinit(); }