From 3bb0ac29e2422675da9e79460fadddea58fb0ac7 Mon Sep 17 00:00:00 2001 From: tofasthacker Date: Sat, 23 Sep 2023 14:17:56 -0400 Subject: [PATCH] Switched TestApp for calculator app --- src/displayapp/DisplayApp.cpp | 2 +- src/displayapp/screens/TestApp.cpp | 473 ++++++++++++++++++++++------- src/displayapp/screens/TestApp.h | 70 +++-- 3 files changed, 422 insertions(+), 123 deletions(-) diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 1971ad27..66fea1ce 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -556,7 +556,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio break; // My Apps case Apps::TestApp: - currentScreen = std::make_unique(*systemTask, brightnessController); + currentScreen = std::make_unique(); break; } currentApp = app; diff --git a/src/displayapp/screens/TestApp.cpp b/src/displayapp/screens/TestApp.cpp index f313776e..11b385e0 100644 --- a/src/displayapp/screens/TestApp.cpp +++ b/src/displayapp/screens/TestApp.cpp @@ -1,128 +1,397 @@ -#include "displayapp/screens/TestApp.h" -#include "displayapp/DisplayApp.h" -#include "displayapp/screens/Symbols.h" +#include +#include +#include +#include "TestApp.h" #include "displayapp/InfiniTimeTheme.h" +#include "Symbols.h" using namespace Pinetime::Applications::Screens; -namespace { - void EventHandler(lv_obj_t* obj, lv_event_t event) { - if (event == LV_EVENT_CLICKED) { - auto* screen = static_cast(obj->user_data); - screen->Toggle(); - } - } -} - -TestApp::TestApp(System::SystemTask& systemTask, Controllers::BrightnessController& brightnessController) - : systemTask {systemTask}, brightnessController {brightnessController} { - - brightnessController.Set(Controllers::BrightnessController::Levels::Low); - - flashLight = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(flashLight, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); - lv_label_set_text_static(flashLight, Symbols::flashlight); - lv_obj_align(flashLight, nullptr, LV_ALIGN_CENTER, 0, 0); - - for (auto& indicator : indicators) { - indicator = lv_obj_create(lv_scr_act(), nullptr); - lv_obj_set_size(indicator, 15, 10); - lv_obj_set_style_local_border_width(indicator, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 2); - } - - lv_obj_align(indicators[1], flashLight, LV_ALIGN_OUT_BOTTOM_MID, 0, 5); - lv_obj_align(indicators[0], indicators[1], LV_ALIGN_OUT_LEFT_MID, -8, 0); - lv_obj_align(indicators[2], indicators[1], LV_ALIGN_OUT_RIGHT_MID, 8, 0); - - SetIndicators(); - SetColors(); - - backgroundAction = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_long_mode(backgroundAction, LV_LABEL_LONG_CROP); - lv_obj_set_size(backgroundAction, 240, 240); - lv_obj_set_pos(backgroundAction, 0, 0); - lv_label_set_text_static(backgroundAction, ""); - lv_obj_set_click(backgroundAction, true); - backgroundAction->user_data = this; - lv_obj_set_event_cb(backgroundAction, EventHandler); - - systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); +static void eventHandler(lv_obj_t* obj, lv_event_t event) { + auto app = static_cast(obj->user_data); + app->OnButtonEvent(obj, event); } TestApp::~TestApp() { lv_obj_clean(lv_scr_act()); - lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); } -void TestApp::SetColors() { - lv_color_t bgColor = isOn ? LV_COLOR_WHITE : LV_COLOR_BLACK; - lv_color_t fgColor = isOn ? Colors::lightGray : LV_COLOR_WHITE; +static const char* buttonMap[] = { + "7", "8", "9", "B"/*Symbols::backspace*/, "\n", "4", "5", "6", "+ -", "\n", "1", "2", "3", "* /", "\n", "0", ".", "(-)", "=", ""}; - lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, bgColor); - lv_obj_set_style_local_text_color(flashLight, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, fgColor); - for (auto& indicator : indicators) { - lv_obj_set_style_local_bg_color(indicator, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, fgColor); - lv_obj_set_style_local_bg_color(indicator, LV_OBJ_PART_MAIN, LV_STATE_DISABLED, bgColor); - lv_obj_set_style_local_border_color(indicator, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, fgColor); +TestApp::TestApp() { + 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, 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 TestApp::OnButtonEvent(lv_obj_t* obj, lv_event_t event) { + if ((obj == buttonMatrix) && (event == LV_EVENT_PRESSED)) { + HandleInput(); } } -void TestApp::SetIndicators() { - using namespace Pinetime::Controllers; +void TestApp::HandleInput() { + const char* buttonText = lv_btnmatrix_get_active_btn_text(buttonMatrix); - if (brightnessLevel == BrightnessController::Levels::High) { - lv_obj_set_state(indicators[1], LV_STATE_DEFAULT); - lv_obj_set_state(indicators[2], LV_STATE_DEFAULT); - } else if (brightnessLevel == BrightnessController::Levels::Medium) { - lv_obj_set_state(indicators[1], LV_STATE_DEFAULT); - lv_obj_set_state(indicators[2], LV_STATE_DISABLED); + if (buttonText == nullptr) { + return; + } + + if ((equalSignPressed && (*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 + auto digit = (*buttonText) - '0'; + auto 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; + } + + NRF_LOG_INFO(". offset: %" PRId64, offset); + NRF_LOG_INFO(". value: %" PRId64, value); + NRF_LOG_INFO(". result: %" PRId64, result); + } break; + + // unary minus + case '(': + value = -value; + + NRF_LOG_INFO(". offset: %" PRId64, offset); + NRF_LOG_INFO(". value: %" PRId64, value); + NRF_LOG_INFO(". result: %" PRId64, result); + + break; + + case '.': + if (offset == FIXED_POINT_OFFSET) { + offset /= 10; + } + + NRF_LOG_INFO(". offset: %" PRId64, offset); + NRF_LOG_INFO(". value: %" PRId64, value); + NRF_LOG_INFO(". result: %" PRId64, result); + 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 'B': + 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; + } + + NRF_LOG_INFO(". offset: %" PRId64, offset); + NRF_LOG_INFO(". value: %" PRId64, value); + NRF_LOG_INFO(". result: %" PRId64, result); + + operation = ' '; + UpdateOperation(); + break; + + case '=': + equalSignPressed = 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(); + } + + NRF_LOG_INFO(". offset: %" PRId64, offset); + NRF_LOG_INFO(". value: %" PRId64, value); + NRF_LOG_INFO(". result: %" PRId64, result); + + break; + } + + UpdateValueLabel(); + UpdateResultLabel(); +} + +void TestApp::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 TestApp::ResetInput() { + value = 0; + offset = FIXED_POINT_OFFSET; + operation = ' '; + equalSignPressed = false; + error = Error::None; +} + +void TestApp::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 min_width = N_DECIMALS; + + // cut "0"-digits on the right + while ((remainder > 0) && (remainder % 10 == 0)) { + remainder /= 10; + min_width--; + } + + if ((integer == 0) && negative) { + lv_label_set_text_fmt(resultLabel, "-0.%0*" PRId64, min_width, remainder); } else { - lv_obj_set_state(indicators[1], LV_STATE_DISABLED); - lv_obj_set_state(indicators[2], LV_STATE_DISABLED); + lv_label_set_text_fmt(resultLabel, "%" PRId64 ".%0*" PRId64, integer, min_width, remainder); } } -void TestApp::Toggle() { - isOn = !isOn; - SetColors(); - if (isOn) { - brightnessController.Set(brightnessLevel); - } else { - brightnessController.Set(Controllers::BrightnessController::Levels::Low); +void TestApp::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 min_width = 0; + int64_t tmp_offset = offset; + + // TODO there has to be a simpler way to do this + if (tmp_offset == 0) { + tmp_offset = 1; + min_width = 1; + } + while (tmp_offset < FIXED_POINT_OFFSET) { + tmp_offset *= 10; + min_width++; + } + min_width--; + + for (uint8_t i = min_width; i < N_DECIMALS; i++) { + printRemainder /= 10; + } + + if ((integer == 0) && negative) { + lv_label_set_text_fmt(valueLabel, "-0.%0*" PRId64, min_width, 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, min_width, printRemainder); + } + } break; } } -bool TestApp::OnTouchEvent(Pinetime::Applications::TouchEvents event) { - using namespace Pinetime::Controllers; +// update the result based on value and operation +void TestApp::Eval() { + switch (operation) { + case ' ': + result = value; + break; - auto SetState = [this]() { - if (isOn) { - brightnessController.Set(brightnessLevel); - } - SetIndicators(); - }; + case '+': + // check for overflow + if (((result > 0) && (value > (MAX_VALUE - result))) || ((result < 0) && (value < (MIN_VALUE - result)))) { + error = Error::TooLarge; + break; + } - if (event == TouchEvents::SwipeLeft) { - if (brightnessLevel == BrightnessController::Levels::High) { - brightnessLevel = BrightnessController::Levels::Medium; - SetState(); - } else if (brightnessLevel == BrightnessController::Levels::Medium) { - brightnessLevel = BrightnessController::Levels::Low; - SetState(); - } - return true; + 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; } - if (event == TouchEvents::SwipeRight) { - if (brightnessLevel == BrightnessController::Levels::Low) { - brightnessLevel = BrightnessController::Levels::Medium; - SetState(); - } else if (brightnessLevel == BrightnessController::Levels::Medium) { - brightnessLevel = BrightnessController::Levels::High; - SetState(); - } - return true; - } - - return false; } diff --git a/src/displayapp/screens/TestApp.h b/src/displayapp/screens/TestApp.h index 164e67c0..91b94f9b 100644 --- a/src/displayapp/screens/TestApp.h +++ b/src/displayapp/screens/TestApp.h @@ -1,38 +1,68 @@ #pragma once -#include "displayapp/screens/Screen.h" -#include "components/brightness/BrightnessController.h" -#include "systemtask/SystemTask.h" -#include -#include +#include "Screen.h" + +namespace { + int64_t constexpr powi(int64_t base, uint8_t exponent) { + int64_t value = 1; + while (exponent) { + value *= base; + exponent--; + } + return value; + } +} namespace Pinetime { - namespace Applications { namespace Screens { - class TestApp : public Screen { public: - TestApp(System::SystemTask& systemTask, Controllers::BrightnessController& brightness); ~TestApp() override; - bool OnTouchEvent(Pinetime::Applications::TouchEvents event) override; - void Toggle(); + TestApp(); + + void OnButtonEvent(lv_obj_t* obj, lv_event_t event); private: - void SetIndicators(); - void SetColors(); + lv_obj_t* buttonMatrix {}; + lv_obj_t* valueLabel {}; + lv_obj_t* resultLabel {}; - Pinetime::System::SystemTask& systemTask; - Controllers::BrightnessController& brightnessController; + void Eval(); + void ResetInput(); + void HandleInput(); + void UpdateValueLabel(); + void UpdateResultLabel() const; + void UpdateOperation() const; - Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High; + // change this if you want to change the number of decimals + static constexpr uint8_t N_DECIMALS = 4; + // this is the constant default offset + static constexpr int64_t FIXED_POINT_OFFSET = powi(10, N_DECIMALS); + // this is the current offset, may wary after pressing '.' + int64_t offset = FIXED_POINT_OFFSET; - lv_obj_t* flashLight; - lv_obj_t* backgroundAction; - lv_obj_t* indicators[3]; - bool isOn = false; + // the screen can show 12 chars + // but two are needed for '.' and '-' + static constexpr uint8_t MAX_DIGITS = 15; + static constexpr int64_t MAX_VALUE = powi(10, MAX_DIGITS) - 1; + // this is assumed in the multiplication overflow! + static constexpr int64_t MIN_VALUE = -MAX_VALUE; + + int64_t value = 0; + int64_t result = 0; + char operation = ' '; + bool equalSignPressed = false; + + enum Error { + TooLarge, + ZeroDivision, + None, + }; + + Error error = Error::None; }; } } -} \ No newline at end of file +}