summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/Calculator.md9
-rw-r--r--doc/ui/calc2.jpgbin0 -> 985632 bytes
-rw-r--r--doc/ui/calc3.jpgbin0 -> 141874 bytes
-rw-r--r--doc/ui/calc4.jpgbin0 -> 131313 bytes
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/displayapp/Apps.h1
-rw-r--r--src/displayapp/DisplayApp.cpp4
-rw-r--r--src/displayapp/screens/ApplicationList.h1
-rw-r--r--src/displayapp/screens/Calculator.cpp394
-rw-r--r--src/displayapp/screens/Calculator.h37
-rw-r--r--src/displayapp/screens/Symbols.h1
11 files changed, 449 insertions, 0 deletions
diff --git a/doc/Calculator.md b/doc/Calculator.md
new file mode 100644
index 0000000..2184e06
--- /dev/null
+++ b/doc/Calculator.md
@@ -0,0 +1,9 @@
+# Calculator Manual
+This is a simple Calculator with support for the four basic arithmetic operations, parenthesis and exponents.
+Here is what you need to know to make full use of it:
+- Swipe left to access parenthesis and exponents:
+ ![](./ui/calc2.jpg)
+- A long tap on the screen will reset the text field to `0`.
+- If the entered term is invalid, the watch will vibrate.
+- results are rounded to 4 digits after the decimal point
+- **TIP:** you can use `^(1/2)` to calculate square roots
diff --git a/doc/ui/calc2.jpg b/doc/ui/calc2.jpg
new file mode 100644
index 0000000..7f04a80
--- /dev/null
+++ b/doc/ui/calc2.jpg
Binary files differ
diff --git a/doc/ui/calc3.jpg b/doc/ui/calc3.jpg
new file mode 100644
index 0000000..ac65c9f
--- /dev/null
+++ b/doc/ui/calc3.jpg
Binary files differ
diff --git a/doc/ui/calc4.jpg b/doc/ui/calc4.jpg
new file mode 100644
index 0000000..8069054
--- /dev/null
+++ b/doc/ui/calc4.jpg
Binary files differ
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f8715e5..49b3845 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -399,6 +399,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/List.cpp
displayapp/screens/BatteryInfo.cpp
displayapp/screens/Steps.cpp
+ displayapp/screens/Calculator.cpp
displayapp/screens/Timer.cpp
displayapp/screens/PassKey.cpp
displayapp/screens/Error.cpp
@@ -607,6 +608,7 @@ set(INCLUDE_FILES
displayapp/screens/HeartRate.h
displayapp/screens/Metronome.h
displayapp/screens/Motion.h
+ displayapp/screens/Calculator.h
displayapp/screens/Timer.h
displayapp/screens/Jumpscore.h
displayapp/screens/Alarm.h
diff --git a/src/displayapp/Apps.h b/src/displayapp/Apps.h
index 4f9fdcc..e1f86ce 100644
--- a/src/displayapp/Apps.h
+++ b/src/displayapp/Apps.h
@@ -40,6 +40,7 @@ namespace Pinetime {
SettingChimes,
SettingShakeThreshold,
SettingBluetooth,
+ Calculator,
Error
};
}
diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp
index 376f73a..a8d6281 100644
--- a/src/displayapp/DisplayApp.cpp
+++ b/src/displayapp/DisplayApp.cpp
@@ -25,6 +25,7 @@
#include "displayapp/screens/SystemInfo.h"
#include "displayapp/screens/Tile.h"
#include "displayapp/screens/Twos.h"
+#include "displayapp/screens/Calculator.h"
#include "displayapp/screens/FlashLight.h"
#include "displayapp/screens/BatteryInfo.h"
#include "displayapp/screens/Steps.h"
@@ -481,6 +482,9 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
case Apps::Steps:
currentScreen = std::make_unique<Screens::Steps>(this, motionController, settingsController);
break;
+ case Apps::Calculator:
+ currentScreen = std::make_unique<Screens::Calculator>(this, motorController);
+ break;
}
currentApp = app;
}
diff --git a/src/displayapp/screens/ApplicationList.h b/src/displayapp/screens/ApplicationList.h
index d2ede1b..1640227 100644
--- a/src/displayapp/screens/ApplicationList.h
+++ b/src/displayapp/screens/ApplicationList.h
@@ -48,6 +48,7 @@ namespace Pinetime {
{Symbols::map, Apps::Navigation},
{Symbols::drum, Apps::Jumpscore},
+ {Symbols::calculator, Apps::Calculator},
};
std::array<std::array<Tile::Applications, appsPerScreen>, ((std::size(list) + appsPerScreen - 1) / appsPerScreen)> r{};;
int idx = 0;
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;
+}
diff --git a/src/displayapp/screens/Calculator.h b/src/displayapp/screens/Calculator.h
new file mode 100644
index 0000000..25c4456
--- /dev/null
+++ b/src/displayapp/screens/Calculator.h
@@ -0,0 +1,37 @@
+
+#pragma once
+
+
+#include "Screen.h"
+#include "components/motor/MotorController.h"
+#include <array>
+#include <string>
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+
+ class Calculator : public Screen {
+ public:
+ ~Calculator() override;
+
+ Calculator(DisplayApp* app, Controllers::MotorController& motorController);
+
+ void OnButtonEvent(lv_obj_t* obj, lv_event_t event);
+
+ bool OnTouchEvent(Pinetime::Applications::TouchEvents event) override;
+
+ private:
+ lv_obj_t *result, *returnButton, *buttonMatrix;
+
+ char text[31];
+ uint8_t position = 0;
+
+ void eval();
+
+ Controllers::MotorController& motorController;
+ };
+
+ }
+ }
+}
diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h
index f973181..d4f80d1 100644
--- a/src/displayapp/screens/Symbols.h
+++ b/src/displayapp/screens/Symbols.h
@@ -37,6 +37,7 @@ namespace Pinetime {
static constexpr const char* chartLine = "\xEF\x88\x81";
static constexpr const char* eye = "\xEF\x81\xAE";
static constexpr const char* home = "\xEF\x80\x95";
+ static constexpr const char* calculator = "\xEF\x87\xAC";
// lv_font_sys_48.c
static constexpr const char* settings = "\xEE\xA4\x82"; // e902