#include #include #include "pebcom.h" #include "pebbleobject.h" #include "calc.h" Window *s_menu_window; Window *s_code_window; // Custom layers Window *s_custom_window; TextLayer *s_heading_text_layer; char header[30] = ""; // Code layers TextLayer *s_input_text_layer; TextLayer *s_result_text_layer; // Menu layers MenuLayer *s_menu_layer; TextLayer *s_list_message_layer; int current_code; // Currently selected button, starts on '(' static int8_t selected_token = 1; // PebbLisp environment static struct Environment env; // Live code text char mytext[SMAX_LENGTH] = ""; // The actual displayed code text char temptext[SMAX_LENGTH] = ""; // The result of execution char resulttext[RESULT_LENGTH] = ""; 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" }; const char *func_tokens[] = { "spent ", "window ", "max ", "min ", "sq ", "cube ", "exp ", "sc ", "cw ", "pw ", "rw ", "atl ", "utl ", "sec ", "mnt ", "hr ", "hrt ", "vibe ", "sub " }; const uint32_t inbox_size = 1024; const uint32_t outbox_size = 1024; bool using_func_tokens = false; /** 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 `mytext`, // and add the current selected_token to the end static void updateText() { const char *token = getToken(selected_token); strcpy(temptext, mytext); strcat(temptext, 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, temptext); } // 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(temptext[i++]) { if(temptext[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(mytext[0] == '\0') { window_stack_remove(window_stack_get_top_window(), true); } else { mytext[strlen(mytext) - 1] = '\0'; updateText(); } } else { using_func_tokens = false; updateText(); } adjustFont(); } // Adds the current string to the main string static void add_token() { strcat(mytext, 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(mytext, &env); char temp[RESULT_LENGTH-2] = ""; stringObj(temp, &obj); snprintf(resulttext, RESULT_LENGTH, "R:%s", temp); text_layer_set_text(s_result_text_layer, resulttext); } // 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(temptext); for(unsigned j = 0; j < strlen(getToken(selected_token)); j++) { temptext[i-(1 + j)] = '\0'; } persist_write_string(current_code, temptext); 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, "R: "); 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(mytext, FORCE_TEXT, SMAX_LENGTH); updateText(); adjustFont(); #else if(persist_exists(current_code)) { persist_read_string(current_code, mytext, SMAX_LENGTH); updateText(); adjustFont(); } #endif } void debug_result(const char* d) { snprintf(resulttext, RESULT_LENGTH, "%s", d); text_layer_set_text(s_result_text_layer, resulttext); 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_code, temptext); 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_code = 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 menu_load(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); s_menu_layer = menu_layer_create(bounds); menu_layer_set_click_config_onto_window(s_menu_layer, window); #if defined(PBL_COLOR) menu_layer_set_normal_colors(s_menu_layer, GColorBlack, GColorWhite); menu_layer_set_highlight_colors(s_menu_layer, GColorDukeBlue, GColorWhite); #endif menu_layer_set_callbacks(s_menu_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_menu_layer)); } static void menu_unload(Window *window) { menu_layer_destroy(s_menu_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, setting the window animation to 'true' window_stack_push(s_custom_window, true); } Object add_window(Object obj1, Object obj2, struct Environment *env) { printf("ADD_WINDOW\n"); if(obj1.type == TYPE_STRING) { strcpy(header, obj1.string); } 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_code = 0; code_window_push(); } char *script_text = script_tuple->value->cstring; snprintf(temptext, sizeof(temptext), "%s", script_text); snprintf(mytext, sizeof(mytext), "%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_menu_window = window_create(); window_set_window_handlers(s_menu_window, (WindowHandlers) { .load = menu_load, .unload = menu_unload }); window_stack_push(s_menu_window, true); app_message_open(inbox_size, outbox_size); app_message_register_inbox_received(inbox_received_callback); } static void deinit(void) { deleteEnv(&env); text_layer_destroy(s_input_text_layer); window_destroy(s_menu_window); } int main(void) { init(); tiny_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_TINY_11)); app_event_loop(); deinit(); }