diff --git a/.devcontainer/build.sh b/.devcontainer/build.sh index 5dcdf91d..3d8aecbf 100644 --- a/.devcontainer/build.sh +++ b/.devcontainer/build.sh @@ -60,7 +60,6 @@ CmakeGenerate() { cmake -G "Unix Makefiles" \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ - -DUSE_OPENOCD=1 \ -DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_VER" \ -DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \ "$SOURCES_DIR" diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 714c60bf..23f71a94 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -17,9 +17,8 @@ jobs: with: fetch-depth: 1000 - - name: Configure git - run: | - git fetch origin "$GITHUB_BASE_REF":"$GITHUB_BASE_REF" --depth=1000 + - name: Fetch base branch + run: git fetch origin "$GITHUB_BASE_REF":"$GITHUB_BASE_REF" - name: Install clang-format run: | @@ -35,3 +34,37 @@ jobs: with: name: Patches path: ./*.patch + test-clang-tidy: + runs-on: ubuntu-latest + container: + image: infinitime/infinitime-build + steps: + # This workaround fixes the error "unsafe repository (REPO is owned by someone else)". + # See https://github.com/actions/checkout/issues/760 and https://github.com/actions/checkout/issues/766 + # The fix in "actions/checkout@v2" was not sufficient as the build process also uses git (to get the current + # commit hash, for example). + - name: Workaround permission issues + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Checkout source files + uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 1000 + - name: Fetch base branch + run: git fetch origin "$GITHUB_BASE_REF":"$GITHUB_BASE_REF" + - name: Install clang-tidy + run: | + apt-get update + apt-get -y install clang-tidy-12 + - name: Prepare environment + shell: bash + env: + SOURCES_DIR: . + run: | + . docker/build.sh + GetGcc + # I guess these already exist inside the docker? + #GetNrfSdk + #GetMcuBoot + CmakeGenerate + - run: tests/test-tidy.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b8a70b6e..35d5ed1c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,10 +6,13 @@ on: branches: [ master, develop ] paths-ignore: - 'doc/**' + - '**.md' pull_request: branches: [ develop ] paths-ignore: - 'doc/**' + - '**.md' + jobs: build-firmware: @@ -45,6 +48,11 @@ jobs: with: name: InfiniTime MCUBoot image ${{ github.head_ref }} path: ./build/output/pinetime-mcuboot-app-image-*.bin + - name: Upload resources artifacts + uses: actions/upload-artifact@v3 + with: + name: InfiniTime resources ${{ github.head_ref }} + path: ./build/output/infinitime-resources-*.zip build-simulator: runs-on: ubuntu-latest @@ -72,8 +80,9 @@ jobs: git -C InfiniSim submodule update --init lv_drivers libpng - name: CMake + # disable BUILD_RESOURCES as this is already done when building the firmware run: | - cmake -G Ninja -S InfiniSim -B build_lv_sim -DInfiniTime_DIR="${PWD}" + cmake -G Ninja -S InfiniSim -B build_lv_sim -DInfiniTime_DIR="${PWD}" -DBUILD_RESOURCES=OFF - name: Build simulator executable run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index be8ef41f..adb1754b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10) set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release") -project(pinetime VERSION 1.10.0 LANGUAGES C CXX ASM) +project(pinetime VERSION 1.11.0 LANGUAGES C CXX ASM) set(CMAKE_C_STANDARD 99) set(CMAKE_CXX_STANDARD 14) @@ -24,28 +24,6 @@ if (NOT NRF5_SDK_PATH) message(FATAL_ERROR "The path to the NRF52 SDK must be specified on the command line (add -DNRF5_SDK_PATH=") endif () -if(USE_JLINK) - if (NOT NRFJPROG) - message(FATAL_ERROR "the path to the tool nrfjprog must be specified on the command line (add -DNRFJPROG=") - endif () -endif() - -if(USE_GDB_CLIENT) - if(NOT GDB_CLIENT_BIN_PATH) - set(GDB_CLIENT_BIN_PATH "arm-none-eabi-gdb") - endif() - - if(NOT GDB_CLIENT_TARGET_REMOTE) - message(FATAL_ERROR "The GDB target must be specified (add -DGDB_CLIENT_TARGET_REMOTE=") - endif() -endif() - -if(USE_OPENOCD) - if(NOT OPENOCD_BIN_PATH) - set(OPENOCD_BIN_PATH "openocd") - endif() -endif() - if(DEFINED USE_DEBUG_PINS AND USE_DEBUG_PINS) add_definitions(-DUSE_DEBUG_PINS) endif() @@ -54,6 +32,10 @@ if(BUILD_DFU) set(BUILD_DFU true) endif() +if(BUILD_RESOURCES) + set(BUILD_RESOURCES true) +endif() + set(TARGET_DEVICE "PINETIME" CACHE STRING "Target device") set_property(CACHE TARGET_DEVICE PROPERTY STRINGS PINETIME MOY-TFK5 MOY-TIN5 MOY-TON5 MOY-UNK) @@ -77,17 +59,6 @@ message(" * Toolchain : " ${ARM_NONE_EABI_TOOLCHAIN_PATH}) message(" * GitRef(S) : " ${PROJECT_GIT_COMMIT_HASH}) message(" * NRF52 SDK : " ${NRF5_SDK_PATH}) message(" * Target device : " ${TARGET_DEVICE}) -set(PROGRAMMER "???") -if(USE_JLINK) - message(" * Programmer/debugger : JLINK") - message(" * NrfJprog : " ${NRFJPROG}) -elseif(USE_GDB_CLIENT) - message(" * Programmer/debugger : GDB Client") - message(" * GDB Client path : " ${GDB_CLIENT_BIN_PATH}) - message(" * GDB Target : " ${GDB_CLIENT_TARGET_REMOTE}) -elseif(USE_OPENOCD) - message(" * Programmer/debugger : OpenOCD Client") -endif() if(USE_DEBUG_PINS) message(" * Debug pins : Enabled") else() @@ -98,6 +69,11 @@ if(BUILD_DFU) else() message(" * Build DFU (using adafruit-nrfutil) : Disabled") endif() +if(BUILD_RESOURCES) + message(" * Build resources : Enabled") +else() + message(" * Build resources : Disabled") +endif() set(VERSION_EDIT_WARNING "// Do not edit this file, it is automatically generated by CMAKE!") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/Version.h) diff --git a/README.md b/README.md index a3d2229b..6fd41586 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/p - [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) - [Versioning](doc/versioning.md) - [Project branches](doc/branches.md) - [Files included in the release notes](doc/filesInReleaseNotes.md) diff --git a/cmake-nRF5x/CMake_nRF5x.cmake b/cmake-nRF5x/CMake_nRF5x.cmake index 9bf63f2a..95f1fdb6 100755 --- a/cmake-nRF5x/CMake_nRF5x.cmake +++ b/cmake-nRF5x/CMake_nRF5x.cmake @@ -229,24 +229,6 @@ macro(nRF5x_setup) "${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_twi.c" ) - # adds target for erasing - if(USE_JLINK) - add_custom_target(FLASH_ERASE - COMMAND ${NRFJPROG} --eraseall -f ${NRF_TARGET} - COMMENT "erasing flashing" - ) - elseif(USE_GDB_CLIENT) - add_custom_target(FLASH_ERASE - COMMAND ${GDB_CLIENT_BIN_PATH} -nx --batch -ex 'target extended-remote ${GDB_CLIENT_TARGET_REMOTE}' -ex 'monitor swdp_scan' -ex 'attach 1' -ex 'mon erase_mass' - COMMENT "erasing flashing" - ) - elseif(USE_OPENOCD) - add_custom_target(FLASH_ERASE - COMMAND ${OPENOCD_BIN_PATH} -f interface/stlink.cfg -c 'transport select hla_swd' -f target/nrf52.cfg -c init -c halt -c 'nrf5 mass_erase' -c reset -c shutdown - COMMENT "erasing flashing" - ) - endif() - if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Darwin") set(TERMINAL "open") elseif(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows") @@ -255,16 +237,6 @@ macro(nRF5x_setup) set(TERMINAL "gnome-terminal") endif() - if(USE_JLINK) - add_custom_target(START_JLINK - COMMAND ${TERMINAL} "${DIR_OF_nRF5x_CMAKE}/runJLinkGDBServer-${NRF_TARGET}" - COMMAND ${TERMINAL} "${DIR_OF_nRF5x_CMAKE}/runJLinkExe-${NRF_TARGET}" - COMMAND sleep 2s - COMMAND ${TERMINAL} "${DIR_OF_nRF5x_CMAKE}/runJLinkRTTClient" - COMMENT "started JLink commands" - ) - endif() - endmacro(nRF5x_setup) # adds a target for comiling and flashing an executable @@ -282,29 +254,6 @@ macro(nRF5x_addExecutable EXECUTABLE_NAME SOURCE_FILES) COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_NAME}.out "${EXECUTABLE_NAME}.hex" COMMENT "post build steps for ${EXECUTABLE_NAME}") - # custom target for flashing the board - if(USE_JLINK) - add_custom_target("FLASH_${EXECUTABLE_NAME}" - DEPENDS ${EXECUTABLE_NAME} - COMMAND ${NRFJPROG} --program ${EXECUTABLE_NAME}.hex -f ${NRF_TARGET} --sectorerase - COMMAND sleep 0.5s - COMMAND ${NRFJPROG} --reset -f ${NRF_TARGET} - COMMENT "flashing ${EXECUTABLE_NAME}.hex" - ) - elseif(USE_GDB_CLIENT) - add_custom_target("FLASH_${EXECUTABLE_NAME}" - DEPENDS ${EXECUTABLE_NAME} - COMMAND ${GDB_CLIENT_BIN_PATH} -nx --batch -ex 'target extended-remote ${GDB_CLIENT_TARGET_REMOTE}' -ex 'monitor swdp_scan' -ex 'attach 1' -ex 'load' -ex 'kill' ${EXECUTABLE_NAME}.hex - COMMENT "flashing ${EXECUTABLE_NAME}.hex" - ) - elseif(USE_OPENOCD) - add_custom_target("FLASH_${EXECUTABLE_NAME}" - DEPENDS ${EXECUTABLE_NAME} - COMMAND ${OPENOCD_BIN_PATH} -c "tcl_port disabled" -c "gdb_port 3333" -c "telnet_port 4444" -f interface/stlink.cfg -c 'transport select hla_swd' -f target/nrf52.cfg -c "program \"${EXECUTABLE_NAME}.hex\"" -c reset -c shutdown - COMMENT "flashing ${EXECUTABLE_NAME}.hex" - ) - endif() - endmacro() # adds app-level scheduler library diff --git a/doc/ExternalResources.md b/doc/ExternalResources.md new file mode 100644 index 00000000..85319f6f --- /dev/null +++ b/doc/ExternalResources.md @@ -0,0 +1,70 @@ +# External resources +Since InfiniTime 1.11 apps and watchfaces can benefit from the external flash memory to store images and fonts. +This external memory is a lot bigger (4MB) than the internal memory that contains the firmware (512KB). + +This page describes how the resources are integrated in InfiniTime from a developer perspective. [This page](gettingStarted/updating-software.md) explains how to install and update the external resources using companion apps. + +## Resources generation + +Resources are generated at build time via the [CMake target `Generate Resources`](https://github.com/InfiniTimeOrg/InfiniTime/blob/develop/src/resources/CMakeLists.txt#L19). +It runs 3 Python scripts that respectively convert the fonts to binary format, convert the images to binary format and package everything in a .zip file. + +The resulting file `infinitime-resources-x.y.z.zip` contains the images and fonts converted in binary `.bin` files and a JSON file `resources.json`. + +Companion apps use this file to upload the files to the watch. + +``` +{ + "resources": [ + { + "filename": "lv_font_dots_40.bin", + "path": "/fonts/lv_font_dots_40.bin" + } + ], + "obsolete_files": [ + { + "path": "/example-of-obsolete-file.bin", + "since": "1.11.0" + } + ] +} +``` + +The resource JSON file describes an array of resources and an array of obsolete files : + +- `resources` : a resource is a file that must be flashed to the watch + - `filename`: name of the resources in the zip file. + - `path` : file path and name where the file must be flashed in the watch FS. + +- `obsolete_files` : files that are not needed anymore in the memory of the watch that can be deleted during the update procedure. + - `path` : path of the file in the watch FS + - `since` : version of InfiniTime that made this file obsolete. + +## Resources update procedure + +The update procedure is based on the [BLE FS API](BLEFS.md). The companion app simply write the binary files to the watch FS using information from the file `resources.json`. + +## Working with external resources in the code + +Load a picture from the external resources: + +``` +lv_obj_t* logo = lv_img_create(lv_scr_act(), nullptr); +lv_img_set_src(logo, "F:/images/logo.bin"); +``` + +Load a font from the external resources: you first need to check that the file actually exists. LVGL will crash when trying to open a font that doesn't exist. + +``` +lv_font_t* font_teko = nullptr; +if (filesystem.FileOpen(&f, "/fonts/font.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_teko = lv_font_load("F:/fonts/font.bin"); +} + +if(font != nullptr) { + lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font); +} + +``` + diff --git a/doc/InfiniTimeVision.md b/doc/InfiniTimeVision.md index 6702d911..da4e1113 100644 --- a/doc/InfiniTimeVision.md +++ b/doc/InfiniTimeVision.md @@ -17,7 +17,7 @@ InfiniTime is not to be used for medical or other health tracking purposes. - Easy and simple navigation - Behaviour should be predictable and easy to understand - Prefer solid default experience over customization -- Personalization is achieved through custom watchfaces. +- Personalization is achieved through custom watch faces. More options may be available through a companion app. - Use standard protocols and methods @@ -25,7 +25,7 @@ InfiniTime is not to be used for medical or other health tracking purposes. The perfect version of InfiniTime would include: -- Capability to sideload apps and watchfaces +- Capability to sideload apps and watch faces - Only a minimal feature set in the flashed firmware. Users would add the features they want. - Ports to other devices diff --git a/doc/MemoryAnalysis.md b/doc/MemoryAnalysis.md index 0feb0f9e..fe493ba0 100644 --- a/doc/MemoryAnalysis.md +++ b/doc/MemoryAnalysis.md @@ -245,7 +245,7 @@ void* __wrap_malloc(size_t size) { Now, your function `__wrap_malloc()` will be called instead of `malloc()`. You can call the actual malloc from the stdlib by calling `__real_malloc()`. -Using this technique, I was able to trace all malloc calls at boot (boot -> digital watchface): +Using this technique, I was able to trace all malloc calls at boot (boot -> digital watch face): - system task = 3464 bytes (SystemTask could potentially be declared as a global variable to avoid heap allocation here) - string music = 31 (maybe we should not use std::string when not needed, as it does heap allocation) diff --git a/doc/buildAndProgram.md b/doc/buildAndProgram.md index 58d0f72e..78bea1b4 100644 --- a/doc/buildAndProgram.md +++ b/doc/buildAndProgram.md @@ -40,12 +40,9 @@ CMake configures the project according to variables you specify the command line ----------|-------------|--------| **ARM_NONE_EABI_TOOLCHAIN_PATH**|path to the toolchain directory|`-DARM_NONE_EABI_TOOLCHAIN_PATH=/home/jf/nrf52/gcc-arm-none-eabi-10.3-2021.10/`| **NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`| -**USE_JLINK, USE_GDB_CLIENT and USE_OPENOCD**|Enable *JLink* mode, *GDB Client* (Black Magic Probe) mode or *OpenOCD* mode (set the one you want to use to `1`)|`-DUSE_JLINK=1` **CMAKE_BUILD_TYPE (\*)**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug` -**NRFJPROG**|Path to the NRFJProg executable. Used only if `USE_JLINK` is 1.|`-DNRFJPROG=/opt/nrfjprog/nrfjprog` -**GDB_CLIENT_BIN_PATH**|Path to arm-none-eabi-gdb executable. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_BIN_PATH=/home/jf/nrf52/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gdb` -**GDB_CLIENT_TARGET_REMOTE**|Target remote connection string. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_TARGET_REMOTE=/dev/ttyACM0` **BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-DBUILD_DFU=1` +**BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [lv_img_conv](https://github.com/lvgl/lv_img_conv). |`-DBUILD_RESOURCES=1` **TARGET_DEVICE**|Target device, used for hardware configuration. Allowed: `PINETIME, MOY-TFK5, MOY-TIN5, MOY-TON5, MOY-UNK`|`-DTARGET_DEVICE=PINETIME` (Default) #### (\*) Note about **CMAKE_BUILD_TYPE** @@ -56,30 +53,16 @@ The *Debug* mode disables all optimizations, which makes the code easier to debu #### (\*\*) Note about **BUILD_DFU** DFU files are the files you'll need to install your build of InfiniTime using OTA (over-the-air) mechanism. To generate the DFU file, the Python tool [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil) is needed on your system. Check that this tool is properly installed before enabling this option. -#### CMake command line for JLink +#### CMake command ``` -cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -DUSE_JLINK=1 -DNRFJPROG=... ../ -``` - -#### CMake command line for GDB Client (Black Magic Probe) - -``` -cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -DUSE_GDB_CLIENT=1 -DGDB_CLIENT_BIN_PATH=... -DGDB_CLIENT_TARGET_REMOTE=... ../ -``` - -#### CMake command line for OpenOCD - -``` -cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -DUSE_OPENOCD=1 -DGDB_CLIENT_BIN_PATH=[optional] ../ +cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... ``` ### Build the project During the project generation, CMake created the following targets: -- **FLASH_ERASE** : mass erase the flash memory of the NRF52. -- **FLASH_pinetime-app** : flash the firmware into the NRF52. - **pinetime-app** : build the standalone (without bootloader support) version of the firmware. - **pinetime-recovery** : build the standalone recovery version of infinitime (light firmware that only supports OTA and basic UI) - **pinetime-recovery-loader** : build the standalone tool that flashes the recovery firmware into the external SPI flash @@ -107,24 +90,6 @@ Binary files are generated into the folder `src`: The same files are generated for **pinetime-recovery** and **pinetime-recoveryloader** -### Program and run - -#### Using CMake targets - -These target have been configured during the project generation by CMake according to the parameters you provided to the command line. - -Mass erase: - -``` -make FLASH_ERASE -``` - -Flash the application: - -``` -make FLASH_pinetime-app -``` - ### How to generate files needed by the factory These files are needed by the Pine64 factory to flash InfiniTime as the default firmware on the PineTimes. diff --git a/doc/code/Apps.md b/doc/code/Apps.md index c756a8b4..ad0f0403 100644 --- a/doc/code/Apps.md +++ b/doc/code/Apps.md @@ -99,7 +99,7 @@ Now, go to the function `DisplayApp::LoadApp` and add another case to the switch The case will be the id you gave your app earlier. If your app needs any additional arguments, this is the place to pass them. -If you want to add your app in the app launcher, add your app in [displayapp/screens/ApplicationList.cpp](/src/displayapp/screens/ApplicationList.cpp) to one of the `CreateScreen` functions, or add another `CreateScreen` function if there are no empty spaces for your app. If your app is a setting, do the same procedure in [displayapp/screens/settings/Settings.cpp](/src/displayapp/screens/settings/Settings.cpp). +If you want to add your app in the app launcher, add your app in [displayapp/screens/ApplicationList.h](/src/displayapp/screens/ApplicationList.h) to the array containing the applications and their corresponding symbol. If your app is a setting, do the same procedure in [displayapp/screens/settings/Settings.h](/src/displayapp/screens/settings/Settings.h). You should now be able to [build](../buildAndProgram.md) the firmware and flash it to your PineTime. Yay! diff --git a/doc/gettingStarted/amazfish-external-resources-1.png b/doc/gettingStarted/amazfish-external-resources-1.png new file mode 100644 index 00000000..7fa1e0cb Binary files /dev/null and b/doc/gettingStarted/amazfish-external-resources-1.png differ diff --git a/doc/gettingStarted/amazfish-external-resources-2.png b/doc/gettingStarted/amazfish-external-resources-2.png new file mode 100644 index 00000000..51a9dff9 Binary files /dev/null and b/doc/gettingStarted/amazfish-external-resources-2.png differ diff --git a/doc/gettingStarted/gettingStarted-1.0.md b/doc/gettingStarted/gettingStarted-1.0.md index 1586de56..989629df 100644 --- a/doc/gettingStarted/gettingStarted-1.0.md +++ b/doc/gettingStarted/gettingStarted-1.0.md @@ -8,7 +8,7 @@ It is highly recommended to update the firmware to the latest version when you r ### Setting the time -By default, InfiniTime starts on the digital watchface. It'll probably display the epoch time (1 Jan 1970, 00:00). +By default, InfiniTime starts on the digital watch face. It'll probably display the epoch time (1 Jan 1970, 00:00). You can sync the time using companion apps. diff --git a/doc/gettingStarted/itd-external-resources.png b/doc/gettingStarted/itd-external-resources.png new file mode 100644 index 00000000..b3ff99be Binary files /dev/null and b/doc/gettingStarted/itd-external-resources.png differ diff --git a/doc/gettingStarted/updating-software.md b/doc/gettingStarted/updating-software.md index d302607e..2b5d5d92 100644 --- a/doc/gettingStarted/updating-software.md +++ b/doc/gettingStarted/updating-software.md @@ -4,7 +4,7 @@ If you just want to flash or upgrade InfiniTime on your PineTime, this page is f ## Checking the version of InfiniTime -You can check the InfiniTime version by first swiping right on the watchface to open quick settings, tapping the cogwheel to open settings, swipe up until you find an entry named "About" and tap on it. +You can check the InfiniTime version by first swiping right on the watch face to open quick settings, tapping the cogwheel to open settings, swipe up until you find an entry named "About" and tap on it. ![InfiniTime 1.0 version](version-1.0.jpg) @@ -35,7 +35,39 @@ Firmware updates must be manually validated. If the firmware isn't validated and You can validate your updated firmware on InfiniTime >= 1.0 by following this simple procedure: -- From the watchface, swipe **right** to display the *quick settings menu* +- From the watch face, swipe **right** to display the *quick settings menu* - Open settings by tapping the cogwheel on the bottom right - Swipe up until you find an entry named **Firmware** and tap on it - If the firmware is not validated yet, you can either validate the running firmware, or reset and revert to the previous firmware version + +# Updating resources + +Since InfiniTime 1.11 apps and watchfaces can take benefit of the external flash memory to store their pictures and fonts. +This external memory is a lot bigger (4MB) than the internal memory where the firmware is flashed (512KB). +Since those resources are not part of the firmware, they need to be flashed and updated separately. + +Resources are packaged into a single .zip file named `infinitime-resources-x.y.z.zip` (where `x`, `y` and `z` are the version numbers of InfiniTime). +You can use the companion app of your choice to flash the resources. + +**Note : at the time of writing this page, [Amazfish](https://github.com/piggz/harbour-amazfish) and [ITD](https://gitea.arsenm.dev/Arsen6331/itd) have already integrated this functionality. Other companion apps will hopefully implement it soon!* + +## Amazfish +Use the `Download file` functionality of Amazfish. + +![Update resources with Amazfish - Download file](amazfish-external-resources-1.png) + +Amazfish automatically detects the file type (firmware or resources) and apply the corresponding flash procedure when you hit the button **Send file**. + +![Update resources with Amazfish](amazfish-external-resources-2.png) + +## ITD + +Run `itctl` with the `res` command: + +``` +itctl res load infinitime-resources-1.10.0.zip +``` + +Example: + +![Update resources using itctl](itd-external-resources.png) diff --git a/docker/Dockerfile b/docker/Dockerfile index f2d187d0..927160db 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -22,6 +22,12 @@ RUN apt-get update -qq \ python3-dev \ git \ apt-utils \ + pkg-config \ + libpixman-1-dev \ + libcairo2-dev \ + libpango-1.0-0 \ + ibpango1.0-dev \ + libpangocairo-1.0-0 \ && curl -sL https://deb.nodesource.com/setup_18.x | bash - \ && apt-get install -y nodejs \ && rm -rf /var/cache/apt/* /var/lib/apt/lists/*; @@ -33,6 +39,10 @@ RUN pip3 install -Iv cryptography==3.3 RUN pip3 install cbor RUN npm i lv_font_conv@1.5.2 -g +RUN npm i ts-node@10.9.1 -g +RUN npm i @swc/core -g +RUN npm i lv_img_conv@0.3.0 -g + # build.sh knows how to compile COPY build.sh /opt/ diff --git a/docker/build.sh b/docker/build.sh index 663390c4..07e0d17e 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -11,6 +11,10 @@ export SOURCES_DIR="${SOURCES_DIR:=/sources}" export BUILD_DIR="${BUILD_DIR:=$SOURCES_DIR/build}" export OUTPUT_DIR="${OUTPUT_DIR:=$SOURCES_DIR/build/output}" +# Specify a folder with read/write access to NPM +export NPM_DIR="$BUILD_DIR/npm" +export npm_config_cache="${NPM_DIR}" + export BUILD_TYPE=${BUILD_TYPE:=Release} export GCC_ARM_VER=${GCC_ARM_VER:="10.3-2021.10"} export NRF_SDK_VER=${NRF_SDK_VER:="nRF5_SDK_15.3.0_59ac345"} @@ -59,10 +63,10 @@ CmakeGenerate() { -S "$SOURCES_DIR" \ -B "$BUILD_DIR" \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ - -DUSE_OPENOCD=1 \ -DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_PATH" \ -DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \ - -DBUILD_DFU=1 + -DBUILD_DFU=1 \ + -DBUILD_RESOURCES=1 } CmakeBuild() { diff --git a/docker/post_build.sh.in b/docker/post_build.sh.in index 8c94471a..5d82f3be 100755 --- a/docker/post_build.sh.in +++ b/docker/post_build.sh.in @@ -15,6 +15,8 @@ cp "$BUILD_DIR/src/pinetime-mcuboot-app-dfu-$PROJECT_VERSION.zip" "$OUTPUT_DIR/p cp "$BUILD_DIR/src/pinetime-mcuboot-recovery-loader-image-$PROJECT_VERSION.bin" "$OUTPUT_DIR/pinetime-mcuboot-recovery-loader-image-$PROJECT_VERSION.bin" cp "$BUILD_DIR/src/pinetime-mcuboot-recovery-loader-dfu-$PROJECT_VERSION.zip" "$OUTPUT_DIR/pinetime-mcuboot-recovery-loader-dfu-$PROJECT_VERSION.zip" +cp "$BUILD_DIR/src/resources/infinitime-resources-$PROJECT_VERSION.zip" "$OUTPUT_DIR/infinitime-resources-$PROJECT_VERSION.zip" + mkdir -p "$OUTPUT_DIR/src" cp $BUILD_DIR/src/*.bin "$OUTPUT_DIR/src/" cp $BUILD_DIR/src/*.hex "$OUTPUT_DIR/src/" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index db4a8e2a..e59c0d81 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -396,6 +396,7 @@ list(APPEND SOURCE_FILES displayapp/screens/Motion.cpp displayapp/screens/FlashLight.cpp displayapp/screens/List.cpp + displayapp/screens/CheckboxList.cpp displayapp/screens/BatteryInfo.cpp displayapp/screens/Steps.cpp displayapp/screens/Timer.cpp @@ -426,8 +427,10 @@ list(APPEND SOURCE_FILES displayapp/icons/bg_clock.c displayapp/screens/WatchFaceAnalog.cpp displayapp/screens/WatchFaceDigital.cpp + displayapp/screens/WatchFaceInfineat.cpp displayapp/screens/WatchFaceTerminal.cpp displayapp/screens/WatchFacePineTimeStyle.cpp + displayapp/screens/WatchFaceCasioStyleG7710.cpp ## @@ -602,6 +605,7 @@ set(INCLUDE_FILES displayapp/screens/FirmwareUpdate.h displayapp/screens/FirmwareValidation.h displayapp/screens/ApplicationList.h + displayapp/screens/CheckboxList.h displayapp/Apps.h displayapp/screens/Notifications.h displayapp/screens/HeartRate.h @@ -940,6 +944,10 @@ add_custom_command(TARGET ${EXECUTABLE_NAME} COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_FILE_NAME}.out "${EXECUTABLE_FILE_NAME}.hex" COMMENT "post build steps for ${EXECUTABLE_FILE_NAME}") +if(BUILD_RESOURCES) + add_dependencies(${EXECUTABLE_NAME} GenerateResources) +endif() + # Build binary intended to be used by bootloader set(EXECUTABLE_MCUBOOT_NAME "pinetime-mcuboot-app") set(EXECUTABLE_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}) @@ -973,6 +981,10 @@ add_custom_command(TARGET ${EXECUTABLE_MCUBOOT_NAME} COMMENT "post build steps for ${EXECUTABLE_MCUBOOT_FILE_NAME}" ) +if(BUILD_RESOURCES) + add_dependencies(${EXECUTABLE_MCUBOOT_NAME} GenerateResources) +endif() + if(BUILD_DFU) add_custom_command(TARGET ${EXECUTABLE_MCUBOOT_NAME} POST_BUILD @@ -1127,68 +1139,7 @@ if(BUILD_DFU) ) endif() +if(BUILD_RESOURCES) + add_subdirectory(resources) +endif() -# FLASH -if (USE_JLINK) - add_custom_target(FLASH_ERASE - COMMAND ${NRFJPROG} --eraseall -f ${NRF_TARGET} - COMMENT "erasing flashing" - ) - add_custom_target("FLASH_${EXECUTABLE_NAME}" - DEPENDS ${EXECUTABLE_NAME} - COMMAND ${NRFJPROG} --program ${EXECUTABLE_FILE_NAME}.hex -f ${NRF_TARGET} --sectorerase - COMMAND sleep 0.5s - COMMAND ${NRFJPROG} --reset -f ${NRF_TARGET} - COMMENT "flashing ${EXECUTABLE_FILE_NAME}.hex" - ) - -elseif (USE_GDB_CLIENT) - add_custom_target(FLASH_ERASE - COMMAND ${GDB_CLIENT_BIN_PATH} -nx --batch -ex 'target extended-remote ${GDB_CLIENT_TARGET_REMOTE}' -ex 'monitor swdp_scan' -ex 'attach 1' -ex 'mon erase_mass' - COMMENT "erasing flashing" - ) - add_custom_target("FLASH_${EXECUTABLE_NAME}" - DEPENDS ${EXECUTABLE_NAME} - COMMAND ${GDB_CLIENT_BIN_PATH} -nx --batch -ex 'target extended-remote ${GDB_CLIENT_TARGET_REMOTE}' -ex 'monitor swdp_scan' -ex 'attach 1' -ex 'load' -ex 'kill' ${EXECUTABLE_FILE_NAME}.hex - COMMENT "flashing ${EXECUTABLE_FILE_NAME}.hex" - ) -elseif (USE_OPENOCD) - if (USE_CMSIS_DAP) - add_custom_target(FLASH_ERASE - COMMAND ${OPENOCD_BIN_PATH} -c 'source [find interface/cmsis-dap.cfg]' -c 'transport select swd' - -c 'source [find target/nrf52.cfg]' - -c 'init' - -c 'halt' - -c 'nrf5 mass_erase' - -c 'halt' - -c 'reset' - -c 'exit' - COMMENT "erasing flashing" - ) - add_custom_target("FLASH_${EXECUTABLE_NAME}" - DEPENDS ${EXECUTABLE_NAME} - COMMAND ${OPENOCD_BIN_PATH} - -c 'tcl_port disabled' - -c 'gdb_port 3333' - -c 'telnet_port 4444' - -c 'source [find interface/cmsis-dap.cfg]' - -c 'transport select swd' - -c 'source [find target/nrf52.cfg]' - -c 'halt' - -c "program \"${EXECUTABLE_FILE_NAME}.hex\"" - -c 'reset' - -c 'shutdown' - COMMENT "flashing ${EXECUTABLE_BIN_NAME}.hex" - ) - else () - add_custom_target(FLASH_ERASE - COMMAND ${OPENOCD_BIN_PATH} -f interface/stlink.cfg -c 'transport select hla_swd' -f target/nrf52.cfg -c init -c halt -c 'nrf5 mass_erase' -c reset -c shutdown - COMMENT "erasing flashing" - ) - add_custom_target("FLASH_${EXECUTABLE_NAME}" - DEPENDS ${EXECUTABLE_NAME} - COMMAND ${OPENOCD_BIN_PATH} -c "tcl_port disabled" -c "gdb_port 3333" -c "telnet_port 4444" -f interface/stlink.cfg -c 'transport select hla_swd' -f target/nrf52.cfg -c "program \"${EXECUTABLE_FILE_NAME}.hex\"" -c reset -c shutdown - COMMENT "flashing ${EXECUTABLE_FILE_NAME}.hex" - ) - endif () -endif () diff --git a/src/components/alarm/AlarmController.cpp b/src/components/alarm/AlarmController.cpp index 9f4e9105..d97e1cff 100644 --- a/src/components/alarm/AlarmController.cpp +++ b/src/components/alarm/AlarmController.cpp @@ -82,7 +82,7 @@ void AlarmController::ScheduleAlarm() { state = AlarmState::Set; } -uint32_t AlarmController::SecondsToAlarm() { +uint32_t AlarmController::SecondsToAlarm() const { return std::chrono::duration_cast(alarmTime - dateTimeController.CurrentDateTime()).count(); } diff --git a/src/components/alarm/AlarmController.h b/src/components/alarm/AlarmController.h index d630a128..91f60f5a 100644 --- a/src/components/alarm/AlarmController.h +++ b/src/components/alarm/AlarmController.h @@ -36,7 +36,7 @@ namespace Pinetime { void ScheduleAlarm(); void DisableAlarm(); void SetOffAlarmNow(); - uint32_t SecondsToAlarm(); + uint32_t SecondsToAlarm() const; void StopAlerting(); enum class AlarmState { Not_Set, Set, Alerting }; enum class RecurType { None, Daily, Weekdays }; diff --git a/src/components/ble/weather/WeatherData.h b/src/components/ble/weather/WeatherData.h index 613d5acb..1a995eb9 100644 --- a/src/components/ble/weather/WeatherData.h +++ b/src/components/ble/weather/WeatherData.h @@ -239,7 +239,7 @@ namespace Pinetime { * In order to represent bursts of wind instead of constant wind, * you have minimum and maximum speeds. * - * As direction can fluctuate wildly and some watchfaces might wish to display it nicely, + * As direction can fluctuate wildly and some watch faces might wish to display it nicely, * we're following the aerospace industry weather report option of specifying a range. */ class Wind : public TimelineHeader { @@ -283,7 +283,7 @@ namespace Pinetime { * as it usually is, but it could change during a trip for ex. * so we allow changing it dynamically. * - * Location info can be for some kind of map watchface + * Location info can be for some kind of map watch face * or daylight calculations, should those be required. * */ @@ -331,7 +331,7 @@ namespace Pinetime { * * These events are a bit more complex because the topic is not simple, * the intention is to heavy-lift the annoying preprocessing from the watch - * this allows watchface or watchapp makers to generate accurate alerts and graphics + * this allows watch face or watchapp makers to generate accurate alerts and graphics * * If this needs further enforced standardization, pull requests are welcome */ diff --git a/src/components/datetime/DateTimeController.cpp b/src/components/datetime/DateTimeController.cpp index ba04705f..4dc16329 100644 --- a/src/components/datetime/DateTimeController.cpp +++ b/src/components/datetime/DateTimeController.cpp @@ -7,6 +7,7 @@ using namespace Pinetime::Controllers; namespace { char const* DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"}; + char const* DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; char const* MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; char const* MonthsStringLow[] = {"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; } @@ -126,6 +127,10 @@ const char* DateTime::MonthShortToStringLow(Months month) { return MonthsStringLow[static_cast(month)]; } +const char* DateTime::DayOfWeekShortToStringLow() const { + return DaysStringShortLow[static_cast(dayOfWeek)]; +} + void DateTime::Register(Pinetime::System::SystemTask* systemTask) { this->systemTask = systemTask; } diff --git a/src/components/datetime/DateTimeController.h b/src/components/datetime/DateTimeController.h index 00bbc2ee..81319d15 100644 --- a/src/components/datetime/DateTimeController.h +++ b/src/components/datetime/DateTimeController.h @@ -64,6 +64,7 @@ namespace Pinetime { const char* MonthShortToString() const; const char* DayOfWeekShortToString() const; static const char* MonthShortToStringLow(Months month); + const char* DayOfWeekShortToStringLow() const; std::chrono::time_point CurrentDateTime() const { return currentDateTime; diff --git a/src/components/motor/MotorController.cpp b/src/components/motor/MotorController.cpp index 90e41d20..db6103f4 100644 --- a/src/components/motor/MotorController.cpp +++ b/src/components/motor/MotorController.cpp @@ -19,7 +19,7 @@ void MotorController::Ring(TimerHandle_t xTimer) { } void MotorController::RunForDuration(uint8_t motorDuration) { - if (xTimerChangePeriod(shortVib, pdMS_TO_TICKS(motorDuration), 0) == pdPASS && xTimerStart(shortVib, 0) == pdPASS) { + if (motorDuration > 0 && xTimerChangePeriod(shortVib, pdMS_TO_TICKS(motorDuration), 0) == pdPASS && xTimerStart(shortVib, 0) == pdPASS) { nrf_gpio_pin_clear(PinMap::Motor); } } diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 478408f6..93f861f3 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -34,16 +34,29 @@ namespace Pinetime { Navy, Magenta, Purple, - Orange + Orange, + Pink }; + enum class PTSGaugeStyle : uint8_t { Full, Half, Numeric }; + struct PineTimeStyle { Colors ColorTime = Colors::Teal; Colors ColorBar = Colors::Teal; Colors ColorBG = Colors::Black; + PTSGaugeStyle gaugeStyle = PTSGaugeStyle::Full; + }; + struct WatchFaceInfineat { + bool showSideCover = true; + int colorIndex = 0; }; Settings(Pinetime::Controllers::FS& fs); + Settings(const Settings&) = delete; + Settings& operator=(const Settings&) = delete; + Settings(Settings&&) = delete; + Settings& operator=(Settings&&) = delete; + void Init(); void SaveSettings(); @@ -94,6 +107,35 @@ namespace Pinetime { return settings.PTS.ColorBG; }; + void SetInfineatShowSideCover(bool show) { + if (show != settings.watchFaceInfineat.showSideCover) { + settings.watchFaceInfineat.showSideCover = show; + settingsChanged = true; + } + }; + bool GetInfineatShowSideCover() const { + return settings.watchFaceInfineat.showSideCover; + }; + + void SetInfineatColorIndex(int index) { + if (index != settings.watchFaceInfineat.colorIndex) { + settings.watchFaceInfineat.colorIndex = index; + settingsChanged = true; + } + }; + int GetInfineatColorIndex() const { + return settings.watchFaceInfineat.colorIndex; + }; + + void SetPTSGaugeStyle(PTSGaugeStyle gaugeStyle) { + if (gaugeStyle != settings.PTS.gaugeStyle) + settingsChanged = true; + settings.PTS.gaugeStyle = gaugeStyle; + }; + PTSGaugeStyle GetPTSGaugeStyle() const { + return settings.PTS.gaugeStyle; + }; + void SetAppMenu(uint8_t menu) { appMenu = menu; }; @@ -212,7 +254,7 @@ namespace Pinetime { private: Pinetime::Controllers::FS& fs; - static constexpr uint32_t settingsVersion = 0x0003; + static constexpr uint32_t settingsVersion = 0x0004; struct SettingsData { uint32_t version = settingsVersion; uint32_t stepsGoal = 10000; @@ -226,6 +268,8 @@ namespace Pinetime { PineTimeStyle PTS; + WatchFaceInfineat watchFaceInfineat; + std::bitset<4> wakeUpMode {0}; uint16_t shakeWakeThreshold = 150; Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium; @@ -236,6 +280,7 @@ namespace Pinetime { uint8_t appMenu = 0; uint8_t settingsMenu = 0; + uint8_t watchFacesMenu = 0; /* ble state is intentionally not saved with the other watch settings and initialized * to off (false) on every boot because we always want ble to be enabled on startup */ diff --git a/src/displayapp/Colors.cpp b/src/displayapp/Colors.cpp index 106c5163..2e9790eb 100644 --- a/src/displayapp/Colors.cpp +++ b/src/displayapp/Colors.cpp @@ -39,6 +39,8 @@ lv_color_t Pinetime::Applications::Convert(Pinetime::Controllers::Settings::Colo return LV_COLOR_MAKE(0xb0, 0x0, 0xb0); case Pinetime::Controllers::Settings::Colors::Orange: return LV_COLOR_ORANGE; + case Pinetime::Controllers::Settings::Colors::Pink: + return LV_COLOR_MAKE(0xFF, 0xAE, 0xC9); default: return LV_COLOR_WHITE; } diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 29684466..108e380d 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -55,7 +55,7 @@ using namespace Pinetime::Applications; using namespace Pinetime::Applications::Display; namespace { - static inline bool in_isr(void) { + inline bool in_isr() { return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0; } } @@ -75,7 +75,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::TimerController& timerController, Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, - Pinetime::Controllers::TouchHandler& touchHandler) + Pinetime::Controllers::TouchHandler& touchHandler, + Pinetime::Controllers::FS& filesystem) : lcd {lcd}, lvgl {lvgl}, touchPanel {touchPanel}, @@ -91,7 +92,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, timerController {timerController}, alarmController {alarmController}, brightnessController {brightnessController}, - touchHandler {touchHandler} { + touchHandler {touchHandler}, + filesystem {filesystem} { } void DisplayApp::Start(System::BootErrors error) { @@ -125,7 +127,7 @@ void DisplayApp::Process(void* instance) { void DisplayApp::InitHw() { brightnessController.Init(); - brightnessController.Set(settingsController.GetBrightness()); + ApplyBrightness(); } void DisplayApp::Refresh() { @@ -156,7 +158,7 @@ void DisplayApp::Refresh() { brightnessController.Set(Controllers::BrightnessController::Levels::Low); break; case Messages::RestoreBrightness: - brightnessController.Set(settingsController.GetBrightness()); + ApplyBrightness(); break; case Messages::GoToSleep: while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) { @@ -167,7 +169,7 @@ void DisplayApp::Refresh() { state = States::Idle; break; case Messages::GoToRunning: - brightnessController.Set(settingsController.GetBrightness()); + ApplyBrightness(); state = States::Running; break; case Messages::UpdateTimeOut: @@ -301,7 +303,7 @@ void DisplayApp::ReturnApp(Apps app, DisplayApp::FullRefreshDirections direction void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction) { touchHandler.CancelTap(); - brightnessController.Set(settingsController.GetBrightness()); + ApplyBrightness(); currentScreen.reset(nullptr); SetFullRefresh(direction); @@ -324,7 +326,8 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction) notificationManager, settingsController, heartRateController, - motionController); + motionController, + filesystem); break; case Apps::Error: @@ -387,7 +390,7 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction) ReturnApp(Apps::QuickSettings, FullRefreshDirections::Down, TouchEvents::SwipeDown); break; case Apps::SettingWatchFace: - currentScreen = std::make_unique(this, settingsController); + currentScreen = std::make_unique(this, settingsController, filesystem); ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown); break; case Apps::SettingTimeFormat: @@ -527,3 +530,12 @@ void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) { void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) { this->systemTask = systemTask; } +void DisplayApp::ApplyBrightness() { + auto brightness = settingsController.GetBrightness(); + if(brightness != Controllers::BrightnessController::Levels::Low && + brightness != Controllers::BrightnessController::Levels::Medium && + brightness != Controllers::BrightnessController::Levels::High) { + brightness = Controllers::BrightnessController::Levels::High; + } + brightnessController.Set(brightness); +} diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index 43972232..4c54e227 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -62,7 +62,8 @@ namespace Pinetime { Pinetime::Controllers::TimerController& timerController, Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, - Pinetime::Controllers::TouchHandler& touchHandler); + Pinetime::Controllers::TouchHandler& touchHandler, + Pinetime::Controllers::FS& filesystem); void Start(System::BootErrors error); void PushMessage(Display::Messages msg); @@ -90,6 +91,7 @@ namespace Pinetime { Pinetime::Controllers::AlarmController& alarmController; Pinetime::Controllers::BrightnessController& brightnessController; Pinetime::Controllers::TouchHandler& touchHandler; + Pinetime::Controllers::FS& filesystem; Pinetime::Controllers::FirmwareValidator validator; @@ -119,6 +121,7 @@ namespace Pinetime { Apps nextApp = Apps::None; DisplayApp::FullRefreshDirections nextDirection; System::BootErrors bootError; + void ApplyBrightness(); }; } } diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp index ca15dbaf..e553aa87 100644 --- a/src/displayapp/DisplayAppRecovery.cpp +++ b/src/displayapp/DisplayAppRecovery.cpp @@ -2,6 +2,7 @@ #include #include #include +#include "components/fs/FS.h" #include "components/rle/RleDecoder.h" #include "touchhandler/TouchHandler.h" #include "displayapp/icons/infinitime/infinitime-nb.c" @@ -24,7 +25,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::TimerController& timerController, Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, - Pinetime::Controllers::TouchHandler& touchHandler) + Pinetime::Controllers::TouchHandler& touchHandler, + Pinetime::Controllers::FS& filesystem) : lcd {lcd}, bleController {bleController} { } diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h index 0e801221..7d4f0fd0 100644 --- a/src/displayapp/DisplayAppRecovery.h +++ b/src/displayapp/DisplayAppRecovery.h @@ -35,6 +35,7 @@ namespace Pinetime { class TimerController; class AlarmController; class BrightnessController; + class FS; } namespace System { @@ -59,7 +60,8 @@ namespace Pinetime { Pinetime::Controllers::TimerController& timerController, Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, - Pinetime::Controllers::TouchHandler& touchHandler); + Pinetime::Controllers::TouchHandler& touchHandler, + Pinetime::Controllers::FS& filesystem); void Start(); void Start(Pinetime::System::BootErrors) { Start(); diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 006b8849..e8e20b9b 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -58,7 +58,7 @@ "sources": [ { "file": "material-design-icons/MaterialIcons-Regular.ttf", - "range": "0xf00b, 0xe3aa-0xe3ac, 0xe7f6-0xe7f7, 0xe8b8, 0xef44" + "range": "0xf00b, 0xe3aa-0xe3ac, 0xe7f6-0xe7f7, 0xe8b8, 0xef44, 0xe40a" } ], "bpp": 1, diff --git a/src/displayapp/screens/CheckboxList.cpp b/src/displayapp/screens/CheckboxList.cpp new file mode 100644 index 00000000..c189b075 --- /dev/null +++ b/src/displayapp/screens/CheckboxList.cpp @@ -0,0 +1,95 @@ +#include "displayapp/DisplayApp.h" +#include "displayapp/screens/CheckboxList.h" +#include "displayapp/screens/Styles.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + static void event_handler(lv_obj_t* obj, lv_event_t event) { + CheckboxList* screen = static_cast(obj->user_data); + screen->UpdateSelected(obj, event); + } +} + +CheckboxList::CheckboxList(const uint8_t screenID, + const uint8_t numScreens, + DisplayApp* app, + const char* optionsTitle, + const char* optionsSymbol, + uint32_t originalValue, + std::function OnValueChanged, + std::array options) + : Screen(app), + screenID {screenID}, + OnValueChanged {std::move(OnValueChanged)}, + options {options}, + value {originalValue}, + pageIndicator(screenID, numScreens) { + // Set the background to Black + lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + + pageIndicator.Create(); + + lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr); + + lv_obj_set_style_local_bg_opa(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_style_local_pad_all(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 10); + lv_obj_set_style_local_pad_inner(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5); + lv_obj_set_style_local_border_width(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0); + + lv_obj_set_pos(container1, 10, 60); + lv_obj_set_width(container1, LV_HOR_RES - 20); + lv_obj_set_height(container1, LV_VER_RES - 50); + lv_cont_set_layout(container1, LV_LAYOUT_COLUMN_LEFT); + + lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(title, optionsTitle); + lv_label_set_align(title, LV_LABEL_ALIGN_CENTER); + lv_obj_align(title, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 10, 15); + + lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE); + lv_label_set_text_static(icon, optionsSymbol); + lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER); + lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); + + for (unsigned int i = 0; i < options.size(); i++) { + if (strcmp(options[i].name, "")) { + cbOption[i] = lv_checkbox_create(container1, nullptr); + lv_checkbox_set_text(cbOption[i], options[i].name); + if (!options[i].enabled) { + lv_checkbox_set_disabled(cbOption[i]); + } + cbOption[i]->user_data = this; + lv_obj_set_event_cb(cbOption[i], event_handler); + SetRadioButtonStyle(cbOption[i]); + + if (static_cast(originalValue - MaxItems * screenID) == i) { + lv_checkbox_set_checked(cbOption[i], true); + } + } + } +} + +CheckboxList::~CheckboxList() { + lv_obj_clean(lv_scr_act()); + OnValueChanged(value); +} + +void CheckboxList::UpdateSelected(lv_obj_t* object, lv_event_t event) { + if (event == LV_EVENT_VALUE_CHANGED) { + for (unsigned int i = 0; i < options.size(); i++) { + if (strcmp(options[i].name, "")) { + if (object == cbOption[i]) { + lv_checkbox_set_checked(cbOption[i], true); + value = MaxItems * screenID + i; + } else { + lv_checkbox_set_checked(cbOption[i], false); + } + if (!options[i].enabled) { + lv_checkbox_set_disabled(cbOption[i]); + } + } + } + } +} diff --git a/src/displayapp/screens/CheckboxList.h b/src/displayapp/screens/CheckboxList.h new file mode 100644 index 00000000..48125d4b --- /dev/null +++ b/src/displayapp/screens/CheckboxList.h @@ -0,0 +1,45 @@ +#pragma once + +#include "displayapp/Apps.h" +#include "displayapp/screens/Screen.h" +#include +#include +#include +#include +#include +#include "displayapp/widgets/PageIndicator.h" + +namespace Pinetime { + namespace Applications { + namespace Screens { + class CheckboxList : public Screen { + public: + static constexpr size_t MaxItems = 4; + struct Item { + const char* name; + bool enabled; + }; + + CheckboxList(const uint8_t screenID, + const uint8_t numScreens, + DisplayApp* app, + const char* optionsTitle, + const char* optionsSymbol, + uint32_t originalValue, + std::function OnValueChanged, + std::array options); + ~CheckboxList() override; + void UpdateSelected(lv_obj_t* object, lv_event_t event); + + private: + const uint8_t screenID; + std::function OnValueChanged; + std::array options; + std::array cbOption; + uint32_t value; + + Widgets::PageIndicator pageIndicator; + }; + } + } +} diff --git a/src/displayapp/screens/Clock.cpp b/src/displayapp/screens/Clock.cpp index 1687dccf..07d307e0 100644 --- a/src/displayapp/screens/Clock.cpp +++ b/src/displayapp/screens/Clock.cpp @@ -10,8 +10,10 @@ #include "displayapp/DisplayApp.h" #include "displayapp/screens/WatchFaceDigital.h" #include "displayapp/screens/WatchFaceTerminal.h" +#include "displayapp/screens/WatchFaceInfineat.h" #include "displayapp/screens/WatchFaceAnalog.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" +#include "displayapp/screens/WatchFaceCasioStyleG7710.h" using namespace Pinetime::Applications::Screens; @@ -19,18 +21,20 @@ Clock::Clock(DisplayApp* app, Controllers::DateTime& dateTimeController, Controllers::Battery& batteryController, Controllers::Ble& bleController, - Controllers::NotificationManager& notificatioManager, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, - Controllers::MotionController& motionController) + Controllers::MotionController& motionController, + Controllers::FS& filesystem) : Screen(app), dateTimeController {dateTimeController}, batteryController {batteryController}, bleController {bleController}, - notificatioManager {notificatioManager}, + notificationManager {notificationManager}, settingsController {settingsController}, heartRateController {heartRateController}, motionController {motionController}, + filesystem {filesystem}, screen {[this, &settingsController]() { switch (settingsController.GetClockFace()) { case 0: @@ -45,6 +49,12 @@ Clock::Clock(DisplayApp* app, case 3: return WatchFaceTerminalScreen(); break; + case 4: + return WatchFaceInfineatScreen(); + break; + case 5: + return WatchFaceCasioStyleG7710(); + break; } return WatchFaceDigitalScreen(); }()} { @@ -68,7 +78,7 @@ std::unique_ptr Clock::WatchFaceDigitalScreen() { dateTimeController, batteryController, bleController, - notificatioManager, + notificationManager, settingsController, heartRateController, motionController); @@ -79,7 +89,7 @@ std::unique_ptr Clock::WatchFaceAnalogScreen() { dateTimeController, batteryController, bleController, - notificatioManager, + notificationManager, settingsController); } @@ -88,7 +98,7 @@ std::unique_ptr Clock::WatchFacePineTimeStyleScreen() { dateTimeController, batteryController, bleController, - notificatioManager, + notificationManager, settingsController, motionController); } @@ -98,8 +108,31 @@ std::unique_ptr Clock::WatchFaceTerminalScreen() { dateTimeController, batteryController, bleController, - notificatioManager, + notificationManager, settingsController, heartRateController, motionController); } + +std::unique_ptr Clock::WatchFaceInfineatScreen() { + return std::make_unique(app, + dateTimeController, + batteryController, + bleController, + notificationManager, + settingsController, + motionController, + filesystem); +} + +std::unique_ptr Clock::WatchFaceCasioStyleG7710() { + return std::make_unique(app, + dateTimeController, + batteryController, + bleController, + notificationManager, + settingsController, + heartRateController, + motionController, + filesystem); +} diff --git a/src/displayapp/screens/Clock.h b/src/displayapp/screens/Clock.h index 1ba752c7..613fef57 100644 --- a/src/displayapp/screens/Clock.h +++ b/src/displayapp/screens/Clock.h @@ -25,10 +25,11 @@ namespace Pinetime { Controllers::DateTime& dateTimeController, Controllers::Battery& batteryController, Controllers::Ble& bleController, - Controllers::NotificationManager& notificatioManager, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, - Controllers::MotionController& motionController); + Controllers::MotionController& motionController, + Controllers::FS& filesystem); ~Clock() override; bool OnTouchEvent(TouchEvents event) override; @@ -38,16 +39,19 @@ namespace Pinetime { Controllers::DateTime& dateTimeController; Controllers::Battery& batteryController; Controllers::Ble& bleController; - Controllers::NotificationManager& notificatioManager; + Controllers::NotificationManager& notificationManager; Controllers::Settings& settingsController; Controllers::HeartRateController& heartRateController; Controllers::MotionController& motionController; + Controllers::FS& filesystem; std::unique_ptr screen; std::unique_ptr WatchFaceDigitalScreen(); std::unique_ptr WatchFaceAnalogScreen(); std::unique_ptr WatchFacePineTimeStyleScreen(); std::unique_ptr WatchFaceTerminalScreen(); + std::unique_ptr WatchFaceInfineatScreen(); + std::unique_ptr WatchFaceCasioStyleG7710(); }; } } diff --git a/src/displayapp/screens/Error.cpp b/src/displayapp/screens/Error.cpp index 1f2c61d6..74f222a9 100644 --- a/src/displayapp/screens/Error.cpp +++ b/src/displayapp/screens/Error.cpp @@ -36,7 +36,8 @@ Error::Error(Pinetime::Applications::DisplayApp* app, System::BootErrors error) lv_obj_set_event_cb(btnOk, ButtonEventCallback); lv_obj_set_size(btnOk, LV_HOR_RES, 50); lv_obj_align(btnOk, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0); - lv_obj_set_style_local_value_str(btnOk, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "Proceed"); + lv_obj_t* lblOk = lv_label_create(btnOk, nullptr); + lv_label_set_text_static(lblOk, "Proceed"); lv_obj_set_style_local_bg_color(btnOk, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE); } diff --git a/src/displayapp/screens/Metronome.cpp b/src/displayapp/screens/Metronome.cpp index 174ac1b6..df87092b 100644 --- a/src/displayapp/screens/Metronome.cpp +++ b/src/displayapp/screens/Metronome.cpp @@ -64,7 +64,8 @@ Metronome::Metronome(DisplayApp* app, Controllers::MotorController& motorControl lv_obj_set_event_cb(playPause, eventHandler); lv_obj_set_size(playPause, 115, 50); lv_obj_align(playPause, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); - lv_obj_set_style_local_value_str(playPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::play); + lblPlayPause = lv_label_create(playPause, nullptr); + lv_label_set_text_static(lblPlayPause, Symbols::play); taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); } @@ -126,12 +127,12 @@ void Metronome::OnEvent(lv_obj_t* obj, lv_event_t event) { if (obj == playPause) { metronomeStarted = !metronomeStarted; if (metronomeStarted) { - lv_obj_set_style_local_value_str(playPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::pause); + lv_label_set_text_static(lblPlayPause, Symbols::pause); systemTask.PushMessage(System::Messages::DisableSleeping); startTime = xTaskGetTickCount(); counter = 1; } else { - lv_obj_set_style_local_value_str(playPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::play); + lv_label_set_text_static(lblPlayPause, Symbols::play); systemTask.PushMessage(System::Messages::EnableSleeping); } } diff --git a/src/displayapp/screens/Metronome.h b/src/displayapp/screens/Metronome.h index 8933b17e..c062959c 100644 --- a/src/displayapp/screens/Metronome.h +++ b/src/displayapp/screens/Metronome.h @@ -31,6 +31,7 @@ namespace Pinetime { lv_obj_t *bpmArc, *bpmTap, *bpmValue; lv_obj_t *bpbDropdown, *currentBpbText; lv_obj_t* playPause; + lv_obj_t* lblPlayPause; lv_task_t* taskRefresh; }; diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 1180ec6f..47177942 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -50,6 +50,7 @@ namespace Pinetime { static constexpr const char* notificationsOn = "\xEE\x9F\xB7"; static constexpr const char* flashlight = "\xEF\x80\x8B"; + static constexpr const char* paintbrushLg = "\xEE\x90\x8A"; } } } diff --git a/src/displayapp/screens/Twos.cpp b/src/displayapp/screens/Twos.cpp index 5d1f4980..9e7418c8 100644 --- a/src/displayapp/screens/Twos.cpp +++ b/src/displayapp/screens/Twos.cpp @@ -7,53 +7,34 @@ using namespace Pinetime::Applications::Screens; Twos::Twos(Pinetime::Applications::DisplayApp* app) : Screen(app) { - // create styles to apply to different valued tiles - lv_style_init(&style_cell1); - lv_style_init(&style_cell2); - lv_style_init(&style_cell3); - lv_style_init(&style_cell4); - lv_style_init(&style_cell5); + struct colorPair { + lv_color_t bg; + lv_color_t fg; + }; - lv_style_set_border_color(&style_cell1, LV_STATE_DEFAULT, lv_color_hex(0xbbada0)); - lv_style_set_border_width(&style_cell1, LV_STATE_DEFAULT, 3); - lv_style_set_bg_opa(&style_cell1, LV_STATE_DEFAULT, LV_OPA_COVER); - lv_style_set_bg_color(&style_cell1, LV_STATE_DEFAULT, lv_color_hex(0xcdc0b4)); - lv_style_set_pad_top(&style_cell1, LV_STATE_DEFAULT, 29); - lv_style_set_text_color(&style_cell1, LV_STATE_DEFAULT, LV_COLOR_BLACK); - - lv_style_set_border_color(&style_cell2, LV_STATE_DEFAULT, lv_color_hex(0xbbada0)); - lv_style_set_border_width(&style_cell2, LV_STATE_DEFAULT, 3); - lv_style_set_bg_opa(&style_cell2, LV_STATE_DEFAULT, LV_OPA_COVER); - lv_style_set_bg_color(&style_cell2, LV_STATE_DEFAULT, lv_color_hex(0xefdfc6)); - lv_style_set_pad_top(&style_cell2, LV_STATE_DEFAULT, 29); - lv_style_set_text_color(&style_cell2, LV_STATE_DEFAULT, LV_COLOR_BLACK); - - lv_style_set_border_color(&style_cell3, LV_STATE_DEFAULT, lv_color_hex(0xbbada0)); - lv_style_set_border_width(&style_cell3, LV_STATE_DEFAULT, 3); - lv_style_set_bg_opa(&style_cell3, LV_STATE_DEFAULT, LV_OPA_COVER); - lv_style_set_bg_color(&style_cell3, LV_STATE_DEFAULT, lv_color_hex(0xef9263)); - lv_style_set_pad_top(&style_cell3, LV_STATE_DEFAULT, 29); - - lv_style_set_border_color(&style_cell4, LV_STATE_DEFAULT, lv_color_hex(0xbbada0)); - lv_style_set_border_width(&style_cell4, LV_STATE_DEFAULT, 3); - lv_style_set_bg_opa(&style_cell4, LV_STATE_DEFAULT, LV_OPA_COVER); - lv_style_set_bg_color(&style_cell4, LV_STATE_DEFAULT, lv_color_hex(0xf76142)); - lv_style_set_pad_top(&style_cell4, LV_STATE_DEFAULT, 29); - - lv_style_set_border_color(&style_cell5, LV_STATE_DEFAULT, lv_color_hex(0xbbada0)); - lv_style_set_border_width(&style_cell5, LV_STATE_DEFAULT, 3); - lv_style_set_bg_opa(&style_cell5, LV_STATE_DEFAULT, LV_OPA_COVER); - lv_style_set_bg_color(&style_cell5, LV_STATE_DEFAULT, lv_color_hex(0x007dc5)); - lv_style_set_pad_top(&style_cell5, LV_STATE_DEFAULT, 29); - - // format grid display + static constexpr colorPair colors[nColors] = { + {LV_COLOR_MAKE(0xcd, 0xc0, 0xb4), LV_COLOR_BLACK}, + {LV_COLOR_MAKE(0xef, 0xdf, 0xc6), LV_COLOR_BLACK}, + {LV_COLOR_MAKE(0xef, 0x92, 0x63), LV_COLOR_WHITE}, + {LV_COLOR_MAKE(0xf7, 0x61, 0x42), LV_COLOR_WHITE}, + {LV_COLOR_MAKE(0x00, 0x7d, 0xc5), LV_COLOR_WHITE}, + }; gridDisplay = lv_table_create(lv_scr_act(), nullptr); - lv_obj_add_style(gridDisplay, LV_TABLE_PART_CELL1, &style_cell1); - lv_obj_add_style(gridDisplay, LV_TABLE_PART_CELL2, &style_cell2); - lv_obj_add_style(gridDisplay, LV_TABLE_PART_CELL3, &style_cell3); - lv_obj_add_style(gridDisplay, LV_TABLE_PART_CELL4, &style_cell4); - lv_obj_add_style(gridDisplay, LV_TABLE_PART_CELL4 + 1, &style_cell5); + + for (size_t i = 0; i < nColors; i++) { + lv_style_init(&cellStyles[i]); + + lv_style_set_border_color(&cellStyles[i], LV_STATE_DEFAULT, lv_color_hex(0xbbada0)); + lv_style_set_border_width(&cellStyles[i], LV_STATE_DEFAULT, 3); + lv_style_set_bg_opa(&cellStyles[i], LV_STATE_DEFAULT, LV_OPA_COVER); + lv_style_set_bg_color(&cellStyles[i], LV_STATE_DEFAULT, colors[i].bg); + lv_style_set_pad_top(&cellStyles[i], LV_STATE_DEFAULT, 29); + lv_style_set_text_color(&cellStyles[i], LV_STATE_DEFAULT, colors[i].fg); + + lv_obj_add_style(gridDisplay, LV_TABLE_PART_CELL1 + i, &cellStyles[i]); + } + lv_table_set_col_cnt(gridDisplay, nCols); lv_table_set_row_cnt(gridDisplay, nRows); for (int col = 0; col < nCols; col++) { @@ -83,11 +64,9 @@ Twos::Twos(Pinetime::Applications::DisplayApp* app) : Screen(app) { } Twos::~Twos() { - lv_style_reset(&style_cell1); - lv_style_reset(&style_cell2); - lv_style_reset(&style_cell3); - lv_style_reset(&style_cell4); - lv_style_reset(&style_cell5); + for (lv_style_t cellStyle : cellStyles) { + lv_style_reset(&cellStyle); + } lv_obj_clean(lv_scr_act()); } diff --git a/src/displayapp/screens/Twos.h b/src/displayapp/screens/Twos.h index 4a6ada0b..da935724 100644 --- a/src/displayapp/screens/Twos.h +++ b/src/displayapp/screens/Twos.h @@ -18,11 +18,8 @@ namespace Pinetime { bool OnTouchEvent(TouchEvents event) override; private: - lv_style_t style_cell1; - lv_style_t style_cell2; - lv_style_t style_cell3; - lv_style_t style_cell4; - lv_style_t style_cell5; + static constexpr int nColors = 5; + lv_style_t cellStyles[nColors]; lv_obj_t* scoreText; lv_obj_t* gridDisplay; diff --git a/src/displayapp/screens/WatchFaceAnalog.cpp b/src/displayapp/screens/WatchFaceAnalog.cpp index 5ebb3304..5e5317ee 100644 --- a/src/displayapp/screens/WatchFaceAnalog.cpp +++ b/src/displayapp/screens/WatchFaceAnalog.cpp @@ -70,7 +70,6 @@ WatchFaceAnalog::WatchFaceAnalog(Pinetime::Applications::DisplayApp* app, plugIcon = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(plugIcon, Symbols::plug); - lv_obj_set_style_local_text_color(plugIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); lv_obj_align(plugIcon, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0); notificationIcon = lv_label_create(lv_scr_act(), NULL); diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp new file mode 100644 index 00000000..bdae0d42 --- /dev/null +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp @@ -0,0 +1,355 @@ +#include "displayapp/screens/WatchFaceCasioStyleG7710.h" + +#include +#include +#include +#include "displayapp/screens/BatteryIcon.h" +#include "displayapp/screens/BleIcon.h" +#include "displayapp/screens/NotificationIcon.h" +#include "displayapp/screens/Symbols.h" +#include "components/battery/BatteryController.h" +#include "components/ble/BleController.h" +#include "components/ble/NotificationManager.h" +#include "components/heartrate/HeartRateController.h" +#include "components/motion/MotionController.h" +#include "components/settings/Settings.h" +using namespace Pinetime::Applications::Screens; + +WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(DisplayApp* app, + Controllers::DateTime& dateTimeController, + Controllers::Battery& batteryController, + Controllers::Ble& bleController, + Controllers::NotificationManager& notificatioManager, + Controllers::Settings& settingsController, + Controllers::HeartRateController& heartRateController, + Controllers::MotionController& motionController, + Controllers::FS& filesystem) + : Screen(app), + currentDateTime {{}}, + dateTimeController {dateTimeController}, + batteryController {batteryController}, + bleController {bleController}, + notificatioManager {notificatioManager}, + settingsController {settingsController}, + heartRateController {heartRateController}, + motionController {motionController} { + + lfs_file f = {}; + if (filesystem.FileOpen(&f, "/fonts/lv_font_dots_40.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_dot40 = lv_font_load("F:/fonts/lv_font_dots_40.bin"); + } + + if (filesystem.FileOpen(&f, "/fonts/7segments_40.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_segment40 = lv_font_load("F:/fonts/7segments_40.bin"); + } + + if (filesystem.FileOpen(&f, "/fonts/7segments_115.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_segment115 = lv_font_load("F:/fonts/7segments_115.bin"); + } + + label_battery_vallue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_align(label_battery_vallue, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); + lv_obj_set_style_local_text_color(label_battery_vallue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_label_set_text_static(label_battery_vallue, "00%"); + + batteryIcon.Create(lv_scr_act()); + batteryIcon.SetColor(color_text); + lv_obj_align(batteryIcon.GetObject(), label_battery_vallue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + + batteryPlug = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(batteryPlug, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_label_set_text_static(batteryPlug, Symbols::plug); + lv_obj_align(batteryPlug, batteryIcon.GetObject(), LV_ALIGN_OUT_LEFT_MID, -5, 0); + + bleIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_label_set_text_static(bleIcon, Symbols::bluetooth); + lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0); + + notificationIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false)); + lv_obj_align(notificationIcon, bleIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0); + + label_day_of_week = lv_label_create(lv_scr_act(), nullptr); + lv_obj_align(label_day_of_week, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 10, 64); + lv_obj_set_style_local_text_color(label_day_of_week, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_obj_set_style_local_text_font(label_day_of_week, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_dot40); + lv_label_set_text_static(label_day_of_week, "SUN"); + + label_week_number = lv_label_create(lv_scr_act(), nullptr); + lv_obj_align(label_week_number, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 5, 22); + lv_obj_set_style_local_text_color(label_week_number, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_obj_set_style_local_text_font(label_week_number, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_dot40); + lv_label_set_text_static(label_week_number, "WK26"); + + label_day_of_year = lv_label_create(lv_scr_act(), nullptr); + lv_obj_align(label_day_of_year, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 100, 30); + lv_obj_set_style_local_text_color(label_day_of_year, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_obj_set_style_local_text_font(label_day_of_year, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment40); + lv_label_set_text_static(label_day_of_year, "181-184"); + + lv_style_init(&style_line); + lv_style_set_line_width(&style_line, LV_STATE_DEFAULT, 2); + lv_style_set_line_color(&style_line, LV_STATE_DEFAULT, color_text); + lv_style_set_line_rounded(&style_line, LV_STATE_DEFAULT, true); + + lv_style_init(&style_border); + lv_style_set_line_width(&style_border, LV_STATE_DEFAULT, 6); + lv_style_set_line_color(&style_border, LV_STATE_DEFAULT, color_text); + lv_style_set_line_rounded(&style_border, LV_STATE_DEFAULT, true); + + line_icons = lv_line_create(lv_scr_act(), nullptr); + lv_line_set_points(line_icons, line_icons_points, 3); + lv_obj_add_style(line_icons, LV_LINE_PART_MAIN, &style_line); + lv_obj_align(line_icons, nullptr, LV_ALIGN_IN_TOP_RIGHT, -10, 18); + + line_day_of_week_number = lv_line_create(lv_scr_act(), nullptr); + lv_line_set_points(line_day_of_week_number, line_day_of_week_number_points, 4); + lv_obj_add_style(line_day_of_week_number, LV_LINE_PART_MAIN, &style_border); + lv_obj_align(line_day_of_week_number, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 8); + + line_day_of_year = lv_line_create(lv_scr_act(), nullptr); + lv_line_set_points(line_day_of_year, line_day_of_year_points, 3); + lv_obj_add_style(line_day_of_year, LV_LINE_PART_MAIN, &style_line); + lv_obj_align(line_day_of_year, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 60); + + label_date = lv_label_create(lv_scr_act(), nullptr); + lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 100, 70); + lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_obj_set_style_local_text_font(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment40); + lv_label_set_text_static(label_date, "6-30"); + + line_date = lv_line_create(lv_scr_act(), nullptr); + lv_line_set_points(line_date, line_date_points, 3); + lv_obj_add_style(line_date, LV_LINE_PART_MAIN, &style_line); + lv_obj_align(line_date, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 100); + + label_time = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment115); + lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 40); + + line_time = lv_line_create(lv_scr_act(), nullptr); + lv_line_set_points(line_time, line_time_points, 3); + lv_obj_add_style(line_time, LV_LINE_PART_MAIN, &style_line); + lv_obj_align(line_time, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, -25); + + label_time_ampm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(label_time_ampm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_label_set_text_static(label_time_ampm, ""); + lv_obj_align(label_time_ampm, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 5, -5); + + backgroundLabel = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_click(backgroundLabel, true); + 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_static(backgroundLabel, ""); + + 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, color_text); + lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2); + + heartbeatValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_label_set_text_static(heartbeatValue, ""); + lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0); + + stepValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_label_set_text_static(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_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_label_set_text_static(stepIcon, Symbols::shoe); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); +} + +WatchFaceCasioStyleG7710::~WatchFaceCasioStyleG7710() { + lv_task_del(taskRefresh); + + lv_style_reset(&style_line); + lv_style_reset(&style_border); + + if (font_dot40 != nullptr) { + lv_font_free(font_dot40); + } + + if (font_segment40 != nullptr) { + lv_font_free(font_segment40); + } + + if (font_segment115 != nullptr) { + lv_font_free(font_segment115); + } + + lv_obj_clean(lv_scr_act()); +} + +void WatchFaceCasioStyleG7710::Refresh() { + powerPresent = batteryController.IsPowerPresent(); + if (powerPresent.IsUpdated()) { + lv_label_set_text_static(batteryPlug, BatteryIcon::GetPlugIcon(powerPresent.Get())); + } + + batteryPercentRemaining = batteryController.PercentRemaining(); + if (batteryPercentRemaining.IsUpdated()) { + auto batteryPercent = batteryPercentRemaining.Get(); + batteryIcon.SetBatteryPercentage(batteryPercent); + lv_label_set_text_fmt(label_battery_vallue, "%d%%", batteryPercent); + } + + bleState = bleController.IsConnected(); + bleRadioEnabled = bleController.IsRadioEnabled(); + if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) { + lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); + } + lv_obj_realign(label_battery_vallue); + lv_obj_realign(batteryIcon.GetObject()); + lv_obj_realign(batteryPlug); + lv_obj_realign(bleIcon); + lv_obj_realign(notificationIcon); + + notificationState = notificatioManager.AreNewNotificationsAvailable(); + if (notificationState.IsUpdated()) { + lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get())); + } + + currentDateTime = dateTimeController.CurrentDateTime(); + + if (currentDateTime.IsUpdated()) { + auto newDateTime = currentDateTime.Get(); + + auto dp = date::floor(newDateTime); + auto time = date::make_time(newDateTime - dp); + auto yearMonthDay = date::year_month_day(dp); + + auto year = static_cast(yearMonthDay.year()); + auto month = static_cast(static_cast(yearMonthDay.month())); + auto day = static_cast(yearMonthDay.day()); + auto dayOfWeek = static_cast(date::weekday(yearMonthDay).iso_encoding()); + + uint8_t hour = time.hours().count(); + uint8_t minute = time.minutes().count(); + auto weekNumberFormat = "%V"; + + if (displayedHour != hour || displayedMinute != minute) { + displayedHour = hour; + displayedMinute = minute; + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[2] = "A"; + if (hour == 0) { + hour = 12; + } else if (hour == 12) { + ampmChar[0] = 'P'; + } else if (hour > 12) { + hour = hour - 12; + ampmChar[0] = 'P'; + } + lv_label_set_text(label_time_ampm, ampmChar); + lv_label_set_text_fmt(label_time, "%2d:%02d", hour, minute); + lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 40); + } else { + lv_label_set_text_fmt(label_time, "%02d:%02d", hour, minute); + lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 40); + } + } + + if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) { + // 24h mode: ddmmyyyy, first DOW=Monday; + lv_label_set_text_fmt(label_date, "%3d-%2d", day, month); + weekNumberFormat = "%V"; // Replaced by the week number of the year (Monday as the first day of the week) as a decimal number + // [01,53]. If the week containing 1 January has four or more days in the new year, then it is considered + // week 1. Otherwise, it is the last week of the previous year, and the next week is week 1. Both January + // 4th and the first Thursday of January are always in week 1. [ tm_year, tm_wday, tm_yday] + } else { + // 12h mode: mmddyyyy, first DOW=Sunday; + lv_label_set_text_fmt(label_date, "%3d-%2d", month, day); + weekNumberFormat = "%U"; // Replaced by the week number of the year as a decimal number [00,53]. The first Sunday of January is the + // first day of week 1; days in the new year before this are in week 0. [ tm_year, tm_wday, tm_yday] + } + + uint8_t weekNumber; + uint16_t dayOfYearNumber, daysTillEndOfYearNumber; + + time_t ttTime = + std::chrono::system_clock::to_time_t(std::chrono::time_point_cast(currentDateTime.Get())); + tm* tmTime = std::localtime(&ttTime); + + dayOfYearNumber = tmTime->tm_yday + 1; // tm_yday day of year [0,365] => yday+1 + daysTillEndOfYearNumber = (yearMonthDay.year().is_leap() ? 366 : 365) - dayOfYearNumber; + + char buffer[8]; + strftime(buffer, 8, weekNumberFormat, tmTime); + weekNumber = atoi(buffer); + + lv_label_set_text_fmt(label_day_of_week, "%s", dateTimeController.DayOfWeekShortToString()); + lv_label_set_text_fmt(label_day_of_year, "%3d-%3d", dayOfYearNumber, daysTillEndOfYearNumber); + lv_label_set_text_fmt(label_week_number, "WK%02d", weekNumber); + + lv_obj_realign(label_day_of_week); + lv_obj_realign(label_day_of_year); + lv_obj_realign(label_week_number); + lv_obj_realign(label_date); + + currentYear = year; + currentMonth = month; + currentDayOfWeek = dayOfWeek; + currentDay = day; + } + } + + heartbeat = heartRateController.HeartRate(); + heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped; + if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) { + if (heartbeatRunning.Get()) { + lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get()); + } else { + 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(); + motionSensorOk = motionController.IsSensorOk(); + if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { + lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); + lv_obj_realign(stepValue); + lv_obj_realign(stepIcon); + } +} +bool WatchFaceCasioStyleG7710::IsAvailable(Pinetime::Controllers::FS& filesystem) { + lfs_file file = {}; + + if (filesystem.FileOpen(&file, "/fonts/lv_font_dots_40.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/fonts/7segments_40.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/fonts/7segments_115.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + return true; +} diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.h b/src/displayapp/screens/WatchFaceCasioStyleG7710.h new file mode 100644 index 00000000..eb7bb9e8 --- /dev/null +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "displayapp/screens/Screen.h" +#include "components/datetime/DateTimeController.h" +#include "components/ble/BleController.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class Battery; + class Ble; + class NotificationManager; + class HeartRateController; + class MotionController; + } + + namespace Applications { + namespace Screens { + + class WatchFaceCasioStyleG7710 : public Screen { + public: + WatchFaceCasioStyleG7710(DisplayApp* app, + Controllers::DateTime& dateTimeController, + Controllers::Battery& batteryController, + Controllers::Ble& bleController, + Controllers::NotificationManager& notificatioManager, + Controllers::Settings& settingsController, + Controllers::HeartRateController& heartRateController, + Controllers::MotionController& motionController, + Controllers::FS& filesystem); + ~WatchFaceCasioStyleG7710() override; + + void Refresh() override; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem); + + private: + uint8_t displayedHour = -1; + uint8_t displayedMinute = -1; + + uint16_t currentYear = 1970; + Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown; + Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown; + uint8_t currentDay = 0; + + DirtyValue batteryPercentRemaining {}; + DirtyValue powerPresent {}; + DirtyValue bleState {}; + DirtyValue bleRadioEnabled {}; + DirtyValue> currentDateTime {}; + DirtyValue motionSensorOk {}; + DirtyValue stepCount {}; + DirtyValue heartbeat {}; + DirtyValue heartbeatRunning {}; + DirtyValue notificationState {}; + + lv_point_t line_icons_points[3] {{0, 5}, {117, 5}, {122, 0}}; + lv_point_t line_day_of_week_number_points[4] {{0, 0}, {100, 0}, {95, 95}, {0, 95}}; + lv_point_t line_day_of_year_points[3] {{0, 5}, {130, 5}, {135, 0}}; + lv_point_t line_date_points[3] {{0, 5}, {135, 5}, {140, 0}}; + lv_point_t line_time_points[3] {{0, 0}, {230, 0}, {235, 5}}; + + lv_color_t color_text = lv_color_hex(0x98B69A); + + lv_style_t style_line; + lv_style_t style_border; + + lv_obj_t* label_time; + lv_obj_t* line_time; + lv_obj_t* label_time_ampm; + lv_obj_t* label_date; + lv_obj_t* line_date; + lv_obj_t* label_day_of_week; + lv_obj_t* label_week_number; + lv_obj_t* line_day_of_week_number; + lv_obj_t* label_day_of_year; + lv_obj_t* line_day_of_year; + lv_obj_t* backgroundLabel; + lv_obj_t* bleIcon; + lv_obj_t* batteryPlug; + lv_obj_t* label_battery_vallue; + lv_obj_t* heartbeatIcon; + lv_obj_t* heartbeatValue; + lv_obj_t* stepIcon; + lv_obj_t* stepValue; + lv_obj_t* notificationIcon; + lv_obj_t* line_icons; + + BatteryIcon batteryIcon; + + Controllers::DateTime& dateTimeController; + Controllers::Battery& batteryController; + Controllers::Ble& bleController; + Controllers::NotificationManager& notificatioManager; + Controllers::Settings& settingsController; + Controllers::HeartRateController& heartRateController; + Controllers::MotionController& motionController; + + lv_task_t* taskRefresh; + lv_font_t* font_dot40 = nullptr; + lv_font_t* font_segment40 = nullptr; + lv_font_t* font_segment115 = nullptr; + }; + } + } +} diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp index 705272f7..47f40dab 100644 --- a/src/displayapp/screens/WatchFaceDigital.cpp +++ b/src/displayapp/screens/WatchFaceDigital.cpp @@ -18,14 +18,14 @@ WatchFaceDigital::WatchFaceDigital(DisplayApp* app, Controllers::DateTime& dateTimeController, Controllers::Battery& batteryController, Controllers::Ble& bleController, - Controllers::NotificationManager& notificatioManager, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, Controllers::MotionController& motionController) : Screen(app), currentDateTime {{}}, dateTimeController {dateTimeController}, - notificatioManager {notificatioManager}, + notificationManager {notificationManager}, settingsController {settingsController}, heartRateController {heartRateController}, motionController {motionController}, @@ -83,7 +83,7 @@ WatchFaceDigital::~WatchFaceDigital() { void WatchFaceDigital::Refresh() { statusIcons.Update(); - notificationState = notificatioManager.AreNewNotificationsAvailable(); + notificationState = notificationManager.AreNewNotificationsAvailable(); if (notificationState.IsUpdated()) { lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get())); } diff --git a/src/displayapp/screens/WatchFaceDigital.h b/src/displayapp/screens/WatchFaceDigital.h index 49935792..60446afa 100644 --- a/src/displayapp/screens/WatchFaceDigital.h +++ b/src/displayapp/screens/WatchFaceDigital.h @@ -28,7 +28,7 @@ namespace Pinetime { Controllers::DateTime& dateTimeController, Controllers::Battery& batteryController, Controllers::Ble& bleController, - Controllers::NotificationManager& notificatioManager, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, Controllers::MotionController& motionController); @@ -66,7 +66,7 @@ namespace Pinetime { lv_obj_t* notificationIcon; Controllers::DateTime& dateTimeController; - Controllers::NotificationManager& notificatioManager; + Controllers::NotificationManager& notificationManager; Controllers::Settings& settingsController; Controllers::HeartRateController& heartRateController; Controllers::MotionController& motionController; diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp new file mode 100644 index 00000000..bd4e4ac8 --- /dev/null +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -0,0 +1,632 @@ +#include "displayapp/screens/WatchFaceInfineat.h" + +#include +#include +#include +#include "displayapp/screens/Symbols.h" +#include "displayapp/screens/BleIcon.h" +#include "components/settings/Settings.h" +#include "components/battery/BatteryController.h" +#include "components/ble/BleController.h" +#include "components/ble/NotificationManager.h" +#include "components/motion/MotionController.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + void event_handler(lv_obj_t* obj, lv_event_t event) { + auto* screen = static_cast(obj->user_data); + screen->UpdateSelected(obj, event); + } +} + +WatchFaceInfineat::WatchFaceInfineat(DisplayApp* app, + Controllers::DateTime& dateTimeController, + Controllers::Battery& batteryController, + Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& filesystem) + : Screen(app), + currentDateTime {{}}, + dateTimeController {dateTimeController}, + batteryController {batteryController}, + bleController {bleController}, + notificationManager {notificationManager}, + settingsController {settingsController}, + motionController {motionController} { + lfs_file f = {}; + if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_teko = lv_font_load("F:/fonts/teko.bin"); + } + + if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_bebas = lv_font_load("F:/fonts/bebas.bin"); + } + + // Black background covering the whole screen + background = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(background, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_size(background, 240, 240); + lv_obj_align(background, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 0, 0); + + // Side Cover + line0 = lv_line_create(lv_scr_act(), nullptr); + line1 = lv_line_create(lv_scr_act(), nullptr); + line2 = lv_line_create(lv_scr_act(), nullptr); + line3 = lv_line_create(lv_scr_act(), nullptr); + line4 = lv_line_create(lv_scr_act(), nullptr); + line5 = lv_line_create(lv_scr_act(), nullptr); + line6 = lv_line_create(lv_scr_act(), nullptr); + line7 = lv_line_create(lv_scr_act(), nullptr); + line8 = lv_line_create(lv_scr_act(), nullptr); + lineBattery = lv_line_create(lv_scr_act(), nullptr); + + lv_style_init(&line0Style); + lv_style_set_line_width(&line0Style, LV_STATE_DEFAULT, 18); + lv_style_set_line_color(&line0Style, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines])); + lv_obj_add_style(line0, LV_LINE_PART_MAIN, &line0Style); + line0Points[0] = {30, 25}; + line0Points[1] = {68, -8}; + lv_line_set_points(line0, line0Points, 2); + + lv_style_init(&line1Style); + lv_style_set_line_width(&line1Style, LV_STATE_DEFAULT, 15); + lv_style_set_line_color(&line1Style, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 1])); + lv_obj_add_style(line1, LV_LINE_PART_MAIN, &line1Style); + line1Points[0] = {26, 167}; + line1Points[1] = {43, 216}; + lv_line_set_points(line1, line1Points, 2); + + lv_style_init(&line2Style); + lv_style_set_line_width(&line2Style, LV_STATE_DEFAULT, 14); + lv_style_set_line_color(&line2Style, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 2])); + lv_obj_add_style(line2, LV_LINE_PART_MAIN, &line2Style); + line2Points[0] = {27, 40}; + line2Points[1] = {27, 196}; + lv_line_set_points(line2, line2Points, 2); + + lv_style_init(&line3Style); + lv_style_set_line_width(&line3Style, LV_STATE_DEFAULT, 22); + lv_style_set_line_color(&line3Style, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 3])); + lv_obj_add_style(line3, LV_LINE_PART_MAIN, &line3Style); + line3Points[0] = {12, 182}; + line3Points[1] = {65, 249}; + lv_line_set_points(line3, line3Points, 2); + + lv_style_init(&line4Style); + lv_style_set_line_width(&line4Style, LV_STATE_DEFAULT, 20); + lv_style_set_line_color(&line4Style, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 4])); + lv_obj_add_style(line4, LV_LINE_PART_MAIN, &line4Style); + line4Points[0] = {17, 99}; + line4Points[1] = {17, 144}; + lv_line_set_points(line4, line4Points, 2); + + lv_style_init(&line5Style); + lv_style_set_line_width(&line5Style, LV_STATE_DEFAULT, 18); + lv_style_set_line_color(&line5Style, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 5])); + lv_obj_add_style(line5, LV_LINE_PART_MAIN, &line5Style); + line5Points[0] = {14, 81}; + line5Points[1] = {40, 127}; + lv_line_set_points(line5, line5Points, 2); + + lv_style_init(&line6Style); + lv_style_set_line_width(&line6Style, LV_STATE_DEFAULT, 18); + lv_style_set_line_color(&line6Style, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 6])); + lv_obj_add_style(line6, LV_LINE_PART_MAIN, &line6Style); + line6Points[0] = {14, 163}; + line6Points[1] = {40, 118}; + lv_line_set_points(line6, line6Points, 2); + + lv_style_init(&line7Style); + lv_style_set_line_width(&line7Style, LV_STATE_DEFAULT, 52); + lv_style_set_line_color(&line7Style, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 7])); + lv_obj_add_style(line7, LV_LINE_PART_MAIN, &line7Style); + line7Points[0] = {-20, 124}; + line7Points[1] = {25, -11}; + lv_line_set_points(line7, line7Points, 2); + + lv_style_init(&line8Style); + lv_style_set_line_width(&line8Style, LV_STATE_DEFAULT, 48); + lv_style_set_line_color(&line8Style, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 8])); + lv_obj_add_style(line8, LV_LINE_PART_MAIN, &line8Style); + line8Points[0] = {-29, 89}; + line8Points[1] = {27, 254}; + lv_line_set_points(line8, line8Points, 2); + + logoPine = lv_img_create(lv_scr_act(), nullptr); + lv_img_set_src(logoPine, "F:/images/pine_small.bin"); + lv_obj_set_pos(logoPine, 15, 106); + + lv_style_init(&lineBatteryStyle); + lv_style_set_line_width(&lineBatteryStyle, LV_STATE_DEFAULT, 24); + lv_style_set_line_color(&lineBatteryStyle, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 4])); + lv_style_set_line_opa(&lineBatteryStyle, LV_STATE_DEFAULT, 190); + lv_obj_add_style(lineBattery, LV_LINE_PART_MAIN, &lineBatteryStyle); + lineBatteryPoints[0] = {27, 105}; + lineBatteryPoints[1] = {27, 106}; + lv_line_set_points(lineBattery, lineBatteryPoints, 2); + lv_obj_move_foreground(lineBattery); + + notificationIcon = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(notificationIcon, + LV_BTN_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 7])); + lv_obj_set_style_local_radius(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); + lv_obj_set_size(notificationIcon, 13, 13); + lv_obj_set_hidden(notificationIcon, true); + + if (!settingsController.GetInfineatShowSideCover()) { + ToggleBatteryIndicatorColor(false); + lv_obj_set_hidden(line0, true); + lv_obj_set_hidden(line1, true); + lv_obj_set_hidden(line2, true); + lv_obj_set_hidden(line3, true); + lv_obj_set_hidden(line4, true); + lv_obj_set_hidden(line5, true); + lv_obj_set_hidden(line6, true); + lv_obj_set_hidden(line7, true); + lv_obj_set_hidden(line8, true); + } + + timeContainer = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(timeContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + if (font_bebas != nullptr) { + lv_obj_set_size(timeContainer, 185, 185); + lv_obj_align(timeContainer, lv_scr_act(), LV_ALIGN_CENTER, 0, -10); + } else { + lv_obj_set_size(timeContainer, 110, 145); + lv_obj_align(timeContainer, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + } + + labelHour = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed); + lv_label_set_text(labelHour, "01"); + if (font_bebas != nullptr) { + lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0); + } else { + lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed); + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); + } + + labelMinutes = lv_label_create(lv_scr_act(), nullptr); + if (font_bebas != nullptr) { + lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + } else { + lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed); + } + lv_label_set_text(labelMinutes, "00"); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr); + if (font_teko != nullptr) { + lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + } else { + lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_20); + } + + lv_label_set_text(labelTimeAmPm, ""); + lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15); + + dateContainer = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(dateContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_size(dateContainer, 60, 30); + lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5); + + labelDate = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); + if (font_teko != nullptr) { + lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + } else { + lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_20); + } + lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0); + lv_label_set_text(labelDate, "Mon 01"); + + bleIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); + lv_label_set_text(bleIcon, Symbols::bluetooth); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 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(0x999999)); + if (font_teko != nullptr) { + lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + } else { + lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_20); + } + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); + lv_label_set_text(stepValue, "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(0x999999)); + lv_label_set_text(stepIcon, Symbols::shoe); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + + // Setting buttons + btnClose = lv_btn_create(lv_scr_act(), nullptr); + btnClose->user_data = this; + lv_obj_set_size(btnClose, 60, 60); + lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80); + lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblClose = lv_label_create(btnClose, nullptr); + lv_label_set_text_static(lblClose, "X"); + lv_obj_set_event_cb(btnClose, event_handler); + lv_obj_set_hidden(btnClose, true); + + btnNextColor = lv_btn_create(lv_scr_act(), nullptr); + btnNextColor->user_data = this; + lv_obj_set_size(btnNextColor, 60, 60); + lv_obj_align(btnNextColor, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 0); + lv_obj_set_style_local_bg_opa(btnNextColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblNextColor = lv_label_create(btnNextColor, nullptr); + lv_label_set_text_static(lblNextColor, ">"); + lv_obj_set_event_cb(btnNextColor, event_handler); + lv_obj_set_hidden(btnNextColor, true); + + btnPrevColor = lv_btn_create(lv_scr_act(), nullptr); + btnPrevColor->user_data = this; + lv_obj_set_size(btnPrevColor, 60, 60); + lv_obj_align(btnPrevColor, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 0); + lv_obj_set_style_local_bg_opa(btnPrevColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblPrevColor = lv_label_create(btnPrevColor, nullptr); + lv_label_set_text_static(lblPrevColor, "<"); + lv_obj_set_event_cb(btnPrevColor, event_handler); + lv_obj_set_hidden(btnPrevColor, true); + + btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); + btnToggleCover->user_data = this; + lv_obj_set_size(btnToggleCover, 60, 60); + lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); + lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; + lblToggle = lv_label_create(btnToggleCover, nullptr); + lv_label_set_text_static(lblToggle, labelToggle); + lv_obj_set_event_cb(btnToggleCover, event_handler); + lv_obj_set_hidden(btnToggleCover, true); + + // Button to access the settings + btnSettings = lv_btn_create(lv_scr_act(), nullptr); + btnSettings->user_data = this; + lv_obj_set_size(btnSettings, 150, 150); + lv_obj_align(btnSettings, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_local_radius(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 30); + lv_obj_set_style_local_bg_opa(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_set_event_cb(btnSettings, event_handler); + labelBtnSettings = lv_label_create(btnSettings, nullptr); + lv_obj_set_style_local_text_font(labelBtnSettings, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); + lv_label_set_text_static(labelBtnSettings, Symbols::settings); + lv_obj_set_hidden(btnSettings, true); + + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); +} + +WatchFaceInfineat::~WatchFaceInfineat() { + lv_task_del(taskRefresh); + + lv_style_reset(&line0Style); + lv_style_reset(&line1Style); + lv_style_reset(&line2Style); + lv_style_reset(&line3Style); + lv_style_reset(&line4Style); + lv_style_reset(&line5Style); + lv_style_reset(&line6Style); + lv_style_reset(&line7Style); + lv_style_reset(&line8Style); + lv_style_reset(&lineBatteryStyle); + + if (font_bebas != nullptr) { + lv_font_free(font_bebas); + } + if (font_teko != nullptr) { + lv_font_free(font_teko); + } + + lv_obj_clean(lv_scr_act()); +} + +bool WatchFaceInfineat::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) { + lv_obj_set_hidden(btnSettings, false); + savedTick = lv_tick_get(); + return true; + } + // Prevent screen from sleeping when double tapping with settings on + if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) { + return true; + } + return false; +} + +void WatchFaceInfineat::CloseMenu() { + settingsController.SaveSettings(); + lv_obj_set_hidden(btnClose, true); + lv_obj_set_hidden(btnNextColor, true); + lv_obj_set_hidden(btnPrevColor, true); + lv_obj_set_hidden(btnToggleCover, true); +} + +bool WatchFaceInfineat::OnButtonPushed() { + if (!lv_obj_get_hidden(btnClose)) { + CloseMenu(); + return true; + } + return false; +} + +void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { + if (event == LV_EVENT_CLICKED) { + bool showSideCover = settingsController.GetInfineatShowSideCover(); + int colorIndex = settingsController.GetInfineatColorIndex(); + + if (object == btnSettings) { + lv_obj_set_hidden(btnSettings, true); + lv_obj_set_hidden(btnClose, false); + lv_obj_set_hidden(btnNextColor, !showSideCover); + lv_obj_set_hidden(btnPrevColor, !showSideCover); + lv_obj_set_hidden(btnToggleCover, false); + } + if (object == btnClose) { + CloseMenu(); + } + if (object == btnToggleCover) { + settingsController.SetInfineatShowSideCover(!showSideCover); + ToggleBatteryIndicatorColor(!showSideCover); + lv_obj_set_hidden(line0, showSideCover); + lv_obj_set_hidden(line1, showSideCover); + lv_obj_set_hidden(line2, showSideCover); + lv_obj_set_hidden(line3, showSideCover); + lv_obj_set_hidden(line4, showSideCover); + lv_obj_set_hidden(line5, showSideCover); + lv_obj_set_hidden(line6, showSideCover); + lv_obj_set_hidden(line7, showSideCover); + lv_obj_set_hidden(line8, showSideCover); + lv_obj_set_hidden(btnNextColor, showSideCover); + lv_obj_set_hidden(btnPrevColor, showSideCover); + const char* labelToggle = showSideCover ? "OFF" : "ON"; + lv_label_set_text_static(lblToggle, labelToggle); + } + if (object == btnNextColor) { + colorIndex = (colorIndex + 1) % nColors; + settingsController.SetInfineatColorIndex(colorIndex); + } + if (object == btnPrevColor) { + colorIndex -= 1; + if (colorIndex < 0) + colorIndex = nColors - 1; + settingsController.SetInfineatColorIndex(colorIndex); + } + if (object == btnNextColor || object == btnPrevColor) { + lv_obj_set_style_local_line_color(line0, + LV_LINE_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[colorIndex * nLines + 0])); + lv_obj_set_style_local_line_color(line1, + LV_LINE_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[colorIndex * nLines + 1])); + lv_obj_set_style_local_line_color(line2, + LV_LINE_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[colorIndex * nLines + 2])); + lv_obj_set_style_local_line_color(line3, + LV_LINE_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[colorIndex * nLines + 3])); + lv_obj_set_style_local_line_color(line4, + LV_LINE_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[colorIndex * nLines + 4])); + lv_obj_set_style_local_line_color(line5, + LV_LINE_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[colorIndex * nLines + 5])); + lv_obj_set_style_local_line_color(line6, + LV_LINE_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[colorIndex * nLines + 6])); + lv_obj_set_style_local_line_color(line7, + LV_LINE_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[colorIndex * nLines + 7])); + lv_obj_set_style_local_line_color(line8, + LV_LINE_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[colorIndex * nLines + 8])); + lv_obj_set_style_local_line_color(lineBattery, + LV_LINE_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[colorIndex * nLines + 4])); + lv_obj_set_style_local_bg_color(notificationIcon, + LV_BTN_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[colorIndex * nLines + 7])); + } + } +} + +void WatchFaceInfineat::Refresh() { + notificationState = notificationManager.AreNewNotificationsAvailable(); + if (notificationState.IsUpdated()) { + lv_obj_set_hidden(notificationIcon, !notificationState.Get()); + lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); + } + + currentDateTime = dateTimeController.CurrentDateTime(); + + if (currentDateTime.IsUpdated()) { + auto newDateTime = currentDateTime.Get(); + + auto dp = date::floor(newDateTime); + auto time = date::make_time(newDateTime - dp); + auto yearMonthDay = date::year_month_day(dp); + + auto year = static_cast(yearMonthDay.year()); + auto month = static_cast(static_cast(yearMonthDay.month())); + auto day = static_cast(yearMonthDay.day()); + auto dayOfWeek = static_cast(date::weekday(yearMonthDay).iso_encoding()); + + int hour = time.hours().count(); + auto minute = time.minutes().count(); + + char minutesChar[3]; + sprintf(minutesChar, "%02d", static_cast(minute)); + + char hoursChar[3]; + char ampmChar[3]; + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + if (hour < 12) { + if (hour == 0) { + hour = 12; + } + sprintf(ampmChar, "AM"); + } else { // hour >= 12 + if (hour != 12) { + hour = hour - 12; + } + sprintf(ampmChar, "PM"); + } + } + sprintf(hoursChar, "%02d", hour); + + 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_fmt(labelHour, "%s", hoursChar); + lv_label_set_text_fmt(labelMinutes, "%s", minutesChar); + } + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + lv_label_set_text(labelTimeAmPm, ampmChar); + lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 10); + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + } + + if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { + lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(), day); + lv_obj_realign(labelDate); + + currentYear = year; + currentMonth = month; + currentDayOfWeek = dayOfWeek; + currentDay = day; + } + } + + batteryPercentRemaining = batteryController.PercentRemaining(); + isCharging = batteryController.IsCharging(); + // We store if battery and charging are updated before calling Get(), + // since Get() sets isUpdated to false. + bool isBatteryUpdated = batteryPercentRemaining.IsUpdated(); + bool isChargingUpdated = isCharging.IsUpdated(); + if (isCharging.Get()) { // Charging battery animation + chargingBatteryPercent += 1; + if (chargingBatteryPercent > 100) { + chargingBatteryPercent = batteryPercentRemaining.Get(); + } + SetBatteryLevel(chargingBatteryPercent); + } else if (isChargingUpdated || isBatteryUpdated) { + chargingBatteryPercent = batteryPercentRemaining.Get(); + SetBatteryLevel(chargingBatteryPercent); + } + + bleState = bleController.IsConnected(); + bleRadioEnabled = bleController.IsRadioEnabled(); + if (bleState.IsUpdated()) { + lv_label_set_text(bleIcon, BleIcon::GetIcon(bleState.Get())); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); + } + + stepCount = motionController.NbSteps(); + motionSensorOk = motionController.IsSensorOk(); + if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { + lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + } + + if (!lv_obj_get_hidden(btnSettings)) { + if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { + lv_obj_set_hidden(btnSettings, true); + savedTick = 0; + } + } +} + +void WatchFaceInfineat::SetBatteryLevel(uint8_t batteryPercent) { + // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100 + lineBatteryPoints[1] = {27, static_cast(105 + 32 * (100 - batteryPercent) / 100)}; + lv_line_set_points(lineBattery, lineBatteryPoints, 2); +} + +void WatchFaceInfineat::ToggleBatteryIndicatorColor(bool showSideCover) { + if (!showSideCover) { // make indicator and notification icon color white + lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100); + lv_obj_set_style_local_image_recolor(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + } else { + lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0); + lv_obj_set_style_local_line_color(lineBattery, + LV_LINE_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 4])); + lv_obj_set_style_local_bg_color(notificationIcon, + LV_BTN_PART_MAIN, + LV_STATE_DEFAULT, + lv_color_hex(infineatColors.orange[settingsController.GetInfineatColorIndex() * nLines + 7])); + } +} + +bool WatchFaceInfineat::IsAvailable(Pinetime::Controllers::FS& filesystem) { + lfs_file file = {}; + + if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + return true; +} diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h new file mode 100644 index 00000000..6c3c30ba --- /dev/null +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -0,0 +1,147 @@ +#pragma once + +#include +#include +#include +#include +#include "displayapp/screens/Screen.h" +#include "components/datetime/DateTimeController.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class Battery; + class Ble; + class NotificationManager; + class MotionController; + } + + namespace Applications { + namespace Screens { + + class WatchFaceInfineat : public Screen { + public: + WatchFaceInfineat(DisplayApp* app, + Controllers::DateTime& dateTimeController, + Controllers::Battery& batteryController, + Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& fs); + + ~WatchFaceInfineat() override; + + bool OnTouchEvent(TouchEvents event) override; + bool OnButtonPushed() override; + void UpdateSelected(lv_obj_t* object, lv_event_t event); + void CloseMenu(); + + void Refresh() override; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem); + + private: + char displayedChar[5] {}; + + 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; + uint32_t savedTick = 0; + uint8_t chargingBatteryPercent = 101; // not a mistake ;) + + DirtyValue batteryPercentRemaining {}; + DirtyValue isCharging {}; + DirtyValue bleState {}; + DirtyValue bleRadioEnabled {}; + DirtyValue> currentDateTime {}; + DirtyValue motionSensorOk {}; + DirtyValue stepCount {}; + DirtyValue notificationState {}; + + lv_obj_t* background; + + // Lines making up the side cover + lv_obj_t* line0; + lv_obj_t* line1; + lv_obj_t* line2; + lv_obj_t* line3; + lv_obj_t* line4; + lv_obj_t* line5; + lv_obj_t* line6; + lv_obj_t* line7; + lv_obj_t* line8; + lv_obj_t* lineBattery; + + lv_style_t line0Style; + lv_style_t line1Style; + lv_style_t line2Style; + lv_style_t line3Style; + lv_style_t line4Style; + lv_style_t line5Style; + lv_style_t line6Style; + lv_style_t line7Style; + lv_style_t line8Style; + lv_style_t lineBatteryStyle; + + lv_point_t line0Points[2]; + lv_point_t line1Points[2]; + lv_point_t line2Points[2]; + lv_point_t line3Points[2]; + lv_point_t line4Points[2]; + lv_point_t line5Points[2]; + lv_point_t line6Points[2]; + lv_point_t line7Points[2]; + lv_point_t line8Points[2]; + lv_point_t lineBatteryPoints[2]; + + lv_obj_t* logoPine; + + lv_obj_t* timeContainer; + lv_obj_t* labelHour; + lv_obj_t* labelMinutes; + lv_obj_t* labelTimeAmPm; + lv_obj_t* dateContainer; + lv_obj_t* labelDate; + lv_obj_t* bleIcon; + lv_obj_t* stepIcon; + lv_obj_t* stepValue; + lv_obj_t* notificationIcon; + lv_obj_t* btnClose; + lv_obj_t* btnNextColor; + lv_obj_t* btnToggleCover; + lv_obj_t* btnPrevColor; + lv_obj_t* btnSettings; + lv_obj_t* labelBtnSettings; + lv_obj_t* lblToggle; + + static constexpr int nLines = 9; + static constexpr int nColors = 7; // must match number of colors in InfineatColors + struct InfineatColors { + int orange[nLines] = {0xfd872b, 0xdb3316, 0x6f1000, 0xfd7a0a, 0xffffff, 0xffffff, 0xffffff, 0xe85102, 0xea1c00}; + int blue[nLines] = {0xe7f8ff, 0x2232d0, 0x182a8b, 0xe7f8ff, 0xffffff, 0xffffff, 0xffffff, 0x5991ff, 0x1636ff}; + int green[nLines] = {0xb8ff9b, 0x088608, 0x004a00, 0xb8ff9b, 0xffffff, 0xffffff, 0xffffff, 0x62d515, 0x007400}; + int rainbow[nLines] = {0x2da400, 0xac09c4, 0xfe0303, 0x0d57ff, 0xffffff, 0xffffff, 0xffffff, 0xe0b900, 0xe85102}; + int gray[nLines] = {0xeeeeee, 0x98959b, 0x191919, 0xeeeeee, 0xffffff, 0xffffff, 0xffffff, 0x919191, 0x3a3a3a}; + int nordBlue[nLines] = {0xc3daf2, 0x4d78ce, 0x153451, 0xc3daf2, 0xffffff, 0xffffff, 0xffffff, 0x5d8ad2, 0x21518a}; + int nordGreen[nLines] = {0xd5f0e9, 0x238373, 0x1d413f, 0xd5f0e9, 0xffffff, 0xffffff, 0xffffff, 0x2fb8a2, 0x11705a}; + } infineatColors; + + Controllers::DateTime& dateTimeController; + Controllers::Battery& batteryController; + Controllers::Ble& bleController; + Controllers::NotificationManager& notificationManager; + Controllers::Settings& settingsController; + Controllers::MotionController& motionController; + + void SetBatteryLevel(uint8_t batteryPercent); + void ToggleBatteryIndicatorColor(bool showSideCover); + + lv_task_t* taskRefresh; + lv_font_t* font_teko = nullptr; + lv_font_t* font_bebas = nullptr; + }; + } + } +} diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.cpp b/src/displayapp/screens/WatchFacePineTimeStyle.cpp index ed09f5dd..b6b3f5a9 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.cpp +++ b/src/displayapp/screens/WatchFacePineTimeStyle.cpp @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . * - * PineTimeStyle watchface for Infinitime created by Kieran Cawthray + * PineTimeStyle watch face for Infinitime created by Kieran Cawthray * Based on WatchFaceDigital * Style/layout copied from TimeStyle for Pebble by Dan Tilden (github.com/tilden) */ @@ -48,7 +48,7 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, Controllers::DateTime& dateTimeController, Controllers::Battery& batteryController, Controllers::Ble& bleController, - Controllers::NotificationManager& notificatioManager, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController) : Screen(app), @@ -56,7 +56,7 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, dateTimeController {dateTimeController}, batteryController {batteryController}, bleController {bleController}, - notificatioManager {notificatioManager}, + notificationManager {notificationManager}, settingsController {settingsController}, motionController {motionController} { @@ -172,13 +172,23 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, } stepGauge = lv_gauge_create(lv_scr_act(), nullptr); lv_gauge_set_needle_count(stepGauge, 1, needle_colors); - lv_obj_set_size(stepGauge, 40, 40); - lv_obj_align(stepGauge, sidebar, LV_ALIGN_IN_BOTTOM_MID, 0, 0); - lv_gauge_set_scale(stepGauge, 360, 11, 0); - lv_gauge_set_angle_offset(stepGauge, 180); - lv_gauge_set_critical_value(stepGauge, 100); lv_gauge_set_range(stepGauge, 0, 100); lv_gauge_set_value(stepGauge, 0, 0); + if (settingsController.GetPTSGaugeStyle() == Pinetime::Controllers::Settings::PTSGaugeStyle::Full) { + lv_obj_set_size(stepGauge, 40, 40); + lv_obj_align(stepGauge, sidebar, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + lv_gauge_set_scale(stepGauge, 360, 11, 0); + lv_gauge_set_angle_offset(stepGauge, 180); + lv_gauge_set_critical_value(stepGauge, 100); + } else if (settingsController.GetPTSGaugeStyle() == Pinetime::Controllers::Settings::PTSGaugeStyle::Half) { + lv_obj_set_size(stepGauge, 37, 37); + lv_obj_align(stepGauge, sidebar, LV_ALIGN_IN_BOTTOM_MID, 0, -10); + lv_gauge_set_scale(stepGauge, 180, 5, 0); + lv_gauge_set_angle_offset(stepGauge, 0); + lv_gauge_set_critical_value(stepGauge, 120); + } else if (settingsController.GetPTSGaugeStyle() == Pinetime::Controllers::Settings::PTSGaugeStyle::Numeric) { + lv_obj_set_hidden(stepGauge, true); + } lv_obj_set_style_local_pad_right(stepGauge, LV_GAUGE_PART_MAIN, LV_STATE_DEFAULT, 3); lv_obj_set_style_local_pad_left(stepGauge, LV_GAUGE_PART_MAIN, LV_STATE_DEFAULT, 3); @@ -191,12 +201,44 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, lv_obj_set_style_local_line_width(stepGauge, LV_GAUGE_PART_NEEDLE, LV_STATE_DEFAULT, 3); lv_obj_set_style_local_pad_inner(stepGauge, LV_GAUGE_PART_NEEDLE, LV_STATE_DEFAULT, 4); + 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_BLACK); + lv_label_set_text_static(stepValue, "0"); + lv_obj_align(stepValue, sidebar, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + if (settingsController.GetPTSGaugeStyle() == Pinetime::Controllers::Settings::PTSGaugeStyle::Numeric) { + lv_obj_set_hidden(stepValue, false); + } else { + lv_obj_set_hidden(stepValue, true); + } + + 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_BLACK); + lv_label_set_text_static(stepIcon, Symbols::shoe); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_TOP_MID, 0, 0); + if (settingsController.GetPTSGaugeStyle() == Pinetime::Controllers::Settings::PTSGaugeStyle::Numeric) { + lv_obj_set_hidden(stepIcon, false); + } else { + lv_obj_set_hidden(stepIcon, true); + } + + // Display seconds + timeDD3 = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(timeDD3, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_label_set_text_static(timeDD3, ":00"); + lv_obj_align(timeDD3, sidebar, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + if (settingsController.GetPTSGaugeStyle() == Pinetime::Controllers::Settings::PTSGaugeStyle::Half) { + lv_obj_set_hidden(timeDD3, false); + } else { + lv_obj_set_hidden(timeDD3, true); + } + btnNextTime = lv_btn_create(lv_scr_act(), nullptr); btnNextTime->user_data = this; lv_obj_set_size(btnNextTime, 60, 60); lv_obj_align(btnNextTime, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, -80); lv_obj_set_style_local_bg_opa(btnNextTime, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); - lv_obj_set_style_local_value_str(btnNextTime, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, ">"); + lv_obj_t* lblNextTime = lv_label_create(btnNextTime, nullptr); + lv_label_set_text_static(lblNextTime, ">"); lv_obj_set_event_cb(btnNextTime, event_handler); lv_obj_set_hidden(btnNextTime, true); @@ -205,7 +247,8 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, lv_obj_set_size(btnPrevTime, 60, 60); lv_obj_align(btnPrevTime, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, -80); lv_obj_set_style_local_bg_opa(btnPrevTime, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); - lv_obj_set_style_local_value_str(btnPrevTime, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "<"); + lv_obj_t* lblPrevTime = lv_label_create(btnPrevTime, nullptr); + lv_label_set_text_static(lblPrevTime, "<"); lv_obj_set_event_cb(btnPrevTime, event_handler); lv_obj_set_hidden(btnPrevTime, true); @@ -214,7 +257,8 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, lv_obj_set_size(btnNextBar, 60, 60); lv_obj_align(btnNextBar, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 0); lv_obj_set_style_local_bg_opa(btnNextBar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); - lv_obj_set_style_local_value_str(btnNextBar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, ">"); + lv_obj_t* lblNextBar = lv_label_create(btnNextBar, nullptr); + lv_label_set_text_static(lblNextBar, ">"); lv_obj_set_event_cb(btnNextBar, event_handler); lv_obj_set_hidden(btnNextBar, true); @@ -223,7 +267,8 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, lv_obj_set_size(btnPrevBar, 60, 60); lv_obj_align(btnPrevBar, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 0); lv_obj_set_style_local_bg_opa(btnPrevBar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); - lv_obj_set_style_local_value_str(btnPrevBar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "<"); + lv_obj_t* lblPrevBar = lv_label_create(btnPrevBar, nullptr); + lv_label_set_text_static(lblPrevBar, "<"); lv_obj_set_event_cb(btnPrevBar, event_handler); lv_obj_set_hidden(btnPrevBar, true); @@ -232,7 +277,8 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, lv_obj_set_size(btnNextBG, 60, 60); lv_obj_align(btnNextBG, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 80); lv_obj_set_style_local_bg_opa(btnNextBG, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); - lv_obj_set_style_local_value_str(btnNextBG, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, ">"); + lv_obj_t* lblNextBG = lv_label_create(btnNextBG, nullptr); + lv_label_set_text_static(lblNextBG, ">"); lv_obj_set_event_cb(btnNextBG, event_handler); lv_obj_set_hidden(btnNextBG, true); @@ -241,7 +287,8 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, lv_obj_set_size(btnPrevBG, 60, 60); lv_obj_align(btnPrevBG, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 80); lv_obj_set_style_local_bg_opa(btnPrevBG, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); - lv_obj_set_style_local_value_str(btnPrevBG, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "<"); + lv_obj_t* lblPrevBG = lv_label_create(btnPrevBG, nullptr); + lv_label_set_text_static(lblPrevBG, "<"); lv_obj_set_event_cb(btnPrevBG, event_handler); lv_obj_set_hidden(btnPrevBG, true); @@ -250,7 +297,8 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, lv_obj_set_size(btnReset, 60, 60); lv_obj_align(btnReset, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); lv_obj_set_style_local_bg_opa(btnReset, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); - lv_obj_set_style_local_value_str(btnReset, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "Rst"); + lv_obj_t* lblReset = lv_label_create(btnReset, nullptr); + lv_label_set_text_static(lblReset, "Rst"); lv_obj_set_event_cb(btnReset, event_handler); lv_obj_set_hidden(btnReset, true); @@ -259,7 +307,8 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, lv_obj_set_size(btnRandom, 60, 60); lv_obj_align(btnRandom, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); lv_obj_set_style_local_bg_opa(btnRandom, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); - lv_obj_set_style_local_value_str(btnRandom, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "Rnd"); + lv_obj_t* lblRandom = lv_label_create(btnRandom, nullptr); + lv_label_set_text_static(lblRandom, "Rnd"); lv_obj_set_event_cb(btnRandom, event_handler); lv_obj_set_hidden(btnRandom, true); @@ -268,22 +317,44 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(DisplayApp* app, lv_obj_set_size(btnClose, 60, 60); lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80); lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); - lv_obj_set_style_local_value_str(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "X"); + lv_obj_t* lblClose = lv_label_create(btnClose, nullptr); + lv_label_set_text_static(lblClose, "X"); lv_obj_set_event_cb(btnClose, event_handler); lv_obj_set_hidden(btnClose, true); - btnSet = lv_btn_create(lv_scr_act(), nullptr); - btnSet->user_data = this; - lv_obj_set_height(btnSet, 150); - lv_obj_set_width(btnSet, 150); - lv_obj_align(btnSet, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); - lv_obj_set_style_local_radius(btnSet, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 30); - lv_obj_set_style_local_bg_opa(btnSet, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); - lv_obj_set_event_cb(btnSet, event_handler); - lbl_btnSet = lv_label_create(btnSet, nullptr); - lv_obj_set_style_local_text_font(lbl_btnSet, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); - lv_label_set_text_static(lbl_btnSet, Symbols::settings); - lv_obj_set_hidden(btnSet, true); + btnSteps = lv_btn_create(lv_scr_act(), nullptr); + btnSteps->user_data = this; + lv_obj_set_size(btnSteps, 160, 60); + lv_obj_align(btnSteps, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_local_bg_opa(btnSteps, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); + lv_obj_t* lblSteps = lv_label_create(btnSteps, nullptr); + lv_label_set_text_static(lblSteps, "Steps style"); + lv_obj_set_event_cb(btnSteps, event_handler); + lv_obj_set_hidden(btnSteps, true); + + btnSetColor = lv_btn_create(lv_scr_act(), nullptr); + btnSetColor->user_data = this; + lv_obj_set_size(btnSetColor, 150, 60); + lv_obj_align(btnSetColor, lv_scr_act(), LV_ALIGN_CENTER, 0, -40); + lv_obj_set_style_local_radius(btnSetColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); + lv_obj_set_style_local_bg_opa(btnSetColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); + lv_obj_set_event_cb(btnSetColor, event_handler); + lbl_btnSetColor = lv_label_create(btnSetColor, nullptr); + lv_obj_set_style_local_text_font(lbl_btnSetColor, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); + lv_label_set_text_static(lbl_btnSetColor, Symbols::paintbrushLg); + lv_obj_set_hidden(btnSetColor, true); + + btnSetOpts = lv_btn_create(lv_scr_act(), nullptr); + btnSetOpts->user_data = this; + lv_obj_set_size(btnSetOpts, 150, 60); + lv_obj_align(btnSetOpts, lv_scr_act(), LV_ALIGN_CENTER, 0, 40); + lv_obj_set_style_local_radius(btnSetOpts, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); + lv_obj_set_style_local_bg_opa(btnSetOpts, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); + lv_obj_set_event_cb(btnSetOpts, event_handler); + lbl_btnSetOpts = lv_label_create(btnSetOpts, nullptr); + lv_obj_set_style_local_text_font(lbl_btnSetOpts, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); + lv_label_set_text_static(lbl_btnSetOpts, Symbols::settings); + lv_obj_set_hidden(btnSetOpts, true); taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); Refresh(); @@ -296,7 +367,8 @@ WatchFacePineTimeStyle::~WatchFacePineTimeStyle() { bool WatchFacePineTimeStyle::OnTouchEvent(Pinetime::Applications::TouchEvents event) { if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnRandom)) { - lv_obj_set_hidden(btnSet, false); + lv_obj_set_hidden(btnSetColor, false); + lv_obj_set_hidden(btnSetOpts, false); savedTick = lv_tick_get(); return true; } @@ -317,6 +389,7 @@ void WatchFacePineTimeStyle::CloseMenu() { lv_obj_set_hidden(btnReset, true); lv_obj_set_hidden(btnRandom, true); lv_obj_set_hidden(btnClose, true); + lv_obj_set_hidden(btnSteps, true); } bool WatchFacePineTimeStyle::OnButtonPushed() { @@ -369,7 +442,7 @@ void WatchFacePineTimeStyle::Refresh() { AlignIcons(); } - notificationState = notificatioManager.AreNewNotificationsAvailable(); + notificationState = notificationManager.AreNewNotificationsAvailable(); if (notificationState.IsUpdated()) { lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get())); AlignIcons(); @@ -390,6 +463,7 @@ void WatchFacePineTimeStyle::Refresh() { uint8_t hour = time.hours().count(); uint8_t minute = time.minutes().count(); + uint8_t second = time.seconds().count(); if (displayedHour != hour || displayedMinute != minute) { displayedHour = hour; @@ -415,6 +489,11 @@ void WatchFacePineTimeStyle::Refresh() { } } + if (displayedSecond != second) { + displayedSecond = second; + lv_label_set_text_fmt(timeDD3, ":%02d", second); + } + if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { lv_label_set_text_static(dateDayOfWeek, dateTimeController.DayOfWeekShortToString()); lv_label_set_text_fmt(dateDay, "%d", day); @@ -431,16 +510,19 @@ void WatchFacePineTimeStyle::Refresh() { stepCount = motionController.NbSteps(); motionSensorOk = motionController.IsSensorOk(); if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { - lv_gauge_set_value(stepGauge, 0, (stepCount.Get() / (settingsController.GetStepsGoal() / 100))); + lv_gauge_set_value(stepGauge, 0, (stepCount.Get() / (settingsController.GetStepsGoal() / 100)) % 100); lv_obj_realign(stepGauge); + lv_label_set_text_fmt(stepValue, "%luK", (stepCount.Get() / 1000)); + lv_obj_realign(stepValue); if (stepCount.Get() > settingsController.GetStepsGoal()) { lv_obj_set_style_local_line_color(stepGauge, LV_GAUGE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); lv_obj_set_style_local_scale_grad_color(stepGauge, LV_GAUGE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); } } - if (!lv_obj_get_hidden(btnSet)) { + if (!lv_obj_get_hidden(btnSetColor)) { if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { - lv_obj_set_hidden(btnSet, true); + lv_obj_set_hidden(btnSetColor, true); + lv_obj_set_hidden(btnSetOpts, true); savedTick = 0; } } @@ -552,8 +634,39 @@ void WatchFacePineTimeStyle::UpdateSelected(lv_obj_t* object, lv_event_t event) if (object == btnClose) { CloseMenu(); } - if (object == btnSet) { - lv_obj_set_hidden(btnSet, true); + if (object == btnSteps) { + if (!lv_obj_get_hidden(stepGauge) && (lv_obj_get_hidden(timeDD3))) { + // show half gauge & seconds + lv_obj_set_hidden(timeDD3, false); + lv_obj_set_size(stepGauge, 37, 37); + lv_obj_align(stepGauge, sidebar, LV_ALIGN_IN_BOTTOM_MID, 0, -10); + lv_gauge_set_scale(stepGauge, 180, 5, 0); + lv_gauge_set_angle_offset(stepGauge, 0); + lv_gauge_set_critical_value(stepGauge, 120); + settingsController.SetPTSGaugeStyle(Controllers::Settings::PTSGaugeStyle::Half); + } else if (!lv_obj_get_hidden(timeDD3) && (lv_obj_get_hidden(stepValue))) { + // show step count & icon + lv_obj_set_hidden(timeDD3, true); + lv_obj_set_hidden(stepGauge, true); + lv_obj_set_hidden(stepValue, false); + lv_obj_set_hidden(stepIcon, false); + settingsController.SetPTSGaugeStyle(Controllers::Settings::PTSGaugeStyle::Numeric); + } else { + // show full gauge + lv_obj_set_hidden(stepGauge, false); + lv_obj_set_hidden(stepValue, true); + lv_obj_set_hidden(stepIcon, true); + lv_obj_set_size(stepGauge, 40, 40); + lv_obj_align(stepGauge, sidebar, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + lv_gauge_set_scale(stepGauge, 360, 11, 0); + lv_gauge_set_angle_offset(stepGauge, 180); + lv_gauge_set_critical_value(stepGauge, 100); + settingsController.SetPTSGaugeStyle(Controllers::Settings::PTSGaugeStyle::Full); + } + } + if (object == btnSetColor) { + lv_obj_set_hidden(btnSetColor, true); + lv_obj_set_hidden(btnSetOpts, true); lv_obj_set_hidden(btnNextTime, false); lv_obj_set_hidden(btnPrevTime, false); lv_obj_set_hidden(btnNextBar, false); @@ -564,13 +677,19 @@ void WatchFacePineTimeStyle::UpdateSelected(lv_obj_t* object, lv_event_t event) lv_obj_set_hidden(btnRandom, false); lv_obj_set_hidden(btnClose, false); } + if (object == btnSetOpts) { + lv_obj_set_hidden(btnSetColor, true); + lv_obj_set_hidden(btnSetOpts, true); + lv_obj_set_hidden(btnSteps, false); + lv_obj_set_hidden(btnClose, false); + } } } Pinetime::Controllers::Settings::Colors WatchFacePineTimeStyle::GetNext(Pinetime::Controllers::Settings::Colors color) { auto colorAsInt = static_cast(color); Pinetime::Controllers::Settings::Colors nextColor; - if (colorAsInt < 16) { + if (colorAsInt < 17) { nextColor = static_cast(colorAsInt + 1); } else { nextColor = static_cast(0); @@ -585,7 +704,7 @@ Pinetime::Controllers::Settings::Colors WatchFacePineTimeStyle::GetPrevious(Pine if (colorAsInt > 0) { prevColor = static_cast(colorAsInt - 1); } else { - prevColor = static_cast(16); + prevColor = static_cast(17); } return prevColor; } diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.h b/src/displayapp/screens/WatchFacePineTimeStyle.h index 6f44f943..3085a1ae 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.h +++ b/src/displayapp/screens/WatchFacePineTimeStyle.h @@ -28,7 +28,7 @@ namespace Pinetime { Controllers::DateTime& dateTimeController, Controllers::Battery& batteryController, Controllers::Ble& bleController, - Controllers::NotificationManager& notificatioManager, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController); ~WatchFacePineTimeStyle() override; @@ -43,6 +43,7 @@ namespace Pinetime { private: uint8_t displayedHour = -1; uint8_t displayedMinute = -1; + uint8_t displayedSecond = -1; uint16_t currentYear = 1970; Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown; @@ -71,10 +72,12 @@ namespace Pinetime { lv_obj_t* btnReset; lv_obj_t* btnRandom; lv_obj_t* btnClose; + lv_obj_t* btnSteps; lv_obj_t* timebar; lv_obj_t* sidebar; lv_obj_t* timeDD1; lv_obj_t* timeDD2; + lv_obj_t* timeDD3; lv_obj_t* timeAMPM; lv_obj_t* dateDayOfWeek; lv_obj_t* dateDay; @@ -89,8 +92,12 @@ namespace Pinetime { lv_obj_t* calendarCrossBar2; lv_obj_t* notificationIcon; lv_obj_t* stepGauge; - lv_obj_t* btnSet; - lv_obj_t* lbl_btnSet; + lv_obj_t* btnSetColor; + lv_obj_t* btnSetOpts; + lv_obj_t* lbl_btnSetColor; + lv_obj_t* lbl_btnSetOpts; + lv_obj_t* stepIcon; + lv_obj_t* stepValue; lv_color_t needle_colors[1]; BatteryIcon batteryIcon; @@ -98,7 +105,7 @@ namespace Pinetime { Controllers::DateTime& dateTimeController; Controllers::Battery& batteryController; Controllers::Ble& bleController; - Controllers::NotificationManager& notificatioManager; + Controllers::NotificationManager& notificationManager; Controllers::Settings& settingsController; Controllers::MotionController& motionController; diff --git a/src/displayapp/screens/WatchFaceTerminal.cpp b/src/displayapp/screens/WatchFaceTerminal.cpp index f5490b44..92189737 100644 --- a/src/displayapp/screens/WatchFaceTerminal.cpp +++ b/src/displayapp/screens/WatchFaceTerminal.cpp @@ -17,7 +17,7 @@ WatchFaceTerminal::WatchFaceTerminal(DisplayApp* app, Controllers::DateTime& dateTimeController, Controllers::Battery& batteryController, Controllers::Ble& bleController, - Controllers::NotificationManager& notificatioManager, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, Controllers::MotionController& motionController) @@ -26,7 +26,7 @@ WatchFaceTerminal::WatchFaceTerminal(DisplayApp* app, dateTimeController {dateTimeController}, batteryController {batteryController}, bleController {bleController}, - notificatioManager {notificatioManager}, + notificationManager {notificationManager}, settingsController {settingsController}, heartRateController {heartRateController}, motionController {motionController} { @@ -100,7 +100,7 @@ void WatchFaceTerminal::Refresh() { } } - notificationState = notificatioManager.AreNewNotificationsAvailable(); + notificationState = notificationManager.AreNewNotificationsAvailable(); if (notificationState.IsUpdated()) { if (notificationState.Get()) { lv_label_set_text_static(notificationIcon, "You have mail."); diff --git a/src/displayapp/screens/WatchFaceTerminal.h b/src/displayapp/screens/WatchFaceTerminal.h index 828dbdb1..a81289a7 100644 --- a/src/displayapp/screens/WatchFaceTerminal.h +++ b/src/displayapp/screens/WatchFaceTerminal.h @@ -26,7 +26,7 @@ namespace Pinetime { Controllers::DateTime& dateTimeController, Controllers::Battery& batteryController, Controllers::Ble& bleController, - Controllers::NotificationManager& notificatioManager, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, Controllers::MotionController& motionController); @@ -68,7 +68,7 @@ namespace Pinetime { Controllers::DateTime& dateTimeController; Controllers::Battery& batteryController; Controllers::Ble& bleController; - Controllers::NotificationManager& notificatioManager; + Controllers::NotificationManager& notificationManager; Controllers::Settings& settingsController; Controllers::HeartRateController& heartRateController; Controllers::MotionController& motionController; diff --git a/src/displayapp/screens/settings/SettingSetDate.cpp b/src/displayapp/screens/settings/SettingSetDate.cpp index 1407a98f..421aef02 100644 --- a/src/displayapp/screens/settings/SettingSetDate.cpp +++ b/src/displayapp/screens/settings/SettingSetDate.cpp @@ -79,9 +79,11 @@ SettingSetDate::SettingSetDate(Pinetime::Applications::DisplayApp* app, Pinetime lv_obj_set_size(btnSetTime, 120, 48); lv_obj_align(btnSetTime, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0); lv_obj_set_style_local_bg_color(btnSetTime, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_MAKE(0x38, 0x38, 0x38)); - lv_obj_set_style_local_value_str(btnSetTime, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "Set"); + lblSetTime = lv_label_create(btnSetTime, nullptr); + lv_label_set_text_static(lblSetTime, "Set"); lv_obj_set_event_cb(btnSetTime, event_handler); lv_btn_set_state(btnSetTime, LV_BTN_STATE_DISABLED); + lv_obj_set_state(lblSetTime, LV_STATE_DISABLED); } SettingSetDate::~SettingSetDate() { @@ -102,10 +104,12 @@ void SettingSetDate::HandleButtonPress() { dateTimeController.Seconds(), nrf_rtc_counter_get(portNRF_RTC_REG)); lv_btn_set_state(btnSetTime, LV_BTN_STATE_DISABLED); + lv_obj_set_state(lblSetTime, LV_STATE_DISABLED); } void SettingSetDate::CheckDay() { const int maxDay = MaximumDayOfMonth(monthCounter.GetValue(), yearCounter.GetValue()); dayCounter.SetMax(maxDay); lv_btn_set_state(btnSetTime, LV_BTN_STATE_RELEASED); + lv_obj_set_state(lblSetTime, LV_STATE_DEFAULT); } diff --git a/src/displayapp/screens/settings/SettingSetDate.h b/src/displayapp/screens/settings/SettingSetDate.h index af0d654e..a0ffc683 100644 --- a/src/displayapp/screens/settings/SettingSetDate.h +++ b/src/displayapp/screens/settings/SettingSetDate.h @@ -21,6 +21,7 @@ namespace Pinetime { Controllers::DateTime& dateTimeController; lv_obj_t* btnSetTime; + lv_obj_t* lblSetTime; Widgets::Counter dayCounter = Widgets::Counter(1, 31, jetbrains_mono_bold_20); Widgets::Counter monthCounter = Widgets::Counter(1, 12, jetbrains_mono_bold_20); diff --git a/src/displayapp/screens/settings/SettingSetTime.cpp b/src/displayapp/screens/settings/SettingSetTime.cpp index 47b786e4..e7d824fd 100644 --- a/src/displayapp/screens/settings/SettingSetTime.cpp +++ b/src/displayapp/screens/settings/SettingSetTime.cpp @@ -67,13 +67,15 @@ SettingSetTime::SettingSetTime(Pinetime::Applications::DisplayApp* app, btnSetTime->user_data = this; lv_obj_set_size(btnSetTime, 120, 50); lv_obj_align(btnSetTime, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0); - lv_obj_set_style_local_value_str(btnSetTime, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "Set"); + lblSetTime = lv_label_create(btnSetTime, nullptr); + lv_label_set_text_static(lblSetTime, "Set"); lv_obj_set_style_local_bg_color(btnSetTime, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Colors::bgAlt); - lv_obj_set_style_local_value_color(btnSetTime, LV_BTN_PART_MAIN, LV_STATE_DISABLED, LV_COLOR_GRAY); + lv_obj_set_style_local_text_color(lblSetTime, LV_LABEL_PART_MAIN, LV_STATE_DISABLED, LV_COLOR_GRAY); lv_obj_set_event_cb(btnSetTime, SetTimeEventHandler); UpdateScreen(); lv_obj_set_state(btnSetTime, LV_STATE_DISABLED); + lv_obj_set_state(lblSetTime, LV_STATE_DISABLED); } SettingSetTime::~SettingSetTime() { @@ -89,6 +91,7 @@ void SettingSetTime::UpdateScreen() { } } lv_obj_set_state(btnSetTime, LV_STATE_DEFAULT); + lv_obj_set_state(lblSetTime, LV_STATE_DEFAULT); } void SettingSetTime::SetTime() { @@ -104,4 +107,5 @@ void SettingSetTime::SetTime() { 0, nrf_rtc_counter_get(portNRF_RTC_REG)); lv_obj_set_state(btnSetTime, LV_STATE_DISABLED); + lv_obj_set_state(lblSetTime, LV_STATE_DISABLED); } diff --git a/src/displayapp/screens/settings/SettingSetTime.h b/src/displayapp/screens/settings/SettingSetTime.h index e0b42bdd..b61962c1 100644 --- a/src/displayapp/screens/settings/SettingSetTime.h +++ b/src/displayapp/screens/settings/SettingSetTime.h @@ -26,6 +26,7 @@ namespace Pinetime { lv_obj_t* lblampm; lv_obj_t* btnSetTime; + lv_obj_t* lblSetTime; Widgets::Counter hourCounter = Widgets::Counter(0, 23, jetbrains_mono_42); Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_42); }; diff --git a/src/displayapp/screens/settings/SettingSteps.cpp b/src/displayapp/screens/settings/SettingSteps.cpp index af5bd6e9..a6b6f4a8 100644 --- a/src/displayapp/screens/settings/SettingSteps.cpp +++ b/src/displayapp/screens/settings/SettingSteps.cpp @@ -48,7 +48,8 @@ SettingSteps::SettingSteps(Pinetime::Applications::DisplayApp* app, Pinetime::Co btnPlus->user_data = this; lv_obj_set_size(btnPlus, 80, 50); lv_obj_align(btnPlus, lv_scr_act(), LV_ALIGN_CENTER, 55, 80); - lv_obj_set_style_local_value_str(btnPlus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "+"); + lv_obj_t* lblPlus = lv_label_create(btnPlus, nullptr); + lv_label_set_text_static(lblPlus, "+"); lv_obj_set_event_cb(btnPlus, event_handler); btnMinus = lv_btn_create(lv_scr_act(), nullptr); @@ -56,7 +57,8 @@ SettingSteps::SettingSteps(Pinetime::Applications::DisplayApp* app, Pinetime::Co lv_obj_set_size(btnMinus, 80, 50); lv_obj_set_event_cb(btnMinus, event_handler); lv_obj_align(btnMinus, lv_scr_act(), LV_ALIGN_CENTER, -55, 80); - lv_obj_set_style_local_value_str(btnMinus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "-"); + lv_obj_t* lblMinus = lv_label_create(btnMinus, nullptr); + lv_label_set_text_static(lblMinus, "-"); } SettingSteps::~SettingSteps() { diff --git a/src/displayapp/screens/settings/SettingWatchFace.cpp b/src/displayapp/screens/settings/SettingWatchFace.cpp index be595a74..217f97b8 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.cpp +++ b/src/displayapp/screens/settings/SettingWatchFace.cpp @@ -1,74 +1,75 @@ #include "displayapp/screens/settings/SettingWatchFace.h" #include #include "displayapp/DisplayApp.h" +#include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/Screen.h" -#include "displayapp/screens/Styles.h" -#include "displayapp/screens/Symbols.h" +#include "components/settings/Settings.h" +#include "displayapp/screens/WatchFaceInfineat.h" +#include "displayapp/screens/WatchFaceCasioStyleG7710.h" using namespace Pinetime::Applications::Screens; -namespace { - void event_handler(lv_obj_t* obj, lv_event_t event) { - auto* screen = static_cast(obj->user_data); - screen->UpdateSelected(obj, event); - } -} +constexpr const char* SettingWatchFace::title; +constexpr const char* SettingWatchFace::symbol; -constexpr std::array SettingWatchFace::options; - -SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) - : Screen(app), settingsController {settingsController} { - - lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr); - - lv_obj_set_style_local_bg_opa(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); - lv_obj_set_style_local_pad_all(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 10); - lv_obj_set_style_local_pad_inner(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5); - lv_obj_set_style_local_border_width(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0); - - lv_obj_set_pos(container1, 10, 60); - lv_obj_set_width(container1, LV_HOR_RES - 20); - lv_obj_set_height(container1, LV_VER_RES - 50); - lv_cont_set_layout(container1, LV_LAYOUT_COLUMN_LEFT); - - lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_text_static(title, "Watch face"); - lv_label_set_align(title, LV_LABEL_ALIGN_CENTER); - lv_obj_align(title, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 10, 15); - - lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE); - lv_label_set_text_static(icon, Symbols::home); - lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER); - lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); - - for (unsigned int i = 0; i < options.size(); i++) { - cbOption[i] = lv_checkbox_create(container1, nullptr); - lv_checkbox_set_text(cbOption[i], options[i]); - cbOption[i]->user_data = this; - lv_obj_set_event_cb(cbOption[i], event_handler); - SetRadioButtonStyle(cbOption[i]); - - if (settingsController.GetClockFace() == i) { - lv_checkbox_set_checked(cbOption[i], true); - } - } +SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app, + Pinetime::Controllers::Settings& settingsController, + Pinetime::Controllers::FS& filesystem) + : Screen(app), + settingsController {settingsController}, + filesystem {filesystem}, + screens {app, + 0, + {[this]() -> std::unique_ptr { + return CreateScreen1(); + }, + [this]() -> std::unique_ptr { + return CreateScreen2(); + }}, + Screens::ScreenListModes::UpDown} { } SettingWatchFace::~SettingWatchFace() { lv_obj_clean(lv_scr_act()); - settingsController.SaveSettings(); } -void SettingWatchFace::UpdateSelected(lv_obj_t* object, lv_event_t event) { - if (event == LV_EVENT_VALUE_CHANGED) { - for (unsigned int i = 0; i < options.size(); i++) { - if (object == cbOption[i]) { - lv_checkbox_set_checked(cbOption[i], true); - settingsController.SetClockFace(i); - } else { - lv_checkbox_set_checked(cbOption[i], false); - } - } - } +bool SettingWatchFace::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + return screens.OnTouchEvent(event); +} + +std::unique_ptr SettingWatchFace::CreateScreen1() { + std::array watchfaces { + {{"Digital face", true}, {"Analog face", true}, {"PineTimeStyle", true}, {"Terminal", true}}}; + return std::make_unique( + 0, + 2, + app, + title, + symbol, + settingsController.GetClockFace(), + [&settings = settingsController](uint32_t clockFace) { + settings.SetClockFace(clockFace); + settings.SaveSettings(); + }, + watchfaces); +} + +std::unique_ptr SettingWatchFace::CreateScreen2() { + std::array watchfaces { + {{"Infineat face", Applications::Screens::WatchFaceInfineat::IsAvailable(filesystem)}, + {"Casio G7710", Applications::Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem)}, + {"", false}, + {"", false}}}; + return std::make_unique( + 1, + 2, + app, + title, + symbol, + settingsController.GetClockFace(), + [&settings = settingsController](uint32_t clockFace) { + settings.SetClockFace(clockFace); + settings.SaveSettings(); + }, + watchfaces); } diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index d65f4a22..158397f8 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -4,8 +4,10 @@ #include #include +#include "displayapp/screens/ScreenList.h" #include "components/settings/Settings.h" #include "displayapp/screens/Screen.h" +#include "displayapp/screens/Symbols.h" namespace Pinetime { @@ -14,16 +16,20 @@ namespace Pinetime { class SettingWatchFace : public Screen { public: - SettingWatchFace(DisplayApp* app, Pinetime::Controllers::Settings& settingsController); + SettingWatchFace(DisplayApp* app, Pinetime::Controllers::Settings& settingsController, Pinetime::Controllers::FS& filesystem); ~SettingWatchFace() override; - void UpdateSelected(lv_obj_t* object, lv_event_t event); + bool OnTouchEvent(TouchEvents event) override; private: - static constexpr std::array options = {"Digital face", "Analog face", "PineTimeStyle", "Terminal"}; Controllers::Settings& settingsController; + Pinetime::Controllers::FS& filesystem; + ScreenList<2> screens; - lv_obj_t* cbOption[options.size()]; + static constexpr const char* title = "Watch face"; + static constexpr const char* symbol = Symbols::home; + std::unique_ptr CreateScreen1(); + std::unique_ptr CreateScreen2(); }; } } diff --git a/src/displayapp/widgets/StatusIcons.cpp b/src/displayapp/widgets/StatusIcons.cpp index 607f3745..aacf13ae 100644 --- a/src/displayapp/widgets/StatusIcons.cpp +++ b/src/displayapp/widgets/StatusIcons.cpp @@ -15,11 +15,9 @@ void StatusIcons::Create() { lv_obj_set_style_local_bg_opa(container, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); bleIcon = lv_label_create(container, nullptr); - lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x0082FC)); lv_label_set_text_static(bleIcon, Screens::Symbols::bluetooth); batteryPlug = lv_label_create(container, nullptr); - lv_obj_set_style_local_text_color(batteryPlug, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); lv_label_set_text_static(batteryPlug, Screens::Symbols::plug); batteryIcon.Create(container); diff --git a/src/libs/lv_conf.h b/src/libs/lv_conf.h index 00f6a1df..063f1d34 100644 --- a/src/libs/lv_conf.h +++ b/src/libs/lv_conf.h @@ -164,7 +164,7 @@ typedef void* lv_anim_user_data_t; #define LV_USE_PATTERN 1 /*1: enable value string drawing on rectangles*/ -#define LV_USE_VALUE_STR 1 +#define LV_USE_VALUE_STR 0 /* 1: Use other blend modes than normal (`LV_BLEND_MODE_...`)*/ #define LV_USE_BLEND_MODES 0 diff --git a/src/main.cpp b/src/main.cpp index 109971bc..ad7a07dc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -131,7 +131,8 @@ Pinetime::Applications::DisplayApp displayApp(lcd, timerController, alarmController, brightnessController, - touchHandler); + touchHandler, + fs); Pinetime::System::SystemTask systemTask(spi, lcd, diff --git a/src/resources/CMakeLists.txt b/src/resources/CMakeLists.txt new file mode 100644 index 00000000..0983aaff --- /dev/null +++ b/src/resources/CMakeLists.txt @@ -0,0 +1,29 @@ + +find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED + HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin") +message(STATUS "Using ${LV_FONT_CONV} to generate font files") + +find_program(LV_IMG_CONV "lv_img_conv" NO_CACHE REQUIRED + HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin") +message(STATUS "Using ${LV_IMG_CONV} to generate font files") + +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12) + # FindPython3 module introduces with CMake 3.12 + # https://cmake.org/cmake/help/latest/module/FindPython3.html + find_package(Python3 REQUIRED) +else() + set(Python3_EXECUTABLE "python") +endif() + +# generate fonts +add_custom_target(GenerateResources + COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-fonts.py --lv-font-conv "${LV_FONT_CONV}" ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json + COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-img.py --lv-img-conv "${LV_IMG_CONV}" ${CMAKE_CURRENT_SOURCE_DIR}/images.json + COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/generate-package.py --config ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json --config ${CMAKE_CURRENT_SOURCE_DIR}/images.json --obsolete obsolete_files.json --output infinitime-resources-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/fonts.json + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/images.json + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + + COMMENT "Generate fonts and images for resource package" +) + diff --git a/src/resources/fonts.json b/src/resources/fonts.json new file mode 100644 index 00000000..a270e6a2 --- /dev/null +++ b/src/resources/fonts.json @@ -0,0 +1,62 @@ +{ + "teko" : { + "sources": [ + { + "file": "fonts/Teko-Light.ttf", + "symbols": "0123456789:/ampMonTueWdhFriSt " + } + ], + "bpp": 1, + "size": 28, + "format": "bin", + "target_path": "/fonts/" + }, + "bebas" : { + "sources": [ + { + "file": "fonts/BebasNeue-Regular.ttf", + "symbols": "0123456789:" + } + ], + "bpp": 1, + "size": 120, + "format": "bin", + "target_path": "/fonts/" + }, + "lv_font_dots_40": { + "sources": [ + { + "file": "fonts/repetitionscrolling.ttf", + "symbols": "0123456789-MONTUEWEDTHUFRISATSUN WK" + } + ], + "bpp": 1, + "size": 40, + "format": "bin", + "target_path": "/fonts/" + }, + "7segments_40" : { + "sources": [ + { + "file": "fonts/7segment.woff", + "symbols": "0123456789: -" + } + ], + "bpp": 1, + "size": 40, + "format": "bin", + "target_path": "/fonts/" + }, + "7segments_115" : { + "sources": [ + { + "file": "fonts/7segment.woff", + "symbols": "0123456789: -" + } + ], + "bpp": 1, + "size": 115, + "format": "bin", + "target_path": "/fonts/" + } +} diff --git a/src/resources/fonts/7segment.woff b/src/resources/fonts/7segment.woff new file mode 100644 index 00000000..79ed9249 Binary files /dev/null and b/src/resources/fonts/7segment.woff differ diff --git a/src/resources/fonts/BebasNeue-Regular.ttf b/src/resources/fonts/BebasNeue-Regular.ttf new file mode 100644 index 00000000..76e22b8b Binary files /dev/null and b/src/resources/fonts/BebasNeue-Regular.ttf differ diff --git a/src/resources/fonts/Teko-Light.ttf b/src/resources/fonts/Teko-Light.ttf new file mode 100644 index 00000000..679f0137 Binary files /dev/null and b/src/resources/fonts/Teko-Light.ttf differ diff --git a/src/resources/fonts/repetitionscrolling.ttf b/src/resources/fonts/repetitionscrolling.ttf new file mode 100644 index 00000000..dc124164 Binary files /dev/null and b/src/resources/fonts/repetitionscrolling.ttf differ diff --git a/src/resources/generate-fonts.py b/src/resources/generate-fonts.py new file mode 100755 index 00000000..20408166 --- /dev/null +++ b/src/resources/generate-fonts.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python + +import io +import sys +import json +import shutil +import typing +import os.path +import argparse +import subprocess + +class Source(object): + def __init__(self, d): + self.file = d['file'] + if not os.path.exists(self.file): + self.file = os.path.join(os.path.dirname(sys.argv[0]), self.file) + self.range = d.get('range') + self.symbols = d.get('symbols') + + +def gen_lvconv_line(lv_font_conv: str, dest: str, size: int, bpp: int, format: str, sources: typing.List[Source], compress:bool=False): + if format != "lvgl" and format != "bin": + format = "bin" if dest.lower().endswith(".bin") else "lvgl" + + args = [lv_font_conv, '--size', str(size), '--output', dest, '--bpp', str(bpp), '--format', format] + if not compress: + args.append('--no-compress') + for source in sources: + args.extend(['--font', source.file]) + if source.range: + args.extend(['--range', source.range]) + if source.symbols: + args.extend(['--symbols', source.symbols]) + + return args + +def main(): + ap = argparse.ArgumentParser(description='auto generate LVGL font files from fonts') + ap.add_argument('config', type=str, help='config file to use') + ap.add_argument('-f', '--font', type=str, action='append', help='Choose specific fonts to generate (default: all)', default=[]) + ap.add_argument('--lv-font-conv', type=str, help='Path to "lv_font_conf" executable', default="lv_font_conv") + args = ap.parse_args() + + if not shutil.which(args.lv_font_conv): + sys.exit(f"Missing lv_font_conv. Make sure it's findable (in PATH) or specify it manually") + if not os.path.exists(args.config): + sys.exit(f'Error: the config file {args.config} does not exist.') + if not os.access(args.config, os.R_OK): + sys.exit(f'Error: the config file {args.config} is not accessible (permissions?).') + with open(args.config, 'r') as fd: + data = json.load(fd) + + fonts_to_run = set(data.keys()) + + if args.font: + enabled_fonts = set() + for font in args.font: + enabled_fonts.add(font[:-2] if font.endswith('.c') else font) + d = enabled_fonts.difference(fonts_to_run) + if d: + print(f'Warning: requested font{"s" if len(d)>1 else ""} missing: {" ".join(d)}') + fonts_to_run = fonts_to_run.intersection(enabled_fonts) + + for name in fonts_to_run: + font = data[name] + sources = font.pop('sources') + patches = font.pop('patches') if 'patches' in font else [] + font['sources'] = [Source(thing) for thing in sources] + extension = 'c' if font['format'] != 'bin' else 'bin' + font.pop('target_path') + line = gen_lvconv_line(args.lv_font_conv, f'{name}.{extension}', **font) + subprocess.check_call(line) + if patches: + for patch in patches: + subprocess.check_call(['/usr/bin/env', 'patch', name+'.'+extension, patch]) + + + +if __name__ == '__main__': + main() diff --git a/src/resources/generate-img.py b/src/resources/generate-img.py new file mode 100755 index 00000000..cdbfc030 --- /dev/null +++ b/src/resources/generate-img.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + +import io +import sys +import json +import shutil +import typing +import os.path +import argparse +import subprocess + +def gen_lvconv_line(lv_img_conv: str, dest: str, color_format: str, output_format: str, binary_format: str, sources: str): + args = [lv_img_conv, sources, '--force', '--output-file', dest, '--color-format', color_format, '--output-format', output_format, '--binary-format', binary_format] + + return args + +def main(): + ap = argparse.ArgumentParser(description='auto generate LVGL font files from fonts') + ap.add_argument('config', type=str, help='config file to use') + ap.add_argument('-i', '--image', type=str, action='append', help='Choose specific images to generate (default: all)', default=[]) + ap.add_argument('--lv-img-conv', type=str, help='Path to "lv_img_conf" executable', default="lv_img_conv") + args = ap.parse_args() + + if not shutil.which(args.lv_img_conv): + sys.exit(f"Missing lv_img_conv. Make sure it's findable (in PATH) or specify it manually") + if not os.path.exists(args.config): + sys.exit(f'Error: the config file {args.config} does not exist.') + if not os.access(args.config, os.R_OK): + sys.exit(f'Error: the config file {args.config} is not accessible (permissions?).') + with open(args.config, 'r') as fd: + data = json.load(fd) + + images_to_run = set(data.keys()) + + if args.image: + enabled_images = set() + for image in args.image: + enabled_images.add(image[:-2] if image.endswith('.c') else image) + d = enabled_images.difference(images_to_run) + if d: + print(f'Warning: requested image{"s" if len(d)>1 else ""} missing: {" ".join(d)}') + images_to_run = images_to_run.intersection(enabled_images) + + for name in images_to_run: + image = data[name] + if not os.path.exists(image['sources']): + image['sources'] = os.path.join(os.path.dirname(sys.argv[0]), image['sources']) + extension = 'bin' + image.pop('target_path') + line = gen_lvconv_line(args.lv_img_conv, f'{name}.{extension}', **image) + subprocess.check_call(line) + + + +if __name__ == '__main__': + main() diff --git a/src/resources/generate-package.py b/src/resources/generate-package.py new file mode 100755 index 00000000..ff02d4fe --- /dev/null +++ b/src/resources/generate-package.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +import io +import sys +import json +import shutil +import typing +import os.path +import argparse +import subprocess +from zipfile import ZipFile + +def main(): + ap = argparse.ArgumentParser(description='auto generate LVGL font files from fonts') + ap.add_argument('--config', '-c', type=str, action='append', help='config file to use') + ap.add_argument('--obsolete', type=str, help='List of obsolete files') + ap.add_argument('--output', type=str, help='output file name') + args = ap.parse_args() + + for config_file in args.config: + if not os.path.exists(config_file): + sys.exit(f'Error: the config file {config_file} does not exist.') + if not os.access(config_file, os.R_OK): + sys.exit(f'Error: the config file {config_file} is not accessible (permissions?).') + + if args.obsolete: + obsolete_file_path = os.path.join(os.path.dirname(sys.argv[0]), args.obsolete) + if not os.path.exists(obsolete_file_path): + sys.exit(f'Error: the "obsolete" file {args.obsolete} does not exist.') + if not os.access(obsolete_file_path, os.R_OK): + sys.exit(f'Error: the "obsolete" file {args.obsolete} is not accessible (permissions?).') + + zf = ZipFile(args.output, mode='w') + resource_files = [] + + for config_file in args.config: + with open(config_file, 'r') as fd: + data = json.load(fd) + + resource_names = set(data.keys()) + for name in resource_names: + resource = data[name] + resource_files.append({ + "filename": name+'.bin', + "path": resource['target_path'] + name+'.bin' + }) + + path = name + '.bin' + if not os.path.exists(path): + path = os.path.join(os.path.dirname(sys.argv[0]), path) + zf.write(path) + + if args.obsolete: + obsolete_file_path = os.path.join(os.path.dirname(sys.argv[0]), args.obsolete) + with open(obsolete_file_path, 'r') as fd: + obsolete_data = json.load(fd) + else: + obsolete_data = {} + output = { + 'resources': resource_files, + 'obsolete_files': obsolete_data + } + + + with open("resources.json", 'w') as fd: + json.dump(output, fd, indent=4) + + zf.write('resources.json') + zf.close() + +if __name__ == '__main__': + main() diff --git a/src/resources/images.json b/src/resources/images.json new file mode 100644 index 00000000..db2ccab0 --- /dev/null +++ b/src/resources/images.json @@ -0,0 +1,9 @@ +{ + "pine_small" : { + "sources": "images/pine_logo.png", + "color_format": "CF_TRUE_COLOR_ALPHA", + "output_format": "bin", + "binary_format": "ARGB8565_RBSWAP", + "target_path": "/images/" + } +} diff --git a/src/resources/images/pine_logo.png b/src/resources/images/pine_logo.png new file mode 100644 index 00000000..aa96be4b Binary files /dev/null and b/src/resources/images/pine_logo.png differ diff --git a/src/resources/images/pine_logo.svg b/src/resources/images/pine_logo.svg new file mode 100644 index 00000000..55f21169 --- /dev/null +++ b/src/resources/images/pine_logo.svg @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + diff --git a/src/resources/obsolete_files.json b/src/resources/obsolete_files.json new file mode 100644 index 00000000..6109ace7 --- /dev/null +++ b/src/resources/obsolete_files.json @@ -0,0 +1,6 @@ +[ + { + "path": "/example-of-obsolete-file.bin", + "since": "1.11.0" + } +] \ No newline at end of file diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 1c871fd2..ef631af7 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -22,7 +22,7 @@ using namespace Pinetime::System; namespace { - static inline bool in_isr(void) { + inline bool in_isr() { return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0; } } diff --git a/tests/test-tidy.sh b/tests/test-tidy.sh new file mode 100755 index 00000000..f1ee5fed --- /dev/null +++ b/tests/test-tidy.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +set -e + +if [ -z "$GITHUB_BASE_REF" ] +then + echo "This script is only meant to be run in a GitHub Workflow" + exit 1 +fi + +CHANGED_FILES=$(git diff --name-only "$GITHUB_BASE_REF"...HEAD) + +for file in $CHANGED_FILES +do + [ -e "$file" ] || continue + case "$file" in + src/libs/*|src/FreeRTOS/*) continue ;; + *.cpp|*.h) + echo "::group::$file" + clang-tidy-12 -p build "$file" || true + echo "::endgroup::" + esac +done + +exit 0