pebblisp/src/calc.c

498 lines
13 KiB
C

#include <stdio.h>
#include <limits.h>
#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 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++;
}
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]));
}
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 (a) (utl tt "\
"(cat (hrt 0 0) \":\" (mnt 0 0))" \
")))" \
"(def ww (cw 0 0))" \
"(def tt (atl ww \"OI\"))" \
"(pw ww 0)" \
"(sub time 0)"
// 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, 128, 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();
// Needs two args
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();
app_event_loop();
deinit();
}