Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
77f1a3e230 | ||
|
11acaeadd3 | ||
4fa8e02289 | |||
0cc8017cbd | |||
d758394ce9 | |||
dd5d65d315 | |||
2bffcf99f0 | |||
|
9b8eb75f34 | ||
|
b191a30947 | ||
|
d930fd4fa2 | ||
|
e6b96c2863 | ||
|
77546c9fe2 | ||
e24323b454 | |||
79820669b6 | |||
1ff782cb0e | |||
5d7e1ae1f3 | |||
415ff6c05c | |||
2cb36d6e80 | |||
cbfd82959e | |||
ce136ea148 | |||
|
eac460f030 | ||
|
46b664b528 |
@ -11,6 +11,7 @@ RUN apt-get update -qq \
|
|||||||
make \
|
make \
|
||||||
python3 \
|
python3 \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
|
python3-pil \
|
||||||
tar \
|
tar \
|
||||||
unzip \
|
unzip \
|
||||||
wget \
|
wget \
|
||||||
|
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -31,6 +31,10 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
- name: Install resource build dependencies
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get -y install --no-install-recommends python3-pil
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: /opt/build.sh all
|
run: /opt/build.sh all
|
||||||
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
|
|||||||
|
|
||||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
|
||||||
|
|
||||||
project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM)
|
project(pinetime VERSION 1.13.1.1 LANGUAGES C CXX ASM)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
|
18
README.md
18
README.md
@ -1,5 +1,8 @@
|
|||||||
# TODO
|
# TODO
|
||||||
## Completed
|
## Completed
|
||||||
|
### Pending version
|
||||||
|
|
||||||
|
### v1.13.1
|
||||||
- [x] Make $s in terminal watch face green
|
- [x] Make $s in terminal watch face green
|
||||||
- [x] Swipe left to access music control
|
- [x] Swipe left to access music control
|
||||||
- [x] Flashlight starts on
|
- [x] Flashlight starts on
|
||||||
@ -9,6 +12,8 @@
|
|||||||
- [x] Swipe left/right goes to watchface/another app
|
- [x] Swipe left/right goes to watchface/another app
|
||||||
- [x] Rearrange quick acces and apps
|
- [x] Rearrange quick acces and apps
|
||||||
- [x] Exponent button on calculator
|
- [x] Exponent button on calculator
|
||||||
|
- [x] Seconds on digital watchface
|
||||||
|
- [x] Add quick ring settings
|
||||||
|
|
||||||
## Pending merge
|
## Pending merge
|
||||||
|
|
||||||
@ -17,18 +22,17 @@
|
|||||||
- [ ] Countdown timer presistant buzz
|
- [ ] Countdown timer presistant buzz
|
||||||
|
|
||||||
### Josh
|
### Josh
|
||||||
- [ ] Seconds on digital watchface
|
- [ ] Smiley face watchface :)
|
||||||
- [ ] Battery on digital watchface
|
- [ ] Remap button double click
|
||||||
- [ ] Remap button double click (to music app)
|
|
||||||
|
|
||||||
### Moses
|
### Moses
|
||||||
|
|
||||||
|
|
||||||
- [ ] Add quick ring settings
|
|
||||||
|
|
||||||
## Medium priority
|
## Medium priority
|
||||||
- [ ] Temp sensor w/ arduino
|
- [ ] Battery on digital watchface
|
||||||
- [ ] Homeassistant control
|
- [ ] More watchfaces!
|
||||||
|
|
||||||
## Lower priority
|
## Lower priority
|
||||||
- [ ] Maybe some prank watchfaces
|
- [ ] Temp sensor w/ arduino
|
||||||
|
- [ ] Homeassistant control
|
||||||
|
@ -42,7 +42,7 @@ CMake configures the project according to variables you specify the command line
|
|||||||
**NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`|
|
**NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`|
|
||||||
**CMAKE_BUILD_TYPE (\*)**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug`
|
**CMAKE_BUILD_TYPE (\*)**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug`
|
||||||
**BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-DBUILD_DFU=1`
|
**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`
|
**BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [python3-pil/pillow](https://pillow.readthedocs.io) module). |`-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)
|
**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**
|
#### (\*) Note about **CMAKE_BUILD_TYPE**
|
||||||
@ -98,4 +98,4 @@ Binary files are generated into the folder `src`:
|
|||||||
- **pinetime-mcuboot-app-image** : MCUBoot image of the firmware
|
- **pinetime-mcuboot-app-image** : MCUBoot image of the firmware
|
||||||
- **pinetime-mcuboot-app-dfu** : DFU file of the firmware
|
- **pinetime-mcuboot-app-dfu** : DFU file of the firmware
|
||||||
|
|
||||||
The same files are generated for **pinetime-recovery** and **pinetime-recoveryloader**
|
The same files are generated for **pinetime-recovery** and **pinetime-recovery-loader**
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
FROM ubuntu:22.04
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ARG NODE_MAJOR=20
|
||||||
RUN apt-get update -qq \
|
RUN apt-get update -qq \
|
||||||
|
&& apt-get install -y ca-certificates curl gnupg \
|
||||||
|
&& mkdir -p /etc/apt/keyrings \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
|
||||||
|
&& apt-get update -qq \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
# x86_64 / generic packages
|
# x86_64 / generic packages
|
||||||
bash \
|
bash \
|
||||||
@ -9,13 +15,14 @@ RUN apt-get update -qq \
|
|||||||
cmake \
|
cmake \
|
||||||
git \
|
git \
|
||||||
make \
|
make \
|
||||||
|
nodejs \
|
||||||
python3 \
|
python3 \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
|
python3-pil \
|
||||||
python-is-python3 \
|
python-is-python3 \
|
||||||
tar \
|
tar \
|
||||||
unzip \
|
unzip \
|
||||||
wget \
|
wget \
|
||||||
curl \
|
|
||||||
# aarch64 packages
|
# aarch64 packages
|
||||||
libffi-dev \
|
libffi-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
@ -28,8 +35,6 @@ RUN apt-get update -qq \
|
|||||||
libpango-1.0-0 \
|
libpango-1.0-0 \
|
||||||
ibpango1.0-dev \
|
ibpango1.0-dev \
|
||||||
libpangocairo-1.0-0 \
|
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/*;
|
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
|
||||||
|
|
||||||
# Git needed for PROJECT_GIT_COMMIT_HASH variable setting
|
# Git needed for PROJECT_GIT_COMMIT_HASH variable setting
|
||||||
@ -39,10 +44,6 @@ RUN pip3 install -Iv cryptography==3.3
|
|||||||
RUN pip3 install cbor
|
RUN pip3 install cbor
|
||||||
RUN npm i lv_font_conv@1.5.2 -g
|
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
|
# build.sh knows how to compile
|
||||||
COPY build.sh /opt/
|
COPY build.sh /opt/
|
||||||
|
|
||||||
|
@ -435,6 +435,7 @@ list(APPEND SOURCE_FILES
|
|||||||
displayapp/screens/WatchFaceTerminal.cpp
|
displayapp/screens/WatchFaceTerminal.cpp
|
||||||
displayapp/screens/WatchFacePineTimeStyle.cpp
|
displayapp/screens/WatchFacePineTimeStyle.cpp
|
||||||
displayapp/screens/WatchFaceCasioStyleG7710.cpp
|
displayapp/screens/WatchFaceCasioStyleG7710.cpp
|
||||||
|
displayapp/screens/WatchFaceFace.cpp
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
||||||
|
@ -404,7 +404,8 @@ namespace Pinetime {
|
|||||||
std::unique_ptr<WeatherData::Clouds>& WeatherService::GetCurrentClouds() {
|
std::unique_ptr<WeatherData::Clouds>& WeatherService::GetCurrentClouds() {
|
||||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||||
for (auto&& header : this->timeline) {
|
for (auto&& header : this->timeline) {
|
||||||
if (header->eventType == WeatherData::eventtype::Clouds && IsEventStillValid(header, currentTimestamp)) {
|
if (header->eventType == WeatherData::eventtype::Clouds && currentTimestamp >= header->timestamp &&
|
||||||
|
IsEventStillValid(header, currentTimestamp)) {
|
||||||
return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(header);
|
return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -415,7 +416,8 @@ namespace Pinetime {
|
|||||||
std::unique_ptr<WeatherData::Obscuration>& WeatherService::GetCurrentObscuration() {
|
std::unique_ptr<WeatherData::Obscuration>& WeatherService::GetCurrentObscuration() {
|
||||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||||
for (auto&& header : this->timeline) {
|
for (auto&& header : this->timeline) {
|
||||||
if (header->eventType == WeatherData::eventtype::Obscuration && IsEventStillValid(header, currentTimestamp)) {
|
if (header->eventType == WeatherData::eventtype::Obscuration && currentTimestamp >= header->timestamp &&
|
||||||
|
IsEventStillValid(header, currentTimestamp)) {
|
||||||
return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(header);
|
return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -426,7 +428,8 @@ namespace Pinetime {
|
|||||||
std::unique_ptr<WeatherData::Precipitation>& WeatherService::GetCurrentPrecipitation() {
|
std::unique_ptr<WeatherData::Precipitation>& WeatherService::GetCurrentPrecipitation() {
|
||||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||||
for (auto&& header : this->timeline) {
|
for (auto&& header : this->timeline) {
|
||||||
if (header->eventType == WeatherData::eventtype::Precipitation && IsEventStillValid(header, currentTimestamp)) {
|
if (header->eventType == WeatherData::eventtype::Precipitation && currentTimestamp >= header->timestamp &&
|
||||||
|
IsEventStillValid(header, currentTimestamp)) {
|
||||||
return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(header);
|
return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -437,7 +440,8 @@ namespace Pinetime {
|
|||||||
std::unique_ptr<WeatherData::Wind>& WeatherService::GetCurrentWind() {
|
std::unique_ptr<WeatherData::Wind>& WeatherService::GetCurrentWind() {
|
||||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||||
for (auto&& header : this->timeline) {
|
for (auto&& header : this->timeline) {
|
||||||
if (header->eventType == WeatherData::eventtype::Wind && IsEventStillValid(header, currentTimestamp)) {
|
if (header->eventType == WeatherData::eventtype::Wind && currentTimestamp >= header->timestamp &&
|
||||||
|
IsEventStillValid(header, currentTimestamp)) {
|
||||||
return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(header);
|
return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -448,7 +452,8 @@ namespace Pinetime {
|
|||||||
std::unique_ptr<WeatherData::Temperature>& WeatherService::GetCurrentTemperature() {
|
std::unique_ptr<WeatherData::Temperature>& WeatherService::GetCurrentTemperature() {
|
||||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||||
for (auto&& header : this->timeline) {
|
for (auto&& header : this->timeline) {
|
||||||
if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp)) {
|
if (header->eventType == WeatherData::eventtype::Temperature && currentTimestamp >= header->timestamp &&
|
||||||
|
IsEventStillValid(header, currentTimestamp)) {
|
||||||
return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(header);
|
return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -459,7 +464,8 @@ namespace Pinetime {
|
|||||||
std::unique_ptr<WeatherData::Humidity>& WeatherService::GetCurrentHumidity() {
|
std::unique_ptr<WeatherData::Humidity>& WeatherService::GetCurrentHumidity() {
|
||||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||||
for (auto&& header : this->timeline) {
|
for (auto&& header : this->timeline) {
|
||||||
if (header->eventType == WeatherData::eventtype::Humidity && IsEventStillValid(header, currentTimestamp)) {
|
if (header->eventType == WeatherData::eventtype::Humidity && currentTimestamp >= header->timestamp &&
|
||||||
|
IsEventStillValid(header, currentTimestamp)) {
|
||||||
return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(header);
|
return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -470,7 +476,8 @@ namespace Pinetime {
|
|||||||
std::unique_ptr<WeatherData::Pressure>& WeatherService::GetCurrentPressure() {
|
std::unique_ptr<WeatherData::Pressure>& WeatherService::GetCurrentPressure() {
|
||||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||||
for (auto&& header : this->timeline) {
|
for (auto&& header : this->timeline) {
|
||||||
if (header->eventType == WeatherData::eventtype::Pressure && IsEventStillValid(header, currentTimestamp)) {
|
if (header->eventType == WeatherData::eventtype::Pressure && currentTimestamp >= header->timestamp &&
|
||||||
|
IsEventStillValid(header, currentTimestamp)) {
|
||||||
return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(header);
|
return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -481,7 +488,8 @@ namespace Pinetime {
|
|||||||
std::unique_ptr<WeatherData::Location>& WeatherService::GetCurrentLocation() {
|
std::unique_ptr<WeatherData::Location>& WeatherService::GetCurrentLocation() {
|
||||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||||
for (auto&& header : this->timeline) {
|
for (auto&& header : this->timeline) {
|
||||||
if (header->eventType == WeatherData::eventtype::Location && IsEventStillValid(header, currentTimestamp)) {
|
if (header->eventType == WeatherData::eventtype::Location && currentTimestamp >= header->timestamp &&
|
||||||
|
IsEventStillValid(header, currentTimestamp)) {
|
||||||
return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(header);
|
return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -492,7 +500,8 @@ namespace Pinetime {
|
|||||||
std::unique_ptr<WeatherData::AirQuality>& WeatherService::GetCurrentQuality() {
|
std::unique_ptr<WeatherData::AirQuality>& WeatherService::GetCurrentQuality() {
|
||||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||||
for (auto&& header : this->timeline) {
|
for (auto&& header : this->timeline) {
|
||||||
if (header->eventType == WeatherData::eventtype::AirQuality && IsEventStillValid(header, currentTimestamp)) {
|
if (header->eventType == WeatherData::eventtype::AirQuality && currentTimestamp >= header->timestamp &&
|
||||||
|
IsEventStillValid(header, currentTimestamp)) {
|
||||||
return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(header);
|
return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -374,18 +374,18 @@ void DisplayApp::Refresh() {
|
|||||||
// What should happen here?
|
// What should happen here?
|
||||||
break;
|
break;
|
||||||
case Messages::Chime:
|
case Messages::Chime:
|
||||||
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
|
|
||||||
time_var = dateTimeController.Minutes();
|
time_var = dateTimeController.Minutes();
|
||||||
if (time_var == 30){
|
if (time_var == 30){
|
||||||
NRF_LOG_INFO("Short: %d", time_var);
|
//NRF_LOG_INFO("Short: %d", time_var);
|
||||||
motorController.StartPattern();
|
motorController.StartPattern();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NRF_LOG_INFO("Long: %d", time_var);
|
//NRF_LOG_INFO("Long: %d", time_var);
|
||||||
motorController.RunForDuration(200);
|
motorController.RunForDuration(200);
|
||||||
}
|
}
|
||||||
|
//LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case Messages::OnChargingEvent:
|
case Messages::OnChargingEvent:
|
||||||
|
@ -9,6 +9,7 @@ namespace Pinetime {
|
|||||||
Terminal = 3,
|
Terminal = 3,
|
||||||
Infineat = 4,
|
Infineat = 4,
|
||||||
CasioStyleG7710 = 5,
|
CasioStyleG7710 = 5,
|
||||||
|
FaceFace = 6,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"file": "JetBrainsMono-Regular.ttf",
|
"file": "JetBrainsMono-Regular.ttf",
|
||||||
"range": "0x25, 0x2b, 0x2d, 0x30-0x3a"
|
"range": "0x25, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bpp": 1,
|
"bpp": 1,
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "displayapp/screens/WatchFaceAnalog.h"
|
#include "displayapp/screens/WatchFaceAnalog.h"
|
||||||
#include "displayapp/screens/WatchFacePineTimeStyle.h"
|
#include "displayapp/screens/WatchFacePineTimeStyle.h"
|
||||||
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
||||||
|
#include "displayapp/screens/WatchFaceFace.h"
|
||||||
|
|
||||||
using namespace Pinetime::Applications::Screens;
|
using namespace Pinetime::Applications::Screens;
|
||||||
using namespace Pinetime::Applications;
|
using namespace Pinetime::Applications;
|
||||||
@ -55,6 +56,9 @@ Clock::Clock(Controllers::DateTime& dateTimeController,
|
|||||||
case WatchFace::CasioStyleG7710:
|
case WatchFace::CasioStyleG7710:
|
||||||
return WatchFaceCasioStyleG7710();
|
return WatchFaceCasioStyleG7710();
|
||||||
break;
|
break;
|
||||||
|
case WatchFace::FaceFace:
|
||||||
|
return WatchFaceFaceScreen();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return WatchFaceDigitalScreen();
|
return WatchFaceDigitalScreen();
|
||||||
}()} {
|
}()} {
|
||||||
@ -130,3 +134,12 @@ std::unique_ptr<Screen> Clock::WatchFaceCasioStyleG7710() {
|
|||||||
motionController,
|
motionController,
|
||||||
filesystem);
|
filesystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::unique_ptr<Screen> Clock::WatchFaceFaceScreen() {
|
||||||
|
return std::make_unique<Screens::WatchFaceFace>(dateTimeController,
|
||||||
|
batteryController,
|
||||||
|
bleController,
|
||||||
|
notificationManager,
|
||||||
|
settingsController);
|
||||||
|
}
|
||||||
|
@ -54,6 +54,7 @@ namespace Pinetime {
|
|||||||
std::unique_ptr<Screen> WatchFaceTerminalScreen();
|
std::unique_ptr<Screen> WatchFaceTerminalScreen();
|
||||||
std::unique_ptr<Screen> WatchFaceInfineatScreen();
|
std::unique_ptr<Screen> WatchFaceInfineatScreen();
|
||||||
std::unique_ptr<Screen> WatchFaceCasioStyleG7710();
|
std::unique_ptr<Screen> WatchFaceCasioStyleG7710();
|
||||||
|
std::unique_ptr<Screen> WatchFaceFaceScreen();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,19 +203,21 @@ Navigation::Navigation(Pinetime::Controllers::NavigationService& nav) : navServi
|
|||||||
lv_obj_align(imgFlag, nullptr, LV_ALIGN_CENTER, 0, -60);
|
lv_obj_align(imgFlag, nullptr, LV_ALIGN_CENTER, 0, -60);
|
||||||
|
|
||||||
txtNarrative = lv_label_create(lv_scr_act(), nullptr);
|
txtNarrative = lv_label_create(lv_scr_act(), nullptr);
|
||||||
lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_BREAK);
|
lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_DOT);
|
||||||
lv_obj_set_width(txtNarrative, LV_HOR_RES);
|
lv_obj_set_width(txtNarrative, LV_HOR_RES);
|
||||||
|
lv_obj_set_height(txtNarrative, 80);
|
||||||
lv_label_set_text_static(txtNarrative, "Navigation");
|
lv_label_set_text_static(txtNarrative, "Navigation");
|
||||||
lv_label_set_align(txtNarrative, LV_LABEL_ALIGN_CENTER);
|
lv_label_set_align(txtNarrative, LV_LABEL_ALIGN_CENTER);
|
||||||
lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 10);
|
lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 30);
|
||||||
|
|
||||||
txtManDist = lv_label_create(lv_scr_act(), nullptr);
|
txtManDist = lv_label_create(lv_scr_act(), nullptr);
|
||||||
lv_label_set_long_mode(txtManDist, LV_LABEL_LONG_BREAK);
|
lv_label_set_long_mode(txtManDist, LV_LABEL_LONG_BREAK);
|
||||||
lv_obj_set_style_local_text_color(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
|
lv_obj_set_style_local_text_color(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
|
||||||
|
lv_obj_set_style_local_text_font(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
|
||||||
lv_obj_set_width(txtManDist, LV_HOR_RES);
|
lv_obj_set_width(txtManDist, LV_HOR_RES);
|
||||||
lv_label_set_text_static(txtManDist, "--M");
|
lv_label_set_text_static(txtManDist, "--M");
|
||||||
lv_label_set_align(txtManDist, LV_LABEL_ALIGN_CENTER);
|
lv_label_set_align(txtManDist, LV_LABEL_ALIGN_CENTER);
|
||||||
lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 60);
|
lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 90);
|
||||||
|
|
||||||
// Route Progress
|
// Route Progress
|
||||||
barProgress = lv_bar_create(lv_scr_act(), nullptr);
|
barProgress = lv_bar_create(lv_scr_act(), nullptr);
|
||||||
|
274
src/displayapp/screens/WatchFaceFace.cpp
Normal file
274
src/displayapp/screens/WatchFaceFace.cpp
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
#include "displayapp/screens/WatchFaceFace.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <lvgl/lvgl.h>
|
||||||
|
#include "displayapp/screens/BatteryIcon.h"
|
||||||
|
#include "displayapp/screens/BleIcon.h"
|
||||||
|
#include "displayapp/screens/Symbols.h"
|
||||||
|
#include "displayapp/screens/NotificationIcon.h"
|
||||||
|
#include "components/settings/Settings.h"
|
||||||
|
#include "displayapp/InfiniTimeTheme.h"
|
||||||
|
|
||||||
|
using namespace Pinetime::Applications::Screens;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int16_t HourLength = 100;
|
||||||
|
constexpr int16_t MinuteLength = 90;
|
||||||
|
constexpr int16_t SecondLength = 110;
|
||||||
|
|
||||||
|
// sin(90) = 1 so the value of _lv_trigo_sin(90) is the scaling factor
|
||||||
|
const auto LV_TRIG_SCALE = _lv_trigo_sin(90);
|
||||||
|
|
||||||
|
int16_t Cosine(int16_t angle) {
|
||||||
|
return _lv_trigo_sin(angle + 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t Sine(int16_t angle) {
|
||||||
|
return _lv_trigo_sin(angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t CoordinateXRelocate(int16_t x) {
|
||||||
|
return (x + LV_HOR_RES / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t CoordinateYRelocate(int16_t y) {
|
||||||
|
return std::abs(y - LV_HOR_RES / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_point_t CoordinateRelocate(int16_t radius, int16_t angle) {
|
||||||
|
return lv_point_t {.x = CoordinateXRelocate(radius * static_cast<int32_t>(Sine(angle)) / LV_TRIG_SCALE),
|
||||||
|
.y = CoordinateYRelocate(radius * static_cast<int32_t>(Cosine(angle)) / LV_TRIG_SCALE)};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchFaceFace::WatchFaceFace(Controllers::DateTime& dateTimeController,
|
||||||
|
const Controllers::Battery& batteryController,
|
||||||
|
const Controllers::Ble& bleController,
|
||||||
|
Controllers::NotificationManager& notificationManager,
|
||||||
|
Controllers::Settings& settingsController)
|
||||||
|
: currentDateTime {{}},
|
||||||
|
batteryIcon(true),
|
||||||
|
dateTimeController {dateTimeController},
|
||||||
|
batteryController {batteryController},
|
||||||
|
bleController {bleController},
|
||||||
|
notificationManager {notificationManager},
|
||||||
|
settingsController {settingsController} {
|
||||||
|
|
||||||
|
sHour = 99;
|
||||||
|
sMinute = 99;
|
||||||
|
sSecond = 99;
|
||||||
|
|
||||||
|
mouth = lv_arc_create(lv_scr_act(), nullptr);
|
||||||
|
lv_arc_set_angles(mouth, 30, 110);
|
||||||
|
lv_arc_set_bg_angles(mouth, 45, 135);
|
||||||
|
lv_obj_set_size(mouth, 130, 130);
|
||||||
|
lv_obj_align(mouth, nullptr, LV_ALIGN_CENTER, 0, 0);
|
||||||
|
|
||||||
|
leye = lv_arc_create(lv_scr_act(), nullptr);
|
||||||
|
lv_arc_set_angles(leye, 0, 360);
|
||||||
|
lv_obj_set_size(leye, 30, 30);
|
||||||
|
lv_obj_align(leye, nullptr, LV_ALIGN_CENTER, -35, -35);
|
||||||
|
|
||||||
|
reye = lv_arc_create(lv_scr_act(), nullptr);
|
||||||
|
lv_arc_set_angles(reye, 0, 360);
|
||||||
|
lv_obj_set_size(reye, 30, 30);
|
||||||
|
lv_obj_align(reye, nullptr, LV_ALIGN_CENTER, 35, -35);
|
||||||
|
|
||||||
|
|
||||||
|
minor_scales = lv_linemeter_create(lv_scr_act(), nullptr);
|
||||||
|
lv_linemeter_set_scale(minor_scales, 300, 51);
|
||||||
|
lv_linemeter_set_angle_offset(minor_scales, 180);
|
||||||
|
lv_obj_set_size(minor_scales, 240, 240);
|
||||||
|
lv_obj_align(minor_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
|
||||||
|
lv_obj_set_style_local_bg_opa(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
|
||||||
|
lv_obj_set_style_local_scale_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
|
||||||
|
lv_obj_set_style_local_scale_end_line_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 1);
|
||||||
|
lv_obj_set_style_local_scale_end_color(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
|
||||||
|
|
||||||
|
major_scales = lv_linemeter_create(lv_scr_act(), nullptr);
|
||||||
|
lv_linemeter_set_scale(major_scales, 300, 11);
|
||||||
|
lv_linemeter_set_angle_offset(major_scales, 180);
|
||||||
|
lv_obj_set_size(major_scales, 240, 240);
|
||||||
|
lv_obj_align(major_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
|
||||||
|
lv_obj_set_style_local_bg_opa(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
|
||||||
|
lv_obj_set_style_local_scale_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 6);
|
||||||
|
lv_obj_set_style_local_scale_end_line_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
|
||||||
|
lv_obj_set_style_local_scale_end_color(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
|
||||||
|
|
||||||
|
large_scales = lv_linemeter_create(lv_scr_act(), nullptr);
|
||||||
|
lv_linemeter_set_scale(large_scales, 180, 3);
|
||||||
|
lv_linemeter_set_angle_offset(large_scales, 180);
|
||||||
|
lv_obj_set_size(large_scales, 240, 240);
|
||||||
|
lv_obj_align(large_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
|
||||||
|
lv_obj_set_style_local_bg_opa(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
|
||||||
|
lv_obj_set_style_local_scale_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 20);
|
||||||
|
lv_obj_set_style_local_scale_end_line_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
|
||||||
|
lv_obj_set_style_local_scale_end_color(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
|
||||||
|
|
||||||
|
twelve = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_label_set_align(twelve, LV_LABEL_ALIGN_CENTER);
|
||||||
|
lv_label_set_text_static(twelve, "12");
|
||||||
|
lv_obj_set_pos(twelve, 110, 10);
|
||||||
|
lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
|
||||||
|
|
||||||
|
batteryIcon.Create(lv_scr_act());
|
||||||
|
lv_obj_align(batteryIcon.GetObject(), nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0);
|
||||||
|
|
||||||
|
plugIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_label_set_text_static(plugIcon, Symbols::plug);
|
||||||
|
lv_obj_align(plugIcon, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0);
|
||||||
|
|
||||||
|
bleIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_label_set_text_static(bleIcon, "");
|
||||||
|
lv_obj_align(bleIcon, nullptr, LV_ALIGN_IN_TOP_RIGHT, -30, 0);
|
||||||
|
|
||||||
|
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_LIME);
|
||||||
|
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
|
||||||
|
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
|
||||||
|
|
||||||
|
// Date - Day / Week day
|
||||||
|
|
||||||
|
label_date_day = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::orange);
|
||||||
|
lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day());
|
||||||
|
lv_label_set_align(label_date_day, LV_LABEL_ALIGN_CENTER);
|
||||||
|
lv_obj_align(label_date_day, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
|
||||||
|
|
||||||
|
minute_body = lv_line_create(lv_scr_act(), nullptr);
|
||||||
|
minute_body_trace = lv_line_create(lv_scr_act(), nullptr);
|
||||||
|
hour_body = lv_line_create(lv_scr_act(), nullptr);
|
||||||
|
hour_body_trace = lv_line_create(lv_scr_act(), nullptr);
|
||||||
|
second_body = lv_line_create(lv_scr_act(), nullptr);
|
||||||
|
|
||||||
|
lv_style_init(&second_line_style);
|
||||||
|
lv_style_set_line_width(&second_line_style, LV_STATE_DEFAULT, 3);
|
||||||
|
lv_style_set_line_color(&second_line_style, LV_STATE_DEFAULT, LV_COLOR_RED);
|
||||||
|
lv_style_set_line_rounded(&second_line_style, LV_STATE_DEFAULT, true);
|
||||||
|
lv_obj_add_style(second_body, LV_LINE_PART_MAIN, &second_line_style);
|
||||||
|
|
||||||
|
lv_style_init(&minute_line_style);
|
||||||
|
lv_style_set_line_width(&minute_line_style, LV_STATE_DEFAULT, 3);
|
||||||
|
lv_style_set_line_color(&minute_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
|
||||||
|
lv_style_set_line_rounded(&minute_line_style, LV_STATE_DEFAULT, true);
|
||||||
|
lv_obj_add_style(minute_body, LV_LINE_PART_MAIN, &minute_line_style);
|
||||||
|
|
||||||
|
lv_style_init(&minute_line_style_trace);
|
||||||
|
lv_style_set_line_width(&minute_line_style_trace, LV_STATE_DEFAULT, 3);
|
||||||
|
lv_style_set_line_color(&minute_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
|
||||||
|
lv_style_set_line_rounded(&minute_line_style_trace, LV_STATE_DEFAULT, false);
|
||||||
|
lv_obj_add_style(minute_body_trace, LV_LINE_PART_MAIN, &minute_line_style_trace);
|
||||||
|
|
||||||
|
lv_style_init(&hour_line_style);
|
||||||
|
lv_style_set_line_width(&hour_line_style, LV_STATE_DEFAULT, 7);
|
||||||
|
lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
|
||||||
|
lv_style_set_line_rounded(&hour_line_style, LV_STATE_DEFAULT, true);
|
||||||
|
lv_obj_add_style(hour_body, LV_LINE_PART_MAIN, &hour_line_style);
|
||||||
|
|
||||||
|
lv_style_init(&hour_line_style_trace);
|
||||||
|
lv_style_set_line_width(&hour_line_style_trace, LV_STATE_DEFAULT, 3);
|
||||||
|
lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
|
||||||
|
lv_style_set_line_rounded(&hour_line_style_trace, LV_STATE_DEFAULT, false);
|
||||||
|
lv_obj_add_style(hour_body_trace, LV_LINE_PART_MAIN, &hour_line_style_trace);
|
||||||
|
|
||||||
|
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
|
||||||
|
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchFaceFace::~WatchFaceFace() {
|
||||||
|
lv_task_del(taskRefresh);
|
||||||
|
|
||||||
|
lv_style_reset(&hour_line_style);
|
||||||
|
lv_style_reset(&hour_line_style_trace);
|
||||||
|
lv_style_reset(&minute_line_style);
|
||||||
|
lv_style_reset(&minute_line_style_trace);
|
||||||
|
lv_style_reset(&second_line_style);
|
||||||
|
|
||||||
|
lv_obj_clean(lv_scr_act());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFaceFace::UpdateClock() {
|
||||||
|
uint8_t hour = dateTimeController.Hours();
|
||||||
|
uint8_t minute = dateTimeController.Minutes();
|
||||||
|
uint8_t second = dateTimeController.Seconds();
|
||||||
|
|
||||||
|
if (sMinute != minute) {
|
||||||
|
auto const angle = minute * 6;
|
||||||
|
minute_point[0] = CoordinateRelocate(10, angle - 90);
|
||||||
|
minute_point[1] = CoordinateRelocate(MinuteLength, angle);
|
||||||
|
minute_point[2] = CoordinateRelocate(10, angle + 90);
|
||||||
|
|
||||||
|
lv_line_set_points(minute_body, minute_point, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sHour != hour || sMinute != minute) {
|
||||||
|
sHour = hour;
|
||||||
|
sMinute = minute;
|
||||||
|
auto const angle = (hour * 30 + minute / 2);
|
||||||
|
|
||||||
|
hour_point[0] = CoordinateRelocate(90, angle);
|
||||||
|
hour_point[1] = CoordinateRelocate(HourLength, angle);
|
||||||
|
|
||||||
|
lv_line_set_points(hour_body, hour_point, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sSecond != second) {
|
||||||
|
sSecond = second;
|
||||||
|
auto const angle = second * 6;
|
||||||
|
|
||||||
|
second_point[0] = CoordinateRelocate(105, angle);
|
||||||
|
second_point[1] = CoordinateRelocate(SecondLength, angle);
|
||||||
|
lv_line_set_points(second_body, second_point, 2);
|
||||||
|
}//TODO: redo seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFaceFace::SetBatteryIcon() {
|
||||||
|
auto batteryPercent = batteryPercentRemaining.Get();
|
||||||
|
batteryIcon.SetBatteryPercentage(batteryPercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFaceFace::Refresh() {
|
||||||
|
isCharging = batteryController.IsCharging();
|
||||||
|
if (isCharging.IsUpdated()) {
|
||||||
|
if (isCharging.Get()) {
|
||||||
|
lv_obj_set_hidden(batteryIcon.GetObject(), true);
|
||||||
|
lv_obj_set_hidden(plugIcon, false);
|
||||||
|
} else {
|
||||||
|
lv_obj_set_hidden(batteryIcon.GetObject(), false);
|
||||||
|
lv_obj_set_hidden(plugIcon, true);
|
||||||
|
SetBatteryIcon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isCharging.Get()) {
|
||||||
|
batteryPercentRemaining = batteryController.PercentRemaining();
|
||||||
|
if (batteryPercentRemaining.IsUpdated()) {
|
||||||
|
SetBatteryIcon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bleState = bleController.IsConnected();
|
||||||
|
if (bleState.IsUpdated()) {
|
||||||
|
if (bleState.Get()) {
|
||||||
|
lv_label_set_text_static(bleIcon, Symbols::bluetooth);
|
||||||
|
} else {
|
||||||
|
lv_label_set_text_static(bleIcon, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationState = notificationManager.AreNewNotificationsAvailable();
|
||||||
|
|
||||||
|
if (notificationState.IsUpdated()) {
|
||||||
|
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDateTime = dateTimeController.CurrentDateTime();
|
||||||
|
if (currentDateTime.IsUpdated()) {
|
||||||
|
UpdateClock();
|
||||||
|
|
||||||
|
currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
|
||||||
|
if (currentDate.IsUpdated()) {
|
||||||
|
lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
src/displayapp/screens/WatchFaceFace.h
Normal file
95
src/displayapp/screens/WatchFaceFace.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#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/battery/BatteryController.h"
|
||||||
|
#include "components/ble/BleController.h"
|
||||||
|
#include "components/ble/NotificationManager.h"
|
||||||
|
#include "displayapp/screens/BatteryIcon.h"
|
||||||
|
#include "utility/DirtyValue.h"
|
||||||
|
|
||||||
|
namespace Pinetime {
|
||||||
|
namespace Controllers {
|
||||||
|
class Settings;
|
||||||
|
class Battery;
|
||||||
|
class Ble;
|
||||||
|
class NotificationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Applications {
|
||||||
|
namespace Screens {
|
||||||
|
|
||||||
|
class WatchFaceFace : public Screen {
|
||||||
|
public:
|
||||||
|
WatchFaceFace(Controllers::DateTime& dateTimeController,
|
||||||
|
const Controllers::Battery& batteryController,
|
||||||
|
const Controllers::Ble& bleController,
|
||||||
|
Controllers::NotificationManager& notificationManager,
|
||||||
|
Controllers::Settings& settingsController);
|
||||||
|
|
||||||
|
~WatchFaceFace() override;
|
||||||
|
|
||||||
|
void Refresh() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t sHour, sMinute, sSecond;
|
||||||
|
|
||||||
|
Utility::DirtyValue<uint8_t> batteryPercentRemaining {0};
|
||||||
|
Utility::DirtyValue<bool> isCharging {};
|
||||||
|
Utility::DirtyValue<bool> bleState {};
|
||||||
|
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
|
||||||
|
Utility::DirtyValue<bool> notificationState {false};
|
||||||
|
using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
|
||||||
|
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
|
||||||
|
|
||||||
|
lv_obj_t* minor_scales;
|
||||||
|
lv_obj_t* major_scales;
|
||||||
|
lv_obj_t* large_scales;
|
||||||
|
lv_obj_t* twelve;
|
||||||
|
|
||||||
|
lv_obj_t* hour_body;
|
||||||
|
lv_obj_t* hour_body_trace;
|
||||||
|
lv_obj_t* minute_body;
|
||||||
|
lv_obj_t* minute_body_trace;
|
||||||
|
lv_obj_t* second_body;
|
||||||
|
|
||||||
|
lv_point_t hour_point[2];
|
||||||
|
lv_point_t minute_point[3];
|
||||||
|
lv_point_t second_point[2];
|
||||||
|
|
||||||
|
lv_style_t hour_line_style;
|
||||||
|
lv_style_t hour_line_style_trace;
|
||||||
|
lv_style_t minute_line_style;
|
||||||
|
lv_style_t minute_line_style_trace;
|
||||||
|
lv_style_t second_line_style;
|
||||||
|
|
||||||
|
lv_obj_t* label_date_day;
|
||||||
|
lv_obj_t* plugIcon;
|
||||||
|
lv_obj_t* notificationIcon;
|
||||||
|
lv_obj_t* bleIcon;
|
||||||
|
|
||||||
|
lv_obj_t* mouth;
|
||||||
|
lv_obj_t* leye;
|
||||||
|
lv_obj_t* reye;
|
||||||
|
|
||||||
|
|
||||||
|
BatteryIcon batteryIcon;
|
||||||
|
|
||||||
|
const Controllers::DateTime& dateTimeController;
|
||||||
|
const Controllers::Battery& batteryController;
|
||||||
|
const Controllers::Ble& bleController;
|
||||||
|
Controllers::NotificationManager& notificationManager;
|
||||||
|
Controllers::Settings& settingsController;
|
||||||
|
|
||||||
|
void UpdateClock();
|
||||||
|
void SetBatteryIcon();
|
||||||
|
|
||||||
|
lv_task_t* taskRefresh;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@
|
|||||||
#include "displayapp/screens/CheckboxList.h"
|
#include "displayapp/screens/CheckboxList.h"
|
||||||
#include "displayapp/screens/WatchFaceInfineat.h"
|
#include "displayapp/screens/WatchFaceInfineat.h"
|
||||||
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
||||||
|
#include "displayapp/screens/WatchFaceFace.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
|
|
||||||
@ -47,8 +48,9 @@ namespace Pinetime {
|
|||||||
{"Terminal", true},
|
{"Terminal", true},
|
||||||
{"Infineat face", Applications::Screens::WatchFaceInfineat::IsAvailable(filesystem)},
|
{"Infineat face", Applications::Screens::WatchFaceInfineat::IsAvailable(filesystem)},
|
||||||
{"Casio G7710", Applications::Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem)},
|
{"Casio G7710", Applications::Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem)},
|
||||||
{"", false},
|
{"Face face", true},
|
||||||
{"", false}}};
|
{"", false}
|
||||||
|
}};
|
||||||
ScreenList<nScreens> screens;
|
ScreenList<nScreens> screens;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
|
|||||||
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
|
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
|
||||||
message(STATUS "Using ${LV_FONT_CONV} to generate font files")
|
message(STATUS "Using ${LV_FONT_CONV} to generate font files")
|
||||||
|
|
||||||
find_program(LV_IMG_CONV "lv_img_conv" NO_CACHE REQUIRED
|
find_program(LV_IMG_CONV "lv_img_conv.py" NO_CACHE REQUIRED
|
||||||
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
|
HINTS "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
message(STATUS "Using ${LV_IMG_CONV} to generate font files")
|
message(STATUS "Using ${LV_IMG_CONV} to generate font files")
|
||||||
|
|
||||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
||||||
|
@ -11,6 +11,9 @@ import subprocess
|
|||||||
|
|
||||||
def gen_lvconv_line(lv_img_conv: str, dest: str, color_format: str, output_format: str, binary_format: str, sources: str):
|
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]
|
args = [lv_img_conv, sources, '--force', '--output-file', dest, '--color-format', color_format, '--output-format', output_format, '--binary-format', binary_format]
|
||||||
|
if lv_img_conv.endswith(".py"):
|
||||||
|
# lv_img_conv is a python script, call with current python executable
|
||||||
|
args = [sys.executable] + args
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
193
src/resources/lv_img_conv.py
Executable file
193
src/resources/lv_img_conv.py
Executable file
@ -0,0 +1,193 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
import decimal
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
def classify_pixel(value, bits):
|
||||||
|
def round_half_up(v):
|
||||||
|
"""python3 implements "propper" "banker's rounding" by rounding to the nearest
|
||||||
|
even number. Javascript rounds to the nearest integer.
|
||||||
|
To have the same output as the original JavaScript implementation add a custom
|
||||||
|
rounding function, which does "school" rounding (to the nearest integer).
|
||||||
|
|
||||||
|
see: https://stackoverflow.com/questions/43851273/how-to-round-float-0-5-up-to-1-0-while-still-rounding-0-45-to-0-0-as-the-usual
|
||||||
|
"""
|
||||||
|
return int(decimal.Decimal(v).quantize(decimal.Decimal('1'), rounding=decimal.ROUND_HALF_UP))
|
||||||
|
tmp = 1 << (8 - bits)
|
||||||
|
val = round_half_up(value / tmp) * tmp
|
||||||
|
if val < 0:
|
||||||
|
val = 0
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def test_classify_pixel():
|
||||||
|
# test difference between round() and round_half_up()
|
||||||
|
assert classify_pixel(18, 5) == 16
|
||||||
|
# school rounding 4.5 to 5, but banker's rounding 4.5 to 4
|
||||||
|
assert classify_pixel(18, 6) == 20
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument("img",
|
||||||
|
help="Path to image to convert to C header file")
|
||||||
|
parser.add_argument("-o", "--output-file",
|
||||||
|
help="output file path (for single-image conversion)",
|
||||||
|
required=True)
|
||||||
|
parser.add_argument("-f", "--force",
|
||||||
|
help="allow overwriting the output file",
|
||||||
|
action="store_true")
|
||||||
|
parser.add_argument("-i", "--image-name",
|
||||||
|
help="name of image structure (not implemented)")
|
||||||
|
parser.add_argument("-c", "--color-format",
|
||||||
|
help="color format of image",
|
||||||
|
default="CF_TRUE_COLOR_ALPHA",
|
||||||
|
choices=[
|
||||||
|
"CF_ALPHA_1_BIT", "CF_ALPHA_2_BIT", "CF_ALPHA_4_BIT",
|
||||||
|
"CF_ALPHA_8_BIT", "CF_INDEXED_1_BIT", "CF_INDEXED_2_BIT", "CF_INDEXED_4_BIT",
|
||||||
|
"CF_INDEXED_8_BIT", "CF_RAW", "CF_RAW_CHROMA", "CF_RAW_ALPHA",
|
||||||
|
"CF_TRUE_COLOR", "CF_TRUE_COLOR_ALPHA", "CF_TRUE_COLOR_CHROMA", "CF_RGB565A8",
|
||||||
|
],
|
||||||
|
required=True)
|
||||||
|
parser.add_argument("-t", "--output-format",
|
||||||
|
help="output format of image",
|
||||||
|
default="bin", # default in original is 'c'
|
||||||
|
choices=["c", "bin"])
|
||||||
|
parser.add_argument("--binary-format",
|
||||||
|
help="binary color format (needed if output-format is binary)",
|
||||||
|
default="ARGB8565_RBSWAP",
|
||||||
|
choices=["ARGB8332", "ARGB8565", "ARGB8565_RBSWAP", "ARGB8888"])
|
||||||
|
parser.add_argument("-s", "--swap-endian",
|
||||||
|
help="swap endian of image (not implemented)",
|
||||||
|
action="store_true")
|
||||||
|
parser.add_argument("-d", "--dither",
|
||||||
|
help="enable dither (not implemented)",
|
||||||
|
action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
img_path = pathlib.Path(args.img)
|
||||||
|
out = pathlib.Path(args.output_file)
|
||||||
|
if not img_path.is_file():
|
||||||
|
print(f"Input file is missing: '{args.img}'")
|
||||||
|
return 1
|
||||||
|
print(f"Beginning conversion of {args.img}")
|
||||||
|
if out.exists():
|
||||||
|
if args.force:
|
||||||
|
print(f"overwriting {args.output_file}")
|
||||||
|
else:
|
||||||
|
pritn(f"Error: refusing to overwrite {args.output_file} without -f specified.")
|
||||||
|
return 1
|
||||||
|
out.touch()
|
||||||
|
|
||||||
|
# only implemented the bare minimum, everything else is not implemented
|
||||||
|
if args.color_format not in ["CF_INDEXED_1_BIT", "CF_TRUE_COLOR_ALPHA"]:
|
||||||
|
raise NotImplementedError(f"argument --color-format '{args.color_format}' not implemented")
|
||||||
|
if args.output_format != "bin":
|
||||||
|
raise NotImplementedError(f"argument --output-format '{args.output_format}' not implemented")
|
||||||
|
if args.binary_format not in ["ARGB8565_RBSWAP", "ARGB8888"]:
|
||||||
|
raise NotImplementedError(f"argument --binary-format '{args.binary_format}' not implemented")
|
||||||
|
if args.image_name:
|
||||||
|
raise NotImplementedError(f"argument --image-name not implemented")
|
||||||
|
if args.swap_endian:
|
||||||
|
raise NotImplementedError(f"argument --swap-endian not implemented")
|
||||||
|
if args.dither:
|
||||||
|
raise NotImplementedError(f"argument --dither not implemented")
|
||||||
|
|
||||||
|
# open image using Pillow
|
||||||
|
img = Image.open(img_path)
|
||||||
|
img_height = img.height
|
||||||
|
img_width = img.width
|
||||||
|
if args.color_format == "CF_TRUE_COLOR_ALPHA" and args.binary_format == "ARGB8888":
|
||||||
|
buf = bytearray(img_height*img_width*4) # 4 bytes (32 bit) per pixel
|
||||||
|
for y in range(img_height):
|
||||||
|
for x in range(img_width):
|
||||||
|
i = (y*img_width + x)*4 # buffer-index
|
||||||
|
pixel = img.getpixel((x,y))
|
||||||
|
r, g, b, a = pixel
|
||||||
|
buf[i + 0] = r
|
||||||
|
buf[i + 1] = g
|
||||||
|
buf[i + 2] = b
|
||||||
|
buf[i + 3] = a
|
||||||
|
|
||||||
|
elif args.color_format == "CF_TRUE_COLOR_ALPHA" and args.binary_format == "ARGB8565_RBSWAP":
|
||||||
|
buf = bytearray(img_height*img_width*3) # 3 bytes (24 bit) per pixel
|
||||||
|
for y in range(img_height):
|
||||||
|
for x in range(img_width):
|
||||||
|
i = (y*img_width + x)*3 # buffer-index
|
||||||
|
pixel = img.getpixel((x,y))
|
||||||
|
r_act = classify_pixel(pixel[0], 5)
|
||||||
|
g_act = classify_pixel(pixel[1], 6)
|
||||||
|
b_act = classify_pixel(pixel[2], 5)
|
||||||
|
a = pixel[3]
|
||||||
|
r_act = min(r_act, 0xF8)
|
||||||
|
g_act = min(g_act, 0xFC)
|
||||||
|
b_act = min(b_act, 0xF8)
|
||||||
|
c16 = ((r_act) << 8) | ((g_act) << 3) | ((b_act) >> 3) # RGR565
|
||||||
|
buf[i + 0] = (c16 >> 8) & 0xFF
|
||||||
|
buf[i + 1] = c16 & 0xFF
|
||||||
|
buf[i + 2] = a
|
||||||
|
|
||||||
|
elif args.color_format == "CF_INDEXED_1_BIT": # ignore binary format, use color format as binary format
|
||||||
|
w = img_width >> 3
|
||||||
|
if img_width & 0x07:
|
||||||
|
w+=1
|
||||||
|
max_p = w * (img_height-1) + ((img_width-1) >> 3) + 8 # +8 for the palette
|
||||||
|
buf = bytearray(max_p+1)
|
||||||
|
|
||||||
|
for y in range(img_height):
|
||||||
|
for x in range(img_width):
|
||||||
|
c, a = img.getpixel((x,y))
|
||||||
|
p = w * y + (x >> 3) + 8 # +8 for the palette
|
||||||
|
buf[p] |= (c & 0x1) << (7 - (x & 0x7))
|
||||||
|
# write palette information, for indexed-1-bit we need palette with two values
|
||||||
|
# write 8 palette bytes
|
||||||
|
buf[0] = 0
|
||||||
|
buf[1] = 0
|
||||||
|
buf[2] = 0
|
||||||
|
buf[3] = 0
|
||||||
|
# Normally there is much math behind this, but for the current use case this is close enough
|
||||||
|
# only needs to be more complicated if we have more than 2 colors in the palette
|
||||||
|
buf[4] = 255
|
||||||
|
buf[5] = 255
|
||||||
|
buf[6] = 255
|
||||||
|
buf[7] = 255
|
||||||
|
else:
|
||||||
|
# raise just to be sure
|
||||||
|
raise NotImplementedError(f"args.color_format '{args.color_format}' with args.binary_format '{args.binary_format}' not implemented")
|
||||||
|
|
||||||
|
# write header
|
||||||
|
match args.color_format:
|
||||||
|
case "CF_TRUE_COLOR_ALPHA":
|
||||||
|
lv_cf = 5
|
||||||
|
case "CF_INDEXED_1_BIT":
|
||||||
|
lv_cf = 7
|
||||||
|
case _:
|
||||||
|
# raise just to be sure
|
||||||
|
raise NotImplementedError(f"args.color_format '{args.color_format}' not implemented")
|
||||||
|
header_32bit = lv_cf | (img_width << 10) | (img_height << 21)
|
||||||
|
buf_out = bytearray(4 + len(buf))
|
||||||
|
buf_out[0] = header_32bit & 0xFF
|
||||||
|
buf_out[1] = (header_32bit & 0xFF00) >> 8
|
||||||
|
buf_out[2] = (header_32bit & 0xFF0000) >> 16
|
||||||
|
buf_out[3] = (header_32bit & 0xFF000000) >> 24
|
||||||
|
buf_out[4:] = buf
|
||||||
|
|
||||||
|
# write byte buffer to file
|
||||||
|
with open(out, "wb") as f:
|
||||||
|
f.write(buf_out)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if "--test" in sys.argv:
|
||||||
|
# run small set of tests and exit
|
||||||
|
print("running tests")
|
||||||
|
test_classify_pixel()
|
||||||
|
print("success!")
|
||||||
|
sys.exit(0)
|
||||||
|
# run normal program
|
||||||
|
sys.exit(main())
|
Loading…
Reference in New Issue
Block a user