From 44922f142d0a9b02c59051bcde05e5dcad917b36 Mon Sep 17 00:00:00 2001 From: Cesura Date: Fri, 28 Oct 2022 01:04:59 +0300 Subject: [PATCH] Add new Horizon watch face --- src/CMakeLists.txt | 1 + src/displayapp/screens/Clock.cpp | 13 + src/displayapp/screens/Clock.h | 1 + src/displayapp/screens/WatchFaceHorizon.cpp | 264 ++++++++++++++++++ src/displayapp/screens/WatchFaceHorizon.h | 88 ++++++ .../screens/settings/SettingWatchFace.cpp | 3 +- src/resources/fonts.json | 26 +- src/resources/fonts/Pinecone-Regular.ttf | Bin 0 -> 8876 bytes 8 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 src/displayapp/screens/WatchFaceHorizon.cpp create mode 100644 src/displayapp/screens/WatchFaceHorizon.h create mode 100644 src/resources/fonts/Pinecone-Regular.ttf diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e59c0d81..27d799bb 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/WatchFaceHorizon.cpp ## diff --git a/src/displayapp/screens/Clock.cpp b/src/displayapp/screens/Clock.cpp index 07d307e0..78c0e197 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/WatchFaceHorizon.h" using namespace Pinetime::Applications::Screens; @@ -55,6 +56,9 @@ Clock::Clock(DisplayApp* app, case 5: return WatchFaceCasioStyleG7710(); break; + case 6: + return WatchFaceHorizonScreen(); + break; } return WatchFaceDigitalScreen(); }()} { @@ -136,3 +140,12 @@ std::unique_ptr Clock::WatchFaceCasioStyleG7710() { motionController, filesystem); } + +std::unique_ptr Clock::WatchFaceHorizonScreen() { + return std::make_unique(app, + dateTimeController, + batteryController, + settingsController, + motionController, + filesystem); +} \ No newline at end of file diff --git a/src/displayapp/screens/Clock.h b/src/displayapp/screens/Clock.h index 613fef57..335f7920 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 WatchFaceHorizonScreen(); }; } } diff --git a/src/displayapp/screens/WatchFaceHorizon.cpp b/src/displayapp/screens/WatchFaceHorizon.cpp new file mode 100644 index 00000000..e3f83c5f --- /dev/null +++ b/src/displayapp/screens/WatchFaceHorizon.cpp @@ -0,0 +1,264 @@ +#include "displayapp/screens/WatchFaceHorizon.h" + +#include +#include +#include +#include "components/settings/Settings.h" +#include "components/battery/BatteryController.h" +#include "components/motion/MotionController.h" + +using namespace Pinetime::Applications::Screens; + +WatchFaceHorizon::WatchFaceHorizon(DisplayApp* app, + Controllers::DateTime& dateTimeController, + Controllers::Battery& batteryController, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& filesystem) + : Screen(app), + currentDateTime {{}}, + dateTimeController {dateTimeController}, + batteryController {batteryController}, + settingsController {settingsController}, + motionController {motionController} { + lfs_file f = {}; + if (filesystem.FileOpen(&f, "/fonts/pinecone_28.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_pinecone_28 = lv_font_load("F:/fonts/pinecone_28.bin"); + } + + if (filesystem.FileOpen(&f, "/fonts/pinecone_70.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_pinecone_70 = lv_font_load("F:/fonts/pinecone_70.bin"); + } + + // Black background covering the whole screen + background = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(background, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_size(background, 240, 240); + lv_obj_align(background, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 0, 0); + + // Battery indicator line + lineBatteryBg = lv_line_create(lv_scr_act(), nullptr); + lineBatteryFg = lv_line_create(lv_scr_act(), nullptr); + + lv_style_init(&lineBatteryBgStyle); + lv_style_set_line_width(&lineBatteryBgStyle, LV_STATE_DEFAULT, 4); + lv_style_set_line_color(&lineBatteryBgStyle, LV_STATE_DEFAULT, lv_color_hex(0x181818)); + lv_obj_add_style(lineBatteryBg, LV_LINE_PART_MAIN, &lineBatteryBgStyle); + lineBatteryBgPoints[0] = {116, 40}; + lineBatteryBgPoints[1] = {116, 200}; + lv_line_set_points(lineBatteryBg, lineBatteryBgPoints, 2); + + lv_style_init(&lineBatteryFgStyle); + lv_style_set_line_width(&lineBatteryFgStyle, LV_STATE_DEFAULT, 4); + lv_style_set_line_color(&lineBatteryFgStyle, LV_STATE_DEFAULT, lv_color_hex(hourlyColors[0])); + lv_obj_add_style(lineBatteryFg, LV_LINE_PART_MAIN, &lineBatteryFgStyle); + lineBatteryFgPoints[0] = {116, 40}; + lineBatteryFgPoints[1] = {116, 200}; + lv_line_set_points(lineBatteryFg, lineBatteryFgPoints, 2); + + // Hour indicator lines at bottom + for (int i = 0; i < 24; i++) { + hourLines[i] = lv_line_create(lv_scr_act(), nullptr); + lv_style_init(&hourLineStyles[i]); + lv_style_set_line_width(&hourLineStyles[i], LV_STATE_DEFAULT, 5); + lv_style_set_line_color(&hourLineStyles[i], LV_STATE_DEFAULT, lv_color_hex(hourlyColors[i])); + lv_obj_add_style(hourLines[i], LV_LINE_PART_MAIN, &hourLineStyles[i]); + hourLinePoints[i][0] = {static_cast(i * 10), 237}; + hourLinePoints[i][1] = {static_cast((i + 1) * 10), 237}; + lv_line_set_points(hourLines[i], hourLinePoints[i], 2); + } + + // Hour (split digits due to non-monospaced font) + labelHourFirstDigit = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelHourFirstDigit, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_pinecone_70); + lv_label_set_text(labelHourFirstDigit, "0"); + lv_obj_align(labelHourFirstDigit, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 130, -43); + + labelHourSecondDigit = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelHourSecondDigit, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_pinecone_70); + lv_label_set_text(labelHourSecondDigit, "0"); + lv_obj_align(labelHourSecondDigit, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 174, -43); + + // Minutes (split digits due to non-monospaced font) + labelMinutesFirstDigit = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelMinutesFirstDigit, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_pinecone_70); + lv_label_set_text(labelMinutesFirstDigit, "0"); + lv_obj_align(labelMinutesFirstDigit, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 130, 44); + + labelMinutesSecondDigit = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelMinutesSecondDigit, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_pinecone_70); + lv_label_set_text(labelMinutesSecondDigit, "0"); + lv_obj_align(labelMinutesSecondDigit, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 174, 44); + + // Day of week + labelDayOfWeek = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelDayOfWeek, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xbebebe)); + lv_obj_set_style_local_text_font(labelDayOfWeek, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_pinecone_28); + lv_obj_align(labelDayOfWeek, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -140, -60); + lv_label_set_text(labelDayOfWeek, "MON"); + + // Month + labelMonth = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelMonth, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_pinecone_28); + lv_obj_align(labelMonth, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -140, -20); + lv_label_set_text(labelMonth, "JAN"); + + // Day of month + labelDate = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xbebebe)); + lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_pinecone_28); + lv_obj_align(labelDate, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -140, 20); + lv_label_set_text(labelDate, "01"); + + // Number of steps + stepValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_pinecone_28); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -140, 60); + lv_label_set_text(stepValue, "0"); + + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); +} + +WatchFaceHorizon::~WatchFaceHorizon() { + lv_task_del(taskRefresh); + + // Reset styles + lv_style_reset(&lineBatteryFgStyle); + lv_style_reset(&lineBatteryBgStyle); + + for (int i = 0; i < 24; i++) { + lv_style_reset(&hourLineStyles[i]); + } + + // Free font resources + if (font_pinecone_28 != nullptr) { + lv_font_free(font_pinecone_28); + } + + if (font_pinecone_70 != nullptr) { + lv_font_free(font_pinecone_70); + } + + lv_obj_clean(lv_scr_act()); +} + +void WatchFaceHorizon::Refresh() { + 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 month = static_cast(static_cast(yearMonthDay.month())); + auto day = static_cast(yearMonthDay.day()); + auto dayOfWeek = static_cast(date::weekday(yearMonthDay).iso_encoding()); + + int64_t hour = time.hours().count(); + int64_t minute = time.minutes().count(); + + char minutesChar[3]; + sprintf(minutesChar, "%02d", static_cast(minute)); + + char hoursChar[3]; + int displayHour = hour; + + // Account for 12-hour time + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + if (hour < 12) { + if (hour == 0) { + displayHour = 12; + } + } else { + if (hour != 12) { + displayHour = hour - 12; + } + } + } + sprintf(hoursChar, "%02d", displayHour); + + // Hour has updated + if (hoursChar[0] != displayedChar[0] || hoursChar[1] != displayedChar[1]) { + displayedChar[0] = hoursChar[0]; + displayedChar[1] = hoursChar[1]; + + // Update colors appropriately + lv_obj_set_style_local_text_color(labelHourFirstDigit, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(hourlyColors[hour])); + lv_obj_set_style_local_text_color(labelHourSecondDigit, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(hourlyColors[hour])); + lv_obj_set_style_local_text_color(labelMonth, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(hourlyColors[hour])); + lv_style_set_line_color(&lineBatteryFgStyle, LV_STATE_DEFAULT, lv_color_hex(hourlyColors[hour])); + + lv_label_set_text_fmt(labelHourFirstDigit, "%c", hoursChar[0]); + lv_label_set_text_fmt(labelHourSecondDigit, "%c", hoursChar[1]); + + // Update hour bar on bottom + for (int i = 0; i < 24; i++) { + if (i <= hour) { + lv_obj_set_hidden(hourLines[i], false); + } else { + lv_obj_set_hidden(hourLines[i], true); + } + } + } + + // Minutes have updated + if (minutesChar[0] != displayedChar[2] || minutesChar[1] != displayedChar[3]) { + displayedChar[2] = minutesChar[0]; + displayedChar[3] = minutesChar[1]; + + lv_label_set_text_fmt(labelMinutesFirstDigit, "%c", minutesChar[0]); + lv_label_set_text_fmt(labelMinutesSecondDigit, "%c", minutesChar[1]); + } + + // Date has updated + if ((month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { + lv_label_set_text_fmt(labelDayOfWeek, "%s", dateTimeController.DayOfWeekShortToString()); + lv_label_set_text_fmt(labelMonth, "%s", dateTimeController.MonthShortToString()); + lv_label_set_text_fmt(labelDate, "%d", day); + + lv_obj_realign(labelDayOfWeek); + lv_obj_realign(labelMonth); + lv_obj_realign(labelDate); + + currentMonth = month; + currentDayOfWeek = dayOfWeek; + currentDay = day; + } + } + + // Set battery line + batteryPercentRemaining = batteryController.PercentRemaining(); + if (batteryPercentRemaining.IsUpdated()) { + lineBatteryFgPoints[0] = {116, static_cast(200 - (1.6 * batteryPercentRemaining.Get()))}; + lv_line_set_points(lineBatteryFg, lineBatteryFgPoints, 2); + } + + // Set step count + stepCount = motionController.NbSteps(); + motionSensorOk = motionController.IsSensorOk(); + if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { + lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); + lv_obj_realign(stepValue); + } +} + +bool WatchFaceHorizon::IsAvailable(Pinetime::Controllers::FS& filesystem) { + lfs_file file = {}; + + if (filesystem.FileOpen(&file, "/fonts/pinecone_28.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/fonts/pinecone_70.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + return true; +} diff --git a/src/displayapp/screens/WatchFaceHorizon.h b/src/displayapp/screens/WatchFaceHorizon.h new file mode 100644 index 00000000..41937f43 --- /dev/null +++ b/src/displayapp/screens/WatchFaceHorizon.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include "displayapp/screens/Screen.h" +#include "components/datetime/DateTimeController.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class Battery; + class MotionController; + } + + namespace Applications { + namespace Screens { + + class WatchFaceHorizon : public Screen { + public: + WatchFaceHorizon(DisplayApp* app, + Controllers::DateTime& dateTimeController, + Controllers::Battery& batteryController, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& fs); + + ~WatchFaceHorizon() override; + + void Refresh() override; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem); + + private: + char displayedChar[5] {}; + + uint16_t currentYear = 1970; + Pinetime::Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown; + Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown; + uint8_t currentDay = 0; + + DirtyValue batteryPercentRemaining {}; + DirtyValue> currentDateTime {}; + DirtyValue motionSensorOk {}; + DirtyValue stepCount {}; + + lv_obj_t* background; + + lv_obj_t* hourLines[24]; + lv_style_t hourLineStyles[24]; + lv_point_t hourLinePoints[24][2]; + + lv_obj_t* labelHourFirstDigit; + lv_obj_t* labelHourSecondDigit; + + lv_obj_t* labelMinutesFirstDigit; + lv_obj_t* labelMinutesSecondDigit; + + lv_obj_t* lineBatteryFg; + lv_obj_t* lineBatteryBg; + lv_style_t lineBatteryFgStyle; + lv_style_t lineBatteryBgStyle; + lv_point_t lineBatteryFgPoints[2]; + lv_point_t lineBatteryBgPoints[2]; + + lv_obj_t* labelDayOfWeek; + lv_obj_t* labelMonth; + lv_obj_t* labelDate; + + lv_obj_t* stepValue; + + Controllers::DateTime& dateTimeController; + Controllers::Battery& batteryController; + Controllers::Settings& settingsController; + Controllers::MotionController& motionController; + + lv_task_t* taskRefresh; + lv_font_t* font_pinecone_28 = nullptr; + lv_font_t* font_pinecone_70 = nullptr; + + int hourlyColors[24] = {0x353f76, 0x415587, 0x4e6a98, 0x5a80a9, 0x6696ba, 0x7fa5b1, 0x98b5a7, 0xb0c49e, + 0xc9d494, 0xe2e38b, 0xe3d780, 0xe4ca75, 0xe5be69, 0xe6b15e, 0xe7a553, 0xd29357, + 0xbd815b, 0xa86f60, 0x935d64, 0x7e4b68, 0x6d4467, 0x4b3766, 0x3a3066, 0x292965}; + }; + } + } +} diff --git a/src/displayapp/screens/settings/SettingWatchFace.cpp b/src/displayapp/screens/settings/SettingWatchFace.cpp index 217f97b8..2ea8be88 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/WatchFaceHorizon.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}, + {"Horizon", Applications::Screens::WatchFaceHorizon::IsAvailable(filesystem)}, {"", false}}}; return std::make_unique( 1, diff --git a/src/resources/fonts.json b/src/resources/fonts.json index a270e6a2..e2be2417 100644 --- a/src/resources/fonts.json +++ b/src/resources/fonts.json @@ -58,5 +58,29 @@ "size": 115, "format": "bin", "target_path": "/fonts/" - } + }, + "pinecone_28" : { + "sources": [ + { + "file": "fonts/Pinecone-Regular.ttf", + "symbols": "0123456789MONTUEWDHFRISAJBPYLGCV" + } + ], + "bpp": 1, + "size": 28, + "format": "bin", + "target_path": "/fonts/" + }, + "pinecone_70" : { + "sources": [ + { + "file": "fonts/Pinecone-Regular.ttf", + "symbols": "0123456789" + } + ], + "bpp": 1, + "size": 70, + "format": "bin", + "target_path": "/fonts/" + } } diff --git a/src/resources/fonts/Pinecone-Regular.ttf b/src/resources/fonts/Pinecone-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b20e57c0a7fe978438a5e329ce62e9e1a81c0072 GIT binary patch literal 8876 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