diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd8ece62..bec98dee 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/WatchFaceNumerals.cpp ## diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 2104a267..ae142086 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -52,6 +52,7 @@ namespace Pinetime { Terminal, Infineat, CasioStyleG7710, + Numerals, }; template diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index d7858760..cc822eea 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -27,6 +27,7 @@ else() set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") 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::Numerals") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() diff --git a/src/displayapp/screens/WatchFaceNumerals.cpp b/src/displayapp/screens/WatchFaceNumerals.cpp new file mode 100644 index 00000000..8fb217e4 --- /dev/null +++ b/src/displayapp/screens/WatchFaceNumerals.cpp @@ -0,0 +1,149 @@ +#include "displayapp/screens/WatchFaceNumerals.h" + +#include +#include +#include "displayapp/screens/Symbols.h" +#include "components/ble/NotificationManager.h" +#include "components/settings/Settings.h" + +using namespace Pinetime::Applications::Screens; + +WatchFaceNumerals::WatchFaceNumerals(Controllers::DateTime& dateTimeController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::FS& filesystem) + : currentDateTime {{}}, + dateTimeController {dateTimeController}, + notificationManager {notificationManager}, + settingsController {settingsController} { + + lfs_file f = {}; + if (filesystem.FileOpen(&f, "/fonts/rounded_large.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_large = lv_font_load("F:/fonts/rounded_large.bin"); + } + + if (filesystem.FileOpen(&f, "/fonts/rounded_small.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_small = lv_font_load("F:/fonts/rounded_small.bin"); + } + + notificationIcon = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA); + lv_obj_set_style_local_radius(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); + lv_obj_set_size(notificationIcon, 18, 18); + lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 2, -65); + lv_obj_set_hidden(notificationIcon, true); + + label_time_hour = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(label_time_hour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_large); + lv_obj_set_style_local_text_color(label_time_hour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY); + lv_obj_align(label_time_hour, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -160, -125); + + label_time_minute = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(label_time_minute, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_large); + lv_obj_set_style_local_text_color(label_time_minute, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA); + lv_obj_align(label_time_minute, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -160, 0); + + label_time_ampm = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(label_time_ampm, ""); + lv_obj_set_style_local_text_font(label_time_ampm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_small); + lv_obj_set_style_local_text_color(label_time_ampm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); + lv_obj_align(label_time_ampm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 2, -28); + + dateDay = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(dateDay, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_label_set_text(dateDay, "--"); + lv_obj_set_style_local_text_font(dateDay, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_small); + lv_obj_align(dateDay, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 0, 0); + + dateDayOfWeek = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(dateDayOfWeek, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_label_set_text(dateDayOfWeek, "---"); + lv_obj_set_style_local_text_font(dateDayOfWeek, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_small); + lv_obj_align(dateDayOfWeek, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 0, 26); + + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); +} + +WatchFaceNumerals::~WatchFaceNumerals() { + lv_task_del(taskRefresh); + + if (font_large != nullptr) { + lv_font_free(font_large); + } + if (font_small != nullptr) { + lv_font_free(font_small); + } + + lv_obj_clean(lv_scr_act()); +} + +void WatchFaceNumerals::Refresh() { + notificationState = notificationManager.AreNewNotificationsAvailable(); + if (notificationState.IsUpdated()) { + lv_obj_set_hidden(notificationIcon, !notificationState.Get()); + } + + currentDateTime = dateTimeController.CurrentDateTime(); + if (currentDateTime.IsUpdated()) { + auto hour = dateTimeController.Hours(); + auto minute = dateTimeController.Minutes(); + auto year = dateTimeController.Year(); + auto month = dateTimeController.Month(); + auto dayOfWeek = dateTimeController.DayOfWeek(); + auto day = dateTimeController.Day(); + + if (displayedHour != hour || displayedMinute != minute) { + displayedHour = hour; + displayedMinute = minute; + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[4] = "A\nM"; + 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); + // Should be padded with blank spaces, but the space character doesn't exist in the font + lv_label_set_text_fmt(label_time_hour, "%02d", hour); + lv_label_set_text_fmt(label_time_minute, "%02d", minute); + } else { + lv_label_set_text_fmt(label_time_hour, "%02d", hour); + lv_label_set_text_fmt(label_time_minute, "%02d", minute); + } + } + + if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { + lv_label_set_text_static(dateDayOfWeek, dateTimeController.DayOfWeekShortToString()); + lv_label_set_text_fmt(dateDay, "%d", day); + lv_obj_realign(dateDay); + + currentYear = year; + currentMonth = month; + currentDayOfWeek = dayOfWeek; + currentDay = day; + } + } +} + +bool WatchFaceNumerals::IsAvailable(Pinetime::Controllers::FS& filesystem) { + lfs_file file = {}; + + if (filesystem.FileOpen(&file, "/fonts/rounded_small.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/fonts/rounded_large.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + return true; +} \ No newline at end of file diff --git a/src/displayapp/screens/WatchFaceNumerals.h b/src/displayapp/screens/WatchFaceNumerals.h new file mode 100644 index 00000000..7d24748d --- /dev/null +++ b/src/displayapp/screens/WatchFaceNumerals.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "displayapp/screens/Screen.h" +#include "components/datetime/DateTimeController.h" +#include "utility/DirtyValue.h" +#include "displayapp/apps/Apps.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class NotificationManager; + } + + namespace Applications { + namespace Screens { + + class WatchFaceNumerals : public Screen { + public: + WatchFaceNumerals(Controllers::DateTime& dateTimeController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::FS& fs); + ~WatchFaceNumerals() 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; + Utility::DirtyValue notificationState {}; + Utility::DirtyValue> currentDateTime {}; + + lv_obj_t* label_time_hour; + lv_obj_t* label_time_minute; + lv_obj_t* label_time_ampm; + lv_obj_t* label_date; + lv_obj_t* notificationIcon; + lv_obj_t* dateDay; + lv_obj_t* dateDayOfWeek; + + Controllers::DateTime& dateTimeController; + Controllers::NotificationManager& notificationManager; + Controllers::Settings& settingsController; + + lv_font_t* font_large = nullptr; + lv_font_t* font_small = nullptr; + + lv_task_t* taskRefresh; + }; + } + + template <> + struct WatchFaceTraits { + static constexpr WatchFace watchFace = WatchFace::Numerals; + static constexpr const char* name = "Numeral face"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceNumerals(controllers.dateTimeController, + controllers.notificationManager, + controllers.settingsController, + controllers.filesystem); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { + return Screens::WatchFaceNumerals::IsAvailable(filesystem); + } + }; + } +} diff --git a/src/resources/fonts.json b/src/resources/fonts.json index c4a63349..ef186257 100644 --- a/src/resources/fonts.json +++ b/src/resources/fonts.json @@ -58,5 +58,29 @@ "size": 115, "format": "bin", "target_path": "/fonts/" + }, + "rounded_large": { + "sources": [ + { + "file": "fonts/BloggerSans-Bold.ttf", + "range": "0x30-0x3a" + } + ], + "bpp": 1, + "size": 150, + "format": "bin", + "target_path": "/fonts/" + }, + "rounded_small": { + "sources": [ + { + "file": "fonts/BloggerSans-Bold.ttf", + "range": "0x20-0x7e, 0x410-0x44f, 0xB0" + } + ], + "bpp": 1, + "size": 30, + "format": "bin", + "target_path": "/fonts/" } } diff --git a/src/resources/fonts/BloggerSans-Bold.ttf b/src/resources/fonts/BloggerSans-Bold.ttf new file mode 100644 index 00000000..d287255d Binary files /dev/null and b/src/resources/fonts/BloggerSans-Bold.ttf differ diff --git a/src/resources/resources.json b/src/resources/resources.json new file mode 100644 index 00000000..3a66f2fa --- /dev/null +++ b/src/resources/resources.json @@ -0,0 +1,50 @@ +{ + "resources": [ + { + "filename": "7segments_40.bin", + "path": "/fonts/7segments_40.bin" + }, + { + "filename": "rounded_large.bin", + "path": "/fonts/rounded_large.bin" + }, + { + "filename": "rounded_small.bin", + "path": "/fonts/rounded_small.bin" + }, + { + "filename": "bebas.bin", + "path": "/fonts/bebas.bin" + }, + { + "filename": "7segments_115.bin", + "path": "/fonts/7segments_115.bin" + }, + { + "filename": "lv_font_dots_40.bin", + "path": "/fonts/lv_font_dots_40.bin" + }, + { + "filename": "teko.bin", + "path": "/fonts/teko.bin" + }, + { + "filename": "navigation1.bin", + "path": "/images/navigation1.bin" + }, + { + "filename": "navigation0.bin", + "path": "/images/navigation0.bin" + }, + { + "filename": "pine_small.bin", + "path": "/images/pine_small.bin" + } + ], + "obsolete_files": [ + { + "path": "/example-of-obsolete-file.bin", + "since": "1.11.0" + } + ] +} \ No newline at end of file