Compare commits
10 commits
ab9eaef3fc
...
a7faa14345
Author | SHA1 | Date | |
---|---|---|---|
a7faa14345 | |||
aa3401bd52 | |||
14092ef883 | |||
17abbdd079 | |||
6c59dd0d1c | |||
5d15a0df3c | |||
0745ebd82c | |||
|
68384fc34e | ||
bf786dac8e | |||
a8a99bc462 |
23 changed files with 560 additions and 28 deletions
14
README.md
14
README.md
|
@ -1,3 +1,17 @@
|
|||
# The Trax.im fork of PineTime OS InfiniTime
|
||||
|
||||
Includes some tweaks such as:
|
||||
|
||||
|Analogue |Digital |Terminal
|
||||
|---|---|---
|
||||
|||
|
||||
|
||||
|Add [Calculator](https://github.com/InfiniTimeOrg/InfiniTime/pull/375) |Tweak Settings |Pulse: never say '0'
|
||||
|---|---|---
|
||||
|||
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
|
8
doc/Calculator.md
Normal file
8
doc/Calculator.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# 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
|
||||
- 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
|
BIN
doc/img/InfiniSim_2024-08-27-ana.png
Normal file
BIN
doc/img/InfiniSim_2024-08-27-ana.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 6.8 KiB |
BIN
doc/img/InfiniSim_2024-08-27-dig.png
Normal file
BIN
doc/img/InfiniSim_2024-08-27-dig.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.5 KiB |
BIN
doc/img/InfiniSim_2024-08-27-nopulse-ana.png
Normal file
BIN
doc/img/InfiniSim_2024-08-27-nopulse-ana.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 6.3 KiB |
BIN
doc/img/InfiniSim_2024-08-27-term.png
Normal file
BIN
doc/img/InfiniSim_2024-08-27-term.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 3.9 KiB |
BIN
doc/img/InfiniSim_2024-08-28-calculator.png
Normal file
BIN
doc/img/InfiniSim_2024-08-28-calculator.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 4.3 KiB |
BIN
doc/img/InfiniSim_2024-08-28-settings1.png
Normal file
BIN
doc/img/InfiniSim_2024-08-28-settings1.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 4.2 KiB |
|
@ -26,6 +26,7 @@ export GCC_ARM_PATH="gcc-arm-none-eabi-$GCC_ARM_VER"
|
|||
|
||||
main() {
|
||||
local target="$1"
|
||||
shift
|
||||
|
||||
mkdir -p "$TOOLS_DIR"
|
||||
|
||||
|
@ -35,7 +36,7 @@ main() {
|
|||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
CmakeGenerate
|
||||
CmakeGenerate "$@"
|
||||
CmakeBuild $target
|
||||
BUILD_RESULT=$?
|
||||
if [ "$DISABLE_POSTBUILD" != "true" -a "$BUILD_RESULT" == 0 ]; then
|
||||
|
@ -68,13 +69,16 @@ CmakeGenerate() {
|
|||
-DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_PATH" \
|
||||
-DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \
|
||||
-DBUILD_DFU=1 \
|
||||
-DBUILD_RESOURCES=1
|
||||
-DBUILD_RESOURCES=1 \
|
||||
"$@"
|
||||
}
|
||||
|
||||
# CmakeBuild [target [CMAKE_ARGS...]]
|
||||
# CMAKE_ARGS can include a '--' arg, with the same meaning as it has in 'cmake'.
|
||||
CmakeBuild() {
|
||||
local target="$1"
|
||||
[ -n "$target" ] && target="--target $target"
|
||||
cmake --build "$BUILD_DIR" --config $BUILD_TYPE $target -- -j$(nproc)
|
||||
[ -n "$target" ] && target="--target $target" && shift
|
||||
cmake --build "$BUILD_DIR" --config $BUILD_TYPE $target "$@" -- -j$(nproc)
|
||||
BUILD_RESULT=$?
|
||||
return $BUILD_RESULT
|
||||
}
|
||||
|
|
48
justfile
Normal file
48
justfile
Normal file
|
@ -0,0 +1,48 @@
|
|||
set positional-arguments
|
||||
|
||||
default:
|
||||
@just --list --justfile {{justfile()}}
|
||||
|
||||
# Specifying apps and watchfaces: see doc/code/Apps.md
|
||||
# defaults: see src/displayapp/apps/CMakeLists.txt
|
||||
USERAPPS_DEFAULT := "StopWatch Alarm Timer Steps HeartRate Music Paint Paddle Twos Metronome Navigation" # Weather Motion
|
||||
|
||||
export USERAPPS := "Steps Motion HeartRate StopWatch Alarm Timer Metronome Music Paint Paddle Calculator"
|
||||
export WATCHFACES := "Analog Digital Terminal"
|
||||
|
||||
build-all: (build-app)
|
||||
|
||||
build-app \
|
||||
ot="PineTime" ots="pine64" \
|
||||
u=`python3 -c "print(','.join(['Apps::'+x for x in '${USERAPPS}'.split()]))"` \
|
||||
w=`python3 -c "print(','.join(['WatchFace::'+x for x in '${WATCHFACES}'.split()]))"` \
|
||||
: (build 'pinetime-mcuboot-app' '-DOWNER_TEXT='+ot '-DOWNER_TEXT_SHORT='+ots '-DENABLE_USERAPPS='+u '-DENABLE_WATCHFACES='+w)
|
||||
|
||||
test1: (build "pinetime-mcuboot-app" "-D`just get-userapps-arg '${USERAPPS}'`" "-D`just get-watchfaces-arg '${WATCHFACES}'`")
|
||||
|
||||
build *TARGET_AND_CMAKE_ARGS:
|
||||
docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) ${MY_REGISTRY}/infinitime-build /opt/build.sh "$@"
|
||||
|
||||
builder:
|
||||
docker build -t ${MY_REGISTRY}/infinitime-build ./docker
|
||||
#docker push
|
||||
|
||||
list-userapps-default:
|
||||
sed -n -e 's/^ *\(#*\)set(DEFAULT_USER_APP_TYPES .*\(Apps::[^"]*\).*/\1\2/;T;p' < ./src/displayapp/apps/CMakeLists.txt
|
||||
|
||||
list-watchfaces-default:
|
||||
sed -n -e 's/^ *\(#*\)set(DEFAULT_WATCHFACE_TYPES .*\(WatchFace::[^"]*\).*/\1\2/;T;p' < ./src/displayapp/apps/CMakeLists.txt
|
||||
|
||||
list-userapps-all:
|
||||
sed -n -e '/enum class \<Apps\>.*{/,/}/p' < ./src/displayapp/apps/Apps.h.in
|
||||
|
||||
list-watchfaces-all:
|
||||
sed -n -e '/enum class \<WatchFace\>.*{/,/}/p' < ./src/displayapp/apps/Apps.h.in
|
||||
|
||||
get-userapps-arg apps:
|
||||
#!/usr/bin/env python3
|
||||
print('ENABLE_USERAPPS=' + ','.join(['Apps::'+x for x in "{{apps}}".split()]), end='')
|
||||
|
||||
get-watchfaces-arg faces:
|
||||
#!/usr/bin/env python3
|
||||
print('ENABLE_WATCHFACES=' + ','.join(['WatchFace::'+x for x in "{{faces}}".split()]), end='')
|
|
@ -390,6 +390,7 @@ list(APPEND SOURCE_FILES
|
|||
displayapp/screens/CheckboxList.cpp
|
||||
displayapp/screens/BatteryInfo.cpp
|
||||
displayapp/screens/Steps.cpp
|
||||
displayapp/screens/Calculator.cpp
|
||||
displayapp/screens/Timer.cpp
|
||||
displayapp/screens/Dice.cpp
|
||||
displayapp/screens/PassKey.cpp
|
||||
|
@ -610,6 +611,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/Dice.h
|
||||
displayapp/screens/Alarm.h
|
||||
|
|
|
@ -23,6 +23,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"
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace Pinetime {
|
|||
StopWatch,
|
||||
Metronome,
|
||||
Motion,
|
||||
Calculator,
|
||||
Steps,
|
||||
Dice,
|
||||
Weather,
|
||||
|
|
|
@ -14,6 +14,7 @@ else ()
|
|||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Calculator")
|
||||
#set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion")
|
||||
set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware")
|
||||
endif ()
|
||||
|
|
|
@ -2,17 +2,16 @@
|
|||
"jetbrains_mono_bold_20": {
|
||||
"sources": [
|
||||
{
|
||||
"file": "JetBrainsMono-Bold.ttf",
|
||||
"file": "open_sans_light.ttf",
|
||||
"range": "0x20-0x7e, 0x410-0x44f, 0xB0"
|
||||
},
|
||||
{
|
||||
"file": "FontAwesome5-Solid+Brands+Regular.woff",
|
||||
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743"
|
||||
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf743, 0xf1ec"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
"size": 20,
|
||||
"patches": ["jetbrains_mono_bold_20.c_zero.patch", "jetbrains_mono_bold_20.c_M.patch"]
|
||||
"size": 20
|
||||
},
|
||||
"jetbrains_mono_42": {
|
||||
"sources": [
|
||||
|
|
394
src/displayapp/screens/Calculator.cpp
Normal file
394
src/displayapp/screens/Calculator.cpp
Normal file
|
@ -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* const buttonMap1[] = {
|
||||
"7", "8", "9", "/", "\n",
|
||||
"4", "5", "6", "x", "\n",
|
||||
"1", "2", "3", "-", "\n",
|
||||
".", "0", "=", "+", "",
|
||||
};
|
||||
|
||||
static const char* const buttonMap2[] = {
|
||||
"7", "8", "9", "(", "\n",
|
||||
"4", "5", "6", ")", "\n",
|
||||
"1", "2", "3", "^", "\n",
|
||||
".", "0", "=", "+", "",
|
||||
};
|
||||
|
||||
Calculator::Calculator(Controllers::MotorController& motorController) : 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, const_cast<const char **>(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, const_cast<const char **>(buttonMap2));
|
||||
return true;
|
||||
}
|
||||
if (event == Pinetime::Applications::TouchEvents::SwipeRight) {
|
||||
lv_btnmatrix_set_map(buttonMatrix, const_cast<const char **>(buttonMap1));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
43
src/displayapp/screens/Calculator.h
Normal file
43
src/displayapp/screens/Calculator.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "Screen.h"
|
||||
#include "Symbols.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace Pinetime::Applications {
|
||||
namespace Screens {
|
||||
class Calculator : public Screen {
|
||||
public:
|
||||
~Calculator() override;
|
||||
|
||||
Calculator(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;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Calculator> {
|
||||
static constexpr Apps app = Apps::Calculator;
|
||||
static constexpr const char* icon = Screens::Symbols::calculator;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Calculator(controllers.motorController);
|
||||
};
|
||||
};
|
||||
}
|
|
@ -36,7 +36,7 @@ namespace Pinetime {
|
|||
template <>
|
||||
struct AppTraits<Apps::Motion> {
|
||||
static constexpr Apps app = Apps::Motion;
|
||||
static constexpr const char* icon = "M";
|
||||
static constexpr const char* icon = "XYZ";
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Motion(controllers.motionController);
|
||||
|
|
|
@ -39,6 +39,7 @@ namespace Pinetime {
|
|||
static constexpr const char* eye = "\xEF\x81\xAE";
|
||||
static constexpr const char* home = "\xEF\x80\x95";
|
||||
static constexpr const char* sleep = "\xEE\xBD\x84";
|
||||
static constexpr const char* calculator = "\xEF\x87\xAC";
|
||||
|
||||
// fontawesome_weathericons.c
|
||||
// static constexpr const char* sun = "\xEF\x86\x85";
|
||||
|
|
|
@ -172,16 +172,16 @@ WatchFaceAnalog::WatchFaceAnalog(Controllers::DateTime& dateTimeController,
|
|||
|
||||
heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat);
|
||||
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
|
||||
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
|
||||
lv_obj_align(heartbeatIcon, heartbeatValue, LV_ALIGN_OUT_TOP_LEFT, 0, 0);
|
||||
|
||||
stepValue = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
|
||||
lv_label_set_text_static(stepValue, "0");
|
||||
lv_label_set_text_static(stepValue, "");
|
||||
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
|
||||
|
||||
stepIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
|
||||
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
|
||||
lv_label_set_text_static(stepIcon, Symbols::shoe);
|
||||
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_TOP_RIGHT, 0, 0);
|
||||
|
||||
|
@ -309,15 +309,17 @@ void WatchFaceAnalog::Refresh() {
|
|||
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
|
||||
lv_label_set_text_static(heartbeatValue, "");
|
||||
}
|
||||
|
||||
lv_obj_realign(heartbeatIcon);
|
||||
lv_obj_realign(heartbeatValue);
|
||||
}
|
||||
|
||||
stepCount = motionController.NbSteps();
|
||||
if (stepCount.IsUpdated()) {
|
||||
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
|
||||
lv_obj_realign(stepValue);
|
||||
lv_obj_realign(stepIcon);
|
||||
if (stepCount.Get()) {
|
||||
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
|
||||
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
|
||||
lv_obj_realign(stepValue);
|
||||
} else {
|
||||
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
|
||||
lv_label_set_text_static(stepValue, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController,
|
|||
|
||||
heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat);
|
||||
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
|
||||
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
|
||||
lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
|
||||
|
||||
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
|
||||
|
@ -83,13 +83,13 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController,
|
|||
lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
|
||||
|
||||
stepIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
|
||||
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
|
||||
lv_label_set_text_static(stepIcon, Symbols::shoe);
|
||||
lv_obj_align(stepIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
|
||||
|
||||
stepValue = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
|
||||
lv_label_set_text_static(stepValue, "0");
|
||||
lv_label_set_text_static(stepValue, "");
|
||||
lv_obj_align(stepValue, stepIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
|
||||
|
||||
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
|
||||
|
@ -171,16 +171,18 @@ void WatchFaceDigital::Refresh() {
|
|||
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
|
||||
lv_label_set_text_static(heartbeatValue, "");
|
||||
}
|
||||
|
||||
lv_obj_realign(heartbeatIcon);
|
||||
lv_obj_realign(heartbeatValue);
|
||||
}
|
||||
|
||||
stepCount = motionController.NbSteps();
|
||||
if (stepCount.IsUpdated()) {
|
||||
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
|
||||
lv_obj_realign(stepValue);
|
||||
lv_obj_realign(stepIcon);
|
||||
if (stepCount.Get()) {
|
||||
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
|
||||
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
|
||||
lv_obj_realign(stepValue);
|
||||
} else {
|
||||
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
|
||||
lv_label_set_text_static(stepValue, "");
|
||||
}
|
||||
}
|
||||
|
||||
currentWeather = weatherService.Current();
|
||||
|
|
|
@ -158,6 +158,10 @@ void WatchFaceTerminal::Refresh() {
|
|||
|
||||
stepCount = motionController.NbSteps();
|
||||
if (stepCount.IsUpdated()) {
|
||||
lv_label_set_text_fmt(stepValue, R_LTGREY("step=")"#ee3377 %lu steps#", stepCount.Get());
|
||||
if (stepCount.Get()) {
|
||||
lv_label_set_text_fmt(stepValue, R_LTGREY("step=")"#ee3377 %lu steps#", stepCount.Get());
|
||||
} else {
|
||||
lv_label_set_text_static(stepValue, R_LTGREY("step=---"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
// QuickSettings is the screen with four icons:
|
||||
//
|
||||
// - brightness
|
||||
// - torch
|
||||
// - notifications mode
|
||||
// - settings
|
||||
//
|
||||
|
||||
#include <cstdint>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
|
|
Loading…
Add table
Reference in a new issue