From 4b854a5d09544b7ad62e29a2b925d67877dffe50 Mon Sep 17 00:00:00 2001 From: Ryan Rix Date: Fri, 7 Jun 2024 16:46:06 -0700 Subject: [PATCH] WIP fuzzy --- shell.nix | 54 +++++++ src/CMakeLists.txt | 1 + src/components/settings/Settings.h | 2 +- src/displayapp/UserApps.h | 1 + src/displayapp/apps/Apps.h.in | 1 + src/displayapp/screens/WatchFaceFuzzy.cpp | 182 ++++++++++++++++++++++ src/displayapp/screens/WatchFaceFuzzy.h | 105 +++++++++++++ 7 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 shell.nix create mode 100644 src/displayapp/screens/WatchFaceFuzzy.cpp create mode 100644 src/displayapp/screens/WatchFaceFuzzy.h diff --git a/shell.nix b/shell.nix new file mode 100644 index 00000000..45b92c8c --- /dev/null +++ b/shell.nix @@ -0,0 +1,54 @@ +{ pkgs ? import {} }: + +with pkgs; let + py4McuBoot = python3.withPackages (ps: with ps; [ + cbor + intelhex + click + cryptography + pillow + ]); + lv_img_convWrapper = pkgs.writeScriptBin "lv_img_conv" '' + npm install lv_img_conv + nodejs node_modules/lv_img_conv/lv_img_conv.js + ''; + buildInfinitime = pkgs.writeScriptBin "build-infinitime" '' + mkdir build + cd build + cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=$ARM_NONE_EABI_TOOLCHAIN_PATH \ + -DNRF5_SDK_PATH=$NRF5_SDK_PATH \ + -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \ + -DBUILD_DFU=$BUILD_DFU \ + -DBUILD_RESOURCES=$BUILD_RESOURCES \ + -DTARGET_DEVICE=$TARGET_DEVICE \ + -DENABLE_WATCHFACES="WatchFace::Analog,WatchFace::Digital,WatchFace::Fuzzy" \ + .. + + cd .. + cmake --build build -j6 + ''; +in mkShell { + packages = [ + gcc-arm-embedded-10 + nrf5-sdk + cmake + nodePackages.lv_font_conv + lv_img_convWrapper + # lv_img_conv + nodejs + py4McuBoot + clang-tools + SDL2 + libpng + adafruit-nrfutil + buildInfinitime + # watchmate # wish this worked -- use flatpak run io.gitlab.azymohliad.WatchMate + ]; + + ARM_NONE_EABI_TOOLCHAIN_PATH="${gcc-arm-embedded-10}"; + NRF5_SDK_PATH="${nrf5-sdk}/share/nRF5_SDK"; + CMAKE_BUILD_TYPE="Release"; + BUILD_DFU=1; + BUILD_RESOURCES=1; + TARGET_DEVICE="PINETIME"; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd8ece62..fd489879 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -426,6 +426,7 @@ list(APPEND SOURCE_FILES displayapp/screens/WatchFaceTerminal.cpp displayapp/screens/WatchFacePineTimeStyle.cpp displayapp/screens/WatchFaceCasioStyleG7710.cpp + displayapp/screens/WatchFaceFuzzy.cpp ## diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 06312077..e3be5256 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -9,7 +9,7 @@ namespace Pinetime { namespace Controllers { class Settings { public: - enum class ClockType : uint8_t { H24, H12 }; + enum class ClockType : uint8_t { H24, H12, FUZZY }; enum class WeatherFormat : uint8_t { Metric, Imperial }; enum class Notification : uint8_t { On, Off, Sleep }; enum class ChimesOption : uint8_t { None, Hours, HalfHours }; diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index 67bbfa7d..d5a176d9 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -9,6 +9,7 @@ #include "displayapp/screens/Tile.h" #include "displayapp/screens/ApplicationList.h" #include "displayapp/screens/WatchFaceDigital.h" +#include "displayapp/screens/WatchFaceFuzzy.h" #include "displayapp/screens/WatchFaceAnalog.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" #include "displayapp/screens/WatchFaceInfineat.h" diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 2104a267..7d809712 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -50,6 +50,7 @@ namespace Pinetime { Analog, PineTimeStyle, Terminal, + Fuzzy, Infineat, CasioStyleG7710, }; diff --git a/src/displayapp/screens/WatchFaceFuzzy.cpp b/src/displayapp/screens/WatchFaceFuzzy.cpp new file mode 100644 index 00000000..92913bbd --- /dev/null +++ b/src/displayapp/screens/WatchFaceFuzzy.cpp @@ -0,0 +1,182 @@ +#include "displayapp/screens/WatchFaceFuzzy.h" + +#include +#include +#include "displayapp/screens/NotificationIcon.h" +#include "displayapp/screens/Symbols.h" +#include "displayapp/screens/WeatherSymbols.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/ble/SimpleWeatherService.h" +#include "components/settings/Settings.h" + +using namespace Pinetime::Applications::Screens; + +WatchFaceFuzzy::WatchFaceFuzzy(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::HeartRateController& heartRateController, + Controllers::MotionController& motionController, + Controllers::SimpleWeatherService& weatherService) + : currentDateTime {{}}, + dateTimeController {dateTimeController}, + notificationManager {notificationManager}, + settingsController {settingsController}, + heartRateController {heartRateController}, + motionController {motionController}, + weatherService {weatherService}, + statusIcons(batteryController, bleController) { + + statusIcons.Create(); + + 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); + + weatherIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); + lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); + lv_label_set_text(weatherIcon, ""); + lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, -20, 50); + lv_obj_set_auto_realign(weatherIcon, true); + + temperature = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); + lv_label_set_text(temperature, ""); + lv_obj_align(temperature, nullptr, LV_ALIGN_IN_TOP_MID, 20, 50); + + label_date = lv_label_create(lv_scr_act(), nullptr); + lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60); + lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); + + label_time = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed); + + lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0); + + heartbeatIcon = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat); + lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B)); + lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); + + heartbeatValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B)); + 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, lv_color_hex(0x00FFE7)); + lv_label_set_text_static(stepValue, "0"); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); + + stepIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7)); + lv_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(); +} + +WatchFaceFuzzy::~WatchFaceFuzzy() { + lv_task_del(taskRefresh); + lv_obj_clean(lv_scr_act()); +} + +void WatchFaceFuzzy::Refresh() { + statusIcons.Update(); + + notificationState = notificationManager.AreNewNotificationsAvailable(); + if (notificationState.IsUpdated()) { + lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get())); + } + + currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); + + if (currentDateTime.IsUpdated()) { + uint8_t hour = dateTimeController.Hours(); + uint8_t minute = dateTimeController.Minutes(); + + printTimeWords(hour, minute); + } + + 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, lv_color_hex(0xCE1B1B)); + 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(); + if (stepCount.IsUpdated()) { + lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); + lv_obj_realign(stepValue); + lv_obj_realign(stepIcon); + } + + currentWeather = weatherService.Current(); + if (currentWeather.IsUpdated()) { + auto optCurrentWeather = currentWeather.Get(); + if (optCurrentWeather) { + int16_t temp = optCurrentWeather->temperature; + char tempUnit = 'C'; + if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { + temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp); + tempUnit = 'F'; + } + temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0); + lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); + lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId)); + } else { + lv_label_set_text_static(temperature, ""); + lv_label_set_text(weatherIcon, ""); + } + lv_obj_realign(temperature); + lv_obj_realign(weatherIcon); + } +} + +char const* WatchFaceFuzzy::mods[] = {"", "five", "ten", "quarter", "twenty", "twenty five", "half"}; +char const* WatchFaceFuzzy::nums[] = {"twelve", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"}; + +void WatchFaceFuzzy::printTimeWords(int h, int m) { + const char* mod; + + if (m <= 30) { + mod = mods[m / 5]; + } else { + mod = mods[(60-m) / 5]; + } + h = (h % 12); + + if (m >= 57) { + sprintf(timeStr, "#ffffff nearly %s#\n#808080 o' clock#", nums[(h+1) % 12]); + } + else if (m == 0 || m <= 4) { + sprintf(timeStr, "#ffffff %s#\n#808080 o' clock#", nums[h]); + } + + else if (m <= 32) { + sprintf(timeStr, "#ffffff %s#\n#808080 past# #FFFFFF %s#", mod, nums[h]); + } + + else if (m > 32) { + sprintf(timeStr, "#ffffff %s#\n#808080 to# #FFFFFF %s#", mod, nums[(h+1) % 12]); + } + + lv_label_set_text(label_time, timeStr); +} diff --git a/src/displayapp/screens/WatchFaceFuzzy.h b/src/displayapp/screens/WatchFaceFuzzy.h new file mode 100644 index 00000000..a7ae5c94 --- /dev/null +++ b/src/displayapp/screens/WatchFaceFuzzy.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include +#include "displayapp/screens/Screen.h" +#include "components/datetime/DateTimeController.h" +#include "components/ble/SimpleWeatherService.h" +#include "components/ble/BleController.h" +#include "displayapp/widgets/StatusIcons.h" +#include "utility/DirtyValue.h" +#include "displayapp/apps/Apps.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class Battery; + class Ble; + class NotificationManager; + class HeartRateController; + class MotionController; + } + + namespace Applications { + namespace Screens { + + class WatchFaceFuzzy : public Screen { + public: + WatchFaceFuzzy(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::HeartRateController& heartRateController, + Controllers::MotionController& motionController, + Controllers::SimpleWeatherService& weather); + ~WatchFaceFuzzy() override; + + void Refresh() override; + + private: + uint8_t displayedHour = -1; + uint8_t displayedMinute = -1; + + Utility::DirtyValue> currentDateTime {}; + Utility::DirtyValue stepCount {}; + Utility::DirtyValue heartbeat {}; + Utility::DirtyValue heartbeatRunning {}; + Utility::DirtyValue notificationState {}; + Utility::DirtyValue> currentWeather {}; + + Utility::DirtyValue> currentDate; + + lv_obj_t* label_time; + lv_obj_t* label_time_ampm; + lv_obj_t* label_date; + lv_obj_t* heartbeatIcon; + lv_obj_t* heartbeatValue; + lv_obj_t* stepIcon; + lv_obj_t* stepValue; + lv_obj_t* notificationIcon; + lv_obj_t* weatherIcon; + lv_obj_t* temperature; + + Controllers::DateTime& dateTimeController; + Controllers::NotificationManager& notificationManager; + Controllers::Settings& settingsController; + Controllers::HeartRateController& heartRateController; + Controllers::MotionController& motionController; + Controllers::SimpleWeatherService& weatherService; + + lv_task_t* taskRefresh; + Widgets::StatusIcons statusIcons; + + + static char const *nums[]; + static char const *mods[]; + char timeStr[64]; + void printTimeWords(int hour, int minute); + }; + } + + template <> + struct WatchFaceTraits { + static constexpr WatchFace watchFace = WatchFace::Fuzzy; + static constexpr const char* name = "Fuzzy face"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceFuzzy(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.notificationManager, + controllers.settingsController, + controllers.heartRateController, + controllers.motionController, + *controllers.weatherController); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + } + }; + } +}