Compare commits
38 Commits
v14
...
word-clock
Author | SHA1 | Date | |
---|---|---|---|
388023c69b | |||
a486fa202c | |||
|
6bbdb581cc | ||
|
16998a8ca8 | ||
|
270d15a8ea | ||
|
bf8c4dfef8 | ||
|
ecbcce41d7 | ||
|
3d75a7dc9f | ||
|
9d749434b5 | ||
|
8ff6bd9cae | ||
|
b5f500e96d | ||
|
1fd2496ee2 | ||
|
8a40eaa9c5 | ||
|
c87a297a2f | ||
15e525a5ba | |||
65976a2b23 | |||
|
86695db883 | ||
|
c8de75ccd9 | ||
|
163eaf5ab4 | ||
|
21e86c4220 | ||
09f7306e31 | |||
a2da74d93a | |||
e3c7295729 | |||
|
990ad0c87e | ||
|
c74ae787ae | ||
9258331e9c | |||
62e89c1bd3 | |||
1fbd00b43d | |||
4449a48de7 | |||
4e6b1c421f | |||
c04bd3bd7d | |||
|
cbabd8914c | ||
|
5fc831542f | ||
|
b0019da9dd | ||
|
3bb0ac29e2 | ||
|
329e42c5d4 | ||
|
ecb91712fd | ||
e334735697 |
106
README.md
106
README.md
@ -1,88 +1,34 @@
|
||||
# [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime)
|
||||
# TODO
|
||||
## Completed
|
||||
- [x] Make $s in terminal watch face green
|
||||
- [x] Swipe left to access music control
|
||||
- [x] Flashlight starts on
|
||||
- [x] Calculator
|
||||
- [x] Redo music control gestures
|
||||
- [x] Swipe up toggles control mode and swipe down exits
|
||||
- [x] Swipe left/right goes to watchface/another app
|
||||
- [x] Rearrange quick acces and apps
|
||||
- [x] Exponent button on calculator
|
||||
|
||||
![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo")
|
||||
## Pending merge
|
||||
|
||||
Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/pinetime/) with many features, written in modern C++.
|
||||
## In progress
|
||||
### Elijah
|
||||
- [ ] Countdown timer presistant buzz
|
||||
|
||||
## New to InfiniTime?
|
||||
### Josh
|
||||
- [ ] Seconds on digital watchface
|
||||
- [ ] Battery on digital watchface
|
||||
- [ ] Remap button double click (to music app)
|
||||
|
||||
- [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md)
|
||||
- [Updating the software](doc/gettingStarted/updating-software.md)
|
||||
- [About the firmware and bootloader](doc/gettingStarted/about-software.md)
|
||||
- [PineTimeStyle Watch face](https://wiki.pine64.org/wiki/PineTimeStyle)
|
||||
- [Weather integration](https://wiki.pine64.org/wiki/Infinitime-Weather)
|
||||
### Moses
|
||||
|
||||
### Companion apps
|
||||
|
||||
- [Gadgetbridge](https://gadgetbridge.org/) (Android)
|
||||
- [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS)
|
||||
- [Siglo](https://github.com/alexr4535/siglo) (Linux)
|
||||
- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS) **[Looking for a new maintainer]**
|
||||
- [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux)
|
||||
- [WatchMate](https://github.com/azymohliad/watchmate) (Linux)
|
||||
- [ ] Add quick ring settings
|
||||
|
||||
***Note** : We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
|
||||
## Medium priority
|
||||
- [ ] Temp sensor w/ arduino
|
||||
- [ ] Homeassistant control
|
||||
|
||||
## Development
|
||||
|
||||
- [InfiniTime Vision](doc/InfiniTimeVision.md)
|
||||
- [Rough structure of the code](doc/code/Intro.md)
|
||||
- [How to implement an application](doc/code/Apps.md)
|
||||
- [Generate the fonts and symbols](src/displayapp/fonts/README.md)
|
||||
- [Tips on designing an app UI](doc/ui_guidelines.md)
|
||||
- [Bootloader, OTA and DFU](bootloader/README.md)
|
||||
- [External resources](doc/ExternalResources.md)
|
||||
|
||||
### Contributing
|
||||
|
||||
- [How to contribute?](CONTRIBUTING.md)
|
||||
- [Coding conventions](doc/coding-convention.md)
|
||||
|
||||
### Build, flash and debug
|
||||
|
||||
- [InfiniTime simulator](https://github.com/InfiniTimeOrg/InfiniSim)
|
||||
- [Build the project](doc/buildAndProgram.md)
|
||||
- [Build the project with Docker](doc/buildWithDocker.md)
|
||||
- [Build the project with VSCode](doc/buildWithVScode.md)
|
||||
- [Flash the firmware using OpenOCD and STLinkV2](doc/openOCD.md)
|
||||
- [Flash the firmware using SWD interface](doc/SWD.md)
|
||||
- [Flash the firmware using JLink](doc/jlink.md)
|
||||
- [Flash the firmware using GDB](doc/gdb.md)
|
||||
- [Stub using NRF52-DK](doc/PinetimeStubWithNrf52DK.md)
|
||||
|
||||
### API
|
||||
|
||||
- [BLE implementation and API](doc/ble.md)
|
||||
|
||||
### Architecture and technical topics
|
||||
|
||||
- [Memory analysis](doc/MemoryAnalysis.md)
|
||||
|
||||
### Project management
|
||||
|
||||
- [Maintainer's guide](doc/maintainer-guide.md)
|
||||
- [Versioning](doc/versioning.md)
|
||||
- [Project branches](doc/branches.md)
|
||||
- [Files included in the release notes](doc/filesInReleaseNotes.md)
|
||||
- [Files needed by the factory](doc/files-needed-by-factory.md)
|
||||
|
||||
## Licenses
|
||||
|
||||
This project is released under the GNU General Public License version 3 or, at your option, any later version.
|
||||
|
||||
It integrates the following projects:
|
||||
|
||||
- RTOS : **[FreeRTOS](https://freertos.org)** under the MIT license
|
||||
- UI : **[LittleVGL/LVGL](https://lvgl.io/)** under the MIT license
|
||||
- BLE stack : **[NimBLE](https://github.com/apache/mynewt-nimble)** under the Apache 2.0 license
|
||||
- Font : **[Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)** under the Apache 2.0 license
|
||||
|
||||
## Credits
|
||||
|
||||
I’m not working alone on this project. First, many people create PR for this project. Then, there is the whole #pinetime community : a lot of people all around the world who are hacking, searching, experimenting and programming the Pinetime. We exchange our ideas, experiments and code in the chat rooms and forums.
|
||||
|
||||
Here are some people I would like to highlight:
|
||||
|
||||
- [Atc1441](https://github.com/atc1441/) : He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver.
|
||||
- [Koen](https://github.com/bosmoment) : He’s working on a firmware based on RiotOS. He integrated similar libs as me : NimBLE, LittleVGL,… His help was invaluable too!
|
||||
- [Lup Yuen Lee](https://github.com/lupyuen) : He is everywhere: he works on a Rust firmware, builds a MCUBoot based bootloader for the Pinetime, designs a Flutter based companion app for smartphones and writes a lot of articles about the Pinetime!
|
||||
## Lower priority
|
||||
- [ ] Maybe some prank watchfaces
|
||||
|
88
README_orig.md
Normal file
88
README_orig.md
Normal file
@ -0,0 +1,88 @@
|
||||
# [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime)
|
||||
|
||||
![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo")
|
||||
|
||||
Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/pinetime/) with many features, written in modern C++.
|
||||
|
||||
## New to InfiniTime?
|
||||
|
||||
- [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md)
|
||||
- [Updating the software](doc/gettingStarted/updating-software.md)
|
||||
- [About the firmware and bootloader](doc/gettingStarted/about-software.md)
|
||||
- [PineTimeStyle Watch face](https://wiki.pine64.org/wiki/PineTimeStyle)
|
||||
- [Weather integration](https://wiki.pine64.org/wiki/Infinitime-Weather)
|
||||
|
||||
### Companion apps
|
||||
|
||||
- [Gadgetbridge](https://gadgetbridge.org/) (Android)
|
||||
- [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS)
|
||||
- [Siglo](https://github.com/alexr4535/siglo) (Linux)
|
||||
- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS) **[Looking for a new maintainer]**
|
||||
- [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux)
|
||||
- [WatchMate](https://github.com/azymohliad/watchmate) (Linux)
|
||||
|
||||
***Note** : We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
|
||||
|
||||
## Development
|
||||
|
||||
- [InfiniTime Vision](doc/InfiniTimeVision.md)
|
||||
- [Rough structure of the code](doc/code/Intro.md)
|
||||
- [How to implement an application](doc/code/Apps.md)
|
||||
- [Generate the fonts and symbols](src/displayapp/fonts/README.md)
|
||||
- [Tips on designing an app UI](doc/ui_guidelines.md)
|
||||
- [Bootloader, OTA and DFU](bootloader/README.md)
|
||||
- [External resources](doc/ExternalResources.md)
|
||||
|
||||
### Contributing
|
||||
|
||||
- [How to contribute?](CONTRIBUTING.md)
|
||||
- [Coding conventions](doc/coding-convention.md)
|
||||
|
||||
### Build, flash and debug
|
||||
|
||||
- [InfiniTime simulator](https://github.com/InfiniTimeOrg/InfiniSim)
|
||||
- [Build the project](doc/buildAndProgram.md)
|
||||
- [Build the project with Docker](doc/buildWithDocker.md)
|
||||
- [Build the project with VSCode](doc/buildWithVScode.md)
|
||||
- [Flash the firmware using OpenOCD and STLinkV2](doc/openOCD.md)
|
||||
- [Flash the firmware using SWD interface](doc/SWD.md)
|
||||
- [Flash the firmware using JLink](doc/jlink.md)
|
||||
- [Flash the firmware using GDB](doc/gdb.md)
|
||||
- [Stub using NRF52-DK](doc/PinetimeStubWithNrf52DK.md)
|
||||
|
||||
### API
|
||||
|
||||
- [BLE implementation and API](doc/ble.md)
|
||||
|
||||
### Architecture and technical topics
|
||||
|
||||
- [Memory analysis](doc/MemoryAnalysis.md)
|
||||
|
||||
### Project management
|
||||
|
||||
- [Maintainer's guide](doc/maintainer-guide.md)
|
||||
- [Versioning](doc/versioning.md)
|
||||
- [Project branches](doc/branches.md)
|
||||
- [Files included in the release notes](doc/filesInReleaseNotes.md)
|
||||
- [Files needed by the factory](doc/files-needed-by-factory.md)
|
||||
|
||||
## Licenses
|
||||
|
||||
This project is released under the GNU General Public License version 3 or, at your option, any later version.
|
||||
|
||||
It integrates the following projects:
|
||||
|
||||
- RTOS : **[FreeRTOS](https://freertos.org)** under the MIT license
|
||||
- UI : **[LittleVGL/LVGL](https://lvgl.io/)** under the MIT license
|
||||
- BLE stack : **[NimBLE](https://github.com/apache/mynewt-nimble)** under the Apache 2.0 license
|
||||
- Font : **[Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)** under the Apache 2.0 license
|
||||
|
||||
## Credits
|
||||
|
||||
I’m not working alone on this project. First, many people create PR for this project. Then, there is the whole #pinetime community : a lot of people all around the world who are hacking, searching, experimenting and programming the Pinetime. We exchange our ideas, experiments and code in the chat rooms and forums.
|
||||
|
||||
Here are some people I would like to highlight:
|
||||
|
||||
- [Atc1441](https://github.com/atc1441/) : He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver.
|
||||
- [Koen](https://github.com/bosmoment) : He’s working on a firmware based on RiotOS. He integrated similar libs as me : NimBLE, LittleVGL,… His help was invaluable too!
|
||||
- [Lup Yuen Lee](https://github.com/lupyuen) : He is everywhere: he works on a Rust firmware, builds a MCUBoot based bootloader for the Pinetime, designs a Flutter based companion app for smartphones and writes a lot of articles about the Pinetime!
|
@ -33,7 +33,7 @@ For cloning the repo, see [these instructions](../doc/buildAndProgram.md#clone-t
|
||||
|
||||
```bash
|
||||
cd <project_root> # e.g. cd ./work/Pinetime
|
||||
docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) infinitime-build
|
||||
docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) infinitime/infinitime-build
|
||||
```
|
||||
|
||||
By default, the container runs as `root`, which is not convenient as all the files generated by the build will also belong to `root`.
|
||||
@ -45,7 +45,7 @@ This means calling the script explicitly as it will override the `CMD`.
|
||||
Here's an example for `pinetime-app`:
|
||||
|
||||
```bash
|
||||
docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) infinitime-build /opt/build.sh pinetime-app
|
||||
docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) infinitime/infinitime-build /opt/build.sh pinetime-app
|
||||
```
|
||||
|
||||
## Using the image from Docker Hub
|
||||
|
94
enviroment_setup.md
Executable file
94
enviroment_setup.md
Executable file
@ -0,0 +1,94 @@
|
||||
## Clone repositories
|
||||
1. Clone InfiniTime:
|
||||
```bash
|
||||
git clone https://github.com/InfiniTimeOrg/InfiniTime.git
|
||||
cd InfiniTime
|
||||
git submodule update --init
|
||||
```
|
||||
|
||||
2. Clone InfiniSim:
|
||||
```bash
|
||||
cd .. #Ignore this line if you already cd'd into the diretory you want it cloned into
|
||||
git clone --recursive https://github.com/InfiniTimeOrg/InfiniSim.git
|
||||
```
|
||||
|
||||
## Install dependencies
|
||||
### For building InfiniTime
|
||||
1. Install Docker
|
||||
2. Pull docker container (optional, container will be pulled when running the docker container if not pulled now).
|
||||
```bash
|
||||
sudo docker pull infinitime/infinitime-build
|
||||
```
|
||||
|
||||
### For building InfiniSim
|
||||
This was the most difficult part for me to get right since the steps on the github page are not entirely complete for my setup. If you encounter errors while building, it's probably because one of the dependencies are missing or have the wrong version installed. If you do have an error while building, check out the issues on github and see if you can find some help there: https://github.com/InfiniTimeOrg/InfiniSim/issues
|
||||
1. The apt install part. This is almost exactly the same as what is on the github page except for npm. When I added npm in to here, I got an older version that didn't work. We'll install the correct version in step 2.
|
||||
```bash
|
||||
sudo apt install -y cmake libsdl2-dev g++ libpng-dev
|
||||
```
|
||||
|
||||
2. Install the correct node version. (At least v14.0.0 is required). Commands from https://github.com/nodesource/distributions First, choose the node version by running one of these lines:
|
||||
```bash
|
||||
NODE_MAJOR=16
|
||||
NODE_MAJOR=18
|
||||
NODE_MAJOR=20
|
||||
```
|
||||
I'm not sure which is the best to use, I went with 16 since that is the closest to the version required by InfiniSim, but I doubt it makes much of a difference. Once you've done that, then install it with these commands:
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ca-certificates curl gnupg
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
|
||||
NODE_MAJOR=20
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install nodejs -y
|
||||
```
|
||||
|
||||
3. Install npm packages
|
||||
```bash
|
||||
npm install lv_font_conv@1.5.2 -g
|
||||
npm install ts-node@10.9.1 @swc/core@1.3.82 lv_img_conv@0.3.0 -g
|
||||
```
|
||||
|
||||
|
||||
## Build InfiniSim
|
||||
### Building with stock InfiniTime
|
||||
Now to see if we did everything the way our computer wants it. First cd into the directory where InfinSim was cloned (`cd InfiniSim` if you are still in the directory you started in). Now you should be able to build InfiniSim with the InfiniTime version that was included with InfiniSim:
|
||||
```bash
|
||||
cmake -S . -B build
|
||||
cmake --build build -j4
|
||||
```
|
||||
|
||||
If that didn't produce any error, now run it!
|
||||
```bash
|
||||
./build/infinisim
|
||||
```
|
||||
|
||||
### Building with your own version of InfiniTime
|
||||
To test your own modifications of InfiniTime:
|
||||
```bash
|
||||
cmake -S . -B build -DInfiniTime_DIR=../InfiniTime
|
||||
cmake --build build -j4
|
||||
```
|
||||
Assuming that the git clone command was run in the same directory for both InfiniSim and InfiniTime. If not, replace `../InfiniTime` with the path to the directory where InfiniTime was cloned to.
|
||||
And now run it if there were no errors!
|
||||
```bash
|
||||
./build/infinisim
|
||||
```
|
||||
|
||||
## Build InfiniTime
|
||||
Now is the fun part, building your own version of InfiniTime and flashing it to your watch!
|
||||
First, cd into the directory into which InfiniTime cloned (`cd ~/InfiniTime` if you cloned from your home directory). Now run the docker container (for more info on this command, see https://github.com/InfiniTimeOrg/InfiniTime/blob/main/doc/buildWithDocker.md):
|
||||
```bash
|
||||
sudo docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) infinitime/infinitime-build
|
||||
```
|
||||
|
||||
If that doesn't have any errors, it will save the files to build/output/. The file you will want to flash to your watch is `pinetime-mcuboot-app-dfu-x.x.x.zip`. I found a pretty nice program called WatchMate that you can use to flash it from linux, but I think you can also use Gadgetbridge to flash the file. In WatchMate, just scroll down to the "Firmware Version" dropdown and click on "Upload from file" >
|
||||
"Firmware" and select the zip file mentioned above. Once that's done, you may also want to send it the `infinitime-resources-x.x.x.zip` if some features are greyed out on the watch (for me it was a couple watch faces and the navigation app that were greyed out). In WatchMate, you'll want to use the "Resources" option instead of the "Firmware" option to upload this file.
|
||||
|
||||
## Celebrate
|
||||
There you go, you've just flashed your own custom os to your watch! If you got all this far without running into any error, you have something to celebrate about as it took me a couple of days to get to this point. If you did have some errors and figured them out, then great, you've got it all figured out now!
|
||||
One final thing, I would recommend making use of git commits and branches so that you can revert if you totally mess something up and don't have to clone the entire repository and dependencies again like I had to do several times before I decided to start doing that.
|
@ -393,6 +393,7 @@ list(APPEND SOURCE_FILES
|
||||
displayapp/screens/ApplicationList.cpp
|
||||
displayapp/screens/Notifications.cpp
|
||||
displayapp/screens/Twos.cpp
|
||||
displayapp/screens/Calculator.cpp
|
||||
displayapp/screens/HeartRate.cpp
|
||||
displayapp/screens/Motion.cpp
|
||||
displayapp/screens/FlashLight.cpp
|
||||
@ -433,6 +434,7 @@ list(APPEND SOURCE_FILES
|
||||
displayapp/screens/WatchFaceTerminal.cpp
|
||||
displayapp/screens/WatchFacePineTimeStyle.cpp
|
||||
displayapp/screens/WatchFaceCasioStyleG7710.cpp
|
||||
displayapp/screens/WatchFaceWordClock.cpp
|
||||
|
||||
##
|
||||
|
||||
|
@ -5,14 +5,58 @@
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
namespace {
|
||||
TimerHandle_t vibTimer;
|
||||
|
||||
void PatternStep(TimerHandle_t xTimer) {
|
||||
/* Vibration pattern format:
|
||||
* {
|
||||
* durationOfVibration,
|
||||
* durationOfPause,
|
||||
* durationOfVibration,
|
||||
* durationOfPause,
|
||||
* ...,
|
||||
* durationOfVibration,
|
||||
* zeroTerminator
|
||||
* }
|
||||
*
|
||||
* Patterns can be any length
|
||||
* The pattern must end with a duration of vibration and a terminator.
|
||||
*/
|
||||
|
||||
static constexpr uint8_t vibrationPattern[] = {10, 100, 50, 200, 10, 0};
|
||||
|
||||
static size_t patternPosition = 0;
|
||||
if (vibrationPattern[patternPosition] != 0 && xTimerChangePeriod(vibTimer, vibrationPattern[patternPosition] << 1, 0) == pdPASS &&
|
||||
xTimerStart(vibTimer, 0) == pdPASS) {
|
||||
if (patternPosition % 2 == 0) {
|
||||
nrf_gpio_pin_clear(Pinetime::PinMap::Motor);
|
||||
} else {
|
||||
nrf_gpio_pin_set(Pinetime::PinMap::Motor);
|
||||
}
|
||||
patternPosition++;
|
||||
} else {
|
||||
patternPosition = 0;
|
||||
nrf_gpio_pin_set(Pinetime::PinMap::Motor);
|
||||
auto* motorController = static_cast<MotorController*>(pvTimerGetTimerID(xTimer));
|
||||
motorController->PatternFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MotorController::Init() {
|
||||
nrf_gpio_cfg_output(PinMap::Motor);
|
||||
nrf_gpio_pin_set(PinMap::Motor);
|
||||
|
||||
vibTimer = xTimerCreate("vibration", 1, pdFALSE, this, PatternStep);
|
||||
shortVib = xTimerCreate("shortVib", 1, pdFALSE, nullptr, StopMotor);
|
||||
longVib = xTimerCreate("longVib", pdMS_TO_TICKS(1000), pdTRUE, this, Ring);
|
||||
}
|
||||
|
||||
void MotorController::PatternFinished() {
|
||||
patternPlaying = false;
|
||||
}
|
||||
|
||||
void MotorController::Ring(TimerHandle_t xTimer) {
|
||||
auto* motorController = static_cast<MotorController*>(pvTimerGetTimerID(xTimer));
|
||||
motorController->RunForDuration(50);
|
||||
@ -24,6 +68,15 @@ void MotorController::RunForDuration(uint8_t motorDuration) {
|
||||
}
|
||||
}
|
||||
|
||||
bool MotorController::StartPattern() {
|
||||
if (!patternPlaying) {
|
||||
patternPlaying = true;
|
||||
PatternStep(vibTimer);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MotorController::StartRinging() {
|
||||
RunForDuration(50);
|
||||
xTimerStart(longVib, 0);
|
||||
|
@ -15,10 +15,13 @@ namespace Pinetime {
|
||||
void RunForDuration(uint8_t motorDuration);
|
||||
void StartRinging();
|
||||
void StopRinging();
|
||||
void PatternFinished();
|
||||
bool StartPattern();
|
||||
|
||||
private:
|
||||
static void Ring(TimerHandle_t xTimer);
|
||||
static void StopMotor(TimerHandle_t xTimer);
|
||||
bool patternPlaying;
|
||||
TimerHandle_t shortVib;
|
||||
TimerHandle_t longVib;
|
||||
};
|
||||
|
@ -37,7 +37,8 @@ namespace Pinetime {
|
||||
SettingChimes,
|
||||
SettingShakeThreshold,
|
||||
SettingBluetooth,
|
||||
Error
|
||||
Error,
|
||||
Calculator
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,9 @@
|
||||
#include "displayapp/screens/Error.h"
|
||||
#include "displayapp/screens/Weather.h"
|
||||
|
||||
// My New Apps
|
||||
#include "displayapp/screens/Calculator.h"
|
||||
|
||||
#include "drivers/Cst816s.h"
|
||||
#include "drivers/St7789.h"
|
||||
#include "drivers/Watchdog.h"
|
||||
@ -289,17 +292,45 @@ void DisplayApp::Refresh() {
|
||||
return TouchEvents::SwipeLeft;
|
||||
}
|
||||
};
|
||||
|
||||
if (!currentScreen->OnTouchEvent(gesture)) {
|
||||
if (currentApp == Apps::Clock) {
|
||||
if (currentApp == Apps::Clock || currentApp == Apps::Music || currentApp == Apps::Calculator || currentApp == Apps::QuickSettings) {
|
||||
switch (gesture) {
|
||||
case TouchEvents::SwipeUp:
|
||||
if (currentApp == Apps::Clock) {
|
||||
LoadNewScreen(Apps::Launcher, DisplayApp::FullRefreshDirections::Up);
|
||||
} else if (currentApp == Apps::QuickSettings) {
|
||||
LoadNewScreen(Apps::Settings, DisplayApp::FullRefreshDirections::Up);
|
||||
}
|
||||
break;
|
||||
case TouchEvents::SwipeDown:
|
||||
if (currentApp == Apps::Clock) {
|
||||
LoadNewScreen(Apps::Notifications, DisplayApp::FullRefreshDirections::Down);
|
||||
} else if (currentApp == Apps::QuickSettings) {
|
||||
LoadNewScreen(Apps::FlashLight, DisplayApp::FullRefreshDirections::Down);
|
||||
}
|
||||
break;
|
||||
case TouchEvents::SwipeRight:
|
||||
if (currentApp == Apps::Clock) {
|
||||
LoadNewScreen(Apps::QuickSettings, DisplayApp::FullRefreshDirections::RightAnim);
|
||||
} else if (currentApp == Apps::QuickSettings) {
|
||||
LoadNewScreen(Apps::Calculator, DisplayApp::FullRefreshDirections::RightAnim);
|
||||
} else if (currentApp == Apps::Calculator) {
|
||||
LoadNewScreen(Apps::Music, DisplayApp::FullRefreshDirections::RightAnim);
|
||||
} else {
|
||||
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::RightAnim);
|
||||
}
|
||||
break;
|
||||
case TouchEvents::SwipeLeft:
|
||||
if (currentApp == Apps::Clock) {
|
||||
LoadNewScreen(Apps::Music, DisplayApp::FullRefreshDirections::LeftAnim);
|
||||
} else if (currentApp == Apps::Music) {
|
||||
LoadNewScreen(Apps::Calculator, DisplayApp::FullRefreshDirections::LeftAnim);
|
||||
} else if (currentApp == Apps::Calculator) {
|
||||
LoadNewScreen(Apps::QuickSettings, DisplayApp::FullRefreshDirections::LeftAnim);
|
||||
} else {
|
||||
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::LeftAnim);
|
||||
}
|
||||
break;
|
||||
case TouchEvents::DoubleTap:
|
||||
PushMessageToSystemTask(System::Messages::GoToSleep);
|
||||
@ -358,7 +389,18 @@ void DisplayApp::Refresh() {
|
||||
break;
|
||||
case Messages::Chime:
|
||||
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
|
||||
motorController.RunForDuration(35);
|
||||
time_var = dateTimeController.Minutes();
|
||||
if (time_var == 30){
|
||||
NRF_LOG_INFO("Short: %d", time_var);
|
||||
motorController.StartPattern();
|
||||
}
|
||||
else
|
||||
{
|
||||
NRF_LOG_INFO("Long: %d", time_var);
|
||||
motorController.RunForDuration(200);
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
case Messages::OnChargingEvent:
|
||||
RestoreBrightness();
|
||||
@ -548,6 +590,10 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||
case Apps::Steps:
|
||||
currentScreen = std::make_unique<Screens::Steps>(motionController, settingsController);
|
||||
break;
|
||||
// My Apps
|
||||
case Apps::Calculator:
|
||||
currentScreen = std::make_unique<Screens::Calculator>();
|
||||
break;
|
||||
}
|
||||
currentApp = app;
|
||||
}
|
||||
|
@ -128,6 +128,7 @@ namespace Pinetime {
|
||||
Utility::StaticStack<Apps, returnAppStackSize> returnAppStack;
|
||||
Utility::StaticStack<FullRefreshDirections, returnAppStackSize> appStackDirections;
|
||||
|
||||
int time_var;
|
||||
bool isDimmed = false;
|
||||
};
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
},
|
||||
{
|
||||
"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"
|
||||
"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, 0xf1ec, 0xf55a"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
|
@ -55,7 +55,8 @@ namespace Pinetime {
|
||||
{"2", Apps::Twos, true},
|
||||
{Symbols::drum, Apps::Metronome, true},
|
||||
{Symbols::map, Apps::Navigation, Applications::Screens::Navigation::IsAvailable(filesystem)},
|
||||
{Symbols::none, Apps::None, false},
|
||||
{Symbols::calculator, Apps::Calculator, true},
|
||||
//{Symbols::none, Apps::None, false},
|
||||
|
||||
// {"M", Apps::Motion},
|
||||
}};
|
||||
|
442
src/displayapp/screens/Calculator.cpp
Normal file
442
src/displayapp/screens/Calculator.cpp
Normal file
@ -0,0 +1,442 @@
|
||||
#include <cmath>
|
||||
#include <cinttypes>
|
||||
#include <libraries/log/nrf_log.h>
|
||||
#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());
|
||||
}
|
||||
|
||||
static const char* buttonMap[] = {
|
||||
"7", "8", "9", "^", "\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, -30, 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, -30, 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);
|
||||
|
||||
// BackSpace Button
|
||||
|
||||
back = lv_btn_create(lv_scr_act(), nullptr);
|
||||
back->user_data = this;
|
||||
lv_obj_set_event_cb(back, eventHandler);
|
||||
lv_obj_set_size(back, 59, 44);
|
||||
lv_obj_align(back, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 179, 16);
|
||||
txtback = lv_label_create(back, nullptr);
|
||||
lv_obj_set_style_local_bg_color(back, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Colors::bgAlt);
|
||||
lv_label_set_text_static(txtback, Symbols::backspace);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void Calculator::OnButtonEvent(lv_obj_t* obj, lv_event_t event) {
|
||||
if ((obj == buttonMatrix) && (event == LV_EVENT_PRESSED)) {
|
||||
HandleInput();
|
||||
}
|
||||
|
||||
if (obj == back && event == LV_EVENT_PRESSED) {
|
||||
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;
|
||||
}
|
||||
operation = ' ';
|
||||
UpdateOperation();
|
||||
UpdateValueLabel();
|
||||
UpdateResultLabel();
|
||||
}
|
||||
}
|
||||
|
||||
void Calculator::HandleInput() {
|
||||
const char* buttonText = lv_btnmatrix_get_active_btn_text(buttonMatrix);
|
||||
|
||||
|
||||
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;
|
||||
|
||||
// Decimal
|
||||
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;
|
||||
default:
|
||||
operation = ' ';
|
||||
break;
|
||||
}
|
||||
UpdateOperation();
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
NRF_LOG_INFO("Operation End: %c", operation);
|
||||
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;
|
||||
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::deepOrange);
|
||||
lv_btnmatrix_set_btn_ctrl(buttonMatrix, 3, 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 = ' ';
|
||||
equalSignPressed = 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 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_label_set_text_fmt(resultLabel, "%" PRId64 ".%0*" PRId64, integer, min_width, 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 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
case '^':
|
||||
// check for zero division
|
||||
if (value == 0) {
|
||||
error = Error::ZeroDivision;
|
||||
break;
|
||||
}
|
||||
place_holder = result;
|
||||
place_holder /= FIXED_POINT_OFFSET;
|
||||
result = pow(place_holder, (value*0.0001))*10000;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
71
src/displayapp/screens/Calculator.h
Normal file
71
src/displayapp/screens/Calculator.h
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#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 Calculator : public Screen {
|
||||
public:
|
||||
~Calculator() override;
|
||||
|
||||
Calculator();
|
||||
|
||||
void OnButtonEvent(lv_obj_t* obj, lv_event_t event);
|
||||
|
||||
private:
|
||||
lv_obj_t* buttonMatrix {};
|
||||
lv_obj_t* valueLabel {};
|
||||
lv_obj_t* resultLabel {};
|
||||
lv_obj_t* back;
|
||||
lv_obj_t* txtback;
|
||||
double place_holder;
|
||||
|
||||
void Eval();
|
||||
void ResetInput();
|
||||
void HandleInput();
|
||||
void UpdateValueLabel();
|
||||
void UpdateResultLabel() const;
|
||||
void UpdateOperation() const;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
#include "displayapp/screens/WatchFaceAnalog.h"
|
||||
#include "displayapp/screens/WatchFacePineTimeStyle.h"
|
||||
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
||||
#include "displayapp/screens/WatchFaceWordClock.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
using namespace Pinetime::Applications;
|
||||
@ -55,6 +56,9 @@ Clock::Clock(Controllers::DateTime& dateTimeController,
|
||||
case WatchFace::CasioStyleG7710:
|
||||
return WatchFaceCasioStyleG7710();
|
||||
break;
|
||||
case WatchFace::WatchFaceWordClock:
|
||||
return WatchFaceWordClock();
|
||||
break;
|
||||
}
|
||||
return WatchFaceDigitalScreen();
|
||||
}()} {
|
||||
@ -131,3 +135,15 @@ std::unique_ptr<Screen> Clock::WatchFaceCasioStyleG7710() {
|
||||
motionController,
|
||||
filesystem);
|
||||
}
|
||||
|
||||
std::unique_ptr<Screen> Clock::WatchFaceWordClock() {
|
||||
return std::make_unique<Screens::WatchFaceWordClock>(dateTimeController,
|
||||
batteryController,
|
||||
bleController,
|
||||
notificationManager,
|
||||
settingsController,
|
||||
heartRateController,
|
||||
motionController,
|
||||
filesystem);
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,7 @@ namespace Pinetime {
|
||||
std::unique_ptr<Screen> WatchFaceTerminalScreen();
|
||||
std::unique_ptr<Screen> WatchFaceInfineatScreen();
|
||||
std::unique_ptr<Screen> WatchFaceCasioStyleG7710();
|
||||
std::unique_ptr<Screen> WatchFaceWordClock();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "displayapp/DisplayApp.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
#include "displayapp/InfiniTimeTheme.h"
|
||||
#include <libraries/log/nrf_log.h>
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
@ -47,6 +48,8 @@ FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessCo
|
||||
lv_obj_set_event_cb(backgroundAction, EventHandler);
|
||||
|
||||
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
|
||||
|
||||
Toggle();
|
||||
}
|
||||
|
||||
FlashLight::~FlashLight() {
|
||||
@ -56,8 +59,17 @@ FlashLight::~FlashLight() {
|
||||
}
|
||||
|
||||
void FlashLight::SetColors() {
|
||||
lv_color_t bgColor = isOn ? LV_COLOR_WHITE : LV_COLOR_BLACK;
|
||||
lv_color_t fgColor = isOn ? Colors::lightGray : LV_COLOR_WHITE;
|
||||
lv_color_t bgColor = LV_COLOR_BLACK;
|
||||
if(State_l == 0){
|
||||
bgColor = LV_COLOR_BLACK;
|
||||
} else if (State_l == 1){
|
||||
bgColor = LV_COLOR_WHITE;
|
||||
}
|
||||
else {
|
||||
bgColor = lv_color_hex(0xff0000);
|
||||
}
|
||||
|
||||
lv_color_t fgColor = State_l ? Colors::lightGray : LV_COLOR_WHITE;
|
||||
|
||||
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);
|
||||
@ -84,9 +96,11 @@ void FlashLight::SetIndicators() {
|
||||
}
|
||||
|
||||
void FlashLight::Toggle() {
|
||||
isOn = !isOn;
|
||||
State_l++;
|
||||
if(State_l > 2)
|
||||
State_l = 0;
|
||||
SetColors();
|
||||
if (isOn) {
|
||||
if (State_l) {
|
||||
brightnessController.Set(brightnessLevel);
|
||||
} else {
|
||||
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
|
||||
@ -97,7 +111,7 @@ bool FlashLight::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
auto SetState = [this]() {
|
||||
if (isOn) {
|
||||
if (State_l) {
|
||||
brightnessController.Set(brightnessLevel);
|
||||
}
|
||||
SetIndicators();
|
||||
|
@ -26,12 +26,13 @@ namespace Pinetime {
|
||||
Pinetime::System::SystemTask& systemTask;
|
||||
Controllers::BrightnessController& brightnessController;
|
||||
|
||||
|
||||
Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High;
|
||||
|
||||
lv_obj_t* flashLight;
|
||||
lv_obj_t* backgroundAction;
|
||||
lv_obj_t* indicators[3];
|
||||
bool isOn = false;
|
||||
int State_l = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,8 @@ inline void lv_img_set_src_arr(lv_obj_t* img, const lv_img_dsc_t* src_img) {
|
||||
*
|
||||
* TODO: Investigate Apple Media Service and AVRCPv1.6 support for seamless integration
|
||||
*/
|
||||
|
||||
|
||||
Music::Music(Pinetime::Controllers::MusicService& music) : musicService(music) {
|
||||
lv_obj_t* label;
|
||||
|
||||
@ -250,6 +252,13 @@ void Music::OnObjectEvent(lv_obj_t* obj, lv_event_t event) {
|
||||
|
||||
bool Music::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
switch (event) {
|
||||
case TouchEvents::SwipeDown: {
|
||||
lv_obj_set_hidden(btnNext, false);
|
||||
lv_obj_set_hidden(btnPrev, false);
|
||||
lv_obj_set_hidden(btnVolDown, true);
|
||||
lv_obj_set_hidden(btnVolUp, true);
|
||||
return true;
|
||||
}
|
||||
case TouchEvents::SwipeUp: {
|
||||
lv_obj_set_hidden(btnVolDown, false);
|
||||
lv_obj_set_hidden(btnVolUp, false);
|
||||
@ -258,24 +267,6 @@ bool Music::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
lv_obj_set_hidden(btnPrev, true);
|
||||
return true;
|
||||
}
|
||||
case TouchEvents::SwipeDown: {
|
||||
if (lv_obj_get_hidden(btnNext)) {
|
||||
lv_obj_set_hidden(btnNext, false);
|
||||
lv_obj_set_hidden(btnPrev, false);
|
||||
lv_obj_set_hidden(btnVolDown, true);
|
||||
lv_obj_set_hidden(btnVolUp, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case TouchEvents::SwipeLeft: {
|
||||
musicService.event(Controllers::MusicService::EVENT_MUSIC_NEXT);
|
||||
return true;
|
||||
}
|
||||
case TouchEvents::SwipeRight: {
|
||||
musicService.event(Controllers::MusicService::EVENT_MUSIC_PREV);
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <FreeRTOS.h>
|
||||
#include "displayapp/DisplayApp.h"
|
||||
#include <lvgl/src/lv_core/lv_obj.h>
|
||||
#include <string>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
|
@ -37,6 +37,10 @@ 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";
|
||||
static constexpr const char* backspace = "\xEF\x95\x9A";
|
||||
|
||||
|
||||
|
||||
// fontawesome_weathericons.c
|
||||
// static constexpr const char* sun = "\xEF\x86\x85";
|
||||
|
@ -10,8 +10,11 @@
|
||||
#include "components/motion/MotionController.h"
|
||||
#include "components/settings/Settings.h"
|
||||
|
||||
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
|
||||
WatchFaceTerminal::WatchFaceTerminal(Controllers::DateTime& dateTimeController,
|
||||
const Controllers::Battery& batteryController,
|
||||
const Controllers::Ble& bleController,
|
||||
@ -43,12 +46,14 @@ WatchFaceTerminal::WatchFaceTerminal(Controllers::DateTime& dateTimeController,
|
||||
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -40);
|
||||
|
||||
label_prompt_1 = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_recolor(label_prompt_1, true);
|
||||
lv_obj_align(label_prompt_1, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -80);
|
||||
lv_label_set_text_static(label_prompt_1, "user@watch:~ $ now");
|
||||
lv_label_set_text_static(label_prompt_1, "user@watch:~ #00ff00 $# now");
|
||||
|
||||
label_prompt_2 = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_recolor(label_prompt_2, true);
|
||||
lv_obj_align(label_prompt_2, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 60);
|
||||
lv_label_set_text_static(label_prompt_2, "user@watch:~ $");
|
||||
lv_label_set_text_static(label_prompt_2, "user@watch:~ #00ff00 $#");
|
||||
|
||||
label_time = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_recolor(label_time, true);
|
||||
@ -144,6 +149,8 @@ void WatchFaceTerminal::Refresh() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
stepCount = motionController.NbSteps();
|
||||
if (stepCount.IsUpdated()) {
|
||||
lv_label_set_text_fmt(stepValue, "[STEP]#ee3377 %lu steps#", stepCount.Get());
|
||||
|
310
src/displayapp/screens/WatchFaceWordClock.cpp
Normal file
310
src/displayapp/screens/WatchFaceWordClock.cpp
Normal file
@ -0,0 +1,310 @@
|
||||
#include "Clock.h"
|
||||
|
||||
#include <date/date.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <cstdio>
|
||||
#include "BatteryIcon.h"
|
||||
#include "BleIcon.h"
|
||||
#include "NotificationIcon.h"
|
||||
#include "Symbols.h"
|
||||
#include "components/battery/BatteryController.h"
|
||||
#include "components/ble/BleController.h"
|
||||
#include "components/ble/NotificationManager.h"
|
||||
#include "components/heartrate/HeartRateController.h"
|
||||
#include "../DisplayApp.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
static void event_handler(lv_obj_t * obj, lv_event_t event) {
|
||||
Clock* screen = static_cast<Clock *>(obj->user_data);
|
||||
screen->OnObjectEvent(obj, event);
|
||||
}
|
||||
WatchFaceDigital::WatchFaceWordClock(DisplayApp* app,
|
||||
Controllers::DateTime& dateTimeController,
|
||||
Controllers::Battery& batteryController,
|
||||
Controllers::Ble& bleController,
|
||||
Controllers::NotificationManager& notificatioManager,
|
||||
Controllers::HeartRateController& heartRateController): Screen(app), currentDateTime{{}},
|
||||
dateTimeController{dateTimeController}, batteryController{batteryController},
|
||||
bleController{bleController}, notificatioManager{notificatioManager},
|
||||
heartRateController{heartRateController} {
|
||||
displayedChar[0] = 0;
|
||||
displayedChar[1] = 0;
|
||||
displayedChar[2] = 0;
|
||||
displayedChar[3] = 0;
|
||||
displayedChar[4] = 0;
|
||||
|
||||
batteryIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text(batteryIcon, Symbols::batteryFull);
|
||||
lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 2);
|
||||
|
||||
batteryPlug = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text(batteryPlug, Symbols::plug);
|
||||
lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
|
||||
|
||||
bleIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text(bleIcon, Symbols::bluetooth);
|
||||
lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
|
||||
|
||||
notificationIcon = lv_label_create(lv_scr_act(), NULL);
|
||||
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
|
||||
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 10, 0);
|
||||
|
||||
label_date = lv_label_create(lv_scr_act(), nullptr);
|
||||
|
||||
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 10, 60);
|
||||
|
||||
label_time_shadow = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_recolor(label_time_shadow, true);
|
||||
lv_obj_align(label_time_shadow, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 7, -23);
|
||||
|
||||
label_time = lv_label_create(lv_scr_act(), nullptr);
|
||||
|
||||
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 5, -25);
|
||||
|
||||
backgroundLabel = lv_label_create(lv_scr_act(), nullptr);
|
||||
backgroundLabel->user_data = this;
|
||||
lv_obj_set_click(backgroundLabel, true);
|
||||
lv_obj_set_event_cb(backgroundLabel, event_handler);
|
||||
lv_label_set_long_mode(backgroundLabel, LV_LABEL_LONG_CROP);
|
||||
lv_obj_set_size(backgroundLabel, 240, 240);
|
||||
lv_obj_set_pos(backgroundLabel, 0, 0);
|
||||
lv_label_set_text(backgroundLabel, "");
|
||||
|
||||
|
||||
heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text(heartbeatIcon, Symbols::heartBeat);
|
||||
lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2);
|
||||
|
||||
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text(heartbeatValue, "0");
|
||||
lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
|
||||
|
||||
heartbeatBpm = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text(heartbeatBpm, "BPM");
|
||||
lv_obj_align(heartbeatBpm, heartbeatValue, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
|
||||
|
||||
stepValue = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text(stepValue, "0");
|
||||
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2);
|
||||
|
||||
stepIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text(stepIcon, Symbols::shoe);
|
||||
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
|
||||
}
|
||||
|
||||
Clock::~Clock() {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
bool Clock::Refresh() {
|
||||
batteryPercentRemaining = batteryController.PercentRemaining();
|
||||
if (batteryPercentRemaining.IsUpdated()) {
|
||||
auto batteryPercent = batteryPercentRemaining.Get();
|
||||
lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
|
||||
auto isCharging = batteryController.IsCharging() || batteryController.IsPowerPresent();
|
||||
lv_label_set_text(batteryPlug, BatteryIcon::GetPlugIcon(isCharging));
|
||||
}
|
||||
|
||||
bleState = bleController.IsConnected();
|
||||
if (bleState.IsUpdated()) {
|
||||
if(bleState.Get() == true) {
|
||||
lv_label_set_text(bleIcon, BleIcon::GetIcon(true));
|
||||
} else {
|
||||
lv_label_set_text(bleIcon, BleIcon::GetIcon(false));
|
||||
}
|
||||
}
|
||||
lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 5);
|
||||
lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
|
||||
lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
|
||||
|
||||
notificationState = notificatioManager.AreNewNotificationsAvailable();
|
||||
if(notificationState.IsUpdated()) {
|
||||
if(notificationState.Get() == true)
|
||||
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(true));
|
||||
else
|
||||
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
|
||||
}
|
||||
|
||||
currentDateTime = dateTimeController.CurrentDateTime();
|
||||
|
||||
if(currentDateTime.IsUpdated()) {
|
||||
auto newDateTime = currentDateTime.Get();
|
||||
|
||||
auto dp = date::floor<date::days>(newDateTime);
|
||||
auto time = date::make_time(newDateTime-dp);
|
||||
auto yearMonthDay = date::year_month_day(dp);
|
||||
|
||||
auto year = (int)yearMonthDay.year();
|
||||
auto month = static_cast<Pinetime::Controllers::DateTime::Months>((unsigned)yearMonthDay.month());
|
||||
auto day = (unsigned)yearMonthDay.day();
|
||||
auto dayOfWeek = static_cast<Pinetime::Controllers::DateTime::Days>(date::weekday(yearMonthDay).iso_encoding());
|
||||
|
||||
auto hour = time.hours().count();
|
||||
auto minute = time.minutes().count();
|
||||
|
||||
char minutesChar[3];
|
||||
sprintf(minutesChar, "%02d", static_cast<int>(minute));
|
||||
|
||||
char hoursChar[3];
|
||||
sprintf(hoursChar, "%02d", static_cast<int>(hour));
|
||||
|
||||
printwords(static_cast<int>(hour), static_cast<int>(minute));
|
||||
|
||||
if(hoursChar[0] != displayedChar[0] || hoursChar[1] != displayedChar[1] || minutesChar[0] != displayedChar[2] || minutesChar[1] != displayedChar[3]) {
|
||||
displayedChar[0] = hoursChar[0];
|
||||
displayedChar[1] = hoursChar[1];
|
||||
displayedChar[2] = minutesChar[0];
|
||||
displayedChar[3] = minutesChar[1];
|
||||
|
||||
lv_label_set_text(label_time_shadow, timeStrShadow);
|
||||
lv_label_set_text(label_time, timeStr);
|
||||
|
||||
}
|
||||
|
||||
if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) {
|
||||
char dateStr[22];
|
||||
sprintf(dateStr, "%s %d %s %d", DayOfWeekToString(dayOfWeek), day, MonthToString(month), year);
|
||||
lv_label_set_text(label_date, dateStr);
|
||||
|
||||
|
||||
currentYear = year;
|
||||
currentMonth = month;
|
||||
currentDayOfWeek = dayOfWeek;
|
||||
currentDay = day;
|
||||
}
|
||||
}
|
||||
|
||||
heartbeat = heartRateController.HeartRate();
|
||||
heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
|
||||
if(heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
|
||||
char heartbeatBuffer[4];
|
||||
if(heartbeatRunning.Get())
|
||||
sprintf(heartbeatBuffer, "%d", heartbeat.Get());
|
||||
else
|
||||
sprintf(heartbeatBuffer, "---");
|
||||
|
||||
lv_label_set_text(heartbeatValue, heartbeatBuffer);
|
||||
lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2);
|
||||
lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
|
||||
lv_obj_align(heartbeatBpm, heartbeatValue, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
|
||||
}
|
||||
|
||||
// TODO stepCount = stepController.GetValue();
|
||||
if(stepCount.IsUpdated()) {
|
||||
char stepBuffer[5];
|
||||
sprintf(stepBuffer, "%lu", stepCount.Get());
|
||||
lv_label_set_text(stepValue, stepBuffer);
|
||||
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2);
|
||||
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
|
||||
}
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
const char *Clock::MonthToString(Pinetime::Controllers::DateTime::Months month) {
|
||||
return Clock::MonthsString[static_cast<uint8_t>(month)];
|
||||
}
|
||||
|
||||
const char *Clock::DayOfWeekToString(Pinetime::Controllers::DateTime::Days dayOfWeek) {
|
||||
return Clock::DaysString[static_cast<uint8_t>(dayOfWeek)];
|
||||
}
|
||||
|
||||
char const *Clock::DaysString[] = {
|
||||
"",
|
||||
"mon.",
|
||||
"tue.",
|
||||
"wed.",
|
||||
"thu.",
|
||||
"fri.",
|
||||
"sat.",
|
||||
"sun."
|
||||
};
|
||||
|
||||
char const *Clock::MonthsString[] = {
|
||||
"",
|
||||
"jan",
|
||||
"feb",
|
||||
"mar",
|
||||
"apr",
|
||||
"may",
|
||||
"jun",
|
||||
"jul",
|
||||
"aug",
|
||||
"sep",
|
||||
"oct",
|
||||
"nov",
|
||||
"dec"
|
||||
};
|
||||
|
||||
char const *Clock::nums[] = { "zero", "one", "two", "three", "four",
|
||||
"five", "six", "seven", "eight", "nine",
|
||||
"ten", "eleven", "twelve", "thirteen",
|
||||
"fourteen", "fifteen", "sixteen", "seventeen",
|
||||
"eighteen", "nineteen", "twenty", "twenty one",
|
||||
"twenty two", "twenty three", "twenty four",
|
||||
"twenty five", "twenty six", "twenty seven",
|
||||
"twenty eight", "twenty nine",
|
||||
};
|
||||
|
||||
void Clock::OnObjectEvent(lv_obj_t *obj, lv_event_t event) {
|
||||
if(obj == backgroundLabel) {
|
||||
if (event == LV_EVENT_CLICKED) {
|
||||
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Clock::printwords(int h, int m) {
|
||||
if (m == 0) {
|
||||
sprintf(timeStrShadow, "#404040 %s#\n#404040 o' clock#\n", nums[h]);
|
||||
sprintf(timeStr, "%s\no' clock\n", nums[h]);
|
||||
|
||||
}
|
||||
|
||||
else if (m == 1){
|
||||
sprintf(timeStrShadow, "#404040 one minute#\n#404040 past# #404040 %s#\n", nums[h]);
|
||||
sprintf(timeStr, "one minute\npast %s\n", nums[h]);
|
||||
|
||||
}
|
||||
|
||||
else if (m == 59) {
|
||||
sprintf(timeStrShadow, "#404040 one minute#\n#404040 to# #404040 %s#\n", nums[(h % 12) + 1]);
|
||||
sprintf(timeStr, "one minute\nto %s\n", nums[(h % 12) + 1]);
|
||||
|
||||
}
|
||||
|
||||
else if (m == 15) {
|
||||
sprintf(timeStrShadow, "#404040 quarter past#\n#404040 %s#\n", nums[h]);
|
||||
sprintf(timeStr, "quarter past\n%s\n", nums[h]);
|
||||
|
||||
}
|
||||
|
||||
else if (m == 30) {
|
||||
sprintf(timeStrShadow, "#404040 half past#\n#404040 %s#\n", nums[h]);
|
||||
sprintf(timeStr, "half past\n%s\n", nums[h]);
|
||||
|
||||
}
|
||||
|
||||
else if (m == 45) {
|
||||
sprintf(timeStr, "quarter to\n%s\n", nums[(h % 12) + 1]);
|
||||
sprintf(timeStrShadow, "#404040 quarter to#\n#404040 %s#\n", nums[(h % 12) + 1]);
|
||||
}
|
||||
|
||||
else if (m <= 30) {
|
||||
sprintf(timeStrShadow, "#404040 %s# #404040 minutes#\n#404040 past# #404040 %s#\n", nums[m], nums[h]);
|
||||
sprintf(timeStr, "%s minutes\npast %s\n", nums[m], nums[h]);
|
||||
}
|
||||
|
||||
else if (m > 30){
|
||||
sprintf(timeStrShadow, "#404040 %s# #404040 minutes#\n#404040 to# #404040 %s#\n", nums[60 - m], nums[(h % 12) + 1]);
|
||||
sprintf(timeStr, "%s minutes\nto %s\n", nums[60 - m], nums[(h % 12) + 1]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool Clock::OnButtonPushed() {
|
||||
running = false;
|
||||
return false;
|
||||
}
|
106
src/displayapp/screens/WatchFaceWordClock.h
Normal file
106
src/displayapp/screens/WatchFaceWordClock.h
Normal file
@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include <lvgl/src/lv_core/lv_obj.h>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "Screen.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
class Battery;
|
||||
class Ble;
|
||||
class NotificationManager;
|
||||
class HeartRateController;
|
||||
}
|
||||
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
|
||||
template <class T>
|
||||
class DirtyValue {
|
||||
public:
|
||||
DirtyValue() = default; // Use NSDMI
|
||||
explicit DirtyValue(T const& v):value{v}{} // Use MIL and const-lvalue-ref
|
||||
bool IsUpdated() const { return isUpdated; }
|
||||
T const& Get() { this->isUpdated = false; return value; } // never expose a non-const lvalue-ref
|
||||
DirtyValue& operator=(const T& other) {
|
||||
if (this->value != other) {
|
||||
this->value = other;
|
||||
this->isUpdated = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
private:
|
||||
T value{}; // NSDMI - default initialise type
|
||||
bool isUpdated{true}; // NSDMI - use brace initilisation
|
||||
};
|
||||
|
||||
class Clock : public Screen {
|
||||
public:
|
||||
Clock(DisplayApp* app,
|
||||
Controllers::DateTime& dateTimeController,
|
||||
Controllers::Battery& batteryController,
|
||||
Controllers::Ble& bleController,
|
||||
Controllers::NotificationManager& notificatioManager,
|
||||
Controllers::HeartRateController& heartRateController);
|
||||
~Clock() override;
|
||||
|
||||
bool Refresh() override;
|
||||
bool OnButtonPushed() override;
|
||||
|
||||
void OnObjectEvent(lv_obj_t *pObj, lv_event_t i);
|
||||
void printwords(int h, int m);
|
||||
|
||||
private:
|
||||
static const char* MonthToString(Pinetime::Controllers::DateTime::Months month);
|
||||
static const char* DayOfWeekToString(Pinetime::Controllers::DateTime::Days dayOfWeek);
|
||||
static char const *DaysString[];
|
||||
static char const *MonthsString[];
|
||||
static char const *nums[];
|
||||
|
||||
char displayedChar[5];
|
||||
char timeStr[64];
|
||||
char timeStrShadow[96];
|
||||
|
||||
uint16_t currentYear = 1970;
|
||||
Pinetime::Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown;
|
||||
Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown;
|
||||
uint8_t currentDay = 0;
|
||||
|
||||
DirtyValue<int> batteryPercentRemaining {};
|
||||
DirtyValue<bool> bleState {};
|
||||
DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime{};
|
||||
DirtyValue<uint32_t> stepCount {};
|
||||
DirtyValue<uint8_t> heartbeat {};
|
||||
DirtyValue<bool> heartbeatRunning {};
|
||||
DirtyValue<bool> notificationState {};
|
||||
|
||||
lv_obj_t* label_time;
|
||||
lv_obj_t* label_date;
|
||||
lv_obj_t* label_time_shadow;
|
||||
lv_obj_t* label_date_shadow;
|
||||
lv_obj_t* backgroundLabel;
|
||||
lv_obj_t* batteryIcon;
|
||||
lv_obj_t* bleIcon;
|
||||
lv_obj_t* batteryPlug;
|
||||
lv_obj_t* heartbeatIcon;
|
||||
lv_obj_t* heartbeatValue;
|
||||
lv_obj_t* heartbeatBpm;
|
||||
lv_obj_t* stepIcon;
|
||||
lv_obj_t* stepValue;
|
||||
lv_obj_t* notificationIcon;
|
||||
|
||||
Controllers::DateTime& dateTimeController;
|
||||
Controllers::Battery& batteryController;
|
||||
Controllers::Ble& bleController;
|
||||
Controllers::NotificationManager& notificatioManager;
|
||||
Controllers::HeartRateController& heartRateController;
|
||||
|
||||
bool running = true;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user