diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e59c0d814b..5d1d7c39c8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -431,6 +431,7 @@ list(APPEND SOURCE_FILES displayapp/screens/WatchFaceTerminal.cpp displayapp/screens/WatchFacePineTimeStyle.cpp displayapp/screens/WatchFaceCasioStyleG7710.cpp + displayapp/screens/WatchFaceDigistyle.cpp ## diff --git a/src/displayapp/screens/Clock.cpp b/src/displayapp/screens/Clock.cpp index 07d307e0f2..151fe2966e 100644 --- a/src/displayapp/screens/Clock.cpp +++ b/src/displayapp/screens/Clock.cpp @@ -14,6 +14,7 @@ #include "displayapp/screens/WatchFaceAnalog.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" +#include "displayapp/screens/WatchFaceDigistyle.h" using namespace Pinetime::Applications::Screens; @@ -55,6 +56,9 @@ Clock::Clock(DisplayApp* app, case 5: return WatchFaceCasioStyleG7710(); break; + case 6: + return WatchFaceDigistyle(); + break; } return WatchFaceDigitalScreen(); }()} { @@ -136,3 +140,15 @@ std::unique_ptr Clock::WatchFaceCasioStyleG7710() { motionController, filesystem); } + +std::unique_ptr Clock::WatchFaceDigistyle() { + return std::make_unique(app, + dateTimeController, + batteryController, + bleController, + notificationManager, + settingsController, + heartRateController, + motionController, + filesystem); +} diff --git a/src/displayapp/screens/Clock.h b/src/displayapp/screens/Clock.h index 613fef57ea..c27eda9747 100644 --- a/src/displayapp/screens/Clock.h +++ b/src/displayapp/screens/Clock.h @@ -52,6 +52,7 @@ namespace Pinetime { std::unique_ptr WatchFaceTerminalScreen(); std::unique_ptr WatchFaceInfineatScreen(); std::unique_ptr WatchFaceCasioStyleG7710(); + std::unique_ptr WatchFaceDigistyle(); }; } } diff --git a/src/displayapp/screens/WatchFaceDigistyle.cpp b/src/displayapp/screens/WatchFaceDigistyle.cpp new file mode 100644 index 0000000000..d955fd3651 --- /dev/null +++ b/src/displayapp/screens/WatchFaceDigistyle.cpp @@ -0,0 +1,249 @@ +#include "displayapp/screens/WatchFaceDigistyle.h" + +#include +#include +#include +#include "displayapp/screens/BatteryIcon.h" +#include "displayapp/screens/BleIcon.h" +#include "displayapp/screens/NotificationIcon.h" +#include "displayapp/screens/Symbols.h" +#include "components/battery/BatteryController.h" +#include "components/ble/BleController.h" +#include "components/ble/NotificationManager.h" +#include "components/heartrate/HeartRateController.h" +#include "components/motion/MotionController.h" +#include "components/settings/Settings.h" + +using namespace Pinetime::Applications::Screens; + +WatchFaceDigistyle::WatchFaceDigistyle(DisplayApp* app, + Controllers::DateTime& dateTimeController, + Controllers::Battery& batteryController, + Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::HeartRateController& heartRateController, + Controllers::MotionController& motionController, + Controllers::FS& filesystem) + : Screen(app), + currentDateTime {{}}, + dateTimeController {dateTimeController}, + batteryController {batteryController}, + bleController {bleController}, + notificationManager {notificationManager}, + settingsController {settingsController}, + heartRateController {heartRateController}, + motionController {motionController} { + + lfs_file f = {}; + if (filesystem.FileOpen(&f, "/fonts/seven_segment_100.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_seeven_segment = lv_font_load("F:/fonts/seven_segment_100.bin"); + } + + img_background = lv_img_create(lv_scr_act(), nullptr); + lv_img_set_src(img_background, "F:/images/digistyle.bin"); + lv_obj_align(img_background, nullptr, LV_ALIGN_CENTER, 0, 0); + + batteryIcon.Create(lv_scr_act()); + batteryIcon.SetColor(LV_COLOR_BLACK); + lv_obj_align(batteryIcon.GetObject(), lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -15, 32); + + batteryPlug = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(batteryPlug, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + 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, LV_COLOR_BLACK); + 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, LV_COLOR_BLACK); + lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false)); + lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 15, 32); + + 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_BLACK); + + 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, LV_COLOR_BLACK); + if (font_seeven_segment != nullptr) { + lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_seeven_segment); + } else { + 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_CENTER, 0, 0); + + 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, LV_COLOR_BLACK); + lv_label_set_text_static(label_time_ampm, ""); + lv_obj_align(label_time_ampm, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -30, -55); + + 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_GRAY); + 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, LV_COLOR_GRAY); + 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_GRAY); + 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, LV_COLOR_GRAY); + 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(); +} + +WatchFaceDigistyle::~WatchFaceDigistyle() { + lv_task_del(taskRefresh); + + if (font_seeven_segment != nullptr) { + lv_font_free(font_seeven_segment); + } + + lv_obj_clean(lv_scr_act()); +} + +void WatchFaceDigistyle::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); + } + + bleState = bleController.IsConnected(); + bleRadioEnabled = bleController.IsRadioEnabled(); + if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) { + lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); + } + lv_obj_realign(batteryPlug); + lv_obj_realign(bleIcon); + + notificationState = notificationManager.AreNewNotificationsAvailable(); + if (notificationState.IsUpdated()) { + lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get())); + } + + currentDateTime = dateTimeController.CurrentDateTime(); + + if (currentDateTime.IsUpdated()) { + auto newDateTime = currentDateTime.Get(); + + auto dp = date::floor(newDateTime); + auto time = date::make_time(newDateTime - dp); + auto yearMonthDay = date::year_month_day(dp); + + auto year = static_cast(yearMonthDay.year()); + auto month = static_cast(static_cast(yearMonthDay.month())); + auto day = static_cast(yearMonthDay.day()); + auto dayOfWeek = static_cast(date::weekday(yearMonthDay).iso_encoding()); + + uint8_t hour = time.hours().count(); + uint8_t minute = time.minutes().count(); + + if (displayedHour != hour || displayedMinute != minute) { + displayedHour = hour; + displayedMinute = minute; + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[3] = "AM"; + 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, 0); + } else { + lv_label_set_text_fmt(label_time, "%02d:%02d", hour, minute); + lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + } + } + + if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) { + lv_label_set_text_fmt(label_date, + "%s %d %s %d", + dateTimeController.DayOfWeekShortToString(), + day, + dateTimeController.MonthShortToString(), + year); + } else { + lv_label_set_text_fmt(label_date, + "%s %s %d %d", + dateTimeController.DayOfWeekShortToString(), + dateTimeController.MonthShortToString(), + day, + year); + } + 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, LV_COLOR_GRAY); + 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_BLACK); + lv_label_set_text_static(heartbeatValue, ""); + } + + lv_obj_realign(heartbeatIcon); + lv_obj_realign(heartbeatValue); + } + + stepCount = motionController.NbSteps(); + motionSensorOk = motionController.IsSensorOk(); + if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { + lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); + lv_obj_realign(stepValue); + lv_obj_realign(stepIcon); + } +} + +bool WatchFaceDigistyle::IsAvailable(Pinetime::Controllers::FS& filesystem) { + lfs_file file = {}; + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/fonts/seven_segment_100.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/images/digistyle.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + return true; +} diff --git a/src/displayapp/screens/WatchFaceDigistyle.h b/src/displayapp/screens/WatchFaceDigistyle.h new file mode 100644 index 0000000000..422717d16c --- /dev/null +++ b/src/displayapp/screens/WatchFaceDigistyle.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include +#include "displayapp/screens/Screen.h" +#include "components/datetime/DateTimeController.h" +#include "components/ble/BleController.h" +#include "displayapp/widgets/StatusIcons.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class Battery; + class Ble; + class NotificationManager; + class HeartRateController; + class MotionController; + } + + namespace Applications { + namespace Screens { + + class WatchFaceDigistyle : public Screen { + public: + WatchFaceDigistyle(DisplayApp* app, + Controllers::DateTime& dateTimeController, + Controllers::Battery& batteryController, + Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::HeartRateController& heartRateController, + Controllers::MotionController& motionController, + Controllers::FS& fs); + ~WatchFaceDigistyle() override; + + void Refresh() override; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem); + + private: + uint8_t displayedHour = -1; + uint8_t displayedMinute = -1; + + uint16_t currentYear = 1970; + Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown; + Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown; + uint8_t currentDay = 0; + + DirtyValue batteryPercentRemaining {}; + DirtyValue powerPresent {}; + DirtyValue bleState {}; + DirtyValue bleRadioEnabled {}; + DirtyValue> currentDateTime {}; + DirtyValue motionSensorOk {}; + DirtyValue stepCount {}; + DirtyValue heartbeat {}; + DirtyValue heartbeatRunning {}; + DirtyValue notificationState {}; + + lv_obj_t* img_background; + lv_font_t* font_seeven_segment = nullptr; + + lv_obj_t* label_time; + lv_obj_t* label_time_ampm; + lv_obj_t* label_date; + lv_obj_t* bleIcon; + lv_obj_t* batteryPlug; + lv_obj_t* heartbeatIcon; + lv_obj_t* heartbeatValue; + lv_obj_t* stepIcon; + lv_obj_t* stepValue; + lv_obj_t* notificationIcon; + + BatteryIcon batteryIcon; + + Controllers::DateTime& dateTimeController; + Controllers::Battery& batteryController; + Controllers::Ble& bleController; + Controllers::NotificationManager& notificationManager; + Controllers::Settings& settingsController; + Controllers::HeartRateController& heartRateController; + Controllers::MotionController& motionController; + + lv_task_t* taskRefresh; + }; + } + } +} diff --git a/src/displayapp/screens/settings/SettingWatchFace.cpp b/src/displayapp/screens/settings/SettingWatchFace.cpp index 217f97b8ea..23a841320e 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.cpp +++ b/src/displayapp/screens/settings/SettingWatchFace.cpp @@ -6,6 +6,7 @@ #include "components/settings/Settings.h" #include "displayapp/screens/WatchFaceInfineat.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" +#include "displayapp/screens/WatchFaceDigistyle.h" using namespace Pinetime::Applications::Screens; @@ -58,7 +59,7 @@ std::unique_ptr SettingWatchFace::CreateScreen2() { std::array watchfaces { {{"Infineat face", Applications::Screens::WatchFaceInfineat::IsAvailable(filesystem)}, {"Casio G7710", Applications::Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem)}, - {"", false}, + {"Digistyle", Applications::Screens::WatchFaceDigistyle::IsAvailable(filesystem)}, {"", false}}}; return std::make_unique( 1, diff --git a/src/resources/fonts.json b/src/resources/fonts.json index a270e6a291..f269d56172 100644 --- a/src/resources/fonts.json +++ b/src/resources/fonts.json @@ -58,5 +58,17 @@ "size": 115, "format": "bin", "target_path": "/fonts/" + }, + "seven_segment_100" : { + "sources": [ + { + "file": "fonts/Seven Segment.ttf", + "symbols": "0123456789: -" + } + ], + "bpp": 1, + "size": 100, + "format": "bin", + "target_path": "/fonts/" } } diff --git a/src/resources/fonts/Seven Segment.ttf b/src/resources/fonts/Seven Segment.ttf new file mode 100644 index 0000000000..214053f88d Binary files /dev/null and b/src/resources/fonts/Seven Segment.ttf differ diff --git a/src/resources/images.json b/src/resources/images.json index db2ccab088..86887b3053 100644 --- a/src/resources/images.json +++ b/src/resources/images.json @@ -5,5 +5,12 @@ "output_format": "bin", "binary_format": "ARGB8565_RBSWAP", "target_path": "/images/" + }, + "digistyle" : { + "sources": "images/digistyle.png", + "color_format": "CF_INDEXED_1_BIT", + "output_format": "bin", + "binary_format": "ARGB8565_RBSWAP", + "target_path": "/images/" } } diff --git a/src/resources/images/digistyle.png b/src/resources/images/digistyle.png new file mode 100644 index 0000000000..cbf9afef8c Binary files /dev/null and b/src/resources/images/digistyle.png differ