Created
March 25, 2025 22:41
-
-
Save maxbrito500/69a9995c9253d4a7f200aeb5d45ca838 to your computer and use it in GitHub Desktop.
A simple calculator app for the Tactility operating system
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Tactility/app/AppManifest.h> | |
#include <Tactility/lvgl/Toolbar.h> | |
#include <lvgl.h> | |
#include <cstring> | |
#include <cstdlib> | |
#include <stack> | |
#include <queue> | |
using namespace tt::app; | |
class CalculatorApp : public App { | |
lv_obj_t* displayLabel; | |
char formulaBuffer[128] = {0}; // Stores the full input expression | |
bool newInput = true; | |
static void button_event_cb(lv_event_t* e) { | |
CalculatorApp* self = static_cast<CalculatorApp*>(lv_event_get_user_data(e)); | |
lv_obj_t* btnm = static_cast<lv_obj_t*>(lv_event_get_target(e)); | |
uint16_t btn_id = lv_btnmatrix_get_selected_btn(btnm); | |
const char* txt = lv_btnmatrix_get_btn_text(btnm, btn_id); | |
self->handleInput(txt); | |
} | |
void handleInput(const char* txt) { | |
if (std::strcmp(txt, "C") == 0) { | |
resetCalculator(); | |
return; | |
} | |
if (std::strcmp(txt, "=") == 0) { | |
evaluateExpression(); | |
return; | |
} | |
if (std::strlen(formulaBuffer) + std::strlen(txt) < sizeof(formulaBuffer) - 1) { | |
if (newInput) { | |
std::memset(formulaBuffer, 0, sizeof(formulaBuffer)); | |
newInput = false; | |
} | |
std::strcat(formulaBuffer, txt); | |
lv_label_set_text(displayLabel, formulaBuffer); | |
} | |
} | |
void evaluateExpression() { | |
double result = computeFormula(); | |
size_t formulaLen = std::strlen(formulaBuffer); | |
size_t maxAvailable = sizeof(formulaBuffer) - formulaLen - 1; | |
if (maxAvailable > 10) { | |
char resultBuffer[32]; | |
snprintf(resultBuffer, sizeof(resultBuffer), " = %.8g", result); | |
strncat(formulaBuffer, resultBuffer, maxAvailable); | |
} else { | |
snprintf(formulaBuffer, sizeof(formulaBuffer), "%.8g", result); | |
} | |
lv_label_set_text(displayLabel, formulaBuffer); | |
newInput = true; | |
} | |
double computeFormula() { | |
return evaluateRPN(infixToRPN(std::string(formulaBuffer))); | |
} | |
int precedence(char op) { | |
if (op == '+' || op == '-') return 1; | |
if (op == '*' || op == '/') return 2; | |
return 0; | |
} | |
std::queue<std::string> infixToRPN(const std::string& infix) { | |
std::stack<char> opStack; | |
std::queue<std::string> output; | |
std::string token; | |
size_t i = 0; | |
while (i < infix.size()) { | |
char ch = infix[i]; | |
if (isdigit(ch)) { | |
token.clear(); | |
while (i < infix.size() && (isdigit(infix[i]) || infix[i] == '.')) { | |
token += infix[i++]; | |
} | |
output.push(token); | |
continue; | |
} | |
if (ch == '(') { | |
opStack.push(ch); | |
} else if (ch == ')') { | |
while (!opStack.empty() && opStack.top() != '(') { | |
output.push(std::string(1, opStack.top())); | |
opStack.pop(); | |
} | |
opStack.pop(); | |
} else if (strchr("+-*/", ch)) { | |
while (!opStack.empty() && precedence(opStack.top()) >= precedence(ch)) { | |
output.push(std::string(1, opStack.top())); | |
opStack.pop(); | |
} | |
opStack.push(ch); | |
} | |
i++; | |
} | |
while (!opStack.empty()) { | |
output.push(std::string(1, opStack.top())); | |
opStack.pop(); | |
} | |
return output; | |
} | |
double evaluateRPN(std::queue<std::string> rpnQueue) { | |
std::stack<double> values; | |
while (!rpnQueue.empty()) { | |
std::string token = rpnQueue.front(); | |
rpnQueue.pop(); | |
if (isdigit(token[0])) { | |
values.push(std::stod(token)); | |
} else if (strchr("+-*/", token[0])) { | |
if (values.size() < 2) return 0; | |
double b = values.top(); values.pop(); | |
double a = values.top(); values.pop(); | |
if (token[0] == '+') values.push(a + b); | |
else if (token[0] == '-') values.push(a - b); | |
else if (token[0] == '*') values.push(a * b); | |
else if (token[0] == '/' && b != 0) values.push(a / b); | |
} | |
} | |
return values.empty() ? 0 : values.top(); | |
} | |
void resetCalculator() { | |
std::memset(formulaBuffer, 0, sizeof(formulaBuffer)); | |
lv_label_set_text(displayLabel, "0"); | |
newInput = true; | |
} | |
void create_buttons(lv_obj_t* parent) { | |
static const char* btn_map[] = { | |
"(", ")", "C", "/", "\n", | |
"7", "8", "9", "*", "\n", | |
"4", "5", "6", "-", "\n", | |
"1", "2", "3", "+", "\n", | |
"0", "=", "", "", "" | |
}; | |
lv_obj_t* btnm = lv_btnmatrix_create(parent); | |
lv_btnmatrix_set_map(btnm, btn_map); | |
lv_obj_set_style_pad_all(btnm, 5, LV_PART_MAIN); | |
lv_obj_set_style_pad_row(btnm, 10, LV_PART_MAIN); | |
lv_obj_set_style_pad_column(btnm, 5, LV_PART_MAIN); | |
lv_obj_set_style_border_width(btnm, 0, LV_PART_MAIN); | |
lv_obj_set_size(btnm, lv_pct(100), lv_pct(60)); | |
lv_obj_align(btnm, LV_ALIGN_BOTTOM_MID, 0, -5); | |
lv_obj_add_event_cb(btnm, button_event_cb, LV_EVENT_VALUE_CHANGED, this); | |
} | |
void create_display(lv_obj_t* parent) { | |
displayLabel = lv_label_create(parent); | |
lv_label_set_text(displayLabel, "0"); | |
lv_obj_set_style_text_align(displayLabel, LV_TEXT_ALIGN_CENTER, 0); | |
lv_obj_set_width(displayLabel, lv_pct(100)); | |
// Center vertically in the remaining space | |
lv_obj_align(displayLabel, LV_ALIGN_CENTER, 0, -40); | |
} | |
void onShow(AppContext& context, lv_obj_t* parent) override { | |
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context); | |
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); | |
create_display(parent); | |
create_buttons(parent); | |
} | |
}; | |
extern const AppManifest calculator_app = { | |
.id = "Calculator", | |
.name = "Calculator", | |
.createApp = create<CalculatorApp> | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment