aboutsummaryrefslogtreecommitdiffstats
path: root/src/displayapp/screens/Calculator.cpp
diff options
context:
space:
mode:
authorMax Friedrich <minacode@users.noreply.github.com>2025-05-13 00:32:03 +0200
committerGitHub <noreply@github.com>2025-05-12 23:32:03 +0100
commit5b20e8e2ba8dae54c7ff5c2d26534b9746df2d15 (patch)
tree8f4337f6a30ac75748e159f96ef4461607b696a4 /src/displayapp/screens/Calculator.cpp
parent6a6981c91251b96cdf33fe9b094b0833b00ebd8f (diff)
Simple calculator (#1483)
Co-authored-by: minacode <minamoto9@web.de> Co-authored-by: Finlay Davidson <finlay.davidson@coderclass.nl> Co-authored-by: SuIƓng N. <Boteium@users.noreply.github.com> Co-authored-by: mark9064 <30447455+mark9064@users.noreply.github.com>
Diffstat (limited to 'src/displayapp/screens/Calculator.cpp')
-rw-r--r--src/displayapp/screens/Calculator.cpp375
1 files changed, 375 insertions, 0 deletions
diff --git a/src/displayapp/screens/Calculator.cpp b/src/displayapp/screens/Calculator.cpp
new file mode 100644
index 00000000..a1f09383
--- /dev/null
+++ b/src/displayapp/screens/Calculator.cpp
@@ -0,0 +1,375 @@
+#include <cmath>
+#include <cinttypes>
+#include "Calculator.h"
+#include "displayapp/InfiniTimeTheme.h"
+#include "Symbols.h"
+
+using namespace Pinetime::Applications::Screens;
+
+static void eventHandler(lv_obj_t* obj, lv_event_t event) {
+ auto app = static_cast<Calculator*>(obj->user_data);
+ app->OnButtonEvent(obj, event);
+}
+
+Calculator::~Calculator() {
+ lv_obj_clean(lv_scr_act());
+}
+
+constexpr const char* const buttonMap[] = {
+ "7", "8", "9", Symbols::backspace, "\n", "4", "5", "6", "+ -", "\n", "1", "2", "3", "* /", "\n", "0", ".", "(-)", "=", ""};
+
+Calculator::Calculator() {
+ resultLabel = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_long_mode(resultLabel, LV_LABEL_LONG_CROP);
+ lv_label_set_align(resultLabel, LV_LABEL_ALIGN_RIGHT);
+ lv_label_set_text_fmt(resultLabel, "%" PRId64, result);
+ lv_obj_set_size(resultLabel, 200, 20);
+ lv_obj_set_pos(resultLabel, 10, 5);
+
+ valueLabel = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_long_mode(valueLabel, LV_LABEL_LONG_CROP);
+ lv_label_set_align(valueLabel, LV_LABEL_ALIGN_RIGHT);
+ lv_label_set_text_fmt(valueLabel, "%" PRId64, value);
+ lv_obj_set_size(valueLabel, 200, 20);
+ lv_obj_set_pos(valueLabel, 10, 35);
+
+ buttonMatrix = lv_btnmatrix_create(lv_scr_act(), nullptr);
+ buttonMatrix->user_data = this;
+ lv_obj_set_event_cb(buttonMatrix, eventHandler);
+ lv_btnmatrix_set_map(buttonMatrix, const_cast<const char**>(buttonMap));
+ lv_btnmatrix_set_one_check(buttonMatrix, true);
+ lv_obj_set_size(buttonMatrix, 238, 180);
+ lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, Colors::bgAlt);
+ lv_obj_set_style_local_pad_inner(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
+ lv_obj_set_style_local_pad_top(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
+ lv_obj_set_style_local_pad_bottom(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
+ lv_obj_set_style_local_pad_left(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
+ lv_obj_set_style_local_pad_right(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
+ lv_obj_align(buttonMatrix, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
+
+ lv_obj_set_style_local_bg_opa(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_OPA_COVER);
+ lv_obj_set_style_local_bg_grad_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128);
+ lv_obj_set_style_local_bg_main_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128);
+}
+
+void Calculator::OnButtonEvent(lv_obj_t* obj, lv_event_t event) {
+ if ((obj == buttonMatrix) && (event == LV_EVENT_PRESSED)) {
+ HandleInput();
+ }
+}
+
+void Calculator::HandleInput() {
+ const char* buttonText = lv_btnmatrix_get_active_btn_text(buttonMatrix);
+
+ if (buttonText == nullptr) {
+ return;
+ }
+
+ if ((equalSignPressedBefore && (*buttonText != '=')) || (error != Error::None)) {
+ ResetInput();
+ UpdateOperation();
+ }
+
+ // we only compare the first char because it is enough
+ switch (*buttonText) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9': {
+ // *buttonText is the first char in buttonText
+ // "- '0'" results in the int value of the char
+ uint8_t digit = (*buttonText) - '0';
+ int8_t sign = (value < 0) ? -1 : 1;
+
+ // if this is true, we already pressed the . button
+ if (offset < FIXED_POINT_OFFSET) {
+ value += sign * offset * digit;
+ offset /= 10;
+ } else if (value <= MAX_VALUE / 10) {
+ value *= 10;
+ value += sign * offset * digit;
+ }
+ } break;
+
+ // unary minus
+ case '(':
+ value = -value;
+ break;
+
+ case '.':
+ if (offset == FIXED_POINT_OFFSET) {
+ offset /= 10;
+ }
+ break;
+
+ // for every operator we:
+ // - eval the current operator if value > FIXED_POINT_OFFSET
+ // - then set the new operator
+ // - + and - as well as * and / cycle on the same button
+ case '+':
+ if (value != 0) {
+ Eval();
+ ResetInput();
+ }
+
+ switch (operation) {
+ case '+':
+ operation = '-';
+ break;
+ case '-':
+ operation = ' ';
+ break;
+ default:
+ operation = '+';
+ break;
+ }
+ UpdateOperation();
+ break;
+
+ case '*':
+ if (value != 0) {
+ Eval();
+ ResetInput();
+ }
+
+ switch (operation) {
+ case '*':
+ operation = '/';
+ break;
+ case '/':
+ operation = ' ';
+ break;
+ default:
+ operation = '*';
+ break;
+ }
+ UpdateOperation();
+ break;
+
+ // this is a little hacky because it matches only the first char
+ case Symbols::backspace[0]:
+ if (value != 0) {
+ // delete one value digit
+ if (offset < FIXED_POINT_OFFSET) {
+ if (offset == 0) {
+ offset = 1;
+ } else {
+ offset *= 10;
+ }
+ } else {
+ value /= 10;
+ }
+ if (offset < FIXED_POINT_OFFSET) {
+ value -= value % (10 * offset);
+ } else {
+ value -= value % offset;
+ }
+ } else if (offset < FIXED_POINT_OFFSET) {
+ if (offset == 0) {
+ offset = 1;
+ } else {
+ offset *= 10;
+ }
+ } else {
+ // reset the result
+ result = 0;
+ }
+
+ if (value == 0) {
+ operation = ' ';
+ UpdateOperation();
+ }
+ break;
+
+ case '=':
+ equalSignPressedBefore = true;
+ Eval();
+ // If the operation is ' ' then we move the value to the result.
+ // We reset the input after this.
+ // This seems more convenient.
+ if (operation == ' ') {
+ ResetInput();
+ }
+ break;
+ }
+
+ UpdateValueLabel();
+ UpdateResultLabel();
+}
+
+void Calculator::UpdateOperation() const {
+ switch (operation) {
+ case '+':
+ lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
+ lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
+ lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
+ lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE);
+ break;
+ case '-':
+ lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
+ lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
+ lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
+ lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE);
+ break;
+ case '*':
+ lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
+ lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
+ lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
+ lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE);
+ break;
+ case '/':
+ lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
+ lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
+ lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
+ lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE);
+ break;
+ default:
+ lv_btnmatrix_clear_btn_ctrl_all(buttonMatrix, LV_BTNMATRIX_CTRL_CHECK_STATE);
+ break;
+ }
+}
+
+void Calculator::ResetInput() {
+ value = 0;
+ offset = FIXED_POINT_OFFSET;
+ operation = ' ';
+ equalSignPressedBefore = false;
+ error = Error::None;
+}
+
+void Calculator::UpdateResultLabel() const {
+ int64_t integer = result / FIXED_POINT_OFFSET;
+ int64_t remainder = result % FIXED_POINT_OFFSET;
+ bool negative = (remainder < 0);
+
+ if (remainder == 0) {
+ lv_label_set_text_fmt(resultLabel, "%" PRId64, integer);
+ return;
+ }
+
+ if (remainder < 0) {
+ remainder = -remainder;
+ }
+
+ uint8_t minWidth = N_DECIMALS;
+
+ // cut "0"-digits on the right
+ while ((remainder > 0) && (remainder % 10 == 0)) {
+ remainder /= 10;
+ minWidth--;
+ }
+
+ if ((integer == 0) && negative) {
+ lv_label_set_text_fmt(resultLabel, "-0.%0*" PRId64, minWidth, remainder);
+ } else {
+ lv_label_set_text_fmt(resultLabel, "%" PRId64 ".%0*" PRId64, integer, minWidth, remainder);
+ }
+}
+
+void Calculator::UpdateValueLabel() {
+ switch (error) {
+ case Error::TooLarge:
+ lv_label_set_text_static(valueLabel, "too large");
+ break;
+ case Error::ZeroDivision:
+ lv_label_set_text_static(valueLabel, "zero division");
+ break;
+ case Error::None:
+ default: {
+ int64_t integer = value / FIXED_POINT_OFFSET;
+ int64_t remainder = value % FIXED_POINT_OFFSET;
+ bool negative = (remainder < 0);
+
+ int64_t printRemainder = remainder < 0 ? -remainder : remainder;
+
+ uint8_t minWidth = 0;
+ int64_t tmpOffset = offset;
+
+ if (tmpOffset == 0) {
+ tmpOffset = 1;
+ minWidth = 1;
+ }
+ while (tmpOffset < FIXED_POINT_OFFSET) {
+ tmpOffset *= 10;
+ minWidth++;
+ }
+ minWidth--;
+
+ for (uint8_t i = minWidth; i < N_DECIMALS; i++) {
+ printRemainder /= 10;
+ }
+
+ if ((integer == 0) && negative) {
+ lv_label_set_text_fmt(valueLabel, "-0.%0*" PRId64, minWidth, printRemainder);
+ } else if (offset == FIXED_POINT_OFFSET) {
+ lv_label_set_text_fmt(valueLabel, "%" PRId64, integer);
+ } else if ((offset == (FIXED_POINT_OFFSET / 10)) && (remainder == 0)) {
+ lv_label_set_text_fmt(valueLabel, "%" PRId64 ".", integer);
+ } else {
+ lv_label_set_text_fmt(valueLabel, "%" PRId64 ".%0*" PRId64, integer, minWidth, printRemainder);
+ }
+ } break;
+ }
+}
+
+// update the result based on value and operation
+void Calculator::Eval() {
+ switch (operation) {
+ case ' ':
+ result = value;
+ break;
+
+ case '+':
+ // check for overflow
+ if (((result > 0) && (value > (MAX_VALUE - result))) || ((result < 0) && (value < (MIN_VALUE - result)))) {
+ error = Error::TooLarge;
+ break;
+ }
+
+ result += value;
+ break;
+ case '-':
+ // check for overflow
+ if (((result < 0) && (value > (MAX_VALUE + result))) || ((result > 0) && (value < (MIN_VALUE + result)))) {
+ error = Error::TooLarge;
+ break;
+ }
+
+ result -= value;
+ break;
+ case '*':
+ // check for overflow
+ // while dividing we eliminate the fixed point offset
+ // therefore we have to multiply it again for the comparison with value
+ // we also assume here that MAX_VALUE == -MIN_VALUE
+ if ((result != 0) && (std::abs(value) > (FIXED_POINT_OFFSET * (MAX_VALUE / std::abs(result))))) {
+ error = Error::TooLarge;
+ break;
+ }
+
+ result *= value;
+ // fixed point offset was multiplied too
+ result /= FIXED_POINT_OFFSET;
+ break;
+ case '/':
+ // check for zero division
+ if (value == 0) {
+ error = Error::ZeroDivision;
+ break;
+ }
+
+ // fixed point offset will be divided too
+ result *= FIXED_POINT_OFFSET;
+ result /= value;
+ break;
+
+ default:
+ break;
+ }
+}