diff options
| author | Raupinger <fgrauper@gmail.com> | 2021-02-23 08:01:58 (GMT) |
|---|---|---|
| committer | Michele Bini <michele.bini@gmail.com> | 2022-06-11 04:07:35 (GMT) |
| commit | 7f08e2c63608185b741fd5270a0aee4bd225f140 (patch) | |
| tree | 4609699c5608e4d8c23659c8c5dfc31097b28a74 /src/displayapp/screens/Calculator.cpp | |
| parent | 2ecb4a6fc7b862280281d9ac762953cb14b7eea6 (diff) | |
Add Calculator screen as new App
A calculator based on the Shunting-yard algorithm as described
here: https://en.wikipedia.org/wiki/Shunting-yard_algorithm
Moving the Motion App into the Settings screen to use the tile in the
ApplicationList for the Calculator.
Diffstat (limited to 'src/displayapp/screens/Calculator.cpp')
| -rw-r--r-- | src/displayapp/screens/Calculator.cpp | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/src/displayapp/screens/Calculator.cpp b/src/displayapp/screens/Calculator.cpp new file mode 100644 index 0000000..b7a3dfe --- /dev/null +++ b/src/displayapp/screens/Calculator.cpp @@ -0,0 +1,394 @@ +#include "Calculator.h" +#include <string> +#include <stack> +#include <cfloat> +#include <cmath> +#include <map> +#include <memory> + +using namespace Pinetime::Applications::Screens; + +// Anonymous Namespace for all the structs +namespace { + struct CalcTreeNode { + virtual double calculate() = 0; + }; + + struct NumNode : CalcTreeNode { + double value; + + double calculate() override { + return value; + }; + }; + + struct BinOp : CalcTreeNode { + std::shared_ptr<CalcTreeNode> left; + std::shared_ptr<CalcTreeNode> right; + + char op; + + double calculate() override { + // make sure we have actual numbers + if (!right || !left) { + errno = EINVAL; + return 0.0; + } + + double rightVal = right->calculate(); + double leftVal = left->calculate(); + switch (op) { + case '^': + // detect overflow + if (log2(leftVal) + rightVal > 31) { + errno = ERANGE; + return 0.0; + } + return pow(leftVal, rightVal); + case 'x': + // detect over/underflowflow + if ((DBL_MAX / abs(rightVal)) < abs(leftVal)) { + errno = ERANGE; + return 0.0; + } + return leftVal * rightVal; + case '/': + // detect under/overflow + if ((DBL_MAX * abs(rightVal)) < abs(leftVal)) { + errno = ERANGE; + return 0.0; + } + // detect divison by zero + if (rightVal == 0.0) { + errno = EDOM; + return 0.0; + } + return leftVal / rightVal; + case '+': + // detect overflow + if ((DBL_MAX - rightVal) < leftVal) { + errno = ERANGE; + return 0.0; + } + return leftVal + rightVal; + case '-': + // detect underflow + if ((DBL_MIN + rightVal) > leftVal) { + errno = ERANGE; + return 0.0; + } + return leftVal - rightVal; + } + errno = EINVAL; + return 0.0; + }; + }; + + uint8_t getPrecedence(char op) { + switch (op) { + case '^': + return 4; + case 'x': + case '/': + return 3; + case '+': + case '-': + return 2; + } + return 0; + } + + bool leftAssociative(char op) { + switch (op) { + case '^': + return false; + case 'x': + case '/': + case '+': + case '-': + return true; + } + return false; + } + +} + +static void eventHandler(lv_obj_t* obj, lv_event_t event) { + auto calc = static_cast<Calculator*>(obj->user_data); + calc->OnButtonEvent(obj, event); +} + +Calculator::~Calculator() { + lv_obj_clean(lv_scr_act()); +} + +static const char* buttonMap1[] = { + "7", "8", "9", "/", "\n", + "4", "5", "6", "x", "\n", + "1", "2", "3", "-", "\n", + ".", "0", "=", "+", "", +}; + +static const char* buttonMap2[] = { + "7", "8", "9", "(", "\n", + "4", "5", "6", ")", "\n", + "1", "2", "3", "^", "\n", + ".", "0", "=", "+", "", +}; + +Calculator::Calculator(DisplayApp* app, Controllers::MotorController& motorController) : Screen(app), motorController {motorController} { + result = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_long_mode(result, LV_LABEL_LONG_BREAK); + lv_label_set_text(result, "0"); + lv_obj_set_size(result, 180, 60); + lv_obj_set_pos(result, 0, 0); + + returnButton = lv_btn_create(lv_scr_act(), nullptr); + lv_obj_set_size(returnButton, 52, 52); + lv_obj_set_pos(returnButton, 186, 0); + lv_obj_t* returnLabel; + returnLabel = lv_label_create(returnButton, nullptr); + lv_label_set_text(returnLabel, "<="); + lv_obj_align(returnLabel, nullptr, LV_ALIGN_CENTER, 0, 0); + returnButton->user_data = this; + lv_obj_set_event_cb(returnButton, eventHandler); + + buttonMatrix = lv_btnmatrix_create(lv_scr_act(), nullptr); + lv_btnmatrix_set_map(buttonMatrix, buttonMap1); + lv_obj_set_size(buttonMatrix, 240, 180); + lv_obj_set_pos(buttonMatrix, 0, 60); + lv_obj_set_style_local_pad_all(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 0); + buttonMatrix->user_data = this; + lv_obj_set_event_cb(buttonMatrix, eventHandler); +} + +void Calculator::eval() { + std::stack<char> input {}; + for (int8_t i = position - 1; i >= 0; i--) { + input.push(text[i]); + } + std::stack<std::shared_ptr<CalcTreeNode>> output {}; + std::stack<char> operators {}; + bool expectingNumber = true; + int8_t sign = +1; + while (!input.empty()) { + if (input.top() == '.') { + input.push('0'); + } + if (isdigit(input.top())) { + char numberStr[31]; + uint8_t strln = 0; + uint8_t pointpos = 0; + while (!input.empty() && (isdigit(input.top()) || input.top() == '.')) { + if (input.top() == '.') { + if (pointpos != 0) { + motorController.RunForDuration(10); + return; + } + pointpos = strln; + } else { + numberStr[strln] = input.top(); + strln++; + } + input.pop(); + } + // replacement for strtod() since using that increased .txt by 76858 bzt + if (pointpos == 0) { + pointpos = strln; + } + double num = 0; + for (uint8_t i = 0; i < pointpos; i++) { + num += (numberStr[i] - '0') * pow(10, pointpos - i - 1); + } + for (uint8_t i = 0; i < strln - pointpos; i++) { + num += (numberStr[i + pointpos] - '0') / pow(10, i + 1); + } + + auto number = std::make_shared<NumNode>(); + number->value = sign * num; + output.push(number); + + sign = +1; + expectingNumber = false; + continue; + } + + if (expectingNumber && input.top() == '+') { + input.pop(); + continue; + } + if (expectingNumber && input.top() == '-') { + sign *= -1; + input.pop(); + continue; + } + + char next = input.top(); + input.pop(); + + switch (next) { + case '+': + case '-': + case '/': + case 'x': + case '^': + // while ((there is an operator at the top of the operator stack) + while (!operators.empty() + // and (the operator at the top of the operator stack is not a left parenthesis)) + && operators.top() != '(' + // and ((the operator at the top of the operator stack has greater precedence) + && (getPrecedence(operators.top()) > getPrecedence(next) + // or (the operator at the top of the operator stack has equal precedence and the token is left associative)) + || (getPrecedence(operators.top()) == getPrecedence(next) && leftAssociative(next)))) { + // need two elements on the output stack to add a binary operator + if (output.size() < 2) { + motorController.RunForDuration(10); + return; + } + auto node = std::make_shared<BinOp>(); + node->right = output.top(); + output.pop(); + node->left = output.top(); + output.pop(); + node->op = operators.top(); + operators.pop(); + output.push(node); + } + operators.push(next); + expectingNumber = true; + break; + case '(': + // we expect there to be a binary operator here but found a left parenthesis. this occurs in terms like this: a+b(c). This should be + // interpreted as a+b*(c) + if (!expectingNumber) { + operators.push('x'); + } + operators.push(next); + expectingNumber = true; + break; + case ')': + while (operators.top() != '(') { + // need two elements on the output stack to add a binary operator + if (output.size() < 2) { + motorController.RunForDuration(10); + return; + } + auto node = std::make_shared<BinOp>(); + node->right = output.top(); + output.pop(); + node->left = output.top(); + output.pop(); + node->op = operators.top(); + operators.pop(); + output.push(node); + if (operators.empty()) { + motorController.RunForDuration(10); + return; + } + } + // discard the left parentheses + operators.pop(); + } + } + while (!operators.empty()) { + char op = operators.top(); + if (op == ')' || op == '(') { + motorController.RunForDuration(10); + return; + } + // need two elements on the output stack to add a binary operator + if (output.size() < 2) { + motorController.RunForDuration(10); + return; + } + auto node = std::make_shared<BinOp>(); + node->right = output.top(); + output.pop(); + node->left = output.top(); + output.pop(); + node->op = op; + operators.pop(); + output.push(node); + } + // perform the calculation + errno = 0; + double resultFloat = output.top()->calculate(); + if (errno != 0) { + motorController.RunForDuration(10); + return; + } + // make sure the result fits in a 32 bit int + if (INT32_MAX < resultFloat || INT32_MIN > resultFloat) { + motorController.RunForDuration(10); + return; + } + // weird workaround because sprintf crashes when trying to use a float + int32_t upper = resultFloat; + int32_t lower = round(std::abs(resultFloat - upper) * 10000); + // round up to the next int value + if (lower >= 10000) { + lower = 0; + upper++; + } + // see if decimal places have to be printed + if (lower != 0) { + if (upper == 0 && resultFloat < 0) { + position = sprintf(text, "-%ld.%ld", upper, lower); + } else { + position = sprintf(text, "%ld.%ld", upper, lower); + } + // remove extra zeros + while (text[position - 1] == '0') { + position--; + } + } else { + position = sprintf(text, "%ld", upper); + } +} + +void Calculator::OnButtonEvent(lv_obj_t* obj, lv_event_t event) { + if (event == LV_EVENT_CLICKED) { + if (obj == buttonMatrix) { + const char* buttonstr = lv_btnmatrix_get_active_btn_text(obj); + if (*buttonstr == '=') { + eval(); + } else { + if (position >= 30) { + motorController.RunForDuration(10); + return; + } + text[position] = *buttonstr; + position++; + } + } else if (obj == returnButton) { + if (position > 1) { + + position--; + } else { + position = 0; + lv_label_set_text(result, "0"); + return; + } + } + + text[position] = '\0'; + lv_label_set_text(result, text); + } +} + +bool Calculator::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + if (event == Pinetime::Applications::TouchEvents::LongTap) { + position = 0; + lv_label_set_text(result, "0"); + return true; + } + if (event == Pinetime::Applications::TouchEvents::SwipeLeft) { + lv_btnmatrix_set_map(buttonMatrix, buttonMap2); + return true; + } + if (event == Pinetime::Applications::TouchEvents::SwipeRight) { + lv_btnmatrix_set_map(buttonMatrix, buttonMap1); + return true; + } + return false; +} |
