#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; // The displayed/processed code text char displayed_code[SMAX_LENGTH] = ""; int excess_chars = 1; // 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[] = { "window ", "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]; } static void trim(size_t len) { size_t end = strlen(displayed_code) - 1; for (size_t i = 0; i < len; i++) { displayed_code[end - i] = '\0'; } } // Update the current code text with the contents of `current_code_text`, // and add the current selected_token to the end static void updateText() { trim(excess_chars); const char* token = getToken(selected_token); const char* add = token[0] == ' ' ? "_" : // Display space as underscore token[0] == '\n' ? "\\n" : // Display newline as \n token; // Display others literally strcat(displayed_code, add); excess_chars = strlen(add); 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) { const char* f = fonts[ 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(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 (displayed_code[0] == '\0') { window_stack_remove(window_stack_get_top_window(), true); } else { trim(excess_chars + 1); excess_chars = 0; updateText(); } } else { using_func_tokens = false; updateText(); } adjustFont(); } // Adds the current token string to the main code string static void add_token() { strcat(displayed_code, 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() { char temp[RESULT_LENGTH + 2] = ""; trim(excess_chars); excess_chars = 0; Object obj = parseEval(displayed_code, &env); updateText(); 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); } #ifdef LOW_MEM #define TIME_FUNC \ "(def time (fn ()(utl tt \n"\ " (cat \"Hey, it's \" \n"\ " (hrt) \":\" (mnt)\n" \ "))))\n" #else #define TIME_FUNC \ "(def time (fn () ((def m (mnt)) (utl tt \n"\ " (cat \"Hey, it's \"\n"\ " (hrt) \":\" (if (< m 10) \"0\" \"\") m)\n"\ "))))\n" #endif #define FORCE_TEXT TIME_FUNC \ "(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(displayed_code, FORCE_TEXT, SMAX_LENGTH); updateText(); adjustFont(); #else if (persist_exists(current_script_num)) { persist_read_string(current_script_num, displayed_code, 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(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(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); 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(); }