From 6a1f9cdfc07f465445ef862674fabaa15d807035 Mon Sep 17 00:00:00 2001 From: Davis Mosenkovs Date: Sat, 18 Jul 2026 21:40:11 +0300 Subject: [PATCH] Add Legend watch face An elegant and very practical digital watch face. Uses only fonts already in InfiniTime and no external resources. Has the standard functionality: * shows time, date (without year) and day of week * supports 24 and 12 hour time formats * BLE, new notification and alarm clock icons * battery percentage (green when charging) * steps and heart rate * temperature and weather icon Additionally: * visual indication (amber dot) for any notification(s) * visual indication (red or blue line) when notifications are muted * battery percentage turns red when 20% or less * stopwatch and timer icons (when active) * colorful sun, snow and thunderstorm weather icons * generally uses brighter colors for more urgent things --- src/CMakeLists.txt | 1 + src/displayapp/UserApps.h | 1 + src/displayapp/apps/Apps.h.in | 1 + src/displayapp/apps/CMakeLists.txt | 1 + src/displayapp/screens/WatchFaceLegend.cpp | 334 +++++++++++++++++++++ src/displayapp/screens/WatchFaceLegend.h | 145 +++++++++ 6 files changed, 483 insertions(+) create mode 100644 src/displayapp/screens/WatchFaceLegend.cpp create mode 100644 src/displayapp/screens/WatchFaceLegend.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e4a354df64..5e512ca352 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -430,6 +430,7 @@ list(APPEND SOURCE_FILES displayapp/screens/WatchFacePineTimeStyle.cpp displayapp/screens/WatchFaceCasioStyleG7710.cpp displayapp/screens/WatchFacePrideFlag.cpp + displayapp/screens/WatchFaceLegend.cpp ## diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index 25926edc40..7837f641e6 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -15,6 +15,7 @@ #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" #include "displayapp/screens/WatchFacePrideFlag.h" +#include "displayapp/screens/WatchFaceLegend.h" namespace Pinetime { namespace Applications { diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index d440b598d1..3a4fac8ea0 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -56,6 +56,7 @@ namespace Pinetime { Infineat, CasioStyleG7710, PrideFlag, + Legend, }; template diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index 93196ed6a0..a57ec40662 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -29,6 +29,7 @@ else() set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PrideFlag") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Legend") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() diff --git a/src/displayapp/screens/WatchFaceLegend.cpp b/src/displayapp/screens/WatchFaceLegend.cpp new file mode 100644 index 0000000000..44ef6c0821 --- /dev/null +++ b/src/displayapp/screens/WatchFaceLegend.cpp @@ -0,0 +1,334 @@ +#include "displayapp/screens/WatchFaceLegend.h" + +#include +#include "displayapp/screens/BleIcon.h" +#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/settings/Settings.h" + +using namespace Pinetime::Applications::Screens; + +WatchFaceLegend::WatchFaceLegend(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, + Controllers::Timer& timerController, + Controllers::StopWatchController& stopWatchController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::HeartRateController& heartRateController, + Controllers::MotionController& motionController, + Controllers::SimpleWeatherService& weatherService) + : dateTimeController {dateTimeController}, + batteryController {batteryController}, + bleController {bleController}, + alarmController {alarmController}, + timerController {timerController}, + stopWatchController {stopWatchController}, + notificationManager {notificationManager}, + settingsController {settingsController}, + heartRateController {heartRateController}, + motionController {motionController}, + weatherService {weatherService} { + + // ---- Top status strip ------------------------------------------------- + // Any notification indicator (top left). Hidden when notifications are empty. + notificationAnyDot = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_size(notificationAnyDot, 7, 7); + lv_obj_set_style_local_bg_color(notificationAnyDot, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, colorAnyNotification); + lv_obj_set_style_local_radius(notificationAnyDot, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); + lv_obj_set_style_local_border_width(notificationAnyDot, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 0); + lv_obj_align(notificationAnyDot, nullptr, LV_ALIGN_IN_TOP_LEFT, 18, 16); + lv_obj_set_hidden(notificationAnyDot, true); // hidden by default + + // Standard new notification indicator (used when GetNotificationStatus() is Off or Sleep). + notificationNewIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(notificationNewIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorRed); + lv_label_set_text_static(notificationNewIcon, ""); + lv_obj_align(notificationNewIcon, notificationAnyDot, LV_ALIGN_OUT_RIGHT_MID, 10, 0); + lv_obj_set_auto_realign(notificationNewIcon, true); + + // Services icons. Empty string when nothing is active, can contain multiple icons. + servicesIcons = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(servicesIcons, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary); + lv_label_set_text_static(servicesIcons, ""); + lv_obj_align(servicesIcons, notificationNewIcon, LV_ALIGN_OUT_RIGHT_MID, 12, 1); + lv_obj_set_auto_realign(servicesIcons, true); + + // Numeric battery (top right). Right-anchored with auto realign so the + // invalidated region stays pinned to the corner when the width changes + // (e.g. 100% -> 99%). + batteryValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(batteryValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary); + lv_label_set_text_static(batteryValue, "--%"); + lv_obj_align(batteryValue, nullptr, LV_ALIGN_IN_TOP_RIGHT, -5, 10); + lv_obj_set_auto_realign(batteryValue, true); + + // BLE icon, left of the battery percentage. + bleIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorBlue); + lv_label_set_text_static(bleIcon, ""); + lv_obj_align(bleIcon, batteryValue, LV_ALIGN_OUT_LEFT_MID, -10, 0); + lv_obj_set_auto_realign(bleIcon, true); + + // Upper divider - displayed (red or blue) when GetNotificationStatus() is Off or Sleep + upperDivider = lv_line_create(lv_scr_act(), nullptr); + lv_line_set_points(upperDivider, dividerPoints, 2); + lv_obj_set_style_local_line_width(upperDivider, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 1); + lv_obj_align(upperDivider, nullptr, LV_ALIGN_IN_TOP_LEFT, 4, 45); + lv_obj_set_hidden(upperDivider, true); // hidden by default + + // ---- Center block ------------------------------------------------------ + labelDate = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary); + lv_label_set_text_static(labelDate, ""); + lv_obj_align(labelDate, nullptr, LV_ALIGN_IN_TOP_LEFT, 4, 64); + + labelTime = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed); + lv_obj_set_style_local_text_color(labelTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorForeground); + lv_label_set_text_static(labelTime, ""); + lv_obj_align(labelTime, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 94); + + labelAmPm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorForeground); + lv_label_set_text_static(labelAmPm, ""); + lv_obj_align(labelAmPm, nullptr, LV_ALIGN_IN_TOP_RIGHT, -4, 64); + lv_obj_set_auto_realign(labelAmPm, true); + + divider = lv_line_create(lv_scr_act(), nullptr); + lv_line_set_points(divider, dividerPoints, 2); + lv_obj_set_style_local_line_color(divider, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, colorDivider); + lv_obj_set_style_local_line_width(divider, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 1); + lv_obj_align(divider, nullptr, LV_ALIGN_IN_TOP_LEFT, 4, 177); + + // ---- Bottom row -------------------------------------------------- + stepValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary); + lv_label_set_text_static(stepValue, "0"); + lv_obj_align(stepValue, nullptr, LV_ALIGN_IN_TOP_LEFT, 4, 186); + + stepCaption = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepCaption, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorCaption); + lv_label_set_text_static(stepCaption, "steps"); + lv_obj_align(stepCaption, nullptr, LV_ALIGN_IN_TOP_LEFT, 4, 210); + + heartbeatValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary); + lv_label_set_text_static(heartbeatValue, "--"); + lv_obj_align(heartbeatValue, nullptr, LV_ALIGN_IN_TOP_LEFT, 100, 186); + + heartbeatCaption = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(heartbeatCaption, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorCaption); + lv_label_set_text_static(heartbeatCaption, "BPM"); + lv_obj_align(heartbeatCaption, nullptr, LV_ALIGN_IN_TOP_LEFT, 100, 210); + + weatherValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(weatherValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary); + lv_label_set_text_static(weatherValue, "--"); + lv_obj_align(weatherValue, nullptr, LV_ALIGN_IN_TOP_LEFT, 188, 186); + + weatherCaption = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(weatherCaption, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorCaption); + lv_label_set_text_static(weatherCaption, "\xC2\xB0"); + lv_obj_align(weatherCaption, nullptr, LV_ALIGN_IN_TOP_LEFT, 188, 210); + + weatherIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary); + lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); + lv_label_set_text_static(weatherIcon, ""); + lv_obj_align(weatherIcon, weatherValue, LV_ALIGN_OUT_LEFT_BOTTOM, -5, 12); + lv_obj_set_auto_realign(weatherIcon, true); + + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); +} + +WatchFaceLegend::~WatchFaceLegend() { + lv_task_del(taskRefresh); + lv_obj_clean(lv_scr_act()); +} + +void WatchFaceLegend::Refresh() { + // ---- Notification indicators ---- + notificationState = notificationManager.AreNewNotificationsAvailable(); + notificationEmpty = notificationManager.IsEmpty(); + if (notificationState.IsUpdated() || notificationEmpty.IsUpdated()) { + bool state = notificationState.Get(); + bool empty = notificationEmpty.Get(); + lv_obj_set_hidden(notificationAnyDot, empty && !state); + lv_label_set_text_static(notificationNewIcon, NotificationIcon::GetIcon(state)); + lv_obj_align(servicesIcons, state ? notificationNewIcon : notificationAnyDot, LV_ALIGN_OUT_RIGHT_MID, empty && !state ? -18 : 12, 1); + } + + // ---- Services (Alarm, Timer, StopWatch) ---- + alarmEnabled = alarmController.IsEnabled(); + timerRunning = timerController.IsRunning(); + stopWatchRunning = stopWatchController.IsRunning(); + if (alarmEnabled.IsUpdated() || timerRunning.IsUpdated() || stopWatchRunning.IsUpdated()) { + lv_label_set_text_fmt(servicesIcons, + "%s%s%s%s%s", + stopWatchRunning.Get() ? Symbols::stopWatch : "", + stopWatchRunning.Get() ? " " : "", + alarmEnabled.Get() ? Symbols::bell : "", + alarmEnabled.Get() ? " " : "", + timerRunning.Get() ? Symbols::hourGlass : ""); + } + + // ---- BLE ---- + bleState = bleController.IsConnected(); + bleRadioEnabled = bleController.IsRadioEnabled(); + if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) { + lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); + } + + // ---- Battery ---- + batteryPercentRemaining = batteryController.PercentRemaining(); + powerPresent = batteryController.IsPowerPresent(); + if (batteryPercentRemaining.IsUpdated() || powerPresent.IsUpdated()) { + uint8_t remaining = batteryPercentRemaining.Get(); + lv_label_set_text_fmt(batteryValue, "%d%%", remaining); + lv_obj_set_style_local_text_color(batteryValue, + LV_LABEL_PART_MAIN, + LV_STATE_DEFAULT, + powerPresent.Get() ? colorCharging + : remaining > 20 ? colorSecondary + : colorRed); + lv_obj_align(bleIcon, batteryValue, LV_ALIGN_OUT_LEFT_MID, -10, 0); + } + + // ---- Upper divider, based on notification config ---- + notificationConfig = settingsController.GetNotificationStatus(); + if (notificationConfig.IsUpdated()) { + switch (notificationConfig.Get()) { + case Controllers::Settings::Notification::Off: + lv_obj_set_style_local_line_color(upperDivider, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, colorRed); + lv_obj_set_hidden(upperDivider, false); + break; + case Controllers::Settings::Notification::Sleep: + lv_obj_set_style_local_line_color(upperDivider, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, colorBlue); + lv_obj_set_hidden(upperDivider, false); + break; + default: + lv_obj_set_hidden(upperDivider, true); + break; + } + } + + // ---- Time and date ---- + currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); + if (currentDateTime.IsUpdated()) { + uint8_t hour = dateTimeController.Hours(); + uint8_t minute = dateTimeController.Minutes(); + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + const char* ampm = "AM"; + if (hour == 0) { + hour = 12; + } else if (hour == 12) { + ampm = "PM"; + } else if (hour > 12) { + hour = hour - 12; + ampm = "PM"; + } + lv_label_set_text_static(labelAmPm, ampm); + } else { + lv_label_set_text_static(labelAmPm, ""); + } + lv_label_set_text_fmt(labelTime, "%02d:%02d", hour, minute); + + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + if (currentDate.IsUpdated()) { + lv_label_set_text_fmt(labelDate, + "%s %02d %s", + dateTimeController.DayOfWeekShortToString(), + dateTimeController.Day(), + dateTimeController.MonthShortToString()); + } + } + + // ---- Steps ---- + stepCount = motionController.NbSteps(); + if (stepCount.IsUpdated()) { + lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); + } + + // ---- Heart rate ---- + heartbeat = heartRateController.HeartRate(); + heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped; + if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) { + if (heartbeatRunning.Get()) { + lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get()); + } else { + lv_label_set_text_static(heartbeatValue, "--"); + } + } + + // ---- Weather ---- + currentWeather = weatherService.Current(); + if (currentWeather.IsUpdated()) { + if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { + lv_label_set_text_static(weatherCaption, + "\xC2\xB0" + "F"); + } else { + lv_label_set_text_static(weatherCaption, + "\xC2\xB0" + "C"); + } + + auto optCurrentWeather = currentWeather.Get(); + if (optCurrentWeather) { + int16_t temp; + if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { + temp = optCurrentWeather->temperature.Fahrenheit(); + } else { + temp = optCurrentWeather->temperature.Celsius(); + } + if (temp > -100) { + lv_label_set_text_fmt(weatherValue, "%d", temp); + } else { + lv_label_set_text_static(weatherValue, "--"); + } + + lv_color_t color; + switch (optCurrentWeather->iconId) { + case Controllers::SimpleWeatherService::Icons::Sun: + color = colorWeatherYellow; + break; + case Controllers::SimpleWeatherService::Icons::Snow: + case Controllers::SimpleWeatherService::Icons::Thunderstorm: + color = colorWeatherLightBlue; + break; + default: + color = colorWeatherDefault; + break; + } + lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color); + lv_label_set_text_static(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId, weatherService.IsNight())); + + if (!weatherDisplayed) { // adjust the layout when necessary + lv_obj_align(weatherValue, nullptr, LV_ALIGN_IN_TOP_LEFT, 200, 186); + lv_obj_align(weatherCaption, nullptr, LV_ALIGN_IN_TOP_LEFT, 200, 210); + lv_obj_align(weatherIcon, weatherValue, LV_ALIGN_OUT_LEFT_BOTTOM, -5, 12); + weatherDisplayed = true; + } + } else { + lv_label_set_text_static(weatherValue, "--"); + lv_label_set_text_static(weatherIcon, ""); + + if (weatherDisplayed) { // adjust the layout when necessary + lv_obj_align(weatherValue, nullptr, LV_ALIGN_IN_TOP_LEFT, 188, 186); + lv_obj_align(weatherCaption, nullptr, LV_ALIGN_IN_TOP_LEFT, 188, 210); + lv_obj_align(weatherIcon, weatherValue, LV_ALIGN_OUT_LEFT_BOTTOM, -5, 12); + weatherDisplayed = false; + } + } + } +} diff --git a/src/displayapp/screens/WatchFaceLegend.h b/src/displayapp/screens/WatchFaceLegend.h new file mode 100644 index 0000000000..f5dd625792 --- /dev/null +++ b/src/displayapp/screens/WatchFaceLegend.h @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include +#include +#include "displayapp/apps/Apps.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/Controllers.h" +#include "components/datetime/DateTimeController.h" +#include "components/ble/SimpleWeatherService.h" +#include "utility/DirtyValue.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class Battery; + class Ble; + class NotificationManager; + class HeartRateController; + class MotionController; + } + + namespace Applications { + namespace Screens { + + class WatchFaceLegend : public Screen { + public: + WatchFaceLegend(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, + Controllers::Timer& timerController, + Controllers::StopWatchController& stopWatchController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::HeartRateController& heartRateController, + Controllers::MotionController& motionController, + Controllers::SimpleWeatherService& weatherService); + ~WatchFaceLegend() override; + + void Refresh() override; + + private: + Utility::DirtyValue batteryPercentRemaining {}; + Utility::DirtyValue powerPresent {}; + Utility::DirtyValue bleState {}; + Utility::DirtyValue bleRadioEnabled {}; + Utility::DirtyValue alarmEnabled {}; + Utility::DirtyValue timerRunning {}; + Utility::DirtyValue stopWatchRunning {}; + Utility::DirtyValue notificationConfig {}; + Utility::DirtyValue notificationState {}; + Utility::DirtyValue notificationEmpty {}; + Utility::DirtyValue stepCount {}; + Utility::DirtyValue heartbeat {}; + Utility::DirtyValue heartbeatRunning {}; + Utility::DirtyValue> currentDateTime {}; + Utility::DirtyValue> currentWeather {}; + + // Tracks the calendar day so the date line is re-rendered at most + // once per day (or after a time sync). + Utility::DirtyValue> currentDate; + + // Tracks whether weather is displayed to adjust the layout only when necessary + bool weatherDisplayed = false; + + // Top status strip + lv_obj_t* notificationAnyDot; + lv_obj_t* notificationNewIcon; + lv_obj_t* servicesIcons; + lv_obj_t* bleIcon; + lv_obj_t* batteryValue; + lv_obj_t* upperDivider; + + // Center block + lv_obj_t* labelDate; + lv_obj_t* labelTime; + lv_obj_t* labelAmPm; + lv_obj_t* divider; + + // Bottom row + lv_obj_t* stepValue; + lv_obj_t* stepCaption; + lv_obj_t* heartbeatValue; + lv_obj_t* heartbeatCaption; + lv_obj_t* weatherValue; + lv_obj_t* weatherCaption; + lv_obj_t* weatherIcon; + + static constexpr lv_color_t colorForeground = LV_COLOR_MAKE(0xF5, 0xF4, 0xEF); + static constexpr lv_color_t colorSecondary = LV_COLOR_MAKE(0x8A, 0x8A, 0x85); + static constexpr lv_color_t colorCaption = LV_COLOR_MAKE(0x4A, 0x4A, 0x46); + static constexpr lv_color_t colorDivider = LV_COLOR_MAKE(0x2A, 0x2A, 0x2A); + static constexpr lv_color_t colorAnyNotification = LV_COLOR_MAKE(0xEF, 0x9D, 0x3A); + static constexpr lv_color_t colorRed = LV_COLOR_MAKE(0xE8, 0x43, 0x3C); + static constexpr lv_color_t colorBlue = LV_COLOR_MAKE(0x37, 0x8A, 0xDD); + static constexpr lv_color_t colorCharging = LV_COLOR_MAKE(0x0A, 0xAD, 0x50); + static constexpr lv_color_t colorWeatherDefault = LV_COLOR_MAKE(0xA6, 0xAC, 0xB0); + static constexpr lv_color_t colorWeatherYellow = LV_COLOR_MAKE(0xF4, 0xC5, 0x42); + static constexpr lv_color_t colorWeatherLightBlue = LV_COLOR_MAKE(0x82, 0xD3, 0xE8); + + lv_point_t dividerPoints[2] {{0, 0}, {232, 0}}; + + Controllers::DateTime& dateTimeController; + const Controllers::Battery& batteryController; + const Controllers::Ble& bleController; + const Controllers::AlarmController& alarmController; + Controllers::Timer& timerController; + Controllers::StopWatchController& stopWatchController; + Controllers::NotificationManager& notificationManager; + Controllers::Settings& settingsController; + Controllers::HeartRateController& heartRateController; + Controllers::MotionController& motionController; + Controllers::SimpleWeatherService& weatherService; + + lv_task_t* taskRefresh; + }; + } + + template <> + struct WatchFaceTraits { + static constexpr WatchFace watchFace = WatchFace::Legend; + static constexpr const char* name = "Legend"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceLegend(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.alarmController, + controllers.timer, + controllers.stopWatchController, + controllers.notificationManager, + controllers.settingsController, + controllers.heartRateController, + controllers.motionController, + *controllers.weatherController); + } + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + } + }; + } +}