pebblisp/src/calc.c

516 lines
14 KiB
C
Raw Normal View History

2016-04-18 04:25:36 -04:00
#include <stdio.h>
#include <limits.h>
#include "pebcom.h"
#include "object.h"
#include "pebbleobject.h"
2016-04-18 04:25:36 -04:00
#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
2021-07-15 20:33:17 -04:00
#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]);
2016-04-18 04:25:36 -04:00
}
// 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];
2020-05-04 18:14:41 -04:00
}
// Update the current code text with the contents of `current_code_text`,
2020-05-04 18:14:41 -04:00
// and add the current selected_token to the end
2016-04-18 04:25:36 -04:00
static void updateText()
{
const char *token = getToken(selected_token);
strcpy(displayed_code, current_code_text);
2020-05-03 17:00:45 -04:00
strcat(displayed_code,
token[0] == ' ' ? "_": // Display space as underscore
token[0] == '\n'? "\\n": // Display newline as \n
token // Display others literally
);
2016-04-18 04:25:36 -04:00
text_layer_set_text(s_input_text_layer, displayed_code);
2016-04-18 04:25:36 -04:00
}
// Cycle through the current list of tokens
static void cycle_tokens(ClickRecognizerRef recognizer, void *context)
{
2020-05-04 18:14:41 -04:00
// Change current token
if(click_recognizer_get_button_id(recognizer) == BUTTON_ID_DOWN) {
2020-05-04 18:14:41 -04:00
selected_token++;
} else {
2020-05-04 18:14:41 -04:00
selected_token--;
}
2016-04-18 04:25:36 -04:00
2020-05-04 18:14:41 -04:00
// If selected token is outside of range, wrap around
if(selected_token < 0) {
2020-05-04 18:14:41 -04:00
selected_token = tokenCount() - 1;
} else if(selected_token >= tokenCount()) {
2020-05-04 18:14:41 -04:00
selected_token = 0;
}
2020-05-04 18:14:41 -04:00
updateText();
2016-04-18 04:25:36 -04:00
}
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();
}
2020-05-03 21:24:36 -04:00
} else {
using_func_tokens = false;
2020-05-03 21:24:36 -04:00
updateText();
}
adjustFont();
2020-05-03 21:24:36 -04:00
}
// Adds the current token string to the main code string
static void add_token()
{
strcat(current_code_text, getToken(selected_token));
2016-04-18 04:25:36 -04:00
selected_token = 0;
if(using_func_tokens) {
using_func_tokens = false;
}
2016-04-18 04:25:36 -04:00
updateText();
adjustFont();
2016-04-18 04:25:36 -04:00
}
/** Code running and saving **/
2020-05-04 18:14:41 -04:00
// Calculate result, display it and reset
static void calculate()
{
Object obj = parseEval(current_code_text, &env);
char temp[RESULT_LENGTH + 2] = "";
2020-05-04 18:14:41 -04:00
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));
}
2021-07-15 20:33:17 -04:00
snprintf(result_text, RESULT_LENGTH, RESULT_PREFIX "%s", temp);
text_layer_set_text(s_result_text_layer, result_text);
2016-04-18 04:25:36 -04:00
}
// Button press handler
static void click_select(ClickRecognizerRef recognizer, void *context)
{
if(!using_func_tokens && selected_token == tokenCount() - 1) {
2016-04-18 04:25:36 -04:00
calculate();
} else if(!using_func_tokens && selected_token == tokenCount() - 2) {
using_func_tokens = true;
selected_token = 0;
updateText();
} else {
add_token();
}
2016-04-18 04:25:36 -04:00
}
// 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);
2020-05-05 19:21:54 -04:00
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);
2016-04-18 04:25:36 -04:00
}
#define FORCE_TEXT \
"(def time (fn () (utl tt \n"\
" (cat \"Hey, it's \" \n"\
" (hrt) \":\" (mnt))\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);
2020-05-05 19:21:54 -04:00
// Register click config provider
window_set_click_config_provider(s_code_window, code_click_subscribe);
2020-05-05 19:21:54 -04:00
// 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);
2021-07-15 20:33:17 -04:00
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);
2020-05-05 19:21:54 -04:00
}
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;
2020-05-05 19:21:54 -04:00
}
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);
2020-05-05 19:21:54 -04:00
}
/** Menu Window **/
2020-05-05 19:21:54 -04:00
static void select_callback(struct MenuLayer *menu_layer,
MenuIndex *cell_index, void *context)
{
current_script_num = cell_index->row;
2020-05-05 19:21:54 -04:00
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)
2020-05-05 19:21:54 -04:00
{
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
2020-05-05 19:21:54 -04:00
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));
2020-05-05 19:21:54 -04:00
}
static void script_menu_unload(Window *window)
2020-05-05 19:21:54 -04:00
{
menu_layer_destroy(s_script_select_layer);
2020-05-05 19:21:54 -04:00
}
/** Custom Window **/
2020-05-05 19:21:54 -04:00
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);
}
2021-07-15 20:33:17 -04:00
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
2020-05-05 19:21:54 -04:00
});
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));
2020-05-05 19:21:54 -04:00
}
static void deinit(void)
{
2020-05-04 18:14:41 -04:00
deleteEnv(&env);
text_layer_destroy(s_input_text_layer);
window_destroy(s_script_menu_window);
2016-04-18 04:25:36 -04:00
}
int main(void)
{
2016-04-18 04:25:36 -04:00
init();
app_event_loop();
deinit();
}