mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-03-18 21:30:38 +01:00
Merge changes I6f080a4c,I676e8b80,I9d2ff286
* changes: Nicer plot Prototype of the history for user inputs. First part for adding history to the user input.
This commit is contained in:
@@ -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)
|
||||
|
||||
216
app/app.cpp
216
app/app.cpp
@@ -1,97 +1,173 @@
|
||||
extern "C" {
|
||||
#include <assert.h>
|
||||
#include <kandinsky.h>
|
||||
#include <stdlib.h>
|
||||
#include <ion.h>
|
||||
}
|
||||
|
||||
#include <poincare.h>
|
||||
#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; x<width; x+=step) {
|
||||
KDDrawLine(center, KDPointMake(x,0), c);
|
||||
ion_sleep();
|
||||
}
|
||||
for (KDCoordinate y=0; y<height; y+=step) {
|
||||
KDDrawLine(center, KDPointMake(width-1,y), c);
|
||||
ion_sleep();
|
||||
}
|
||||
for (KDCoordinate x=0; x<width; x+=step) {
|
||||
KDDrawLine(center, KDPointMake(width-1-x,height-1), c);
|
||||
ion_sleep();
|
||||
}
|
||||
for (KDCoordinate y=0; y<height; y+=step) {
|
||||
KDDrawLine(center, KDPointMake(0,height-1-y), c);
|
||||
ion_sleep();
|
||||
}
|
||||
}
|
||||
typedef struct user_expression_t {
|
||||
char* text_input;
|
||||
Expression* expression;
|
||||
Expression* simplified;
|
||||
ExpressionLayout* expression_layout;
|
||||
ExpressionLayout* simplified_layout;
|
||||
} user_expression_t;
|
||||
|
||||
void plot(Expression * e, float xMin, float xMax, float yMin, float yMax) {
|
||||
Context plotContext;
|
||||
KDCoordinate screenWidth = SCREEN_WIDTH;
|
||||
KDCoordinate screenHeight = SCREEN_HEIGHT;
|
||||
KDPoint previousPoint;
|
||||
for (KDCoordinate i=0;i<screenWidth; i++) {
|
||||
float x = xMin + (xMax-xMin)/screenWidth*i;
|
||||
Float xExp = Float(x);
|
||||
plotContext.setExpressionForSymbolName(&xExp, "x");
|
||||
float y = e->approximate(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; i<kInputMemory; i++) {
|
||||
m_expressions[i] = {nullptr, nullptr, nullptr, nullptr, nullptr};
|
||||
}
|
||||
}
|
||||
previousPoint = currentPoint;
|
||||
|
||||
// Adds a user expression as the most recent one.
|
||||
void append_expression(user_expression_t user_expression) {
|
||||
if (m_numberOfExpressions < kInputMemory) {
|
||||
m_numberOfExpressions++;
|
||||
}
|
||||
m_position = (m_position + 1) % kInputMemory;
|
||||
if (m_position < 0) {
|
||||
m_numberOfExpressions += kInputMemory;
|
||||
}
|
||||
|
||||
// The circular buffer is full, now we have to erase stuff.
|
||||
if (m_numberOfExpressions == kInputMemory) {
|
||||
user_expression_t tmp = m_expressions[m_position];
|
||||
if (tmp.text_input) {
|
||||
free(tmp.text_input);
|
||||
if (tmp.expression) {
|
||||
free(tmp.expression);
|
||||
if (tmp.expression_layout) {
|
||||
free(tmp.expression_layout);
|
||||
}
|
||||
if (tmp.simplified && tmp.simplified != tmp.expression) {
|
||||
free(tmp.simplified);
|
||||
if (tmp.simplified_layout) {
|
||||
free(tmp.simplified_layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_expressions[m_position] = user_expression;
|
||||
}
|
||||
|
||||
// Gets the i th latest expression.
|
||||
user_expression_t get_expression(uint8_t index) const {
|
||||
//assert(index < m_numberOfExpressions);
|
||||
int pos = (m_position - index) % kInputMemory;
|
||||
if (pos < 0) {
|
||||
pos += kInputMemory;
|
||||
}
|
||||
return m_expressions[pos];
|
||||
}
|
||||
|
||||
uint8_t numberOfExpressions() const {
|
||||
return m_numberOfExpressions;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t m_numberOfExpressions;
|
||||
uint8_t m_position;
|
||||
user_expression_t m_expressions[kInputMemory];
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
static user_expression_t create_user_input(char* text) {
|
||||
user_expression_t user_expression = {text, nullptr, nullptr, nullptr, nullptr};
|
||||
user_expression.expression = Expression::parse(user_expression.text_input);
|
||||
if (user_expression.expression) {
|
||||
user_expression.expression_layout = user_expression.expression->createLayout();
|
||||
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; i<user_inputs.numberOfExpressions(); i++) {
|
||||
yOffset = print_user_input(user_inputs.get_expression(i), yOffset);
|
||||
if (yOffset>SCREEN_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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
73
app/plot.cpp
Normal file
73
app/plot.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
extern "C" {
|
||||
#include <assert.h>
|
||||
#include <kandinsky.h>
|
||||
#include <stdlib.h>
|
||||
#include <ion.h>
|
||||
}
|
||||
|
||||
#include <poincare.h>
|
||||
|
||||
#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.
|
||||
}
|
||||
7
app/plot.h
Normal file
7
app/plot.h
Normal file
@@ -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
|
||||
@@ -1,10 +1,12 @@
|
||||
extern "C" {
|
||||
#include <assert.h>
|
||||
#include <kandinsky.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ion.h>
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
18
app/utils.h
18
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 <ion.h>
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ typedef enum {
|
||||
DOWN_ARROW,
|
||||
TRIG_MENU,
|
||||
DELETE,
|
||||
PLOT,
|
||||
ERROR = 0xffffffff,
|
||||
} ion_event_t;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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", "+", "-",
|
||||
|
||||
Reference in New Issue
Block a user