diff --git a/app/Makefile b/app/Makefile index d662c1a4d..af3cc5a0c 100644 --- a/app/Makefile +++ b/app/Makefile @@ -1,6 +1,7 @@ app_objs += $(addprefix app/,\ - app.o\ - utils.o) + app.o\ + plot.o\ + utils.o) products += $(app_objs) app.elf app.hex app.bin app.elf: $(app_objs) diff --git a/app/app.cpp b/app/app.cpp index 2651db497..0af824ecb 100644 --- a/app/app.cpp +++ b/app/app.cpp @@ -1,97 +1,173 @@ extern "C" { +#include #include #include #include } #include +#include "../poincare/src/layout/string_layout.h" +#include "plot.h" #include "utils.h" -void draw_lines_from_center() { - KDCoordinate width = SCREEN_WIDTH; - KDCoordinate height = SCREEN_HEIGHT; +static const char* kParsingErrorMessage = "PARSING ERROR"; - KDColor c = 0xFF; +////////////////////////////////////////////////////// - KDPoint center = KDPointMake(width/2, height/2); - int step = 2; +static constexpr uint8_t kInputMemory = 15; - for (KDCoordinate x=0; xapproximate(plotContext); - KDCoordinate j = ((y-yMin)/(yMax-yMin)*screenHeight); - KDPoint currentPoint = KDPointMake(i,screenHeight-j); - if (i>0) { - KDDrawLine(previousPoint, currentPoint, 0xFF); +class UserExpressions { + public: + UserExpressions() { + m_numberOfExpressions = 0; + m_position = kInputMemory; + for (int i=0; icreateLayout(); + user_expression.simplified = user_expression.expression->simplify(); + user_expression.simplified_layout = user_expression.simplified->createLayout(); + } else { + user_expression.expression_layout = + new StringLayout(kParsingErrorMessage, strlen(kParsingErrorMessage)); } + return user_expression; } -void funnyPlot() { - Expression * e = Expression::parse((char *)"1/x"); - plot(e, 1.0f, 4.0f, 0.0f, 1.0f); - delete e; +static int16_t print_user_input(user_expression_t user_expression, int16_t yOffset) { + if (user_expression.expression_layout) { + int16_t height = user_expression.expression_layout->size().height; + if (yOffset + height < SCREEN_HEIGHT) { + user_expression.expression_layout->draw(KDPointMake(0, yOffset)); + } + yOffset += height; + } + if (user_expression.simplified_layout) { + int16_t height = user_expression.simplified_layout->size().height; + if (yOffset + height < SCREEN_HEIGHT) { + int16_t xOffset = SCREEN_WIDTH - user_expression.simplified_layout->size().width; + user_expression.simplified_layout->draw(KDPointMake(xOffset, yOffset)); + } + yOffset += height; + } + return yOffset; +} + +static void print_user_inputs(const UserExpressions& user_inputs, uint8_t startingAt) { + int16_t yOffset = 0; + for (uint8_t i=startingAt; iSCREEN_HEIGHT) { + break; + } + } } static void interactive_expression_parsing() { + UserExpressions user_inputs = UserExpressions(); + int index = 0; while (1) { - char * text_input = get_text(); - clear_screen(); - Expression * e = Expression::parse(text_input); - if (e) { - ExpressionLayout * l = e->createLayout(); - int16_t yOffset = 10; - if (l) { - l->draw(KDPointMake(0, yOffset)); - yOffset += l->size().height; - delete l; - } - Expression * simplified = e->simplify(); - // Print the simplification. - if (simplified) { - ExpressionLayout * simplified_l = simplified->createLayout(); - if (simplified_l) { - int16_t xOffset = SCREEN_WIDTH - simplified_l->size().width; - simplified_l->draw(KDPointMake(xOffset, yOffset)); - delete simplified_l; - } - if (simplified != e) { - delete simplified; - } - } - delete e; + text_event_t text_event; + if (index == 0) { + text_event = get_text(nullptr); } else { - KDDrawString("PARSING ERROR", KDPointMake(10,10), 0); + text_event = get_text(user_inputs.get_expression(index - 1).text_input); } - // We dealocate the memory allocated by get_text; - free(text_input); + if (text_event.event == EQUAL) { + user_inputs.append_expression(create_user_input(text_event.text)); + } else if (text_event.event == UP_ARROW) { + index--; + if (index < 0) { + index = 0; + } + } else if (text_event.event == DOWN_ARROW) { + if (user_inputs.numberOfExpressions() != 0) { + index++; + if (index >= user_inputs.numberOfExpressions()) { + index = user_inputs.numberOfExpressions() - 1; + } + } + } else if (text_event.event == PLOT) { + user_inputs.append_expression(create_user_input(text_event.text)); + // We check that the expression is correct. + if (user_inputs.get_expression(0).expression) { + clear_screen(); + plot(user_inputs.get_expression(0).expression, -3, 3, -2, 10); + } + } else { + assert(false); // unreachable. + } + clear_screen(); + print_user_inputs(user_inputs, index ? index - 1 : 0); } } diff --git a/app/plot.cpp b/app/plot.cpp new file mode 100644 index 000000000..a0ef6add4 --- /dev/null +++ b/app/plot.cpp @@ -0,0 +1,73 @@ +extern "C" { +#include +#include +#include +#include +} + +#include + +#include "utils.h" +#include "plot.h" + +constexpr KDCoordinate kScreenWidth = SCREEN_WIDTH; +const KDCoordinate kScreenHeight = SCREEN_HEIGHT; + +// For now we only plot the axes at the origin. +// TODO: print axes not at the origin with some values too. +// TODO: label the axes. +// TODO: put the values on the axes. +static void plot_axes(float xMin, float xMax, float yMin, float yMax) { + if (xMin < 0 && xMax > 0) { + float total = xMax - xMin; + float ratio = xMax / total; + KDCoordinate width = ratio * kScreenWidth; + KDDrawLine(KDPointMake(width, 0), KDPointMake(width, kScreenHeight-1), 0xff); + } + if (yMin < 0 && yMax > 0) { + float total = yMax - yMin; + float ratio = yMax / total; + KDCoordinate height = ratio * kScreenHeight; + KDDrawLine(KDPointMake(0, height), KDPointMake(kScreenWidth-1, height), 0xff); + } +} + +// TODO: Add a cursor. +// TODO: print the expression. +void plot(Expression * e, float xMin, float xMax, float yMin, float yMax) { + assert(e); + + Context plotContext; + + // Plot the original axes. + plot_axes(xMin, xMax, yMin, yMax); + + // We need to initialize the first point. + Float xExp = Float(xMin); + plotContext.setExpressionForSymbolName(&xExp, "x"); + float y = e->approximate(plotContext); + KDCoordinate j = ((y-yMin) / (yMax-yMin) * kScreenHeight); + if (j < 0) { + j = 0; + } else if (j >= kScreenHeight) { + j = kScreenHeight - 1; + } + KDPoint previousPoint = KDPointMake(0, kScreenHeight-j); + + for (KDCoordinate i = 1; i < kScreenWidth; i++) { + float x = xMin + (xMax-xMin)/kScreenWidth*i; + Float xExp = Float(x); + plotContext.setExpressionForSymbolName(&xExp, "x"); + float y = e->approximate(plotContext); + KDCoordinate j = ((y-yMin) / (yMax-yMin) * kScreenHeight); + KDPoint currentPoint = KDPointMake(i, kScreenHeight-j); + if (currentPoint.y < 0) { + currentPoint.y = 0; + } else if (currentPoint.y >= kScreenHeight) { + currentPoint.y = kScreenHeight - 1; + } + KDDrawLine(previousPoint, currentPoint, 0xFF); + previousPoint = currentPoint; + } + ion_get_event(); // We wait for a text input. +} diff --git a/app/plot.h b/app/plot.h new file mode 100644 index 000000000..f7a4570bb --- /dev/null +++ b/app/plot.h @@ -0,0 +1,7 @@ +#ifndef APP_PLOT_H +#define APP_PLOT_H + +// Note that currently the only variable is 'x' +void plot(Expression * e, float xMin, float xMax, float yMin, float yMax); + +#endif diff --git a/app/utils.cpp b/app/utils.cpp index 01efea1d3..d3b7be9bc 100644 --- a/app/utils.cpp +++ b/app/utils.cpp @@ -1,10 +1,12 @@ extern "C" { +#include #include #include #include -#include } +#include "utils.h" + #define PROMPT_HEIGHT 30 void clear_screen() { @@ -105,10 +107,18 @@ static int get_trig_input(char* input) { } } -char* get_text() { +text_event_t get_text(char* txt) { char input[255] = {0}; int index = 0; int max = 0; + text_event_t text_event = {nullptr, ERROR}; + + if (txt != nullptr) { + index = strlen(txt); + max = index; + memcpy(input, txt, (size_t) index); + } + input[max] = ' '; input[max+1] = '\0'; @@ -117,6 +127,10 @@ char* get_text() { print_prompt(input, index); ion_event_t event = ion_get_event(); if (event == EQUAL) { + input[max] = '\0'; + text_event.event = EQUAL; + text_event.text = (char*) malloc(sizeof(char) * (index + 1)); + memcpy(text_event.text, input, (size_t) (index + 1)); break; } else if (event == LEFT_ARROW) { index--; @@ -154,12 +168,23 @@ char* get_text() { input[i] = input[i+1]; } max--; + } else if (event == UP_ARROW) { + text_event.event = UP_ARROW; + break; + } else if (event == DOWN_ARROW) { + text_event.event = DOWN_ARROW; + break; + } else if (event == PLOT) { + text_event.event = PLOT; + input[max] = '\0'; + text_event.text = (char*) malloc(sizeof(char) * (index + 1)); + memcpy(text_event.text, input, (size_t) (index + 1)); + break; + } else { + assert(false); // unreachable. } } - + clear_prompt(); - input[max] = '\0'; - char* output = (char*) malloc(sizeof(char) * (index + 1)); - memcpy(output, input, (size_t) (index + 1)); - return output; + return text_event; } diff --git a/app/utils.h b/app/utils.h index ca78fcae1..bc48f0b8d 100644 --- a/app/utils.h +++ b/app/utils.h @@ -1,9 +1,21 @@ #ifndef APP_UTILS_H #define APP_UTILS_H -/* Returns a pointer to an input text, allocated by the functions (it is thus - * the caller's role to free it). */ -char* get_text(); +extern "C" { +#include +} + +typedef struct { + char* text; + ion_event_t event; +} text_event_t; + +/* Returns a pointer to an input text allocated by the functions. + * Also returns an event, which is its return reason. + * + * This function can get a text to work on instead of starting from an empty + * string. */ +text_event_t get_text(char* txt); void clear_screen(); diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index babf18ad8..18ac3c759 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -79,6 +79,7 @@ typedef enum { DOWN_ARROW, TRIG_MENU, DELETE, + PLOT, ERROR = 0xffffffff, } ion_event_t; diff --git a/ion/src/shared/events.c b/ion/src/shared/events.c index e49b0a031..d57e3a457 100644 --- a/ion/src/shared/events.c +++ b/ion/src/shared/events.c @@ -50,7 +50,7 @@ static ion_key_event_t ion_get_key_event() { static const ion_event_t kEventForKeyDown[ION_NUMBER_OF_KEYS] = { UPPER_CASE_A, UPPER_CASE_B, UPPER_CASE_C, UPPER_CASE_D, UPPER_CASE_E, UPPER_CASE_F, UPPER_CASE_G, DELETE, UP_ARROW, DOWN_ARROW, - UPPER_CASE_K, UPPER_CASE_L, TRIG_MENU, LEFT_ARROW, RIGHT_ARROW, + UPPER_CASE_K, PLOT, TRIG_MENU, LEFT_ARROW, RIGHT_ARROW, SEVEN, EIGHT, NINE, LEFT_PARENTHESIS, RIGHT_PARENTHESIS, FOUR, FIVE, SIX, PRODUCT, DIVISION, ONE, TWO, THREE, PLUS, MINUS, diff --git a/ion/src/simulator/keyboard/fltkkbd.cpp b/ion/src/simulator/keyboard/fltkkbd.cpp index 0df48c990..e08b341ea 100644 --- a/ion/src/simulator/keyboard/fltkkbd.cpp +++ b/ion/src/simulator/keyboard/fltkkbd.cpp @@ -7,7 +7,7 @@ static const char* kCharForKey[KEYBOARD_ROWS * KEYBOARD_COLUMNS] = { "A", "B", "C", "D", "E", "F", "G", "DEL", "UP", "DOWN", - "K", "L", "TRIG", "LEFT", "RIGHT", + "K", "PLOT", "TRIG", "LEFT", "RIGHT", "7", "8", "9", "(", ")", "4", "5", "6", "*", "/", "1", "2", "3", "+", "-",