Merge branch 'develop' into bootloader-doc

# Conflicts:
#	README.md
#	doc/gettingStarted/updating-software.md
This commit is contained in:
Jean-François Milants 2022-10-10 17:14:22 +02:00
commit 1bfb128617
77 changed files with 2563 additions and 483 deletions

View File

@ -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"

View File

@ -1,72 +0,0 @@
# GitHub Actions Workflow to build Simulator for PineTime Smart Watch LVGL Interface
name: Build PineTime LVGL Simulator
on:
push:
branches: [ master, develop ]
paths-ignore:
- 'doc/**'
pull_request:
branches: [ develop ]
paths-ignore:
- 'doc/**'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
#########################################################################################
# Download and Install Dependencies
- name: Install cmake
uses: lukka/get-cmake@v3.18.3
- name: Install SDL2 development package
run: |
sudo apt-get update
sudo apt-get -y install libsdl2-dev
- name: Install lv_font_conv
run:
npm i -g lv_font_conv@1.5.2
#########################################################################################
# Checkout
- name: Checkout source files
uses: actions/checkout@v2
with:
submodules: recursive
#########################################################################################
# get InfiniSim repo
- name: Get InfiniSim repo
run: |
git clone https://github.com/InfiniTimeOrg/InfiniSim.git --depth 1 --branch main
git -C InfiniSim submodule update --init lv_drivers libpng
#########################################################################################
# CMake
- name: CMake
run: |
cmake -G Ninja -S InfiniSim -B build_lv_sim -DInfiniTime_DIR="${PWD}"
#########################################################################################
# Build and Upload simulator
- name: Build simulator executable
run: |
cmake --build build_lv_sim
- name: Upload simulator executable
uses: actions/upload-artifact@v3
with:
name: infinisim-${{ github.head_ref }}
path: build_lv_sim/infinisim

View File

@ -1,24 +1,21 @@
# GitHub Actions Workflow to build FreeRTOS Firmware for PineTime Smart Watch
# See https://lupyuen.github.io/pinetime-rust-mynewt/articles/cloud
# Based on https://github.com/JF002/InfiniTime/blob/master/doc/buildAndProgram.md
# and https://github.com/JF002/InfiniTime/blob/master/bootloader/README.md
name: Build PineTime Firmware
name: CI
# Run this workflow whenever the build may be affected
on:
push:
branches: [ master, develop ]
paths-ignore:
- 'doc/**'
- '**.md'
pull_request:
branches: [ develop ]
paths-ignore:
- 'doc/**'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
- '**.md'
jobs:
build:
build-firmware:
runs-on: ubuntu-latest
container:
image: infinitime/infinitime-build
@ -51,3 +48,42 @@ jobs:
with:
name: InfiniTime MCUBoot image ${{ github.head_ref }}
path: ./build/output/pinetime-mcuboot-app-image-*.bin
build-simulator:
runs-on: ubuntu-latest
steps:
- name: Install cmake
uses: lukka/get-cmake@v3.18.3
- name: Install SDL2 development package
run: |
sudo apt-get update
sudo apt-get -y install libsdl2-dev
- name: Install lv_font_conv
run:
npm i -g lv_font_conv@1.5.2
- name: Checkout source files
uses: actions/checkout@v2
with:
submodules: recursive
- name: Get InfiniSim repo
run: |
git clone https://github.com/InfiniTimeOrg/InfiniSim.git --depth 1 --branch main
git -C InfiniSim submodule update --init lv_drivers libpng
- name: CMake
run: |
cmake -G Ninja -S InfiniSim -B build_lv_sim -DInfiniTime_DIR="${PWD}"
- name: Build simulator executable
run: |
cmake --build build_lv_sim
- name: Upload simulator executable
uses: actions/upload-artifact@v3
with:
name: infinisim-${{ github.head_ref }}
path: build_lv_sim/infinisim

View File

@ -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=<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=<path>")
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=<target>")
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)

View File

@ -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](doc/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)

View File

@ -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

70
doc/ExternalResources.md Normal file
View File

@ -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);
}
```

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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!

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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)
@ -37,6 +37,11 @@ If the new firmware is working correctly, open the settings and select the "Firm
Firmware validation setting:
- 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
![Firmware Validation setting](firmwareValidationSetting.png)
The firmware is not validated yet. Tap 'Validate' to validate it, or 'Reset' to rollback to the previous version.
@ -46,3 +51,35 @@ The firmware is not validated yet. Tap 'Validate' to validate it, or 'Reset' to
The firmware is validated!
![Firmware Validated](firmwareValidated.png)
# 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)

View File

@ -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/

View File

@ -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() {

View File

@ -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/"

View File

@ -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 ()

View File

@ -82,7 +82,7 @@ void AlarmController::ScheduleAlarm() {
state = AlarmState::Set;
}
uint32_t AlarmController::SecondsToAlarm() {
uint32_t AlarmController::SecondsToAlarm() const {
return std::chrono::duration_cast<std::chrono::seconds>(alarmTime - dateTimeController.CurrentDateTime()).count();
}

View File

@ -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 };

View File

@ -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
*/

View File

@ -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<uint8_t>(month)];
}
const char* DateTime::DayOfWeekShortToStringLow() const {
return DaysStringShortLow[static_cast<uint8_t>(dayOfWeek)];
}
void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
this->systemTask = systemTask;
}

View File

@ -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<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const {
return currentDateTime;

View File

@ -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);
}
}

View File

@ -34,12 +34,20 @@ 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);
@ -94,10 +102,47 @@ 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;
};
void SetWatchfacesMenu(uint8_t menu) {
watchFacesMenu = menu;
};
uint8_t GetWatchfacesMenu() const {
return watchFacesMenu;
};
uint8_t GetAppMenu() const {
return appMenu;
};
@ -212,7 +257,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 +271,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 +283,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
*/

View File

@ -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;
}

View File

@ -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:
@ -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);
}

View File

@ -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();
};
}
}

View File

@ -2,6 +2,7 @@
#include <FreeRTOS.h>
#include <task.h>
#include <libraries/log/nrf_log.h>
#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} {
}

View File

@ -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();

View File

@ -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,

View File

@ -0,0 +1,116 @@
#include "displayapp/screens/CheckboxList.h"
#include "displayapp/DisplayApp.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<CheckboxList*>(obj->user_data);
screen->UpdateSelected(obj, event);
}
}
CheckboxList::CheckboxList(const uint8_t screenID,
const uint8_t numScreens,
DisplayApp* app,
Controllers::Settings& settingsController,
const char* optionsTitle,
const char* optionsSymbol,
void (Controllers::Settings::*SetOptionIndex)(uint8_t),
uint8_t (Controllers::Settings::*GetOptionIndex)() const,
std::array<const char*, MaxItems> options)
: Screen(app),
screenID {screenID},
settingsController {settingsController},
SetOptionIndex {SetOptionIndex},
GetOptionIndex {GetOptionIndex},
options {options} {
settingsController.SetWatchfacesMenu(screenID);
// 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);
if (numScreens > 1) {
pageIndicatorBasePoints[0].x = LV_HOR_RES - 1;
pageIndicatorBasePoints[0].y = 0;
pageIndicatorBasePoints[1].x = LV_HOR_RES - 1;
pageIndicatorBasePoints[1].y = LV_VER_RES;
pageIndicatorBase = lv_line_create(lv_scr_act(), NULL);
lv_obj_set_style_local_line_width(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3);
lv_obj_set_style_local_line_color(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111));
lv_line_set_points(pageIndicatorBase, pageIndicatorBasePoints.data(), 2);
const uint16_t indicatorSize = LV_VER_RES / numScreens;
const uint16_t indicatorPos = indicatorSize * screenID;
pageIndicatorPoints[0].x = LV_HOR_RES - 1;
pageIndicatorPoints[0].y = indicatorPos;
pageIndicatorPoints[1].x = LV_HOR_RES - 1;
pageIndicatorPoints[1].y = indicatorPos + indicatorSize;
pageIndicator = lv_line_create(lv_scr_act(), NULL);
lv_obj_set_style_local_line_width(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3);
lv_obj_set_style_local_line_color(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
lv_line_set_points(pageIndicator, pageIndicatorPoints.data(), 2);
}
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], "")) {
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 (static_cast<unsigned int>((settingsController.*GetOptionIndex)() - MaxItems * screenID) == i) {
lv_checkbox_set_checked(cbOption[i], true);
}
}
}
}
CheckboxList::~CheckboxList() {
lv_obj_clean(lv_scr_act());
}
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], "")) {
if (object == cbOption[i]) {
lv_checkbox_set_checked(cbOption[i], true);
(settingsController.*SetOptionIndex)(MaxItems * screenID + i);
} else {
lv_checkbox_set_checked(cbOption[i], false);
}
}
}
}
}

View File

@ -0,0 +1,48 @@
#pragma once
#include <lvgl/lvgl.h>
#include <cstdint>
#include <memory>
#include <array>
#include "displayapp/screens/Screen.h"
#include "displayapp/Apps.h"
#include "components/settings/Settings.h"
namespace Pinetime {
namespace Applications {
namespace Screens {
class CheckboxList : public Screen {
public:
static constexpr size_t MaxItems = 4;
CheckboxList(const uint8_t screenID,
const uint8_t numScreens,
DisplayApp* app,
Controllers::Settings& settingsController,
const char* optionsTitle,
const char* optionsSymbol,
void (Controllers::Settings::*SetOptionIndex)(uint8_t),
uint8_t (Controllers::Settings::*GetOptionIndex)() const,
std::array<const char*, MaxItems> options);
~CheckboxList() override;
void UpdateSelected(lv_obj_t* object, lv_event_t event);
private:
const uint8_t screenID;
Controllers::Settings& settingsController;
const char* optionsTitle;
const char* optionsSymbol;
void (Controllers::Settings::*SetOptionIndex)(uint8_t);
uint8_t (Controllers::Settings::*GetOptionIndex)() const;
std::array<const char*, MaxItems> options;
std::array<lv_obj_t*, MaxItems> cbOption;
std::array<lv_point_t, 2> pageIndicatorBasePoints;
std::array<lv_point_t, 2> pageIndicatorPoints;
lv_obj_t* pageIndicatorBase;
lv_obj_t* pageIndicator;
};
}
}
}

View File

@ -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<Screen> Clock::WatchFaceDigitalScreen() {
dateTimeController,
batteryController,
bleController,
notificatioManager,
notificationManager,
settingsController,
heartRateController,
motionController);
@ -79,7 +89,7 @@ std::unique_ptr<Screen> Clock::WatchFaceAnalogScreen() {
dateTimeController,
batteryController,
bleController,
notificatioManager,
notificationManager,
settingsController);
}
@ -88,7 +98,7 @@ std::unique_ptr<Screen> Clock::WatchFacePineTimeStyleScreen() {
dateTimeController,
batteryController,
bleController,
notificatioManager,
notificationManager,
settingsController,
motionController);
}
@ -98,8 +108,31 @@ std::unique_ptr<Screen> Clock::WatchFaceTerminalScreen() {
dateTimeController,
batteryController,
bleController,
notificatioManager,
notificationManager,
settingsController,
heartRateController,
motionController);
}
std::unique_ptr<Screen> Clock::WatchFaceInfineatScreen() {
return std::make_unique<Screens::WatchFaceInfineat>(app,
dateTimeController,
batteryController,
bleController,
notificationManager,
settingsController,
motionController,
filesystem);
}
std::unique_ptr<Screen> Clock::WatchFaceCasioStyleG7710() {
return std::make_unique<Screens::WatchFaceCasioStyleG7710>(app,
dateTimeController,
batteryController,
bleController,
notificationManager,
settingsController,
heartRateController,
motionController,
filesystem);
}

View File

@ -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> screen;
std::unique_ptr<Screen> WatchFaceDigitalScreen();
std::unique_ptr<Screen> WatchFaceAnalogScreen();
std::unique_ptr<Screen> WatchFacePineTimeStyleScreen();
std::unique_ptr<Screen> WatchFaceTerminalScreen();
std::unique_ptr<Screen> WatchFaceInfineatScreen();
std::unique_ptr<Screen> WatchFaceCasioStyleG7710();
};
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
};

View File

@ -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";
}
}
}

View File

@ -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());
}

View File

@ -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;

View File

@ -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);

View File

@ -0,0 +1,335 @@
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
#include <date/date.h>
#include <lvgl/lvgl.h>
#include <cstdio>
#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<date::days>(newDateTime);
auto time = date::make_time(newDateTime - dp);
auto yearMonthDay = date::year_month_day(dp);
auto year = static_cast<int>(yearMonthDay.year());
auto month = static_cast<Pinetime::Controllers::DateTime::Months>(static_cast<unsigned>(yearMonthDay.month()));
auto day = static_cast<unsigned>(yearMonthDay.day());
auto dayOfWeek = static_cast<Pinetime::Controllers::DateTime::Days>(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<std::chrono::system_clock::duration>(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);
}
}

View File

@ -0,0 +1,109 @@
#pragma once
#include <displayapp/screens/BatteryIcon.h>
#include <lvgl/src/lv_core/lv_obj.h>
#include <chrono>
#include <cstdint>
#include <memory>
#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;
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<uint8_t> batteryPercentRemaining {};
DirtyValue<bool> powerPresent {};
DirtyValue<bool> bleState {};
DirtyValue<bool> bleRadioEnabled {};
DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
DirtyValue<bool> motionSensorOk {};
DirtyValue<uint32_t> stepCount {};
DirtyValue<uint8_t> heartbeat {};
DirtyValue<bool> heartbeatRunning {};
DirtyValue<bool> 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;
};
}
}
}

View File

@ -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()));
}

View File

@ -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;

View File

@ -0,0 +1,611 @@
#include "displayapp/screens/WatchFaceInfineat.h"
#include <date/date.h>
#include <lvgl/lvgl.h>
#include <cstdio>
#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<WatchFaceInfineat*>(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<date::days>(newDateTime);
auto time = date::make_time(newDateTime - dp);
auto yearMonthDay = date::year_month_day(dp);
auto year = static_cast<int>(yearMonthDay.year());
auto month = static_cast<Pinetime::Controllers::DateTime::Months>(static_cast<unsigned>(yearMonthDay.month()));
auto day = static_cast<unsigned>(yearMonthDay.day());
auto dayOfWeek = static_cast<Pinetime::Controllers::DateTime::Days>(date::weekday(yearMonthDay).iso_encoding());
int hour = time.hours().count();
auto minute = time.minutes().count();
char minutesChar[3];
sprintf(minutesChar, "%02d", static_cast<int>(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<lv_coord_t>(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]));
}
}

View File

@ -0,0 +1,145 @@
#pragma once
#include <lvgl/lvgl.h>
#include <chrono>
#include <cstdint>
#include <memory>
#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;
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<uint8_t> batteryPercentRemaining {};
DirtyValue<bool> isCharging {};
DirtyValue<bool> bleState {};
DirtyValue<bool> bleRadioEnabled {};
DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
DirtyValue<bool> motionSensorOk {};
DirtyValue<uint32_t> stepCount {};
DirtyValue<bool> 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;
};
}
}
}

View File

@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* 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<uint8_t>(color);
Pinetime::Controllers::Settings::Colors nextColor;
if (colorAsInt < 16) {
if (colorAsInt < 17) {
nextColor = static_cast<Controllers::Settings::Colors>(colorAsInt + 1);
} else {
nextColor = static_cast<Controllers::Settings::Colors>(0);
@ -585,7 +704,7 @@ Pinetime::Controllers::Settings::Colors WatchFacePineTimeStyle::GetPrevious(Pine
if (colorAsInt > 0) {
prevColor = static_cast<Controllers::Settings::Colors>(colorAsInt - 1);
} else {
prevColor = static_cast<Controllers::Settings::Colors>(16);
prevColor = static_cast<Controllers::Settings::Colors>(17);
}
return prevColor;
}

View File

@ -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;

View File

@ -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.");

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
};

View File

@ -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() {

View File

@ -1,58 +1,29 @@
#include "displayapp/screens/settings/SettingWatchFace.h"
#include <lvgl/lvgl.h>
#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"
using namespace Pinetime::Applications::Screens;
namespace {
void event_handler(lv_obj_t* obj, lv_event_t event) {
auto* screen = static_cast<SettingWatchFace*>(obj->user_data);
screen->UpdateSelected(obj, event);
}
}
constexpr std::array<const char*, 4> SettingWatchFace::options;
constexpr const char* SettingWatchFace::title;
constexpr const char* SettingWatchFace::symbol;
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);
}
}
: Screen(app),
settingsController {settingsController},
screens {app,
settingsController.GetWatchfacesMenu(),
{[this]() -> std::unique_ptr<Screen> {
return CreateScreen1();
},
[this]() -> std::unique_ptr<Screen> {
return CreateScreen2();
}},
Screens::ScreenListModes::UpDown} {
}
SettingWatchFace::~SettingWatchFace() {
@ -60,15 +31,32 @@ SettingWatchFace::~SettingWatchFace() {
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<Screen> SettingWatchFace::CreateScreen1() {
std::array<const char*, 4> watchfaces {"Digital face", "Analog face", "PineTimeStyle", "Terminal"};
return std::make_unique<Screens::CheckboxList>(0,
2,
app,
settingsController,
title,
symbol,
&Controllers::Settings::SetClockFace,
&Controllers::Settings::GetClockFace,
watchfaces);
}
std::unique_ptr<Screen> SettingWatchFace::CreateScreen2() {
std::array<const char*, 4> watchfaces {"Infineat face", "Casio G7710", "", ""};
return std::make_unique<Screens::CheckboxList>(1,
2,
app,
settingsController,
title,
symbol,
&Controllers::Settings::SetClockFace,
&Controllers::Settings::GetClockFace,
watchfaces);
}

View File

@ -4,8 +4,10 @@
#include <cstdint>
#include <lvgl/lvgl.h>
#include "displayapp/screens/ScreenList.h"
#include "components/settings/Settings.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.h"
namespace Pinetime {
@ -17,13 +19,16 @@ namespace Pinetime {
SettingWatchFace(DisplayApp* app, Pinetime::Controllers::Settings& settingsController);
~SettingWatchFace() override;
void UpdateSelected(lv_obj_t* object, lv_event_t event);
bool OnTouchEvent(TouchEvents event) override;
private:
static constexpr std::array<const char*, 4> options = {"Digital face", "Analog face", "PineTimeStyle", "Terminal"};
Controllers::Settings& settingsController;
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<Screen> CreateScreen1();
std::unique_ptr<Screen> CreateScreen2();
};
}
}

View File

@ -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);

View File

@ -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

View File

@ -131,7 +131,8 @@ Pinetime::Applications::DisplayApp displayApp(lcd,
timerController,
alarmController,
brightnessController,
touchHandler);
touchHandler,
fs);
Pinetime::System::SystemTask systemTask(spi,
lcd,

View File

@ -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"
)

62
src/resources/fonts.json Normal file
View File

@ -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/"
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

80
src/resources/generate-fonts.py Executable file
View File

@ -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()

56
src/resources/generate-img.py Executable file
View File

@ -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()

View File

@ -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()

View File

@ -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/"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,253 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="110.49872"
height="150.24246"
viewBox="0 0 29.236118 39.751652"
version="1.1"
id="svg2418"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
sodipodi:docname="pine_logo.svg"
inkscape:export-filename="/home/diegomiguel/Syncthing/Watchface/pine_logo_new_2_transparent.png"
inkscape:export-xdpi="19.807983"
inkscape:export-ydpi="19.807983"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview2420"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
units="px"
inkscape:zoom="4.1424077"
inkscape:cx="69.886892"
inkscape:cy="73.387272"
inkscape:window-width="1920"
inkscape:window-height="1026"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1"
inkscape:current-layer="g972"
inkscape:snap-page="true" />
<defs
id="defs2415" />
<g
inkscape:label="Layer 1"
id="layer1"
transform="translate(-91.35232,-110.1768)"
inkscape:groupmode="layer">
<rect
style="display:none;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#4d4d4d;stroke-width:0"
id="rect2129"
width="29.236118"
height="39.751652"
x="91.352318"
y="110.1768"
inkscape:label="bg" />
<g
id="g32004"
style="display:none;stroke:none"
inkscape:export-filename="/Users/diegomiguel/Syncthing/Watchface/logo_pine.png"
inkscape:export-xdpi="19.965168"
inkscape:export-ydpi="19.965168"
inkscape:label="pine_logo"
transform="translate(75.060638,-5.5438717)">
<g
id="g13016"
inkscape:label="pine"
style="display:inline;fill:#6f2d00;fill-opacity:1;stroke:none"
transform="matrix(1.1631294,0,0,1.1631294,-5.0422885,-22.11978)"
inkscape:export-filename="/Users/diegomiguel/Syncthing/Watchface/logo_pine.png"
inkscape:export-xdpi="31.276381"
inkscape:export-ydpi="31.276381"
sodipodi:insensitive="true">
<path
id="path5716"
style="fill:#6f2d00;fill-opacity:1;stroke:none;stroke-width:2.34917;stroke-linecap:round"
inkscape:transform-center-x="1.2687941"
d="M 116.82422,535.70898 102.4375,542.5 l -14.386719,6.78906 14.386719,6.78906 11,5.19141 v 15.80664 h 3.38672 v -14.20703 -3.93555 -9.64453 z"
transform="scale(0.26458333)" />
<path
style="fill:#6f2d00;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 18.341872,136.77692 3.331558,7.57837 7.029221,-3.22172 z"
id="path5936"
sodipodi:nodetypes="cccc" />
<path
sodipodi:type="star"
style="fill:#6f2d00;fill-opacity:1;stroke:none;stroke-width:1.51181;stroke-linecap:round"
id="path7773"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="116.64632"
sodipodi:cy="501.86975"
sodipodi:r1="14.699218"
sodipodi:r2="7.3496094"
sodipodi:arg1="1.0471976"
sodipodi:arg2="2.0943951"
inkscape:rounded="0"
inkscape:randomized="0"
transform="matrix(0.39243637,0,0,0.31059853,-17.750778,-19.228227)"
inkscape:transform-center-x="1.4421265"
d="m 123.99592,514.59965 -11.02441,-6.36495 -11.02441,-6.36495 11.02441,-6.36495 11.02442,-6.36494 0,12.72989 z" />
<path
sodipodi:type="star"
style="fill:#6f2d00;fill-opacity:1;stroke:none;stroke-width:1.51181;stroke-linecap:round"
id="path7877"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="116.64632"
sodipodi:cy="501.86975"
sodipodi:r1="14.699218"
sodipodi:r2="7.3496094"
sodipodi:arg1="1.0471976"
sodipodi:arg2="2.0943951"
inkscape:rounded="0"
inkscape:randomized="0"
transform="matrix(-0.3940968,0,0,-0.29190487,69.062729,278.57074)"
inkscape:transform-center-x="-1.4482278"
inkscape:transform-center-y="3.6892669e-06"
d="m 123.99592,514.59965 -11.02441,-6.36495 -11.02441,-6.36495 11.02441,-6.36495 11.02442,-6.36494 0,12.72989 z" />
<path
sodipodi:type="star"
style="fill:#6f2d00;fill-opacity:1;stroke:none;stroke-width:1.51181;stroke-linecap:round"
id="path7929"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="116.64632"
sodipodi:cy="501.86975"
sodipodi:r1="14.699218"
sodipodi:r2="7.3496094"
sodipodi:arg1="1.0471976"
sodipodi:arg2="2.0943951"
inkscape:rounded="0"
inkscape:randomized="0"
transform="matrix(0.34926521,0,0,0.27033526,-12.397729,-7.5515591)"
inkscape:transform-center-x="1.28348"
inkscape:transform-center-y="1.7340579e-06"
d="m 123.99592,514.59965 -11.02441,-6.36495 -11.02441,-6.36495 11.02441,-6.36495 11.02442,-6.36494 0,12.72989 z" />
<path
style="fill:#6f2d00;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 24.903849,122.34368 -1.378447,3.99721 5.0395,-2.31516 z"
id="path7964"
sodipodi:nodetypes="cccc" />
<path
style="fill:#6f2d00;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 30.909733,118.50827 v 4.63122 l -3.122345,-1.29967 z"
id="path11445" />
</g>
<g
id="g13032"
transform="matrix(-1.1631294,0,0,1.1631294,66.861771,-22.11978)"
style="fill:#de5a00;fill-opacity:1;stroke:none"
inkscape:label="pine"
inkscape:export-filename="/Users/diegomiguel/Syncthing/Watchface/logo_pine.png"
inkscape:export-xdpi="31.276381"
inkscape:export-ydpi="31.276381">
<path
id="path13018"
style="fill:#de5a00;fill-opacity:1;stroke:none;stroke-width:2.34917;stroke-linecap:round"
inkscape:transform-center-x="1.2687941"
d="M 116.82422,535.70898 102.4375,542.5 l -14.386719,6.78906 14.386719,6.78906 11,5.19141 v 15.80664 h 3.38672 v -14.20703 -3.93555 -9.64453 z"
transform="scale(0.26458333)" />
<path
style="fill:#de5a00;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 18.341872,136.77692 3.331558,7.57837 7.029221,-3.22172 z"
id="path13020"
sodipodi:nodetypes="cccc" />
<path
sodipodi:type="star"
style="fill:#de5a00;fill-opacity:1;stroke:none;stroke-width:1.51181;stroke-linecap:round"
id="path13022"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="116.64632"
sodipodi:cy="501.86975"
sodipodi:r1="14.699218"
sodipodi:r2="7.3496094"
sodipodi:arg1="1.0471976"
sodipodi:arg2="2.0943951"
inkscape:rounded="0"
inkscape:randomized="0"
transform="matrix(0.39243637,0,0,0.31059853,-17.750778,-19.228227)"
inkscape:transform-center-x="1.4421265"
d="m 123.99592,514.59965 -11.02441,-6.36495 -11.02441,-6.36495 11.02441,-6.36495 11.02442,-6.36494 0,12.72989 z" />
<path
sodipodi:type="star"
style="fill:#de5a00;fill-opacity:1;stroke:none;stroke-width:1.51181;stroke-linecap:round"
id="path13024"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="116.64632"
sodipodi:cy="501.86975"
sodipodi:r1="14.699218"
sodipodi:r2="7.3496094"
sodipodi:arg1="1.0471976"
sodipodi:arg2="2.0943951"
inkscape:rounded="0"
inkscape:randomized="0"
transform="matrix(-0.3940968,0,0,-0.29190487,69.062729,278.57074)"
inkscape:transform-center-x="-1.4482278"
inkscape:transform-center-y="3.6892669e-06"
d="m 123.99592,514.59965 -11.02441,-6.36495 -11.02441,-6.36495 11.02441,-6.36495 11.02442,-6.36494 0,12.72989 z" />
<path
sodipodi:type="star"
style="fill:#de5a00;fill-opacity:1;stroke:none;stroke-width:1.51181;stroke-linecap:round"
id="path13026"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="116.64632"
sodipodi:cy="501.86975"
sodipodi:r1="14.699218"
sodipodi:r2="7.3496094"
sodipodi:arg1="1.0471976"
sodipodi:arg2="2.0943951"
inkscape:rounded="0"
inkscape:randomized="0"
transform="matrix(0.34926521,0,0,0.27033526,-12.397729,-7.5515591)"
inkscape:transform-center-x="1.28348"
inkscape:transform-center-y="1.7340579e-06"
d="m 123.99592,514.59965 -11.02441,-6.36495 -11.02441,-6.36495 11.02441,-6.36495 11.02442,-6.36494 0,12.72989 z" />
<path
style="fill:#de5a00;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 24.903849,122.34368 -1.378447,3.99721 5.0395,-2.31516 z"
id="path13028"
sodipodi:nodetypes="cccc" />
<path
style="fill:#de5a00;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 30.909733,118.50827 v 4.63122 l -3.122345,-1.29967 z"
id="path13030" />
</g>
</g>
<g
id="g972"
style="display:inline;stroke:none"
inkscape:export-filename="/Users/diegomiguel/Syncthing/Watchface/logo_pine.png"
inkscape:export-xdpi="19.965168"
inkscape:export-ydpi="19.965168"
inkscape:label="pine_logo"
transform="translate(75.060638,-5.5438717)">
<path
id="path952"
style="fill:#0f0f0f;fill-opacity:1;stroke:none;stroke-width:0.307744px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 30.909731,115.72067 v 5.38671 l -3.631692,-1.51168 z m -6.985621,4.46108 -1.603312,4.64927 5.861591,-2.69283 z m 6.98562,10.72311 -4.478564,-2.00136 -4.478563,-2.00136 4.478563,-2.00136 4.478568,-2.00137 v 4.00273 z m -12.461069,-3.7293 5.05343,2.16104 5.053431,2.16105 -5.053431,2.16105 -5.053434,2.16104 v -4.32209 z m 12.461067,14.24725 -5.032139,-2.29945 -5.032139,-2.29944 5.032139,-2.29943 5.032144,-2.29945 v 4.59888 z m -14.618046,-4.45333 3.875033,8.81462 8.175894,-3.74728 z m 14.618058,5.77232 -4.427436,2.0899 -4.427436,2.08929 4.427436,2.0893 3.385191,1.59762 v 4.86441 h 1.042245 v -4.37214 -1.21114 -2.96805 z"
inkscape:label="left" />
<path
id="path968"
style="fill:#575757;fill-opacity:1;stroke:none;stroke-width:0.307744px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 30.909752,115.72067 v 5.38671 l 3.631691,-1.51168 z m 6.98562,4.46108 1.603312,4.64927 -5.86159,-2.69283 z m -6.985619,10.72311 4.478564,-2.00136 4.478563,-2.00136 -4.478563,-2.00136 -4.478568,-2.00136 v 4.00272 z m 12.461067,-3.72931 -5.05343,2.16105 -5.05343,2.16105 5.05343,2.16104 5.053435,2.16105 v -4.32209 z m -12.461065,14.24726 5.032139,-2.29945 5.032139,-2.29944 -5.032139,-2.29944 -5.032144,-2.29944 v 4.59888 z m 14.618045,-4.45333 -3.875033,8.81462 -8.175893,-3.74728 z m -14.618058,5.77231 4.427436,2.0899 4.427436,2.0893 -4.427436,2.0893 -3.385191,1.59763 v 4.8644 h -1.042245 v -4.37213 -1.21115 -2.96805 z"
inkscape:label="right" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,6 @@
[
{
"path": "/example-of-obsolete-file.bin",
"since": "1.11.0"
}
]