diff --git a/src/displayapp/WatchFaces.h b/src/displayapp/WatchFaces.h index 2982347a..da0d7067 100644 --- a/src/displayapp/WatchFaces.h +++ b/src/displayapp/WatchFaces.h @@ -5,10 +5,11 @@ namespace Pinetime { enum class WatchFace : uint8_t { Digital = 0, Analog = 1, - PineTimeStyle = 2, - Terminal = 3, - Infineat = 4, - CasioStyleG7710 = 5, + Binary = 2, + PineTimeStyle = 3, + Terminal = 4, + Infineat = 5, + CasioStyleG7710 = 6, }; } } diff --git a/src/displayapp/screens/Clock.cpp b/src/displayapp/screens/Clock.cpp index 4219b090..3373da19 100644 --- a/src/displayapp/screens/Clock.cpp +++ b/src/displayapp/screens/Clock.cpp @@ -10,6 +10,7 @@ #include "displayapp/screens/WatchFaceDigital.h" #include "displayapp/screens/WatchFaceTerminal.h" #include "displayapp/screens/WatchFaceInfineat.h" +#include "displayapp/screens/WatchFaceBinary.h" #include "displayapp/screens/WatchFaceAnalog.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" @@ -43,6 +44,9 @@ Clock::Clock(Controllers::DateTime& dateTimeController, case WatchFace::Analog: return WatchFaceAnalogScreen(); break; + case WatchFace::Binary: + return WatchFaceBinaryScreen(); + break; case WatchFace::PineTimeStyle: return WatchFacePineTimeStyleScreen(); break; @@ -83,6 +87,16 @@ std::unique_ptr Clock::WatchFaceDigitalScreen() { motionController); } +std::unique_ptr Clock::WatchFaceBinaryScreen() { + return std::make_unique(dateTimeController, + batteryController, + bleController, + notificationManager, + settingsController, + heartRateController, + motionController); +} + std::unique_ptr Clock::WatchFaceAnalogScreen() { return std::make_unique(dateTimeController, batteryController, diff --git a/src/displayapp/screens/Clock.h b/src/displayapp/screens/Clock.h index f3591f43..ba4e4e67 100644 --- a/src/displayapp/screens/Clock.h +++ b/src/displayapp/screens/Clock.h @@ -50,6 +50,7 @@ namespace Pinetime { std::unique_ptr screen; std::unique_ptr WatchFaceDigitalScreen(); std::unique_ptr WatchFaceAnalogScreen(); + std::unique_ptr WatchFaceBinaryScreen(); std::unique_ptr WatchFacePineTimeStyleScreen(); std::unique_ptr WatchFaceTerminalScreen(); std::unique_ptr WatchFaceInfineatScreen(); diff --git a/src/displayapp/screens/WatchFaceBinary.cpp b/src/displayapp/screens/WatchFaceBinary.cpp new file mode 100644 index 00000000..638fc552 --- /dev/null +++ b/src/displayapp/screens/WatchFaceBinary.cpp @@ -0,0 +1,245 @@ +#include "displayapp/screens/WatchFaceBinary.h" + +#include +#include +#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" + + +#define LV_COLOR_MATRIX_GREEN LV_COLOR_MAKE(0x00, 0xC0, 0x16) +#define BINARY_ON_COLOR LV_COLOR_RED +#define BINARY_OFF_COLOR LV_COLOR_GRAY + +const uint8_t pointSize = 32; +const uint8_t widthSpacer = 8; +const uint8_t offsetX = ( LV_HOR_RES - ( ( pointSize * 6 ) + 2 * widthSpacer ) ) / 5; +const uint8_t offsetY = pointSize * 1.25; +const int16_t hourY = 32; +const int16_t minuteY = hourY + offsetY; +const int16_t secondY = minuteY + offsetY; +const uint8_t tflHeight = (pointSize - 3) / 2; +const uint8_t tflOffsetX = 4; +const int8_t tflOffsetY = -4; +bool is12HourModeSet; + +using namespace Pinetime::Applications::Screens; + +WatchFaceBinary::WatchFaceBinary(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::HeartRateController& heartRateController, + Controllers::MotionController& motionController) + : currentDateTime {{}}, + dateTimeController {dateTimeController}, + notificationManager {notificationManager}, + settingsController {settingsController}, + heartRateController {heartRateController}, + motionController {motionController}, + 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); + + 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)); + + 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); + + for ( uint8_t i = 0; i < 6; i++ ) + { + // Hours + if ( 5 > i ) + { + hour_points[i] = lv_obj_create(lv_scr_act(), nullptr); + + lv_obj_set_style_local_bg_color(hour_points[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, BINARY_OFF_COLOR); + lv_obj_set_style_local_radius(hour_points[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); + lv_obj_set_size(hour_points[i], pointSize, pointSize); + lv_obj_set_pos(hour_points[i], widthSpacer + (pointSize + offsetX) * i, hourY); + } + // Minutes + minute_points[i] = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(minute_points[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, BINARY_OFF_COLOR); + lv_obj_set_style_local_radius(minute_points[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); + lv_obj_set_size(minute_points[i], pointSize, pointSize); + lv_obj_set_pos(minute_points[i], widthSpacer + (pointSize + offsetX) * i, minuteY); + // Seconds + second_points[i] = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(second_points[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, BINARY_OFF_COLOR); + lv_obj_set_style_local_radius(second_points[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); + lv_obj_set_size(second_points[i], pointSize, pointSize); + lv_obj_set_pos(second_points[i], widthSpacer + (pointSize + offsetX) * i, secondY); + } + + label_am = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(label_am, "AM"); + lv_obj_set_pos(label_am, widthSpacer + tflOffsetX + (pointSize + offsetX) * 5, hourY + tflOffsetY); + lv_obj_set_style_local_text_color(label_am, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, BINARY_OFF_COLOR); + lv_obj_set_size(label_am, pointSize, tflHeight); + + label_pm = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(label_pm, "PM"); + lv_obj_set_pos(label_pm, widthSpacer + tflOffsetX + (pointSize + offsetX) * 5, hourY + tflOffsetY + tflHeight + 1); + lv_obj_set_style_local_text_color(label_pm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, BINARY_OFF_COLOR); + lv_obj_set_size(label_pm, pointSize, tflHeight); + + is12HourModeSet = ( settingsController.GetClockType() == Controllers::Settings::ClockType::H12 ); + lv_obj_set_hidden(hour_points[4], is12HourModeSet); + lv_obj_set_hidden(label_am, !is12HourModeSet); + lv_obj_set_hidden(label_pm, !is12HourModeSet); + + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); +} + +WatchFaceBinary::~WatchFaceBinary() { + lv_task_del(taskRefresh); + lv_obj_clean(lv_scr_act()); +} + +void WatchFaceBinary::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(); + uint8_t seconds = dateTimeController.Seconds(); + + if ( settingsController.GetClockType() == Controllers::Settings::ClockType::H12 ) + { + if ( !is12HourModeSet ) + { + is12HourModeSet = true; + } + + if ( ( 12 <= hour ) && ( 24 > hour ) ) + { + if ( 12 != hour ) + { + hour -= 12; + } + lv_obj_set_style_local_text_color(label_am, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, BINARY_OFF_COLOR); + lv_obj_set_style_local_text_color(label_pm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, BINARY_ON_COLOR); + } + else + { + if ( 0 == hour ) + { + hour = 12; + } + lv_obj_set_style_local_text_color(label_am, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, BINARY_ON_COLOR); + lv_obj_set_style_local_text_color(label_pm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, BINARY_OFF_COLOR); + } + hour <<= 1; + } + else if ( is12HourModeSet ) + { + is12HourModeSet = false; + } + lv_obj_set_hidden(hour_points[4], is12HourModeSet); + lv_obj_set_hidden(label_am, !is12HourModeSet); + lv_obj_set_hidden(label_pm, !is12HourModeSet); + + for ( uint8_t i = 0; i < 6; i++ ) + { + // Hours + if ( 5 > i ) + { + switch ( hour >> i & 0b1 ) + { + case 1: + lv_obj_set_style_local_bg_color(hour_points[4 - i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, BINARY_ON_COLOR); + break; + default: + lv_obj_set_style_local_bg_color(hour_points[4 - i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, BINARY_OFF_COLOR); + break; + } + } + // Minutes + switch ( minute >> i & 0b1 ) + { + case 1: + lv_obj_set_style_local_bg_color(minute_points[5 - i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, BINARY_ON_COLOR); + break; + default: + lv_obj_set_style_local_bg_color(minute_points[5 - i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, BINARY_OFF_COLOR); + break; + } + // Seconds + switch ( seconds >> i & 0b1 ) + { + case 1: + lv_obj_set_style_local_bg_color(second_points[5 - i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, BINARY_ON_COLOR); + break; + default: + lv_obj_set_style_local_bg_color(second_points[5 - i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, BINARY_OFF_COLOR); + break; + } + } + + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + if (currentDate.IsUpdated()) { + uint16_t year = dateTimeController.Year(); + uint8_t day = dateTimeController.Day(); + 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); + } + } + + 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); + } +} diff --git a/src/displayapp/screens/WatchFaceBinary.h b/src/displayapp/screens/WatchFaceBinary.h new file mode 100644 index 00000000..ddd408ba --- /dev/null +++ b/src/displayapp/screens/WatchFaceBinary.h @@ -0,0 +1,73 @@ +#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" +#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 WatchFaceBinary : public Screen { + public: + WatchFaceBinary(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::HeartRateController& heartRateController, + Controllers::MotionController& motionController); + ~WatchFaceBinary() override; + + void Refresh() override; + + private: + Utility::DirtyValue batteryPercentRemaining {}; + Utility::DirtyValue powerPresent {}; + Utility::DirtyValue bleState {}; + Utility::DirtyValue bleRadioEnabled {}; + Utility::DirtyValue> currentDateTime {}; + Utility::DirtyValue heartbeat {}; + Utility::DirtyValue heartbeatRunning {}; + Utility::DirtyValue notificationState {}; + using days = std::chrono::duration>; // TODO: days is standard in c++20 + Utility::DirtyValue> currentDate; + + lv_obj_t* label_date; + lv_obj_t* label_am; + lv_obj_t* label_pm; + lv_obj_t* heartbeatIcon; + lv_obj_t* heartbeatValue; + lv_obj_t* notificationIcon; + + lv_obj_t* hour_points[5]; + lv_obj_t* minute_points[6]; + lv_obj_t* second_points[6]; + + Controllers::DateTime& dateTimeController; + Controllers::NotificationManager& notificationManager; + Controllers::Settings& settingsController; + Controllers::HeartRateController& heartRateController; + Controllers::MotionController& motionController; + + lv_task_t* taskRefresh; + Widgets::StatusIcons statusIcons; + }; + } + } +} diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 45a50e3d..d1264b58 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -43,11 +43,11 @@ namespace Pinetime { std::array watchfaces { {{"Digital face", true}, {"Analog face", true}, + {"Binary", true}, {"PineTimeStyle", true}, {"Terminal", true}, {"Infineat face", Applications::Screens::WatchFaceInfineat::IsAvailable(filesystem)}, {"Casio G7710", Applications::Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem)}, - {"", false}, {"", false}}}; ScreenList screens; };