diff options
Diffstat (limited to 'src/displayapp/screens')
76 files changed, 2377 insertions, 1140 deletions
diff --git a/src/displayapp/screens/Alarm.cpp b/src/displayapp/screens/Alarm.cpp index 4e6ce797..4cf43921 100644 --- a/src/displayapp/screens/Alarm.cpp +++ b/src/displayapp/screens/Alarm.cpp @@ -19,6 +19,10 @@ #include "displayapp/screens/Screen.h" #include "displayapp/screens/Symbols.h" #include "displayapp/InfiniTimeTheme.h" +#include "components/settings/Settings.h" +#include "components/alarm/AlarmController.h" +#include "components/motor/MotorController.h" +#include "systemtask/SystemTask.h" using namespace Pinetime::Applications::Screens; using Pinetime::Controllers::AlarmController; @@ -44,7 +48,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController, Controllers::Settings::ClockType clockType, System::SystemTask& systemTask, Controllers::MotorController& motorController) - : alarmController {alarmController}, systemTask {systemTask}, motorController {motorController} { + : alarmController {alarmController}, wakeLock(systemTask), motorController {motorController} { hourCounter.Create(); lv_obj_align(hourCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); @@ -73,7 +77,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController, btnStop = lv_btn_create(lv_scr_act(), nullptr); btnStop->user_data = this; lv_obj_set_event_cb(btnStop, btnEventHandler); - lv_obj_set_size(btnStop, 115, 50); + lv_obj_set_size(btnStop, 240, 70); lv_obj_align(btnStop, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); lv_obj_set_style_local_bg_color(btnStop, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); txtStop = lv_label_create(btnStop, nullptr); @@ -113,7 +117,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController, UpdateAlarmTime(); - if (alarmController.State() == Controllers::AlarmController::AlarmState::Alerting) { + if (alarmController.IsAlerting()) { SetAlerting(); } else { SetSwitchState(LV_ANIM_OFF); @@ -121,14 +125,15 @@ Alarm::Alarm(Controllers::AlarmController& alarmController, } Alarm::~Alarm() { - if (alarmController.State() == AlarmController::AlarmState::Alerting) { + if (alarmController.IsAlerting()) { StopAlerting(); } lv_obj_clean(lv_scr_act()); + alarmController.SaveAlarm(); } void Alarm::DisableAlarm() { - if (alarmController.State() == AlarmController::AlarmState::Set) { + if (alarmController.IsEnabled()) { alarmController.DisableAlarm(); lv_switch_off(enableSwitch, LV_ANIM_ON); } @@ -168,7 +173,7 @@ bool Alarm::OnButtonPushed() { HideInfo(); return true; } - if (alarmController.State() == AlarmController::AlarmState::Alerting) { + if (alarmController.IsAlerting()) { StopAlerting(); return true; } @@ -177,7 +182,7 @@ bool Alarm::OnButtonPushed() { bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) { // Don't allow closing the screen by swiping while the alarm is alerting - return alarmController.State() == AlarmController::AlarmState::Alerting && event == TouchEvents::SwipeDown; + return alarmController.IsAlerting() && event == TouchEvents::SwipeDown; } void Alarm::OnValueChanged() { @@ -198,10 +203,14 @@ void Alarm::UpdateAlarmTime() { void Alarm::SetAlerting() { lv_obj_set_hidden(enableSwitch, true); + lv_obj_set_hidden(btnRecur, true); + lv_obj_set_hidden(btnInfo, true); + hourCounter.HideControls(); + minuteCounter.HideControls(); lv_obj_set_hidden(btnStop, false); taskStopAlarm = lv_task_create(StopAlarmTaskCallback, pdMS_TO_TICKS(60 * 1000), LV_TASK_PRIO_MID, this); motorController.StartRinging(); - systemTask.PushMessage(System::Messages::DisableSleeping); + wakeLock.Lock(); } void Alarm::StopAlerting() { @@ -212,21 +221,20 @@ void Alarm::StopAlerting() { lv_task_del(taskStopAlarm); taskStopAlarm = nullptr; } - systemTask.PushMessage(System::Messages::EnableSleeping); - lv_obj_set_hidden(enableSwitch, false); + wakeLock.Release(); lv_obj_set_hidden(btnStop, true); + hourCounter.ShowControls(); + minuteCounter.ShowControls(); + lv_obj_set_hidden(btnInfo, false); + lv_obj_set_hidden(btnRecur, false); + lv_obj_set_hidden(enableSwitch, false); } void Alarm::SetSwitchState(lv_anim_enable_t anim) { - switch (alarmController.State()) { - case AlarmController::AlarmState::Set: - lv_switch_on(enableSwitch, anim); - break; - case AlarmController::AlarmState::Not_Set: - lv_switch_off(enableSwitch, anim); - break; - default: - break; + if (alarmController.IsEnabled()) { + lv_switch_on(enableSwitch, anim); + } else { + lv_switch_off(enableSwitch, anim); } } @@ -243,7 +251,7 @@ void Alarm::ShowInfo() { txtMessage = lv_label_create(btnMessage, nullptr); lv_obj_set_style_local_bg_color(btnMessage, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY); - if (alarmController.State() == AlarmController::AlarmState::Set) { + if (alarmController.IsEnabled()) { auto timeToAlarm = alarmController.SecondsToAlarm(); auto daysToAlarm = timeToAlarm / 86400; diff --git a/src/displayapp/screens/Alarm.h b/src/displayapp/screens/Alarm.h index 91177366..a875b275 100644 --- a/src/displayapp/screens/Alarm.h +++ b/src/displayapp/screens/Alarm.h @@ -17,21 +17,23 @@ */ #pragma once +#include "displayapp/apps/Apps.h" +#include "components/settings/Settings.h" #include "displayapp/screens/Screen.h" -#include "systemtask/SystemTask.h" -#include "displayapp/LittleVgl.h" -#include "components/alarm/AlarmController.h" #include "displayapp/widgets/Counter.h" +#include "displayapp/Controllers.h" +#include "systemtask/WakeLock.h" +#include "Symbols.h" namespace Pinetime { namespace Applications { namespace Screens { class Alarm : public Screen { public: - Alarm(Controllers::AlarmController& alarmController, - Controllers::Settings::ClockType clockType, - System::SystemTask& systemTask, - Controllers::MotorController& motorController); + explicit Alarm(Controllers::AlarmController& alarmController, + Controllers::Settings::ClockType clockType, + System::SystemTask& systemTask, + Controllers::MotorController& motorController); ~Alarm() override; void SetAlerting(); void OnButtonEvent(lv_obj_t* obj, lv_event_t event); @@ -42,7 +44,7 @@ namespace Pinetime { private: Controllers::AlarmController& alarmController; - System::SystemTask& systemTask; + System::WakeLock wakeLock; Controllers::MotorController& motorController; lv_obj_t *btnStop, *txtStop, *btnRecur, *txtRecur, *btnInfo, *enableSwitch; @@ -63,6 +65,19 @@ namespace Pinetime { Widgets::Counter hourCounter = Widgets::Counter(0, 23, jetbrains_mono_76); Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76); }; + } + + template <> + struct AppTraits<Apps::Alarm> { + static constexpr Apps app = Apps::Alarm; + static constexpr const char* icon = Screens::Symbols::bell; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Alarm(controllers.alarmController, + controllers.settingsController.GetClockType(), + *controllers.systemTask, + controllers.motorController); + }; }; - }; + } } diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp index 0a65a5d4..fb46b413 100644 --- a/src/displayapp/screens/ApplicationList.cpp +++ b/src/displayapp/screens/ApplicationList.cpp @@ -1,13 +1,12 @@ #include "displayapp/screens/ApplicationList.h" +#include "displayapp/screens/Tile.h" #include <lvgl/lvgl.h> #include <functional> -#include "displayapp/Apps.h" -#include "displayapp/DisplayApp.h" +#include <algorithm> +#include "components/settings/Settings.h" using namespace Pinetime::Applications::Screens; -constexpr std::array<Tile::Applications, ApplicationList::applications.size()> ApplicationList::applications; - auto ApplicationList::CreateScreenList() const { std::array<std::function<std::unique_ptr<Screen>()>, nScreens> screens; for (size_t i = 0; i < screens.size(); i++) { @@ -18,16 +17,22 @@ auto ApplicationList::CreateScreenList() const { return screens; } -ApplicationList::ApplicationList(Pinetime::Applications::DisplayApp* app, +ApplicationList::ApplicationList(DisplayApp* app, Pinetime::Controllers::Settings& settingsController, const Pinetime::Controllers::Battery& batteryController, const Pinetime::Controllers::Ble& bleController, - Controllers::DateTime& dateTimeController) + const Pinetime::Controllers::AlarmController& alarmController, + Controllers::DateTime& dateTimeController, + Pinetime::Controllers::FS& filesystem, + std::array<Tile::Applications, UserAppTypes::Count>&& apps) : app {app}, settingsController {settingsController}, batteryController {batteryController}, bleController {bleController}, + alarmController {alarmController}, dateTimeController {dateTimeController}, + filesystem {filesystem}, + apps {std::move(apps)}, screens {app, settingsController.GetAppMenu(), CreateScreenList(), Screens::ScreenListModes::UpDown} { } @@ -40,9 +45,14 @@ bool ApplicationList::OnTouchEvent(Pinetime::Applications::TouchEvents event) { } std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) const { - std::array<Tile::Applications, appsPerScreen> apps; + std::array<Tile::Applications, appsPerScreen> pageApps; + for (int i = 0; i < appsPerScreen; i++) { - apps[i] = applications[screenNum * appsPerScreen + i]; + if (i + (screenNum * appsPerScreen) >= apps.size()) { + pageApps[i] = {"", Pinetime::Applications::Apps::None, false}; + } else { + pageApps[i] = apps[i + (screenNum * appsPerScreen)]; + } } return std::make_unique<Screens::Tile>(screenNum, @@ -51,6 +61,7 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) co settingsController, batteryController, bleController, + alarmController, dateTimeController, - apps); + pageApps); } diff --git a/src/displayapp/screens/ApplicationList.h b/src/displayapp/screens/ApplicationList.h index 7bdd1154..4a57d7c0 100644 --- a/src/displayapp/screens/ApplicationList.h +++ b/src/displayapp/screens/ApplicationList.h @@ -2,14 +2,12 @@ #include <array> #include <memory> - -#include "displayapp/screens/Screen.h" -#include "displayapp/screens/ScreenList.h" -#include "components/datetime/DateTimeController.h" -#include "components/settings/Settings.h" -#include "components/battery/BatteryController.h" -#include "displayapp/screens/Symbols.h" -#include "displayapp/screens/Tile.h" +#include "displayapp/apps/Apps.h" +#include "Screen.h" +#include "ScreenList.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" +#include "Tile.h" namespace Pinetime { namespace Applications { @@ -20,7 +18,10 @@ namespace Pinetime { Pinetime::Controllers::Settings& settingsController, const Pinetime::Controllers::Battery& batteryController, const Pinetime::Controllers::Ble& bleController, - Controllers::DateTime& dateTimeController); + const Pinetime::Controllers::AlarmController& alarmController, + Controllers::DateTime& dateTimeController, + Pinetime::Controllers::FS& filesystem, + std::array<Tile::Applications, UserAppTypes::Count>&& apps); ~ApplicationList() override; bool OnTouchEvent(TouchEvents event) override; @@ -32,30 +33,15 @@ namespace Pinetime { Controllers::Settings& settingsController; const Pinetime::Controllers::Battery& batteryController; const Pinetime::Controllers::Ble& bleController; + const Pinetime::Controllers::AlarmController& alarmController; Controllers::DateTime& dateTimeController; + Pinetime::Controllers::FS& filesystem; + std::array<Tile::Applications, UserAppTypes::Count> apps; static constexpr int appsPerScreen = 6; - // Increment this when more space is needed - static constexpr int nScreens = 2; - - static constexpr std::array<Tile::Applications, appsPerScreen * nScreens> applications {{ - {Symbols::stopWatch, Apps::StopWatch}, - {Symbols::clock, Apps::Alarm}, - {Symbols::hourGlass, Apps::Timer}, - {Symbols::shoe, Apps::Steps}, - {Symbols::heartBeat, Apps::HeartRate}, - {Symbols::music, Apps::Music}, - - {Symbols::paintbrush, Apps::Paint}, - {Symbols::paddle, Apps::Paddle}, - {"2", Apps::Twos}, - {Symbols::drum, Apps::Metronome}, - {Symbols::map, Apps::Navigation}, - {Symbols::none, Apps::None}, + static constexpr int nScreens = UserAppTypes::Count > 0 ? (UserAppTypes::Count - 1) / appsPerScreen + 1 : 1; - // {"M", Apps::Motion}, - }}; ScreenList<nScreens> screens; }; } diff --git a/src/displayapp/screens/BatteryInfo.cpp b/src/displayapp/screens/BatteryInfo.cpp index ab0a2bd4..20401988 100644 --- a/src/displayapp/screens/BatteryInfo.cpp +++ b/src/displayapp/screens/BatteryInfo.cpp @@ -10,33 +10,35 @@ BatteryInfo::BatteryInfo(const Pinetime::Controllers::Battery& batteryController batteryPercent = batteryController.PercentRemaining(); batteryVoltage = batteryController.Voltage(); - charging_bar = lv_bar_create(lv_scr_act(), nullptr); - lv_obj_set_size(charging_bar, 200, 15); - lv_bar_set_range(charging_bar, 0, 100); - lv_obj_align(charging_bar, nullptr, LV_ALIGN_CENTER, 0, 10); - lv_bar_set_anim_time(charging_bar, 1000); - lv_obj_set_style_local_radius(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, Colors::bgAlt); - lv_obj_set_style_local_bg_opa(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_OPA_100); - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_RED); - lv_bar_set_value(charging_bar, batteryPercent, LV_ANIM_ON); + chargingArc = lv_arc_create(lv_scr_act(), nullptr); + lv_arc_set_rotation(chargingArc, 270); + lv_arc_set_bg_angles(chargingArc, 0, 360); + lv_arc_set_adjustable(chargingArc, false); + lv_obj_set_size(chargingArc, 180, 180); + lv_obj_align(chargingArc, nullptr, LV_ALIGN_CENTER, 0, -30); + lv_arc_set_value(chargingArc, batteryPercent); + lv_obj_set_style_local_bg_opa(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, LV_OPA_0); + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, Colors::bgAlt); + lv_obj_set_style_local_border_width(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, 2); + lv_obj_set_style_local_radius(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, 0); + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_LIME); status = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(status, "Reading Battery status"); lv_label_set_align(status, LV_LABEL_ALIGN_CENTER); - lv_obj_align(status, charging_bar, LV_ALIGN_OUT_BOTTOM_MID, 0, 20); + lv_obj_align(status, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -17); percent = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(percent, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76); + lv_obj_set_style_local_text_font(percent, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); lv_label_set_text_fmt(percent, "%02i%%", batteryPercent); lv_label_set_align(percent, LV_LABEL_ALIGN_LEFT); - lv_obj_align(percent, nullptr, LV_ALIGN_CENTER, 0, -60); + lv_obj_align(percent, chargingArc, LV_ALIGN_CENTER, 0, 0); voltage = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(voltage, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::orange); lv_label_set_text_fmt(voltage, "%1i.%02i volts", batteryVoltage / 1000, batteryVoltage % 1000 / 10); lv_label_set_align(voltage, LV_LABEL_ALIGN_CENTER); - lv_obj_align(voltage, nullptr, LV_ALIGN_CENTER, 0, 95); + lv_obj_align(voltage, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -7); taskRefresh = lv_task_create(RefreshTaskCallback, 5000, LV_TASK_PRIO_MID, this); Refresh(); @@ -53,22 +55,23 @@ void BatteryInfo::Refresh() { batteryVoltage = batteryController.Voltage(); if (batteryController.IsCharging()) { - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_RED); + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_LIME); lv_label_set_text_static(status, "Charging"); } else if (batteryPercent == 100) { - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_BLUE); + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_BLUE); lv_label_set_text_static(status, "Fully charged"); } else if (batteryPercent < 10) { - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_YELLOW); + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_RED); lv_label_set_text_static(status, "Battery low"); } else { - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, Colors::highlight); + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_GREEN); lv_label_set_text_static(status, "Discharging"); } lv_label_set_text_fmt(percent, "%02i%%", batteryPercent); + lv_obj_align(percent, chargingArc, LV_ALIGN_CENTER, 0, 0); - lv_obj_align(status, charging_bar, LV_ALIGN_OUT_BOTTOM_MID, 0, 20); + lv_obj_align(status, voltage, LV_ALIGN_IN_BOTTOM_MID, 0, -27); lv_label_set_text_fmt(voltage, "%1i.%02i volts", batteryVoltage / 1000, batteryVoltage % 1000 / 10); - lv_bar_set_value(charging_bar, batteryPercent, LV_ANIM_ON); + lv_arc_set_value(chargingArc, batteryPercent); } diff --git a/src/displayapp/screens/BatteryInfo.h b/src/displayapp/screens/BatteryInfo.h index aa01d464..27bbaa00 100644 --- a/src/displayapp/screens/BatteryInfo.h +++ b/src/displayapp/screens/BatteryInfo.h @@ -24,7 +24,7 @@ namespace Pinetime { lv_obj_t* voltage; lv_obj_t* percent; - lv_obj_t* charging_bar; + lv_obj_t* chargingArc; lv_obj_t* status; lv_task_t* taskRefresh; diff --git a/src/displayapp/screens/Calculator.cpp b/src/displayapp/screens/Calculator.cpp new file mode 100644 index 00000000..a1f09383 --- /dev/null +++ b/src/displayapp/screens/Calculator.cpp @@ -0,0 +1,375 @@ +#include <cmath> +#include <cinttypes> +#include "Calculator.h" +#include "displayapp/InfiniTimeTheme.h" +#include "Symbols.h" + +using namespace Pinetime::Applications::Screens; + +static void eventHandler(lv_obj_t* obj, lv_event_t event) { + auto app = static_cast<Calculator*>(obj->user_data); + app->OnButtonEvent(obj, event); +} + +Calculator::~Calculator() { + lv_obj_clean(lv_scr_act()); +} + +constexpr const char* const buttonMap[] = { + "7", "8", "9", Symbols::backspace, "\n", "4", "5", "6", "+ -", "\n", "1", "2", "3", "* /", "\n", "0", ".", "(-)", "=", ""}; + +Calculator::Calculator() { + resultLabel = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_long_mode(resultLabel, LV_LABEL_LONG_CROP); + lv_label_set_align(resultLabel, LV_LABEL_ALIGN_RIGHT); + lv_label_set_text_fmt(resultLabel, "%" PRId64, result); + lv_obj_set_size(resultLabel, 200, 20); + lv_obj_set_pos(resultLabel, 10, 5); + + valueLabel = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_long_mode(valueLabel, LV_LABEL_LONG_CROP); + lv_label_set_align(valueLabel, LV_LABEL_ALIGN_RIGHT); + lv_label_set_text_fmt(valueLabel, "%" PRId64, value); + lv_obj_set_size(valueLabel, 200, 20); + lv_obj_set_pos(valueLabel, 10, 35); + + buttonMatrix = lv_btnmatrix_create(lv_scr_act(), nullptr); + buttonMatrix->user_data = this; + lv_obj_set_event_cb(buttonMatrix, eventHandler); + lv_btnmatrix_set_map(buttonMatrix, const_cast<const char**>(buttonMap)); + lv_btnmatrix_set_one_check(buttonMatrix, true); + lv_obj_set_size(buttonMatrix, 238, 180); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, Colors::bgAlt); + lv_obj_set_style_local_pad_inner(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_top(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_bottom(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_left(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_right(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_align(buttonMatrix, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + lv_obj_set_style_local_bg_opa(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_OPA_COVER); + lv_obj_set_style_local_bg_grad_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128); + lv_obj_set_style_local_bg_main_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128); +} + +void Calculator::OnButtonEvent(lv_obj_t* obj, lv_event_t event) { + if ((obj == buttonMatrix) && (event == LV_EVENT_PRESSED)) { + HandleInput(); + } +} + +void Calculator::HandleInput() { + const char* buttonText = lv_btnmatrix_get_active_btn_text(buttonMatrix); + + if (buttonText == nullptr) { + return; + } + + if ((equalSignPressedBefore && (*buttonText != '=')) || (error != Error::None)) { + ResetInput(); + UpdateOperation(); + } + + // we only compare the first char because it is enough + switch (*buttonText) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + // *buttonText is the first char in buttonText + // "- '0'" results in the int value of the char + uint8_t digit = (*buttonText) - '0'; + int8_t sign = (value < 0) ? -1 : 1; + + // if this is true, we already pressed the . button + if (offset < FIXED_POINT_OFFSET) { + value += sign * offset * digit; + offset /= 10; + } else if (value <= MAX_VALUE / 10) { + value *= 10; + value += sign * offset * digit; + } + } break; + + // unary minus + case '(': + value = -value; + break; + + case '.': + if (offset == FIXED_POINT_OFFSET) { + offset /= 10; + } + break; + + // for every operator we: + // - eval the current operator if value > FIXED_POINT_OFFSET + // - then set the new operator + // - + and - as well as * and / cycle on the same button + case '+': + if (value != 0) { + Eval(); + ResetInput(); + } + + switch (operation) { + case '+': + operation = '-'; + break; + case '-': + operation = ' '; + break; + default: + operation = '+'; + break; + } + UpdateOperation(); + break; + + case '*': + if (value != 0) { + Eval(); + ResetInput(); + } + + switch (operation) { + case '*': + operation = '/'; + break; + case '/': + operation = ' '; + break; + default: + operation = '*'; + break; + } + UpdateOperation(); + break; + + // this is a little hacky because it matches only the first char + case Symbols::backspace[0]: + if (value != 0) { + // delete one value digit + if (offset < FIXED_POINT_OFFSET) { + if (offset == 0) { + offset = 1; + } else { + offset *= 10; + } + } else { + value /= 10; + } + if (offset < FIXED_POINT_OFFSET) { + value -= value % (10 * offset); + } else { + value -= value % offset; + } + } else if (offset < FIXED_POINT_OFFSET) { + if (offset == 0) { + offset = 1; + } else { + offset *= 10; + } + } else { + // reset the result + result = 0; + } + + if (value == 0) { + operation = ' '; + UpdateOperation(); + } + break; + + case '=': + equalSignPressedBefore = true; + Eval(); + // If the operation is ' ' then we move the value to the result. + // We reset the input after this. + // This seems more convenient. + if (operation == ' ') { + ResetInput(); + } + break; + } + + UpdateValueLabel(); + UpdateResultLabel(); +} + +void Calculator::UpdateOperation() const { + switch (operation) { + case '+': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + case '-': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + case '*': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + case '/': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + default: + lv_btnmatrix_clear_btn_ctrl_all(buttonMatrix, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + } +} + +void Calculator::ResetInput() { + value = 0; + offset = FIXED_POINT_OFFSET; + operation = ' '; + equalSignPressedBefore = false; + error = Error::None; +} + +void Calculator::UpdateResultLabel() const { + int64_t integer = result / FIXED_POINT_OFFSET; + int64_t remainder = result % FIXED_POINT_OFFSET; + bool negative = (remainder < 0); + + if (remainder == 0) { + lv_label_set_text_fmt(resultLabel, "%" PRId64, integer); + return; + } + + if (remainder < 0) { + remainder = -remainder; + } + + uint8_t minWidth = N_DECIMALS; + + // cut "0"-digits on the right + while ((remainder > 0) && (remainder % 10 == 0)) { + remainder /= 10; + minWidth--; + } + + if ((integer == 0) && negative) { + lv_label_set_text_fmt(resultLabel, "-0.%0*" PRId64, minWidth, remainder); + } else { + lv_label_set_text_fmt(resultLabel, "%" PRId64 ".%0*" PRId64, integer, minWidth, remainder); + } +} + +void Calculator::UpdateValueLabel() { + switch (error) { + case Error::TooLarge: + lv_label_set_text_static(valueLabel, "too large"); + break; + case Error::ZeroDivision: + lv_label_set_text_static(valueLabel, "zero division"); + break; + case Error::None: + default: { + int64_t integer = value / FIXED_POINT_OFFSET; + int64_t remainder = value % FIXED_POINT_OFFSET; + bool negative = (remainder < 0); + + int64_t printRemainder = remainder < 0 ? -remainder : remainder; + + uint8_t minWidth = 0; + int64_t tmpOffset = offset; + + if (tmpOffset == 0) { + tmpOffset = 1; + minWidth = 1; + } + while (tmpOffset < FIXED_POINT_OFFSET) { + tmpOffset *= 10; + minWidth++; + } + minWidth--; + + for (uint8_t i = minWidth; i < N_DECIMALS; i++) { + printRemainder /= 10; + } + + if ((integer == 0) && negative) { + lv_label_set_text_fmt(valueLabel, "-0.%0*" PRId64, minWidth, printRemainder); + } else if (offset == FIXED_POINT_OFFSET) { + lv_label_set_text_fmt(valueLabel, "%" PRId64, integer); + } else if ((offset == (FIXED_POINT_OFFSET / 10)) && (remainder == 0)) { + lv_label_set_text_fmt(valueLabel, "%" PRId64 ".", integer); + } else { + lv_label_set_text_fmt(valueLabel, "%" PRId64 ".%0*" PRId64, integer, minWidth, printRemainder); + } + } break; + } +} + +// update the result based on value and operation +void Calculator::Eval() { + switch (operation) { + case ' ': + result = value; + break; + + case '+': + // check for overflow + if (((result > 0) && (value > (MAX_VALUE - result))) || ((result < 0) && (value < (MIN_VALUE - result)))) { + error = Error::TooLarge; + break; + } + + result += value; + break; + case '-': + // check for overflow + if (((result < 0) && (value > (MAX_VALUE + result))) || ((result > 0) && (value < (MIN_VALUE + result)))) { + error = Error::TooLarge; + break; + } + + result -= value; + break; + case '*': + // check for overflow + // while dividing we eliminate the fixed point offset + // therefore we have to multiply it again for the comparison with value + // we also assume here that MAX_VALUE == -MIN_VALUE + if ((result != 0) && (std::abs(value) > (FIXED_POINT_OFFSET * (MAX_VALUE / std::abs(result))))) { + error = Error::TooLarge; + break; + } + + result *= value; + // fixed point offset was multiplied too + result /= FIXED_POINT_OFFSET; + break; + case '/': + // check for zero division + if (value == 0) { + error = Error::ZeroDivision; + break; + } + + // fixed point offset will be divided too + result *= FIXED_POINT_OFFSET; + result /= value; + break; + + default: + break; + } +} diff --git a/src/displayapp/screens/Calculator.h b/src/displayapp/screens/Calculator.h new file mode 100644 index 00000000..9971f275 --- /dev/null +++ b/src/displayapp/screens/Calculator.h @@ -0,0 +1,83 @@ +#pragma once + +#include "displayapp/screens/Screen.h" +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" + +namespace { + constexpr int64_t powi(int64_t base, uint8_t exponent) { + int64_t value = 1; + while (exponent) { + value *= base; + exponent--; + } + return value; + } +} + +namespace Pinetime { + namespace Applications { + namespace Screens { + class Calculator : public Screen { + public: + ~Calculator() override; + + Calculator(); + + void OnButtonEvent(lv_obj_t* obj, lv_event_t event); + + private: + lv_obj_t* buttonMatrix {}; + lv_obj_t* valueLabel {}; + lv_obj_t* resultLabel {}; + + void Eval(); + void ResetInput(); + void HandleInput(); + void UpdateValueLabel(); + void UpdateResultLabel() const; + void UpdateOperation() const; + + // change this if you want to change the number of decimals + // keep in mind, that we only have 12 digits in total (see MAX_DIGITS) + // due to the fixed point implementation + static constexpr uint8_t N_DECIMALS = 3; + // this is the constant default offset + static constexpr int64_t FIXED_POINT_OFFSET = powi(10, N_DECIMALS); + // this is the current offset, may vary after pressing '.' + int64_t offset = FIXED_POINT_OFFSET; + + // the screen can show 12 chars + // but two are needed for '.' and '-' + static constexpr uint8_t MAX_DIGITS = 12; + static constexpr int64_t MAX_VALUE = powi(10, MAX_DIGITS) - 1; + // this is assumed in the multiplication overflow! + static constexpr int64_t MIN_VALUE = -MAX_VALUE; + + int64_t value = 0; + int64_t result = 0; + char operation = ' '; + bool equalSignPressedBefore = false; + + enum Error { + TooLarge, + ZeroDivision, + None, + }; + + Error error = Error::None; + }; + } + + template <> + struct AppTraits<Apps::Calculator> { + static constexpr Apps app = Apps::Calculator; + static constexpr const char* icon = Screens::Symbols::calculator; + + static Screens::Screen* Create(AppControllers& /* controllers */) { + return new Screens::Calculator(); + }; + }; + } +} diff --git a/src/displayapp/screens/CheckboxList.h b/src/displayapp/screens/CheckboxList.h index c208bc48..c6119970 100644 --- a/src/displayapp/screens/CheckboxList.h +++ b/src/displayapp/screens/CheckboxList.h @@ -1,6 +1,6 @@ #pragma once -#include "displayapp/Apps.h" +#include "displayapp/apps/Apps.h" #include "displayapp/screens/Screen.h" #include <array> #include <cstdint> diff --git a/src/displayapp/screens/Clock.cpp b/src/displayapp/screens/Clock.cpp deleted file mode 100644 index a03dc68b..00000000 --- a/src/displayapp/screens/Clock.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include "displayapp/screens/Clock.h" - -#include <lvgl/lvgl.h> -#include "components/battery/BatteryController.h" -#include "components/motion/MotionController.h" -#include "components/ble/BleController.h" -#include "components/ble/NotificationManager.h" -#include "components/settings/Settings.h" -#include "displayapp/DisplayApp.h" -#include "displayapp/screens/WatchFaceDigital.h" -#include "displayapp/screens/WatchFaceTerminal.h" -#include "displayapp/screens/WatchFaceInfineat.h" -#include "displayapp/screens/WatchFaceAnalog.h" -#include "displayapp/screens/WatchFacePineTimeStyle.h" -#include "displayapp/screens/WatchFaceCasioStyleG7710.h" - -using namespace Pinetime::Applications::Screens; - -Clock::Clock(Controllers::DateTime& dateTimeController, - const Controllers::Battery& batteryController, - const Controllers::Ble& bleController, - Controllers::NotificationManager& notificationManager, - Controllers::Settings& settingsController, - Controllers::HeartRateController& heartRateController, - Controllers::MotionController& motionController, - Controllers::FS& filesystem) - : dateTimeController {dateTimeController}, - batteryController {batteryController}, - bleController {bleController}, - notificationManager {notificationManager}, - settingsController {settingsController}, - heartRateController {heartRateController}, - motionController {motionController}, - filesystem {filesystem}, - screen {[this, &settingsController]() { - switch (settingsController.GetClockFace()) { - case 0: - return WatchFaceDigitalScreen(); - break; - case 1: - return WatchFaceAnalogScreen(); - break; - case 2: - return WatchFacePineTimeStyleScreen(); - break; - case 3: - return WatchFaceTerminalScreen(); - break; - case 4: - return WatchFaceInfineatScreen(); - break; - case 5: - return WatchFaceCasioStyleG7710(); - break; - } - return WatchFaceDigitalScreen(); - }()} { - settingsController.SetAppMenu(0); -} - -Clock::~Clock() { - lv_obj_clean(lv_scr_act()); -} - -bool Clock::OnTouchEvent(Pinetime::Applications::TouchEvents event) { - return screen->OnTouchEvent(event); -} - -bool Clock::OnButtonPushed() { - return screen->OnButtonPushed(); -} - -std::unique_ptr<Screen> Clock::WatchFaceDigitalScreen() { - return std::make_unique<Screens::WatchFaceDigital>(dateTimeController, - batteryController, - bleController, - notificationManager, - settingsController, - heartRateController, - motionController); -} - -std::unique_ptr<Screen> Clock::WatchFaceAnalogScreen() { - return std::make_unique<Screens::WatchFaceAnalog>(dateTimeController, - batteryController, - bleController, - notificationManager, - settingsController); -} - -std::unique_ptr<Screen> Clock::WatchFacePineTimeStyleScreen() { - return std::make_unique<Screens::WatchFacePineTimeStyle>(dateTimeController, - batteryController, - bleController, - notificationManager, - settingsController, - motionController); -} - -std::unique_ptr<Screen> Clock::WatchFaceTerminalScreen() { - return std::make_unique<Screens::WatchFaceTerminal>(dateTimeController, - batteryController, - bleController, - notificationManager, - settingsController, - heartRateController, - motionController); -} - -std::unique_ptr<Screen> Clock::WatchFaceInfineatScreen() { - return std::make_unique<Screens::WatchFaceInfineat>(dateTimeController, - batteryController, - bleController, - notificationManager, - settingsController, - motionController, - filesystem); -} - -std::unique_ptr<Screen> Clock::WatchFaceCasioStyleG7710() { - return std::make_unique<Screens::WatchFaceCasioStyleG7710>(dateTimeController, - batteryController, - bleController, - notificationManager, - settingsController, - heartRateController, - motionController, - filesystem); -} diff --git a/src/displayapp/screens/Clock.h b/src/displayapp/screens/Clock.h deleted file mode 100644 index 8c987fbb..00000000 --- a/src/displayapp/screens/Clock.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include <lvgl/src/lv_core/lv_obj.h> -#include <chrono> -#include <cstdint> -#include <memory> -#include <components/heartrate/HeartRateController.h> -#include "displayapp/screens/Screen.h" -#include "components/datetime/DateTimeController.h" - -namespace Pinetime { - namespace Controllers { - class Settings; - class Battery; - class Ble; - class NotificationManager; - class MotionController; - } - - namespace Applications { - namespace Screens { - class Clock : public Screen { - public: - Clock(Controllers::DateTime& dateTimeController, - const Controllers::Battery& batteryController, - const Controllers::Ble& bleController, - Controllers::NotificationManager& notificationManager, - Controllers::Settings& settingsController, - Controllers::HeartRateController& heartRateController, - Controllers::MotionController& motionController, - Controllers::FS& filesystem); - ~Clock() override; - - bool OnTouchEvent(TouchEvents event) override; - bool OnButtonPushed() override; - - private: - Controllers::DateTime& dateTimeController; - const Controllers::Battery& batteryController; - const Controllers::Ble& bleController; - Controllers::NotificationManager& notificationManager; - Controllers::Settings& settingsController; - Controllers::HeartRateController& heartRateController; - Controllers::MotionController& motionController; - Controllers::FS& filesystem; - - std::unique_ptr<Screen> screen; - std::unique_ptr<Screen> WatchFaceDigitalScreen(); - std::unique_ptr<Screen> WatchFaceAnalogScreen(); - std::unique_ptr<Screen> WatchFacePineTimeStyleScreen(); - std::unique_ptr<Screen> WatchFaceTerminalScreen(); - std::unique_ptr<Screen> WatchFaceInfineatScreen(); - std::unique_ptr<Screen> WatchFaceCasioStyleG7710(); - }; - } - } -} diff --git a/src/displayapp/screens/Dice.cpp b/src/displayapp/screens/Dice.cpp new file mode 100644 index 00000000..302c5f3f --- /dev/null +++ b/src/displayapp/screens/Dice.cpp @@ -0,0 +1,199 @@ +#include "displayapp/screens/Dice.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/Symbols.h" +#include "components/settings/Settings.h" +#include "components/motor/MotorController.h" +#include "components/motion/MotionController.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + lv_obj_t* MakeLabel(lv_font_t* font, + lv_color_t color, + lv_label_long_mode_t longMode, + uint8_t width, + lv_label_align_t labelAlignment, + const char* text, + lv_obj_t* reference, + lv_align_t alignment, + int8_t x, + int8_t y) { + lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font); + lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color); + lv_label_set_long_mode(label, longMode); + if (width != 0) { + lv_obj_set_width(label, width); + } + lv_label_set_align(label, labelAlignment); + lv_label_set_text(label, text); + lv_obj_align(label, reference, alignment, x, y); + return label; + } + + void btnRollEventHandler(lv_obj_t* obj, lv_event_t event) { + auto* screen = static_cast<Dice*>(obj->user_data); + if (event == LV_EVENT_CLICKED) { + screen->Roll(); + } + } +} + +Dice::Dice(Controllers::MotionController& motionController, + Controllers::MotorController& motorController, + Controllers::Settings& settingsController) + : motorController {motorController}, motionController {motionController}, settingsController {settingsController} { + std::seed_seq sseq {static_cast<uint32_t>(xTaskGetTickCount()), + static_cast<uint32_t>(motionController.X()), + static_cast<uint32_t>(motionController.Y()), + static_cast<uint32_t>(motionController.Z())}; + gen.seed(sseq); + + lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20, + LV_COLOR_WHITE, + LV_LABEL_LONG_EXPAND, + 0, + LV_LABEL_ALIGN_CENTER, + "count", + lv_scr_act(), + LV_ALIGN_IN_TOP_LEFT, + 0, + 0); + + lv_obj_t* dCounterLabel = MakeLabel(&jetbrains_mono_bold_20, + LV_COLOR_WHITE, + LV_LABEL_LONG_EXPAND, + 0, + LV_LABEL_ALIGN_CENTER, + "sides", + nCounterLabel, + LV_ALIGN_OUT_RIGHT_MID, + 20, + 0); + + nCounter.Create(); + lv_obj_align(nCounter.GetObject(), nCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); + nCounter.SetValue(1); + + dCounter.Create(); + lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); + dCounter.SetValue(6); + + std::uniform_int_distribution<> distrib(0, resultColors.size() - 1); + currentColorIndex = distrib(gen); + + resultTotalLabel = MakeLabel(&jetbrains_mono_42, + resultColors[currentColorIndex], + LV_LABEL_LONG_BREAK, + 120, + LV_LABEL_ALIGN_CENTER, + "", + lv_scr_act(), + LV_ALIGN_IN_TOP_RIGHT, + 11, + 38); + resultIndividualLabel = MakeLabel(&jetbrains_mono_bold_20, + resultColors[currentColorIndex], + LV_LABEL_LONG_BREAK, + 90, + LV_LABEL_ALIGN_CENTER, + "", + resultTotalLabel, + LV_ALIGN_OUT_BOTTOM_MID, + 0, + 10); + + Roll(); + openingRoll = false; + + btnRoll = lv_btn_create(lv_scr_act(), nullptr); + btnRoll->user_data = this; + lv_obj_set_event_cb(btnRoll, btnRollEventHandler); + lv_obj_set_size(btnRoll, 240, 50); + lv_obj_align(btnRoll, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + btnRollLabel = MakeLabel(&jetbrains_mono_bold_20, + LV_COLOR_WHITE, + LV_LABEL_LONG_EXPAND, + 0, + LV_LABEL_ALIGN_CENTER, + Symbols::dice, + btnRoll, + LV_ALIGN_CENTER, + 0, + 0); + + // Spagetti code in motion controller: it only updates the shake speed when shake to wake is on... + enableShakeForDice = !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake); + if (enableShakeForDice) { + settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, true); + } + refreshTask = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); +} + +Dice::~Dice() { + // reset the shake to wake mode. + if (enableShakeForDice) { + settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, false); + enableShakeForDice = false; + } + lv_task_del(refreshTask); + lv_obj_clean(lv_scr_act()); +} + +void Dice::Refresh() { + // we only reset the hysteresis when at rest + if (motionController.CurrentShakeSpeed() >= settingsController.GetShakeThreshold()) { + if (currentRollHysteresis <= 0) { + // this timestamp is used for the screen timeout + lv_disp_get_next(NULL)->last_activity_time = lv_tick_get(); + + Roll(); + } + } else if (currentRollHysteresis > 0) + --currentRollHysteresis; +} + +void Dice::Roll() { + uint8_t resultIndividual; + uint16_t resultTotal = 0; + std::uniform_int_distribution<> distrib(1, dCounter.GetValue()); + + lv_label_set_text(resultIndividualLabel, ""); + + if (nCounter.GetValue() == 1) { + resultTotal = distrib(gen); + if (dCounter.GetValue() == 2) { + switch (resultTotal) { + case 1: + lv_label_set_text(resultIndividualLabel, "HEADS"); + break; + case 2: + lv_label_set_text(resultIndividualLabel, "TAILS"); + break; + } + } + } else { + for (uint8_t i = 0; i < nCounter.GetValue(); i++) { + resultIndividual = distrib(gen); + resultTotal += resultIndividual; + lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str()); + if (i < (nCounter.GetValue() - 1)) { + lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, "+"); + } + } + } + + lv_label_set_text_fmt(resultTotalLabel, "%d", resultTotal); + if (openingRoll == false) { + motorController.RunForDuration(30); + NextColor(); + currentRollHysteresis = rollHysteresis; + } +} + +void Dice::NextColor() { + currentColorIndex = (currentColorIndex + 1) % resultColors.size(); + lv_obj_set_style_local_text_color(resultTotalLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]); + lv_obj_set_style_local_text_color(resultIndividualLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]); +} diff --git a/src/displayapp/screens/Dice.h b/src/displayapp/screens/Dice.h new file mode 100644 index 00000000..da91657d --- /dev/null +++ b/src/displayapp/screens/Dice.h @@ -0,0 +1,61 @@ +#pragma once + +#include "displayapp/apps/Apps.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/widgets/Counter.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" + +#include <array> +#include <random> + +namespace Pinetime { + namespace Applications { + namespace Screens { + class Dice : public Screen { + public: + Dice(Controllers::MotionController& motionController, + Controllers::MotorController& motorController, + Controllers::Settings& settingsController); + ~Dice() override; + void Roll(); + void Refresh() override; + + private: + lv_obj_t* btnRoll; + lv_obj_t* btnRollLabel; + lv_obj_t* resultTotalLabel; + lv_obj_t* resultIndividualLabel; + lv_task_t* refreshTask; + bool enableShakeForDice = false; + + std::mt19937 gen; + + std::array<lv_color_t, 3> resultColors = {LV_COLOR_YELLOW, LV_COLOR_MAGENTA, LV_COLOR_AQUA}; + uint8_t currentColorIndex; + void NextColor(); + + Widgets::Counter nCounter = Widgets::Counter(1, 9, jetbrains_mono_42); + Widgets::Counter dCounter = Widgets::Counter(2, 99, jetbrains_mono_42); + + bool openingRoll = true; + uint8_t currentRollHysteresis = 0; + static constexpr uint8_t rollHysteresis = 10; + + Controllers::MotorController& motorController; + Controllers::MotionController& motionController; + Controllers::Settings& settingsController; + }; + } + + template <> + struct AppTraits<Apps::Dice> { + static constexpr Apps app = Apps::Dice; + static constexpr const char* icon = Screens::Symbols::dice; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController); + }; + }; + } +} diff --git a/src/displayapp/screens/FirmwareUpdate.cpp b/src/displayapp/screens/FirmwareUpdate.cpp index c40240c9..7d00ef39 100644 --- a/src/displayapp/screens/FirmwareUpdate.cpp +++ b/src/displayapp/screens/FirmwareUpdate.cpp @@ -2,6 +2,7 @@ #include <lvgl/lvgl.h> #include "components/ble/BleController.h" #include "displayapp/DisplayApp.h" +#include "displayapp/InfiniTimeTheme.h" using namespace Pinetime::Applications::Screens; @@ -12,6 +13,9 @@ FirmwareUpdate::FirmwareUpdate(const Pinetime::Controllers::Ble& bleController) lv_obj_align(titleLabel, nullptr, LV_ALIGN_IN_TOP_MID, 0, 50); bar1 = lv_bar_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(bar1, LV_BAR_PART_BG, LV_STATE_DEFAULT, Colors::bgAlt); + lv_obj_set_style_local_bg_opa(bar1, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_OPA_100); + lv_obj_set_style_local_radius(bar1, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); lv_obj_set_size(bar1, 200, 30); lv_obj_align(bar1, nullptr, LV_ALIGN_CENTER, 0, 0); lv_bar_set_range(bar1, 0, 1000); @@ -75,7 +79,7 @@ void FirmwareUpdate::DisplayProgression() const { const uint32_t total = bleController.FirmwareUpdateTotalBytes(); const int16_t permille = current / (total / 1000); - lv_label_set_text_fmt(percentLabel, "%d %%", permille / 10); + lv_label_set_text_fmt(percentLabel, "%d%%", permille / 10); lv_bar_set_value(bar1, permille, LV_ANIM_OFF); } diff --git a/src/displayapp/screens/FirmwareValidation.cpp b/src/displayapp/screens/FirmwareValidation.cpp index 4a1b3d9f..2a9919d5 100644 --- a/src/displayapp/screens/FirmwareValidation.cpp +++ b/src/displayapp/screens/FirmwareValidation.cpp @@ -17,8 +17,8 @@ namespace { FirmwareValidation::FirmwareValidation(Pinetime::Controllers::FirmwareValidator& validator) : validator {validator} { labelVersion = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_fmt(labelVersion, - "Version : %lu.%lu.%lu\n" - "ShortRef : %s", + "Version: %lu.%lu.%lu\n" + "ShortRef: %s", Version::Major(), Version::Minor(), Version::Patch(), diff --git a/src/displayapp/screens/FlashLight.cpp b/src/displayapp/screens/FlashLight.cpp index 1b7cf39c..7e0caff1 100644 --- a/src/displayapp/screens/FlashLight.cpp +++ b/src/displayapp/screens/FlashLight.cpp @@ -15,8 +15,9 @@ namespace { } FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessController& brightnessController) - : systemTask {systemTask}, brightnessController {brightnessController} { + : wakeLock(systemTask), brightnessController {brightnessController} { + previousBrightnessLevel = brightnessController.Level(); brightnessController.Set(Controllers::BrightnessController::Levels::Low); flashLight = lv_label_create(lv_scr_act(), nullptr); @@ -46,13 +47,13 @@ FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessCo backgroundAction->user_data = this; lv_obj_set_event_cb(backgroundAction, EventHandler); - systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); + wakeLock.Lock(); } FlashLight::~FlashLight() { lv_obj_clean(lv_scr_act()); lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); + brightnessController.Set(previousBrightnessLevel); } void FlashLight::SetColors() { diff --git a/src/displayapp/screens/FlashLight.h b/src/displayapp/screens/FlashLight.h index 2b710ed5..00ef4a7e 100644 --- a/src/displayapp/screens/FlashLight.h +++ b/src/displayapp/screens/FlashLight.h @@ -3,6 +3,7 @@ #include "displayapp/screens/Screen.h" #include "components/brightness/BrightnessController.h" #include "systemtask/SystemTask.h" +#include "systemtask/WakeLock.h" #include <cstdint> #include <lvgl/lvgl.h> @@ -23,10 +24,11 @@ namespace Pinetime { void SetIndicators(); void SetColors(); - Pinetime::System::SystemTask& systemTask; + Pinetime::System::WakeLock wakeLock; Controllers::BrightnessController& brightnessController; Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High; + Controllers::BrightnessController::Levels previousBrightnessLevel; lv_obj_t* flashLight; lv_obj_t* backgroundAction; diff --git a/src/displayapp/screens/HeartRate.cpp b/src/displayapp/screens/HeartRate.cpp index f611fa26..1a84d349 100644 --- a/src/displayapp/screens/HeartRate.cpp +++ b/src/displayapp/screens/HeartRate.cpp @@ -29,7 +29,7 @@ namespace { } HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, System::SystemTask& systemTask) - : heartRateController {heartRateController}, systemTask {systemTask} { + : heartRateController {heartRateController}, wakeLock(systemTask) { bool isHrRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped; label_hr = lv_label_create(lv_scr_act(), nullptr); @@ -41,7 +41,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); } - lv_label_set_text_static(label_hr, "000"); + lv_label_set_text_static(label_hr, "---"); lv_obj_align(label_hr, nullptr, LV_ALIGN_CENTER, 0, -40); label_bpm = lv_label_create(lv_scr_act(), nullptr); @@ -63,7 +63,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst label_startStop = lv_label_create(btn_startStop, nullptr); UpdateStartStopButton(isHrRunning); if (isHrRunning) { - systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); + wakeLock.Lock(); } taskRefresh = lv_task_create(RefreshTaskCallback, 100, LV_TASK_PRIO_MID, this); @@ -72,7 +72,6 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst HeartRate::~HeartRate() { lv_task_del(taskRefresh); lv_obj_clean(lv_scr_act()); - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); } void HeartRate::Refresh() { @@ -82,10 +81,14 @@ void HeartRate::Refresh() { case Controllers::HeartRateController::States::NoTouch: case Controllers::HeartRateController::States::NotEnoughData: // case Controllers::HeartRateController::States::Stopped: - lv_label_set_text_static(label_hr, "000"); + lv_label_set_text_static(label_hr, "---"); break; default: - lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate()); + if (heartRateController.HeartRate() == 0) { + lv_label_set_text_static(label_hr, "---"); + } else { + lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate()); + } } lv_label_set_text_static(label_status, ToString(state)); @@ -97,12 +100,12 @@ void HeartRate::OnStartStopEvent(lv_event_t event) { if (heartRateController.State() == Controllers::HeartRateController::States::Stopped) { heartRateController.Start(); UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped); - systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); + wakeLock.Lock(); lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::highlight); } else { heartRateController.Stop(); UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped); - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); + wakeLock.Release(); lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); } } diff --git a/src/displayapp/screens/HeartRate.h b/src/displayapp/screens/HeartRate.h index 78ae63db..88b4918c 100644 --- a/src/displayapp/screens/HeartRate.h +++ b/src/displayapp/screens/HeartRate.h @@ -4,6 +4,8 @@ #include <chrono> #include "displayapp/screens/Screen.h" #include "systemtask/SystemTask.h" +#include "systemtask/WakeLock.h" +#include "Symbols.h" #include <lvgl/src/lv_core/lv_style.h> #include <lvgl/src/lv_core/lv_obj.h> @@ -26,7 +28,7 @@ namespace Pinetime { private: Controllers::HeartRateController& heartRateController; - Pinetime::System::SystemTask& systemTask; + Pinetime::System::WakeLock wakeLock; void UpdateStartStopButton(bool isRunning); lv_obj_t* label_hr; lv_obj_t* label_bpm; @@ -37,5 +39,15 @@ namespace Pinetime { lv_task_t* taskRefresh; }; } + + template <> + struct AppTraits<Apps::HeartRate> { + static constexpr Apps app = Apps::HeartRate; + static constexpr const char* icon = Screens::Symbols::heartBeat; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::HeartRate(controllers.heartRateController, *controllers.systemTask); + }; + }; } } diff --git a/src/displayapp/screens/InfiniPaint.h b/src/displayapp/screens/InfiniPaint.h index ec184c44..b1f9741a 100644 --- a/src/displayapp/screens/InfiniPaint.h +++ b/src/displayapp/screens/InfiniPaint.h @@ -5,6 +5,9 @@ #include <algorithm> // std::fill #include "displayapp/screens/Screen.h" #include "components/motor/MotorController.h" +#include "Symbols.h" +#include "displayapp/apps/Apps.h" +#include <displayapp/Controllers.h> namespace Pinetime { namespace Components { @@ -35,5 +38,15 @@ namespace Pinetime { uint8_t color = 2; }; } + + template <> + struct AppTraits<Apps::Paint> { + static constexpr Apps app = Apps::Paint; + static constexpr const char* icon = Screens::Symbols::paintbrush; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::InfiniPaint(controllers.lvgl, controllers.motorController); + }; + }; } } diff --git a/src/displayapp/screens/List.h b/src/displayapp/screens/List.h index 564229e6..17a25f82 100644 --- a/src/displayapp/screens/List.h +++ b/src/displayapp/screens/List.h @@ -5,7 +5,7 @@ #include <array> #include "displayapp/screens/Screen.h" #include "displayapp/widgets/PageIndicator.h" -#include "displayapp/Apps.h" +#include "displayapp/apps/Apps.h" #include "components/settings/Settings.h" #define MAXLISTITEMS 4 diff --git a/src/displayapp/screens/Metronome.cpp b/src/displayapp/screens/Metronome.cpp index 314fde73..6b758470 100644 --- a/src/displayapp/screens/Metronome.cpp +++ b/src/displayapp/screens/Metronome.cpp @@ -22,7 +22,7 @@ namespace { } Metronome::Metronome(Controllers::MotorController& motorController, System::SystemTask& systemTask) - : motorController {motorController}, systemTask {systemTask} { + : motorController {motorController}, wakeLock(systemTask) { bpmArc = lv_arc_create(lv_scr_act(), nullptr); bpmArc->user_data = this; @@ -72,7 +72,6 @@ Metronome::Metronome(Controllers::MotorController& motorController, System::Syst Metronome::~Metronome() { lv_task_del(taskRefresh); - systemTask.PushMessage(System::Messages::EnableSleeping); lv_obj_clean(lv_scr_act()); } @@ -128,12 +127,12 @@ void Metronome::OnEvent(lv_obj_t* obj, lv_event_t event) { metronomeStarted = !metronomeStarted; if (metronomeStarted) { lv_label_set_text_static(lblPlayPause, Symbols::pause); - systemTask.PushMessage(System::Messages::DisableSleeping); + wakeLock.Lock(); startTime = xTaskGetTickCount(); counter = 1; } else { lv_label_set_text_static(lblPlayPause, Symbols::play); - systemTask.PushMessage(System::Messages::EnableSleeping); + wakeLock.Release(); } } break; diff --git a/src/displayapp/screens/Metronome.h b/src/displayapp/screens/Metronome.h index 13b0d664..fab7ff87 100644 --- a/src/displayapp/screens/Metronome.h +++ b/src/displayapp/screens/Metronome.h @@ -1,8 +1,10 @@ #pragma once #include "systemtask/SystemTask.h" +#include "systemtask/WakeLock.h" #include "components/motor/MotorController.h" #include "displayapp/screens/Screen.h" +#include "Symbols.h" namespace Pinetime { namespace Applications { @@ -20,7 +22,7 @@ namespace Pinetime { TickType_t startTime = 0; TickType_t tappedTime = 0; Controllers::MotorController& motorController; - System::SystemTask& systemTask; + System::WakeLock wakeLock; int16_t bpm = 120; uint8_t bpb = 4; uint8_t counter = 1; @@ -36,5 +38,15 @@ namespace Pinetime { lv_task_t* taskRefresh; }; } + + template <> + struct AppTraits<Apps::Metronome> { + static constexpr Apps app = Apps::Metronome; + static constexpr const char* icon = Screens::Symbols::drum; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Metronome(controllers.motorController, *controllers.systemTask); + }; + }; } } diff --git a/src/displayapp/screens/Motion.cpp b/src/displayapp/screens/Motion.cpp index 87c55eea..ecbed317 100644 --- a/src/displayapp/screens/Motion.cpp +++ b/src/displayapp/screens/Motion.cpp @@ -53,9 +53,9 @@ void Motion::Refresh() { lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps()); lv_label_set_text_fmt(label, - "X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d#", - motionController.X() / 0x10, - motionController.Y() / 0x10, - motionController.Z() / 0x10); + "X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d# mg", + motionController.X(), + motionController.Y(), + motionController.Z()); lv_obj_align(label, nullptr, LV_ALIGN_IN_TOP_MID, 0, 10); } diff --git a/src/displayapp/screens/Motion.h b/src/displayapp/screens/Motion.h index e4cbe483..e13e068c 100644 --- a/src/displayapp/screens/Motion.h +++ b/src/displayapp/screens/Motion.h @@ -6,6 +6,8 @@ #include <lvgl/src/lv_core/lv_style.h> #include <lvgl/src/lv_core/lv_obj.h> #include <components/motion/MotionController.h> +#include "displayapp/Controllers.h" +#include "displayapp/apps/Apps.h" namespace Pinetime { namespace Applications { @@ -30,5 +32,15 @@ namespace Pinetime { lv_task_t* taskRefresh; }; } + + template <> + struct AppTraits<Apps::Motion> { + static constexpr Apps app = Apps::Motion; + static constexpr const char* icon = "M"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Motion(controllers.motionController); + }; + }; } } diff --git a/src/displayapp/screens/Music.h b/src/displayapp/screens/Music.h index 847c6e74..52253321 100644 --- a/src/displayapp/screens/Music.h +++ b/src/displayapp/screens/Music.h @@ -21,6 +21,9 @@ #include <lvgl/src/lv_core/lv_obj.h> #include <string> #include "displayapp/screens/Screen.h" +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" namespace Pinetime { namespace Controllers { @@ -82,5 +85,15 @@ namespace Pinetime { /** Watchapp */ }; } + + template <> + struct AppTraits<Apps::Music> { + static constexpr Apps app = Apps::Music; + static constexpr const char* icon = Screens::Symbols::music; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Music(*controllers.musicService); + }; + }; } } diff --git a/src/displayapp/screens/Navigation.cpp b/src/displayapp/screens/Navigation.cpp index 7baea09d..ee9f2a00 100644 --- a/src/displayapp/screens/Navigation.cpp +++ b/src/displayapp/screens/Navigation.cpp @@ -23,105 +23,166 @@ using namespace Pinetime::Applications::Screens; -LV_FONT_DECLARE(lv_font_navi_80) +/* Notes about the navigation icons : + * - Icons are generated from a TTF font converted in PNG images. Those images are all appended + * vertically into a single PNG images. Since LVGL support images width and height up to + * 2048 px, the icons needs to be split into 2 separate PNG pictures. More info in + * src/displayapp/fonts/README.md + * - To make the handling of those icons easier, they must all have the same width and height + * - Those PNG are then converted into BINARY format using the classical image generator + * (in src/resources/generate-img.py) + * - The array `iconMap` maps each icon with an index. This index corresponds to the position of + * the icon in the file. All index lower than 25 (`maxIconsPerFile`) represent icons located + * in the first file (navigation0.bin). All the other icons are located in the second file + * (navigation1.bin). Since all icons have the same height, this index must be multiplied by + * 80px (`iconHeight`) to get the actual position (in pixels) of the icon in the image. + * - This is how the images are laid out in the PNG files : + * *---------------* + * | ICON 0 | + * | FILE 0 | + * | INDEX = 0 | + * | PIXEL# = 0 | + * *---------------* + * | ICON 1 | + * | FILE 0 | + * | INDEX = 1 | + * | PIXEL# = -80 | + * *---------------* + * | ICON 2 | + * | FILE 0 | + * | INDEX = 2 | + * | PIXEL# = -160 | + * *---------------* + * | ... | + * *---------------* + * | ICON 25 | + * | FILE 1 | + * | INDEX = 25 | + * | PIXEL# = 0 | + * *---------------* + * | ICON 26 | + * | FILE 1 | + * | INDEX = 26 | + * | PIXEL# = -80 | + * *---------------* + * - The source images are located in `src/resources/navigation0.png` and `src/resources/navigation1.png` + */ namespace { - constexpr std::array<std::pair<const char*, const char*>, 86> m_iconMap = {{ - {"arrive-left", "\xEE\xA4\x81"}, - {"arrive-right", "\xEE\xA4\x82"}, - {"arrive-straight", "\xEE\xA4\x80"}, - {"arrive", "\xEE\xA4\x80"}, - {"close", "\xEE\xA4\x83"}, - {"continue-left", "\xEE\xA4\x85"}, - {"continue-right", "\xEE\xA4\x86"}, - {"continue-slight-left", "\xEE\xA4\x87"}, - {"continue-slight-right", "\xEE\xA4\x88"}, - {"continue-straight", "\xEE\xA4\x84"}, - {"continue-uturn", "\xEE\xA4\x89"}, - {"continue", "\xEE\xA4\x84"}, - {"depart-left", "\xEE\xA4\x8B"}, - {"depart-right", "\xEE\xA4\x8C"}, - {"depart-straight", "\xEE\xA4\x8A"}, - {"end-of-road-left", "\xEE\xA4\x8D"}, - {"end-of-road-right", "\xEE\xA4\x8E"}, - {"ferry", "\xEE\xA4\x8F"}, - {"flag", "\xEE\xA4\x90"}, - {"fork-left", "\xEE\xA4\x92"}, - {"fork-right", "\xEE\xA4\x93"}, - {"fork-slight-left", "\xEE\xA4\x94"}, - {"fork-slight-right", "\xEE\xA4\x95"}, - {"fork-straight", "\xEE\xA4\x96"}, - {"invalid", "\xEE\xA4\x84"}, - {"invalid-left", "\xEE\xA4\x85"}, - {"invalid-right", "\xEE\xA4\x86"}, - {"invalid-slight-left", "\xEE\xA4\x87"}, - {"invalid-slight-right", "\xEE\xA4\x88"}, - {"invalid-straight", "\xEE\xA4\x84"}, - {"invalid-uturn", "\xEE\xA4\x89"}, - {"merge-left", "\xEE\xA4\x97"}, - {"merge-right", "\xEE\xA4\x98"}, - {"merge-slight-left", "\xEE\xA4\x99"}, - {"merge-slight-right", "\xEE\xA4\x9A"}, - {"merge-straight", "\xEE\xA4\x84"}, - {"new-name-left", "\xEE\xA4\x85"}, - {"new-name-right", "\xEE\xA4\x86"}, - {"new-name-sharp-left", "\xEE\xA4\x9B"}, - {"new-name-sharp-right", "\xEE\xA4\x9C"}, - {"new-name-slight-left", "\xEE\xA4\x87"}, - {"new-name-slight-right", "\xEE\xA4\x88"}, - {"new-name-straight", "\xEE\xA4\x84"}, - {"notification-left", "\xEE\xA4\x85"}, - {"notification-right", "\xEE\xA4\x86"}, - {"notification-sharp-left", "\xEE\xA4\x9B"}, - {"notification-sharp-right", "\xEE\xA4\xA5"}, - {"notification-slight-left", "\xEE\xA4\x87"}, - {"notification-slight-right", "\xEE\xA4\x88"}, - {"notification-straight", "\xEE\xA4\x84"}, - {"off-ramp-left", "\xEE\xA4\x9D"}, - {"off-ramp-right", "\xEE\xA4\x9E"}, - {"off-ramp-slight-left", "\xEE\xA4\x9F"}, - {"off-ramp-slight-right", "\xEE\xA4\xA0"}, - {"on-ramp-left", "\xEE\xA4\x85"}, - {"on-ramp-right", "\xEE\xA4\x86"}, - {"on-ramp-sharp-left", "\xEE\xA4\x9B"}, - {"on-ramp-sharp-right", "\xEE\xA4\xA5"}, - {"on-ramp-slight-left", "\xEE\xA4\x87"}, - {"on-ramp-slight-right", "\xEE\xA4\x88"}, - {"on-ramp-straight", "\xEE\xA4\x84"}, - {"rotary", "\xEE\xA4\xA1"}, - {"rotary-left", "\xEE\xA4\xA2"}, - {"rotary-right", "\xEE\xA4\xA3"}, - {"rotary-sharp-left", "\xEE\xA4\xA4"}, - {"rotary-sharp-right", "\xEE\xA4\xA5"}, - {"rotary-slight-left", "\xEE\xA4\xA6"}, - {"rotary-slight-right", "\xEE\xA4\xA7"}, - {"rotary-straight", "\xEE\xA4\xA8"}, - {"roundabout", "\xEE\xA4\xA1"}, - {"roundabout-left", "\xEE\xA4\xA2"}, - {"roundabout-right", "\xEE\xA4\xA3"}, - {"roundabout-sharp-left", "\xEE\xA4\xA4"}, - {"roundabout-sharp-right", "\xEE\xA4\xA5"}, - {"roundabout-slight-left", "\xEE\xA4\xA6"}, - {"roundabout-slight-right", "\xEE\xA4\xA7"}, - {"roundabout-straight", "\xEE\xA4\xA8"}, - {"turn-left", "\xEE\xA4\x85"}, - {"turn-right", "\xEE\xA4\x86"}, - {"turn-sharp-left", "\xEE\xA4\x9B"}, - {"turn-sharp-right", "\xEE\xA4\xA5"}, - {"turn-slight-left", "\xEE\xA4\x87"}, - {"turn-slight-right", "\xEE\xA4\x88"}, - {"turn-straight", "\xEE\xA4\x84"}, - {"updown", "\xEE\xA4\xA9"}, - {"uturn", "\xEE\xA4\x89"}, + struct Icon { + const char* fileName; + int16_t offset; + }; + + constexpr uint16_t iconHeight = -80; + constexpr uint8_t flagIndex = 18; + constexpr uint8_t maxIconsPerFile = 25; + const char* iconsFile0 = "F:/images/navigation0.bin"; + const char* iconsFile1 = "F:/images/navigation1.bin"; + + constexpr std::array<std::pair<const char*, uint8_t>, 86> iconMap = {{ + {"arrive-left", 1}, + {"arrive-right", 2}, + {"arrive-straight", 0}, + {"arrive", 0}, + {"close", 3}, + {"continue-left", 5}, + {"continue-right", 6}, + {"continue-slight-left", 7}, + {"continue-slight-right", 8}, + {"continue-straight", 4}, + {"continue-uturn", 9}, + {"continue", 4}, + {"depart-left", 11}, + {"depart-right", 12}, + {"depart-straight", 10}, + {"end-of-road-left", 13}, + {"end-of-road-right", 14}, + {"ferry", 15}, + {"flag", 16}, + {"fork-left", 18}, + {"fork-right", 19}, + {"fork-slight-left", 20}, + {"fork-slight-right", 21}, + {"fork-straight", 22}, + {"invalid", 4}, + {"invalid-left", 5}, + {"invalid-right", 6}, + {"invalid-slight-left", 7}, + {"invalid-slight-right", 8}, + {"invalid-straight", 4}, + {"invalid-uturn", 9}, + {"merge-left", 23}, + {"merge-right", 24}, + {"merge-slight-left", 25}, + {"merge-slight-right", 26}, + {"merge-straight", 4}, + {"new-name-left", 5}, + {"new-name-right", 6}, + {"new-name-sharp-left", 27}, + {"new-name-sharp-right", 28}, + {"new-name-slight-left", 7}, + {"new-name-slight-right", 8}, + {"new-name-straight", 4}, + {"notification-left", 5}, + {"notification-right", 6}, + {"notification-sharp-left", 27}, + {"notification-sharp-right", 37}, + {"notification-slight-left", 7}, + {"notification-slight-right", 8}, + {"notification-straight", 4}, + {"off-ramp-left", 29}, + {"off-ramp-right", 30}, + {"off-ramp-slight-left", 31}, + {"off-ramp-slight-right", 32}, + {"on-ramp-left", 5}, + {"on-ramp-right", 6}, + {"on-ramp-sharp-left", 27}, + {"on-ramp-sharp-right", 37}, + {"on-ramp-slight-left", 7}, + {"on-ramp-slight-right", 8}, + {"on-ramp-straight", 4}, + {"rotary", 33}, + {"rotary-left", 34}, + {"rotary-right", 35}, + {"rotary-sharp-left", 36}, + {"rotary-sharp-right", 37}, + {"rotary-slight-left", 38}, + {"rotary-slight-right", 39}, + {"rotary-straight", 40}, + {"roundabout", 33}, + {"roundabout-left", 34}, + {"roundabout-right", 35}, + {"roundabout-sharp-left", 36}, + {"roundabout-sharp-right", 37}, + {"roundabout-slight-left", 38}, + {"roundabout-slight-right", 39}, + {"roundabout-straight", 40}, + {"turn-left", 5}, + {"turn-right", 6}, + {"turn-sharp-left", 27}, + {"turn-sharp-right", 37}, + {"turn-slight-left", 7}, + {"turn-slight-right", 8}, + {"turn-straight", 4}, + {"updown", 41}, + {"uturn", 9}, }}; - const char* iconForName(const std::string& icon) { - for (auto iter : m_iconMap) { + Icon GetIcon(uint8_t index) { + if (index < maxIconsPerFile) { + return {iconsFile0, static_cast<int16_t>(iconHeight * index)}; + } + return {iconsFile1, static_cast<int16_t>(iconHeight * (index - maxIconsPerFile))}; + } + + Icon GetIcon(const std::string& icon) { + for (const auto& iter : iconMap) { if (iter.first == icon) { - return iter.second; + return GetIcon(iter.second); } } - return "\xEE\xA4\x90"; + return GetIcon(flagIndex); } } @@ -130,27 +191,33 @@ namespace { * */ Navigation::Navigation(Pinetime::Controllers::NavigationService& nav) : navService(nav) { - - imgFlag = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(imgFlag, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_navi_80); - lv_obj_set_style_local_text_color(imgFlag, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_CYAN); - lv_label_set_text_static(imgFlag, iconForName("flag")); + const auto& image = GetIcon("flag"); + imgFlag = lv_img_create(lv_scr_act(), nullptr); + lv_img_set_auto_size(imgFlag, false); + lv_obj_set_size(imgFlag, 80, 80); + lv_img_set_src(imgFlag, image.fileName); + lv_img_set_offset_x(imgFlag, 0); + lv_img_set_offset_y(imgFlag, image.offset); + lv_obj_set_style_local_image_recolor_opa(imgFlag, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_COVER); + lv_obj_set_style_local_image_recolor(imgFlag, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_CYAN); lv_obj_align(imgFlag, nullptr, LV_ALIGN_CENTER, 0, -60); txtNarrative = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_BREAK); + lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_DOT); lv_obj_set_width(txtNarrative, LV_HOR_RES); + lv_obj_set_height(txtNarrative, 80); lv_label_set_text_static(txtNarrative, "Navigation"); lv_label_set_align(txtNarrative, LV_LABEL_ALIGN_CENTER); - lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 10); + lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 30); txtManDist = lv_label_create(lv_scr_act(), nullptr); lv_label_set_long_mode(txtManDist, LV_LABEL_LONG_BREAK); lv_obj_set_style_local_text_color(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN); + lv_obj_set_style_local_text_font(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); lv_obj_set_width(txtManDist, LV_HOR_RES); lv_label_set_text_static(txtManDist, "--M"); lv_label_set_align(txtManDist, LV_LABEL_ALIGN_CENTER); - lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 60); + lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 90); // Route Progress barProgress = lv_bar_create(lv_scr_act(), nullptr); @@ -173,7 +240,11 @@ Navigation::~Navigation() { void Navigation::Refresh() { if (flag != navService.getFlag()) { flag = navService.getFlag(); - lv_label_set_text_static(imgFlag, iconForName(flag)); + const auto& image = GetIcon(flag); + lv_img_set_src(imgFlag, image.fileName); + lv_obj_set_style_local_image_recolor_opa(imgFlag, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_COVER); + lv_obj_set_style_local_image_recolor(imgFlag, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_CYAN); + lv_img_set_offset_y(imgFlag, image.offset); } if (narrative != navService.getNarrative()) { @@ -196,3 +267,19 @@ void Navigation::Refresh() { } } } + +bool Navigation::IsAvailable(Pinetime::Controllers::FS& filesystem) { + lfs_file file = {}; + + if (filesystem.FileOpen(&file, "/images/navigation0.bin", LFS_O_RDONLY) < 0) { + return false; + } + filesystem.FileClose(&file); + + if (filesystem.FileOpen(&file, "/images/navigation1.bin", LFS_O_RDONLY) < 0) { + return false; + } + filesystem.FileClose(&file); + + return true; +} diff --git a/src/displayapp/screens/Navigation.h b/src/displayapp/screens/Navigation.h index 6495edb2..5c7a0429 100644 --- a/src/displayapp/screens/Navigation.h +++ b/src/displayapp/screens/Navigation.h @@ -22,20 +22,25 @@ #include <string> #include "displayapp/screens/Screen.h" #include <array> +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" namespace Pinetime { namespace Controllers { class NavigationService; + class FS; } namespace Applications { namespace Screens { class Navigation : public Screen { public: - Navigation(Pinetime::Controllers::NavigationService& nav); + explicit Navigation(Pinetime::Controllers::NavigationService& nav); ~Navigation() override; void Refresh() override; + static bool IsAvailable(Pinetime::Controllers::FS& filesystem); private: lv_obj_t* imgFlag; @@ -48,10 +53,20 @@ namespace Pinetime { std::string flag; std::string narrative; std::string manDist; - int progress; + int progress = 0; lv_task_t* taskRefresh; }; } + + template <> + struct AppTraits<Apps::Navigation> { + static constexpr Apps app = Apps::Navigation; + static constexpr const char* icon = Screens::Symbols::map; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Navigation(*controllers.navigationService); + }; + }; } } diff --git a/src/displayapp/screens/Notifications.cpp b/src/displayapp/screens/Notifications.cpp index 037c43a7..837c4683 100644 --- a/src/displayapp/screens/Notifications.cpp +++ b/src/displayapp/screens/Notifications.cpp @@ -20,7 +20,7 @@ Notifications::Notifications(DisplayApp* app, notificationManager {notificationManager}, alertNotificationService {alertNotificationService}, motorController {motorController}, - systemTask {systemTask}, + wakeLock(systemTask), mode {mode} { notificationManager.ClearNewNotificationFlag(); @@ -40,7 +40,7 @@ Notifications::Notifications(DisplayApp* app, validDisplay = false; } if (mode == Modes::Preview) { - systemTask.PushMessage(System::Messages::DisableSleeping); + wakeLock.Lock(); if (notification.category == Controllers::NotificationManager::Categories::IncomingCall) { motorController.StartRinging(); } else { @@ -65,7 +65,6 @@ Notifications::~Notifications() { lv_task_del(taskRefresh); // make sure we stop any vibrations before exiting motorController.StopRinging(); - systemTask.PushMessage(System::Messages::EnableSleeping); lv_obj_clean(lv_scr_act()); } @@ -82,7 +81,6 @@ void Notifications::Refresh() { } else if (mode == Modes::Preview && dismissingNotification) { running = false; - currentItem = std::make_unique<NotificationItem>(alertNotificationService, motorController); } else if (dismissingNotification) { dismissingNotification = false; @@ -113,15 +111,15 @@ void Notifications::Refresh() { alertNotificationService, motorController); } else { - currentItem = std::make_unique<NotificationItem>(alertNotificationService, motorController); + running = false; } } - running = currentItem->IsRunning() && running; + running = running && currentItem->IsRunning(); } void Notifications::OnPreviewInteraction() { - systemTask.PushMessage(System::Messages::EnableSleeping); + wakeLock.Release(); motorController.StopRinging(); if (timeoutLine != nullptr) { lv_obj_del(timeoutLine); @@ -173,7 +171,9 @@ bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) { } else if (nextMessage.valid) { currentId = nextMessage.id; } else { - // don't update id, won't be found be refresh and try to load latest message or no message box + // don't update id, notification manager will try to fetch + // but not find it. Refresh will try to load latest message + // or dismiss to watchface } DismissToBlack(); return true; @@ -246,8 +246,8 @@ namespace { Notifications::NotificationItem::NotificationItem(Pinetime::Controllers::AlertNotificationService& alertNotificationService, Pinetime::Controllers::MotorController& motorController) - : NotificationItem("Notification", - "No notification to display", + : NotificationItem("Notifications", + "No notifications to display", 0, Controllers::NotificationManager::Categories::Unknown, 0, diff --git a/src/displayapp/screens/Notifications.h b/src/displayapp/screens/Notifications.h index 114316b3..8488dc5b 100644 --- a/src/displayapp/screens/Notifications.h +++ b/src/displayapp/screens/Notifications.h @@ -8,6 +8,7 @@ #include "components/ble/NotificationManager.h" #include "components/motor/MotorController.h" #include "systemtask/SystemTask.h" +#include "systemtask/WakeLock.h" namespace Pinetime { namespace Controllers { @@ -73,7 +74,7 @@ namespace Pinetime { Pinetime::Controllers::NotificationManager& notificationManager; Pinetime::Controllers::AlertNotificationService& alertNotificationService; Pinetime::Controllers::MotorController& motorController; - System::SystemTask& systemTask; + System::WakeLock wakeLock; Modes mode = Modes::Normal; std::unique_ptr<NotificationItem> currentItem; Pinetime::Controllers::NotificationManager::Notification::Id currentId; diff --git a/src/displayapp/screens/Paddle.h b/src/displayapp/screens/Paddle.h index 33dac191..586cccf4 100644 --- a/src/displayapp/screens/Paddle.h +++ b/src/displayapp/screens/Paddle.h @@ -3,6 +3,9 @@ #include <lvgl/lvgl.h> #include <cstdint> #include "displayapp/screens/Screen.h" +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" namespace Pinetime { namespace Components { @@ -45,5 +48,15 @@ namespace Pinetime { lv_task_t* taskRefresh; }; } + + template <> + struct AppTraits<Apps::Paddle> { + static constexpr Apps app = Apps::Paddle; + static constexpr const char* icon = Screens::Symbols::paddle; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Paddle(controllers.lvgl); + }; + }; } } diff --git a/src/displayapp/screens/Screen.h b/src/displayapp/screens/Screen.h index 09bd6131..9f6e0ede 100644 --- a/src/displayapp/screens/Screen.h +++ b/src/displayapp/screens/Screen.h @@ -9,41 +9,6 @@ namespace Pinetime { class DisplayApp; namespace Screens { - - template <class T> - class DirtyValue { - public: - DirtyValue() = default; // Use NSDMI - - explicit DirtyValue(T const& v) : value {v} { - } // Use MIL and const-lvalue-ref - - bool IsUpdated() { - if (this->isUpdated) { - this->isUpdated = false; - return true; - } - return false; - } - - T const& Get() { - this->isUpdated = false; - return value; - } // never expose a non-const lvalue-ref - - DirtyValue& operator=(const T& other) { - if (this->value != other) { - this->value = other; - this->isUpdated = true; - } - return *this; - } - - private: - T value {}; // NSDMI - default initialise type - bool isUpdated {true}; // NSDMI - use brace initialisation - }; - class Screen { private: virtual void Refresh() { diff --git a/src/displayapp/screens/Steps.h b/src/displayapp/screens/Steps.h index 5dc07eff..6443582f 100644 --- a/src/displayapp/screens/Steps.h +++ b/src/displayapp/screens/Steps.h @@ -4,6 +4,9 @@ #include <lvgl/lvgl.h> #include "displayapp/screens/Screen.h" #include <components/motion/MotionController.h> +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" namespace Pinetime { @@ -39,5 +42,15 @@ namespace Pinetime { lv_task_t* taskRefresh; }; } + + template <> + struct AppTraits<Apps::Steps> { + static constexpr Apps app = Apps::Steps; + static constexpr const char* icon = Screens::Symbols::shoe; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Steps(controllers.motionController, controllers.settingsController); + }; + }; } } diff --git a/src/displayapp/screens/StopWatch.cpp b/src/displayapp/screens/StopWatch.cpp index 72904c88..ff852beb 100644 --- a/src/displayapp/screens/StopWatch.cpp +++ b/src/displayapp/screens/StopWatch.cpp @@ -12,8 +12,9 @@ namespace { const int hundredths = (timeElapsedCentis % 100); const int secs = (timeElapsedCentis / 100) % 60; - const int mins = (timeElapsedCentis / 100) / 60; - return TimeSeparated_t {mins, secs, hundredths}; + const int mins = ((timeElapsedCentis / 100) / 60) % 60; + const int hours = ((timeElapsedCentis / 100) / 60) / 60; + return TimeSeparated_t {hours, mins, secs, hundredths}; } void play_pause_event_handler(lv_obj_t* obj, lv_event_t event) { @@ -33,7 +34,7 @@ namespace { constexpr TickType_t blinkInterval = pdMS_TO_TICKS(1000); } -StopWatch::StopWatch(System::SystemTask& systemTask) : systemTask {systemTask} { +StopWatch::StopWatch(System::SystemTask& systemTask) : wakeLock(systemTask) { static constexpr uint8_t btnWidth = 115; static constexpr uint8_t btnHeight = 80; btnPlayPause = lv_btn_create(lv_scr_act(), nullptr); @@ -78,7 +79,6 @@ StopWatch::StopWatch(System::SystemTask& systemTask) : systemTask {systemTask} { StopWatch::~StopWatch() { lv_task_del(taskRefresh); - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); lv_obj_clean(lv_scr_act()); } @@ -110,6 +110,12 @@ void StopWatch::SetInterfaceStopped() { lv_label_set_text_static(time, "00:00"); lv_label_set_text_static(msecTime, "00"); + if (isHoursLabelUpdated) { + lv_obj_set_style_local_text_font(time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76); + lv_obj_realign(time); + isHoursLabelUpdated = false; + } + lv_label_set_text_static(lapText, ""); lv_label_set_text_static(txtPlayPause, Symbols::play); lv_label_set_text_static(txtStopLap, Symbols::lapsFlag); @@ -128,7 +134,7 @@ void StopWatch::Start() { SetInterfaceRunning(); startTime = xTaskGetTickCount(); currentState = States::Running; - systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); + wakeLock.Lock(); } void StopWatch::Pause() { @@ -138,7 +144,7 @@ void StopWatch::Pause() { oldTimeElapsed = laps[lapsDone]; blinkTime = xTaskGetTickCount() + blinkInterval; currentState = States::Halted; - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); + wakeLock.Release(); } void StopWatch::Refresh() { @@ -146,7 +152,16 @@ void StopWatch::Refresh() { laps[lapsDone] = oldTimeElapsed + xTaskGetTickCount() - startTime; TimeSeparated_t currentTimeSeparated = convertTicksToTimeSegments(laps[lapsDone]); - lv_label_set_text_fmt(time, "%02d:%02d", currentTimeSeparated.mins, currentTimeSeparated.secs); + if (currentTimeSeparated.hours == 0) { + lv_label_set_text_fmt(time, "%02d:%02d", currentTimeSeparated.mins, currentTimeSeparated.secs); + } else { + lv_label_set_text_fmt(time, "%02d:%02d:%02d", currentTimeSeparated.hours, currentTimeSeparated.mins, currentTimeSeparated.secs); + if (!isHoursLabelUpdated) { + lv_obj_set_style_local_text_font(time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); + lv_obj_realign(time); + isHoursLabelUpdated = true; + } + } lv_label_set_text_fmt(msecTime, "%02d", currentTimeSeparated.hundredths); } else if (currentState == States::Halted) { const TickType_t currentTime = xTaskGetTickCount(); @@ -182,8 +197,12 @@ void StopWatch::stopLapBtnEventHandler() { continue; } TimeSeparated_t times = convertTicksToTimeSegments(laps[i]); - char buffer[16]; - sprintf(buffer, "#%2d %2d:%02d.%02d\n", i + 1, times.mins, times.secs, times.hundredths); + char buffer[17]; + if (times.hours == 0) { + snprintf(buffer, sizeof(buffer), "#%2d %2d:%02d.%02d\n", i + 1, times.mins, times.secs, times.hundredths); + } else { + snprintf(buffer, sizeof(buffer), "#%2d %2d:%02d:%02d.%02d\n", i + 1, times.hours, times.mins, times.secs, times.hundredths); + } lv_label_ins_text(lapText, LV_LABEL_POS_LAST, buffer); } } else if (currentState == States::Halted) { diff --git a/src/displayapp/screens/StopWatch.h b/src/displayapp/screens/StopWatch.h index 409e3a19..55a178dc 100644 --- a/src/displayapp/screens/StopWatch.h +++ b/src/displayapp/screens/StopWatch.h @@ -7,48 +7,68 @@ #include "portmacro_cmsis.h" #include "systemtask/SystemTask.h" +#include "systemtask/WakeLock.h" +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" -namespace Pinetime::Applications::Screens { +namespace Pinetime { + namespace Applications { + namespace Screens { - enum class States { Init, Running, Halted }; + enum class States { Init, Running, Halted }; - struct TimeSeparated_t { - int mins; - int secs; - int hundredths; - }; + struct TimeSeparated_t { + int hours; + int mins; + int secs; + int hundredths; + }; - class StopWatch : public Screen { - public: - explicit StopWatch(System::SystemTask& systemTask); - ~StopWatch() override; - void Refresh() override; + class StopWatch : public Screen { + public: + explicit StopWatch(System::SystemTask& systemTask); + ~StopWatch() override; + void Refresh() override; - void playPauseBtnEventHandler(); - void stopLapBtnEventHandler(); - bool OnButtonPushed() override; + void playPauseBtnEventHandler(); + void stopLapBtnEventHandler(); + bool OnButtonPushed() override; - private: - void SetInterfacePaused(); - void SetInterfaceRunning(); - void SetInterfaceStopped(); + private: + void SetInterfacePaused(); + void SetInterfaceRunning(); + void SetInterfaceStopped(); - void Reset(); - void Start(); - void Pause(); + void Reset(); + void Start(); + void Pause(); - Pinetime::System::SystemTask& systemTask; - States currentState = States::Init; - TickType_t startTime; - TickType_t oldTimeElapsed = 0; - TickType_t blinkTime = 0; - static constexpr int maxLapCount = 20; - TickType_t laps[maxLapCount + 1]; - static constexpr int displayedLaps = 2; - int lapsDone = 0; - lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap, *txtPlayPause, *txtStopLap; - lv_obj_t* lapText; + Pinetime::System::WakeLock wakeLock; + States currentState = States::Init; + TickType_t startTime; + TickType_t oldTimeElapsed = 0; + TickType_t blinkTime = 0; + static constexpr int maxLapCount = 20; + TickType_t laps[maxLapCount + 1]; + static constexpr int displayedLaps = 2; + int lapsDone = 0; + lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap, *txtPlayPause, *txtStopLap; + lv_obj_t* lapText; + bool isHoursLabelUpdated = false; - lv_task_t* taskRefresh; - }; + lv_task_t* taskRefresh; + }; + } + + template <> + struct AppTraits<Apps::StopWatch> { + static constexpr Apps app = Apps::StopWatch; + static constexpr const char* icon = Screens::Symbols::stopWatch; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::StopWatch(*controllers.systemTask); + }; + }; + } } diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 934cdc3f..40699b3d 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -11,6 +11,7 @@ namespace Pinetime { static constexpr const char* plug = "\xEF\x87\xA6"; static constexpr const char* shoe = "\xEF\x95\x8B"; static constexpr const char* clock = "\xEF\x80\x97"; + static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* info = "\xEF\x84\xA9"; static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; @@ -34,9 +35,24 @@ namespace Pinetime { static constexpr const char* hourGlass = "\xEF\x89\x92"; static constexpr const char* lapsFlag = "\xEF\x80\xA4"; static constexpr const char* drum = "\xEF\x95\xA9"; + static constexpr const char* dice = "\xEF\x94\xA2"; static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; + static constexpr const char* calculator = "\xEF\x87\xAC"; + static constexpr const char* backspace = "\xEF\x95\x9A"; + + // fontawesome_weathericons.c + // static constexpr const char* sun = "\xEF\x86\x85"; + static constexpr const char* cloudSun = "\xEF\x9B\x84"; + static constexpr const char* cloudSunRain = "\xEF\x9D\x83"; + static constexpr const char* cloudShowersHeavy = "\xEF\x9D\x80"; + static constexpr const char* smog = "\xEF\x9D\x9F"; + static constexpr const char* cloud = "\xEF\x83\x82"; + static constexpr const char* cloudMeatball = "\xEF\x9C\xBB"; + static constexpr const char* bolt = "\xEF\x83\xA7"; + static constexpr const char* snowflake = "\xEF\x8B\x9C"; + static constexpr const char* ban = "\xEF\x81\x9E"; // lv_font_sys_48.c static constexpr const char* settings = "\xEE\xA2\xB8"; diff --git a/src/displayapp/screens/SystemInfo.cpp b/src/displayapp/screens/SystemInfo.cpp index 511ecf50..2392f3be 100644 --- a/src/displayapp/screens/SystemInfo.cpp +++ b/src/displayapp/screens/SystemInfo.cpp @@ -38,15 +38,16 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app, const Pinetime::Controllers::Ble& bleController, const Pinetime::Drivers::Watchdog& watchdog, Pinetime::Controllers::MotionController& motionController, - const Pinetime::Drivers::Cst816S& touchPanel) - : app {app}, - dateTimeController {dateTimeController}, + const Pinetime::Drivers::Cst816S& touchPanel, + const Pinetime::Drivers::SpiNorFlash& spiNorFlash) + : dateTimeController {dateTimeController}, batteryController {batteryController}, brightnessController {brightnessController}, bleController {bleController}, watchdog {watchdog}, motionController {motionController}, touchPanel {touchPanel}, + spiNorFlash {spiNorFlash}, screens {app, 0, {[this]() -> std::unique_ptr<Screen> { @@ -101,24 +102,24 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen1() { std::unique_ptr<Screen> SystemInfo::CreateScreen2() { auto batteryPercent = batteryController.PercentRemaining(); const auto* resetReason = [this]() { - switch (watchdog.ResetReason()) { - case Drivers::Watchdog::ResetReasons::Watchdog: + switch (watchdog.GetResetReason()) { + case Drivers::Watchdog::ResetReason::Watchdog: return "wtdg"; - case Drivers::Watchdog::ResetReasons::HardReset: + case Drivers::Watchdog::ResetReason::HardReset: return "hardr"; - case Drivers::Watchdog::ResetReasons::NFC: + case Drivers::Watchdog::ResetReason::NFC: return "nfc"; - case Drivers::Watchdog::ResetReasons::SoftReset: + case Drivers::Watchdog::ResetReason::SoftReset: return "softr"; - case Drivers::Watchdog::ResetReasons::CpuLockup: + case Drivers::Watchdog::ResetReason::CpuLockup: return "cpulock"; - case Drivers::Watchdog::ResetReasons::SystemOff: + case Drivers::Watchdog::ResetReason::SystemOff: return "off"; - case Drivers::Watchdog::ResetReasons::LpComp: + case Drivers::Watchdog::ResetReason::LpComp: return "lpcomp"; - case Drivers::Watchdog::ResetReasons::DebugInterface: + case Drivers::Watchdog::ResetReason::DebugInterface: return "dbg"; - case Drivers::Watchdog::ResetReasons::ResetPin: + case Drivers::Watchdog::ResetReason::ResetPin: return "rst"; default: return "?"; @@ -177,6 +178,8 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen2() { return std::make_unique<Screens::Label>(1, 5, label); } +extern int mallocFailedCount; +extern int stackOverflowCount; std::unique_ptr<Screen> SystemInfo::CreateScreen3() { lv_mem_monitor_t mon; lv_mem_monitor(&mon); @@ -184,26 +187,32 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() { lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); lv_label_set_recolor(label, true); const auto& bleAddr = bleController.Address(); + auto spiFlashId = spiNorFlash.GetIdentification(); lv_label_set_text_fmt(label, "#808080 BLE MAC#\n" - " %02x:%02x:%02x:%02x:%02x:%02x" + " %02x:%02x:%02x:%02x:%02x:%02x\n" "\n" - "#808080 LVGL Memory#\n" - " #808080 used# %d (%d%%)\n" - " #808080 max used# %lu\n" - " #808080 frag# %d%%\n" - " #808080 free# %d", + "#808080 SPI Flash# %02x-%02x-%02x\n" + "\n" + "#808080 Memory heap#\n" + " #808080 Free# %d/%d\n" + " #808080 Min free# %d\n" + " #808080 Alloc err# %d\n" + " #808080 Ovrfl err# %d", bleAddr[5], bleAddr[4], bleAddr[3], bleAddr[2], bleAddr[1], bleAddr[0], - static_cast<int>(mon.total_size - mon.free_size), - mon.used_pct, - mon.max_used, - mon.frag_pct, - static_cast<int>(mon.free_biggest_size)); + spiFlashId.manufacturer, + spiFlashId.type, + spiFlashId.density, + xPortGetFreeHeapSize(), + xPortGetHeapSize(), + xPortGetMinimumEverFreeHeapSize(), + mallocFailedCount, + stackOverflowCount); lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); return std::make_unique<Screens::Label>(2, 5, label); } @@ -232,11 +241,16 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen4() { lv_table_set_col_width(infoTask, 3, 90); auto nb = uxTaskGetSystemState(tasksStatus, maxTaskCount, nullptr); +// g++ emits a spurious warning (and thus error because we compile with -Werror) +// due to the way std::sort is implemented +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" std::sort(tasksStatus, tasksStatus + nb, sortById); +#pragma GCC diagnostic pop for (uint8_t i = 0; i < nb && i < maxTaskCount; i++) { - char buffer[7] = {0}; + char buffer[11] = {0}; - sprintf(buffer, "%lu", tasksStatus[i].xTaskNumber); + snprintf(buffer, sizeof(buffer), "%lu", tasksStatus[i].xTaskNumber); lv_table_set_cell_value(infoTask, i + 1, 0, buffer); switch (tasksStatus[i].eCurrentState) { case eReady: @@ -260,9 +274,9 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen4() { lv_table_set_cell_value(infoTask, i + 1, 1, buffer); lv_table_set_cell_value(infoTask, i + 1, 2, tasksStatus[i].pcTaskName); if (tasksStatus[i].usStackHighWaterMark < 20) { - sprintf(buffer, "%d low", tasksStatus[i].usStackHighWaterMark); + snprintf(buffer, sizeof(buffer), "%" PRIu16 " low", tasksStatus[i].usStackHighWaterMark); } else { - sprintf(buffer, "%d", tasksStatus[i].usStackHighWaterMark); + snprintf(buffer, sizeof(buffer), "%" PRIu16, tasksStatus[i].usStackHighWaterMark); } lv_table_set_cell_value(infoTask, i + 1, 3, buffer); } diff --git a/src/displayapp/screens/SystemInfo.h b/src/displayapp/screens/SystemInfo.h index 199af51e..301662dc 100644 --- a/src/displayapp/screens/SystemInfo.h +++ b/src/displayapp/screens/SystemInfo.h @@ -29,12 +29,12 @@ namespace Pinetime { const Pinetime::Controllers::Ble& bleController, const Pinetime::Drivers::Watchdog& watchdog, Pinetime::Controllers::MotionController& motionController, - const Pinetime::Drivers::Cst816S& touchPanel); + const Pinetime::Drivers::Cst816S& touchPanel, + const Pinetime::Drivers::SpiNorFlash& spiNorFlash); ~SystemInfo() override; bool OnTouchEvent(TouchEvents event) override; private: - DisplayApp* app; Pinetime::Controllers::DateTime& dateTimeController; const Pinetime::Controllers::Battery& batteryController; Pinetime::Controllers::BrightnessController& brightnessController; @@ -42,6 +42,7 @@ namespace Pinetime { const Pinetime::Drivers::Watchdog& watchdog; Pinetime::Controllers::MotionController& motionController; const Pinetime::Drivers::Cst816S& touchPanel; + const Pinetime::Drivers::SpiNorFlash& spiNorFlash; ScreenList<5> screens; diff --git a/src/displayapp/screens/Tile.cpp b/src/displayapp/screens/Tile.cpp index 1266f379..7c585a4b 100644 --- a/src/displayapp/screens/Tile.cpp +++ b/src/displayapp/screens/Tile.cpp @@ -1,5 +1,4 @@ #include "displayapp/screens/Tile.h" -#include "displayapp/DisplayApp.h" #include "displayapp/screens/BatteryIcon.h" #include "components/ble/BleController.h" #include "displayapp/InfiniTimeTheme.h" @@ -30,9 +29,13 @@ Tile::Tile(uint8_t screenID, Controllers::Settings& settingsController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::DateTime& dateTimeController, std::array<Applications, 6>& applications) - : app {app}, dateTimeController {dateTimeController}, pageIndicator(screenID, numScreens), statusIcons(batteryController, bleController) { + : app {app}, + dateTimeController {dateTimeController}, + pageIndicator(screenID, numScreens), + statusIcons(batteryController, bleController, alarmController) { settingsController.SetAppMenu(screenID); @@ -76,7 +79,7 @@ Tile::Tile(uint8_t screenID, for (uint8_t i = 0; i < 6; i++) { lv_btnmatrix_set_btn_ctrl(btnm1, i, LV_BTNMATRIX_CTRL_CLICK_TRIG); - if (applications[i].application == Apps::None) { + if (applications[i].application == Apps::None || !applications[i].enabled) { lv_btnmatrix_set_btn_ctrl(btnm1, i, LV_BTNMATRIX_CTRL_DISABLED); } } diff --git a/src/displayapp/screens/Tile.h b/src/displayapp/screens/Tile.h index 91acb26c..c16151d0 100644 --- a/src/displayapp/screens/Tile.h +++ b/src/displayapp/screens/Tile.h @@ -4,7 +4,7 @@ #include <cstdint> #include <memory> #include "displayapp/screens/Screen.h" -#include "displayapp/Apps.h" +#include "displayapp/apps/Apps.h" #include "components/datetime/DateTimeController.h" #include "components/settings/Settings.h" #include "components/battery/BatteryController.h" @@ -19,6 +19,7 @@ namespace Pinetime { struct Applications { const char* icon; Pinetime::Applications::Apps application; + bool enabled; }; explicit Tile(uint8_t screenID, @@ -27,6 +28,7 @@ namespace Pinetime { Controllers::Settings& settingsController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::DateTime& dateTimeController, std::array<Applications, 6>& applications); diff --git a/src/displayapp/screens/Timer.cpp b/src/displayapp/screens/Timer.cpp index df78a5a0..31cde733 100644 --- a/src/displayapp/screens/Timer.cpp +++ b/src/displayapp/screens/Timer.cpp @@ -17,7 +17,7 @@ static void btnEventHandler(lv_obj_t* obj, lv_event_t event) { } } -Timer::Timer(Controllers::TimerController& timerController) : timerController {timerController} { +Timer::Timer(Controllers::Timer& timerController) : timer {timerController} { lv_obj_t* colonLabel = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_font(colonLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76); @@ -59,10 +59,10 @@ Timer::Timer(Controllers::TimerController& timerController) : timerController {t lv_obj_set_event_cb(btnPlayPause, btnEventHandler); lv_obj_set_size(btnPlayPause, LV_HOR_RES, 50); - txtPlayPause = lv_label_create(lv_scr_act(), nullptr); - lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0); + // Create the label as a child of the button so it stays centered by default + txtPlayPause = lv_label_create(btnPlayPause, nullptr); - if (timerController.IsRunning()) { + if (timer.IsRunning()) { SetTimerRunning(); } else { SetTimerStopped(); @@ -85,7 +85,7 @@ void Timer::MaskReset() { buttonPressing = false; // A click event is processed before a release event, // so the release event would override the "Pause" text without this check - if (!timerController.IsRunning()) { + if (!timer.IsRunning()) { lv_label_set_text_static(txtPlayPause, "Start"); } maskPosition = 0; @@ -103,10 +103,8 @@ void Timer::UpdateMask() { } void Timer::Refresh() { - if (timerController.IsRunning()) { - auto secondsRemaining = std::chrono::duration_cast<std::chrono::seconds>(timerController.GetTimeRemaining()); - minuteCounter.SetValue(secondsRemaining.count() / 60); - secondCounter.SetValue(secondsRemaining.count() % 60); + if (timer.IsRunning()) { + DisplayTime(); } else if (buttonPressing && xTaskGetTickCount() > pressTime + pdMS_TO_TICKS(150)) { lv_label_set_text_static(txtPlayPause, "Reset"); maskPosition += 15; @@ -119,6 +117,14 @@ void Timer::Refresh() { } } +void Timer::DisplayTime() { + displaySeconds = std::chrono::duration_cast<std::chrono::seconds>(timer.GetTimeRemaining()); + if (displaySeconds.IsUpdated()) { + minuteCounter.SetValue(displaySeconds.Get().count() / 60); + secondCounter.SetValue(displaySeconds.Get().count() % 60); + } +} + void Timer::SetTimerRunning() { minuteCounter.HideControls(); secondCounter.HideControls(); @@ -132,22 +138,19 @@ void Timer::SetTimerStopped() { } void Timer::ToggleRunning() { - if (timerController.IsRunning()) { - auto secondsRemaining = std::chrono::duration_cast<std::chrono::seconds>(timerController.GetTimeRemaining()); - minuteCounter.SetValue(secondsRemaining.count() / 60); - secondCounter.SetValue(secondsRemaining.count() % 60); - timerController.StopTimer(); + if (timer.IsRunning()) { + DisplayTime(); + timer.StopTimer(); SetTimerStopped(); } else if (secondCounter.GetValue() + minuteCounter.GetValue() > 0) { auto timerDuration = std::chrono::minutes(minuteCounter.GetValue()) + std::chrono::seconds(secondCounter.GetValue()); - timerController.StartTimer(timerDuration); + timer.StartTimer(timerDuration); Refresh(); SetTimerRunning(); } } void Timer::Reset() { - minuteCounter.SetValue(0); - secondCounter.SetValue(0); + DisplayTime(); SetTimerStopped(); } diff --git a/src/displayapp/screens/Timer.h b/src/displayapp/screens/Timer.h index a6e26063..a07c729b 100644 --- a/src/displayapp/screens/Timer.h +++ b/src/displayapp/screens/Timer.h @@ -1,45 +1,60 @@ #pragma once #include "displayapp/screens/Screen.h" -#include "components/datetime/DateTimeController.h" #include "systemtask/SystemTask.h" #include "displayapp/LittleVgl.h" #include "displayapp/widgets/Counter.h" +#include "utility/DirtyValue.h" #include <lvgl/lvgl.h> -#include "components/timer/TimerController.h" +#include "components/timer/Timer.h" +#include "Symbols.h" -namespace Pinetime::Applications::Screens { - class Timer : public Screen { - public: - Timer(Controllers::TimerController& timerController); - ~Timer() override; - void Refresh() override; - void Reset(); - void ToggleRunning(); - void ButtonPressed(); - void MaskReset(); +namespace Pinetime::Applications { + namespace Screens { + class Timer : public Screen { + public: + Timer(Controllers::Timer& timerController); + ~Timer() override; + void Refresh() override; + void Reset(); + void ToggleRunning(); + void ButtonPressed(); + void MaskReset(); - private: - void SetTimerRunning(); - void SetTimerStopped(); - void UpdateMask(); - Controllers::TimerController& timerController; + private: + void SetTimerRunning(); + void SetTimerStopped(); + void UpdateMask(); + void DisplayTime(); + Pinetime::Controllers::Timer& timer; - lv_obj_t* btnPlayPause; - lv_obj_t* txtPlayPause; + lv_obj_t* btnPlayPause; + lv_obj_t* txtPlayPause; - lv_obj_t* btnObjectMask; - lv_obj_t* highlightObjectMask; - lv_objmask_mask_t* btnMask; - lv_objmask_mask_t* highlightMask; + lv_obj_t* btnObjectMask; + lv_obj_t* highlightObjectMask; + lv_objmask_mask_t* btnMask; + lv_objmask_mask_t* highlightMask; - lv_task_t* taskRefresh; - Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76); - Widgets::Counter secondCounter = Widgets::Counter(0, 59, jetbrains_mono_76); + lv_task_t* taskRefresh; + Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76); + Widgets::Counter secondCounter = Widgets::Counter(0, 59, jetbrains_mono_76); - bool buttonPressing = false; - lv_coord_t maskPosition = 0; - TickType_t pressTime = 0; + bool buttonPressing = false; + lv_coord_t maskPosition = 0; + TickType_t pressTime = 0; + Utility::DirtyValue<std::chrono::seconds> displaySeconds; + }; + } + + template <> + struct AppTraits<Apps::Timer> { + static constexpr Apps app = Apps::Timer; + static constexpr const char* icon = Screens::Symbols::hourGlass; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Timer(controllers.timer); + }; }; } diff --git a/src/displayapp/screens/Twos.cpp b/src/displayapp/screens/Twos.cpp index 8157a160..6f2eff40 100644 --- a/src/displayapp/screens/Twos.cpp +++ b/src/displayapp/screens/Twos.cpp @@ -242,7 +242,7 @@ void Twos::updateGridDisplay() { const unsigned int col = i % nCols; if (grid[row][col].value > 0) { char buffer[7]; - sprintf(buffer, "%d", grid[row][col].value); + snprintf(buffer, sizeof(buffer), "%u", grid[row][col].value); lv_table_set_cell_value(gridDisplay, row, col, buffer); } else { lv_table_set_cell_value(gridDisplay, row, col, ""); diff --git a/src/displayapp/screens/Twos.h b/src/displayapp/screens/Twos.h index e731eae6..52449fd3 100644 --- a/src/displayapp/screens/Twos.h +++ b/src/displayapp/screens/Twos.h @@ -1,7 +1,8 @@ #pragma once -#include <lvgl/src/lv_core/lv_obj.h> +#include "displayapp/apps/Apps.h" #include "displayapp/screens/Screen.h" +#include "displayapp/Controllers.h" namespace Pinetime { namespace Applications { @@ -35,5 +36,15 @@ namespace Pinetime { bool placeNewTile(); }; } + + template <> + struct AppTraits<Apps::Twos> { + static constexpr Apps app = Apps::Twos; + static constexpr const char* icon = "2"; + + static Screens::Screen* Create(AppControllers& /*controllers*/) { + return new Screens::Twos(); + }; + }; } } diff --git a/src/displayapp/screens/WatchFaceAnalog.cpp b/src/displayapp/screens/WatchFaceAnalog.cpp index 76d01cf1..80a1c8b9 100644 --- a/src/displayapp/screens/WatchFaceAnalog.cpp +++ b/src/displayapp/screens/WatchFaceAnalog.cpp @@ -8,8 +8,6 @@ #include "components/settings/Settings.h" #include "displayapp/InfiniTimeTheme.h" -LV_IMG_DECLARE(bg_clock); - using namespace Pinetime::Applications::Screens; namespace { @@ -60,9 +58,41 @@ WatchFaceAnalog::WatchFaceAnalog(Controllers::DateTime& dateTimeController, sMinute = 99; sSecond = 99; - lv_obj_t* bg_clock_img = lv_img_create(lv_scr_act(), nullptr); - lv_img_set_src(bg_clock_img, &bg_clock); - lv_obj_align(bg_clock_img, nullptr, LV_ALIGN_CENTER, 0, 0); + minor_scales = lv_linemeter_create(lv_scr_act(), nullptr); + lv_linemeter_set_scale(minor_scales, 300, 51); + lv_linemeter_set_angle_offset(minor_scales, 180); + lv_obj_set_size(minor_scales, 240, 240); + lv_obj_align(minor_scales, nullptr, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_local_bg_opa(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_style_local_scale_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4); + lv_obj_set_style_local_scale_end_line_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_scale_end_color(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY); + + major_scales = lv_linemeter_create(lv_scr_act(), nullptr); + lv_linemeter_set_scale(major_scales, 300, 11); + lv_linemeter_set_angle_offset(major_scales, 180); + lv_obj_set_size(major_scales, 240, 240); + lv_obj_align(major_scales, nullptr, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_local_bg_opa(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_style_local_scale_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 6); + lv_obj_set_style_local_scale_end_line_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4); + lv_obj_set_style_local_scale_end_color(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + + large_scales = lv_linemeter_create(lv_scr_act(), nullptr); + lv_linemeter_set_scale(large_scales, 180, 3); + lv_linemeter_set_angle_offset(large_scales, 180); + lv_obj_set_size(large_scales, 240, 240); + lv_obj_align(large_scales, nullptr, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_local_bg_opa(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_style_local_scale_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 20); + lv_obj_set_style_local_scale_end_line_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4); + lv_obj_set_style_local_scale_end_color(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA); + + twelve = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_align(twelve, LV_LABEL_ALIGN_CENTER); + lv_label_set_text_static(twelve, "12"); + lv_obj_set_pos(twelve, 110, 10); + lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA); batteryIcon.Create(lv_scr_act()); lv_obj_align(batteryIcon.GetObject(), nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0); @@ -226,7 +256,7 @@ void WatchFaceAnalog::Refresh() { if (currentDateTime.IsUpdated()) { UpdateClock(); - currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get()); + currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get()); if (currentDate.IsUpdated()) { lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day()); } diff --git a/src/displayapp/screens/WatchFaceAnalog.h b/src/displayapp/screens/WatchFaceAnalog.h index b32293da..958ff64d 100644 --- a/src/displayapp/screens/WatchFaceAnalog.h +++ b/src/displayapp/screens/WatchFaceAnalog.h @@ -9,7 +9,8 @@ #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" #include "components/ble/NotificationManager.h" -#include <displayapp/screens/BatteryIcon.h> +#include "displayapp/screens/BatteryIcon.h" +#include "utility/DirtyValue.h" namespace Pinetime { namespace Controllers { @@ -37,13 +38,17 @@ namespace Pinetime { private: uint8_t sHour, sMinute, sSecond; - DirtyValue<uint8_t> batteryPercentRemaining {0}; - DirtyValue<bool> isCharging {}; - DirtyValue<bool> bleState {}; - DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime; - DirtyValue<bool> notificationState {false}; - using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20 - DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate; + Utility::DirtyValue<uint8_t> batteryPercentRemaining {0}; + Utility::DirtyValue<bool> isCharging {}; + Utility::DirtyValue<bool> bleState {}; + Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime; + Utility::DirtyValue<bool> notificationState {false}; + Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate; + + lv_obj_t* minor_scales; + lv_obj_t* major_scales; + lv_obj_t* large_scales; + lv_obj_t* twelve; lv_obj_t* hour_body; lv_obj_t* hour_body_trace; @@ -70,7 +75,7 @@ namespace Pinetime { BatteryIcon batteryIcon; - const Controllers::DateTime& dateTimeController; + Controllers::DateTime& dateTimeController; const Controllers::Battery& batteryController; const Controllers::Ble& bleController; Controllers::NotificationManager& notificationManager; @@ -82,5 +87,23 @@ namespace Pinetime { lv_task_t* taskRefresh; }; } + + template <> + struct WatchFaceTraits<WatchFace::Analog> { + static constexpr WatchFace watchFace = WatchFace::Analog; + static constexpr const char* name = "Analog face"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceAnalog(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.notificationManager, + controllers.settingsController); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + } + }; } } diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp index ca37c8fc..c695f852 100644 --- a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp @@ -48,14 +48,14 @@ WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTi font_segment115 = lv_font_load("F:/fonts/7segments_115.bin"); } - label_battery_vallue = lv_label_create(lv_scr_act(), nullptr); - lv_obj_align(label_battery_vallue, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); - lv_obj_set_style_local_text_color(label_battery_vallue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); - lv_label_set_text_static(label_battery_vallue, "00%"); + label_battery_value = lv_label_create(lv_scr_act(), nullptr); + lv_obj_align(label_battery_value, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); + lv_obj_set_style_local_text_color(label_battery_value, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_label_set_text_static(label_battery_value, "00%"); batteryIcon.Create(lv_scr_act()); batteryIcon.SetColor(color_text); - lv_obj_align(batteryIcon.GetObject(), label_battery_vallue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + lv_obj_align(batteryIcon.GetObject(), label_battery_value, LV_ALIGN_OUT_LEFT_MID, -5, 0); batteryPlug = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(batteryPlug, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); @@ -203,7 +203,7 @@ void WatchFaceCasioStyleG7710::Refresh() { if (batteryPercentRemaining.IsUpdated()) { auto batteryPercent = batteryPercentRemaining.Get(); batteryIcon.SetBatteryPercentage(batteryPercent); - lv_label_set_text_fmt(label_battery_vallue, "%d%%", batteryPercent); + lv_label_set_text_fmt(label_battery_value, "%d%%", batteryPercent); } bleState = bleController.IsConnected(); @@ -211,7 +211,7 @@ void WatchFaceCasioStyleG7710::Refresh() { if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) { lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); } - lv_obj_realign(label_battery_vallue); + lv_obj_realign(label_battery_value); lv_obj_realign(batteryIcon.GetObject()); lv_obj_realign(batteryPlug); lv_obj_realign(bleIcon); @@ -222,43 +222,36 @@ void WatchFaceCasioStyleG7710::Refresh() { lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get())); } - currentDateTime = dateTimeController.CurrentDateTime(); - + currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(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(); - auto dayOfYear = dateTimeController.DayOfYear(); - - auto weekNumberFormat = "%V"; + uint8_t hour = dateTimeController.Hours(); + uint8_t minute = dateTimeController.Minutes(); - if (displayedHour != hour || displayedMinute != minute) { - displayedHour = hour; - displayedMinute = minute; - - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - char ampmChar[2] = "A"; - 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, 40); - } else { - lv_label_set_text_fmt(label_time, "%02d:%02d", hour, minute); - lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 40); + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[2] = "A"; + 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); + } else { + lv_label_set_text_fmt(label_time, "%02d:%02d", hour, minute); } + lv_obj_realign(label_time); + + currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get()); + if (currentDate.IsUpdated()) { + const char* weekNumberFormat = "%V"; - if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { + uint16_t year = dateTimeController.Year(); + Controllers::DateTime::Months month = dateTimeController.Month(); + uint8_t day = dateTimeController.Day(); + int dayOfYear = dateTimeController.DayOfYear(); if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) { // 24h mode: ddmmyyyy, first DOW=Monday; lv_label_set_text_fmt(label_date, "%3d-%2d", day, month); @@ -293,11 +286,6 @@ void WatchFaceCasioStyleG7710::Refresh() { lv_obj_realign(label_day_of_year); lv_obj_realign(label_week_number); lv_obj_realign(label_date); - - currentYear = year; - currentMonth = month; - currentDayOfWeek = dayOfWeek; - currentDay = day; } } @@ -317,8 +305,7 @@ void WatchFaceCasioStyleG7710::Refresh() { } stepCount = motionController.NbSteps(); - motionSensorOk = motionController.IsSensorOk(); - if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { + if (stepCount.IsUpdated()) { lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); lv_obj_realign(stepValue); lv_obj_realign(stepIcon); diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.h b/src/displayapp/screens/WatchFaceCasioStyleG7710.h index 0445c9f2..0f46a692 100644 --- a/src/displayapp/screens/WatchFaceCasioStyleG7710.h +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.h @@ -5,9 +5,12 @@ #include <chrono> #include <cstdint> #include <memory> +#include <displayapp/Controllers.h> #include "displayapp/screens/Screen.h" #include "components/datetime/DateTimeController.h" #include "components/ble/BleController.h" +#include "utility/DirtyValue.h" +#include "displayapp/apps/Apps.h" namespace Pinetime { namespace Controllers { @@ -39,24 +42,16 @@ namespace Pinetime { 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<uint8_t> batteryPercentRemaining {}; - DirtyValue<bool> powerPresent {}; - DirtyValue<bool> bleState {}; - DirtyValue<bool> bleRadioEnabled {}; - DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {}; - DirtyValue<bool> motionSensorOk {}; - DirtyValue<uint32_t> stepCount {}; - DirtyValue<uint8_t> heartbeat {}; - DirtyValue<bool> heartbeatRunning {}; - DirtyValue<bool> notificationState {}; + Utility::DirtyValue<uint8_t> batteryPercentRemaining {}; + Utility::DirtyValue<bool> powerPresent {}; + Utility::DirtyValue<bool> bleState {}; + Utility::DirtyValue<bool> bleRadioEnabled {}; + Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {}; + Utility::DirtyValue<uint32_t> stepCount {}; + Utility::DirtyValue<uint8_t> heartbeat {}; + Utility::DirtyValue<bool> heartbeatRunning {}; + Utility::DirtyValue<bool> notificationState {}; + Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate; lv_point_t line_icons_points[3] {{0, 5}, {117, 5}, {122, 0}}; lv_point_t line_day_of_week_number_points[4] {{0, 0}, {100, 0}, {95, 95}, {0, 95}}; @@ -82,7 +77,7 @@ namespace Pinetime { lv_obj_t* backgroundLabel; lv_obj_t* bleIcon; lv_obj_t* batteryPlug; - lv_obj_t* label_battery_vallue; + lv_obj_t* label_battery_value; lv_obj_t* heartbeatIcon; lv_obj_t* heartbeatValue; lv_obj_t* stepIcon; @@ -106,5 +101,26 @@ namespace Pinetime { lv_font_t* font_segment115 = nullptr; }; } + + template <> + struct WatchFaceTraits<WatchFace::CasioStyleG7710> { + static constexpr WatchFace watchFace = WatchFace::CasioStyleG7710; + static constexpr const char* name = "Casio G7710"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceCasioStyleG7710(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.notificationManager, + controllers.settingsController, + controllers.heartRateController, + controllers.motionController, + controllers.filesystem); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { + return Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem); + } + }; } } diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp index ad35b5c9..3163c6e7 100644 --- a/src/displayapp/screens/WatchFaceDigital.cpp +++ b/src/displayapp/screens/WatchFaceDigital.cpp @@ -2,13 +2,16 @@ #include <lvgl/lvgl.h> #include <cstdio> + #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/ble/SimpleWeatherService.h" #include "components/settings/Settings.h" using namespace Pinetime::Applications::Screens; @@ -16,17 +19,20 @@ using namespace Pinetime::Applications::Screens; WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, - Controllers::MotionController& motionController) + Controllers::MotionController& motionController, + Controllers::SimpleWeatherService& weatherService) : currentDateTime {{}}, dateTimeController {dateTimeController}, notificationManager {notificationManager}, settingsController {settingsController}, heartRateController {heartRateController}, motionController {motionController}, - statusIcons(batteryController, bleController) { + weatherService {weatherService}, + statusIcons(batteryController, bleController, alarmController) { statusIcons.Create(); @@ -35,6 +41,18 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController, lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false)); lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); + weatherIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); + lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); + lv_label_set_text(weatherIcon, ""); + lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, -20, 50); + lv_obj_set_auto_realign(weatherIcon, true); + + temperature = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); + lv_label_set_text(temperature, ""); + lv_obj_align(temperature, nullptr, LV_ALIGN_IN_TOP_MID, 20, 50); + 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)); @@ -85,40 +103,34 @@ void WatchFaceDigital::Refresh() { lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get())); } - currentDateTime = dateTimeController.CurrentDateTime(); + currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(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(); + uint8_t hour = dateTimeController.Hours(); + uint8_t minute = dateTimeController.Minutes(); - 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_IN_RIGHT_MID, 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 (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_IN_RIGHT_MID, 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)) { + currentDate = std::chrono::time_point_cast<std::chrono::days>(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", @@ -135,11 +147,6 @@ void WatchFaceDigital::Refresh() { year); } lv_obj_realign(label_date); - - currentYear = year; - currentMonth = month; - currentDayOfWeek = dayOfWeek; - currentDay = day; } } @@ -159,10 +166,29 @@ void WatchFaceDigital::Refresh() { } stepCount = motionController.NbSteps(); - motionSensorOk = motionController.IsSensorOk(); - if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { + if (stepCount.IsUpdated()) { lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); lv_obj_realign(stepValue); lv_obj_realign(stepIcon); } + + currentWeather = weatherService.Current(); + if (currentWeather.IsUpdated()) { + auto optCurrentWeather = currentWeather.Get(); + if (optCurrentWeather) { + int16_t temp = optCurrentWeather->temperature.Celsius(); + char tempUnit = 'C'; + if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { + temp = optCurrentWeather->temperature.Fahrenheit(); + tempUnit = 'F'; + } + lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); + lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId)); + } else { + lv_label_set_text_static(temperature, ""); + lv_label_set_text(weatherIcon, ""); + } + lv_obj_realign(temperature); + lv_obj_realign(weatherIcon); + } } diff --git a/src/displayapp/screens/WatchFaceDigital.h b/src/displayapp/screens/WatchFaceDigital.h index 0931f007..3005cea5 100644 --- a/src/displayapp/screens/WatchFaceDigital.h +++ b/src/displayapp/screens/WatchFaceDigital.h @@ -6,14 +6,18 @@ #include <memory> #include "displayapp/screens/Screen.h" #include "components/datetime/DateTimeController.h" +#include "components/ble/SimpleWeatherService.h" #include "components/ble/BleController.h" #include "displayapp/widgets/StatusIcons.h" +#include "utility/DirtyValue.h" +#include "displayapp/apps/Apps.h" namespace Pinetime { namespace Controllers { class Settings; class Battery; class Ble; + class AlarmController; class NotificationManager; class HeartRateController; class MotionController; @@ -27,10 +31,12 @@ namespace Pinetime { WatchFaceDigital(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, - Controllers::MotionController& motionController); + Controllers::MotionController& motionController, + Controllers::SimpleWeatherService& weather); ~WatchFaceDigital() override; void Refresh() override; @@ -39,21 +45,14 @@ namespace Pinetime { 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<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {}; + Utility::DirtyValue<uint32_t> stepCount {}; + Utility::DirtyValue<uint8_t> heartbeat {}; + Utility::DirtyValue<bool> heartbeatRunning {}; + Utility::DirtyValue<bool> notificationState {}; + Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {}; - DirtyValue<uint8_t> batteryPercentRemaining {}; - DirtyValue<bool> powerPresent {}; - DirtyValue<bool> bleState {}; - DirtyValue<bool> bleRadioEnabled {}; - DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {}; - DirtyValue<bool> motionSensorOk {}; - DirtyValue<uint32_t> stepCount {}; - DirtyValue<uint8_t> heartbeat {}; - DirtyValue<bool> heartbeatRunning {}; - DirtyValue<bool> notificationState {}; + Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate; lv_obj_t* label_time; lv_obj_t* label_time_ampm; @@ -63,16 +62,41 @@ namespace Pinetime { lv_obj_t* stepIcon; lv_obj_t* stepValue; lv_obj_t* notificationIcon; + lv_obj_t* weatherIcon; + lv_obj_t* temperature; Controllers::DateTime& dateTimeController; Controllers::NotificationManager& notificationManager; Controllers::Settings& settingsController; Controllers::HeartRateController& heartRateController; Controllers::MotionController& motionController; + Controllers::SimpleWeatherService& weatherService; lv_task_t* taskRefresh; Widgets::StatusIcons statusIcons; }; } + + template <> + struct WatchFaceTraits<WatchFace::Digital> { + static constexpr WatchFace watchFace = WatchFace::Digital; + static constexpr const char* name = "Digital face"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceDigital(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.alarmController, + controllers.notificationManager, + controllers.settingsController, + controllers.heartRateController, + controllers.motionController, + *controllers.weatherController); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + } + }; } } diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index ab0898e0..40f2abbb 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -397,74 +397,52 @@ void WatchFaceInfineat::Refresh() { lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); } - currentDateTime = dateTimeController.CurrentDateTime(); - + currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(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(); - - char minutesChar[3]; - sprintf(minutesChar, "%02d", static_cast<int>(minute)); - - char hoursChar[3]; - char ampmChar[3]; + uint8_t hour = dateTimeController.Hours(); + uint8_t minute = dateTimeController.Minutes(); if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - if (hour < 12) { - if (hour == 0) { - hour = 12; - } - sprintf(ampmChar, "AM"); - } else { // hour >= 12 - if (hour != 12) { - hour = hour - 12; - } - sprintf(ampmChar, "PM"); + 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(labelTimeAmPm, ampmChar); } - sprintf(hoursChar, "%02d", hour); - - if ((hoursChar[0] != displayedChar[0]) || (hoursChar[1] != displayedChar[1]) || (minutesChar[0] != displayedChar[2]) || - (minutesChar[1] != displayedChar[3])) { - displayedChar[0] = hoursChar[0]; - displayedChar[1] = hoursChar[1]; - displayedChar[2] = minutesChar[0]; - displayedChar[3] = minutesChar[1]; - - lv_label_set_text_fmt(labelHour, "%s", hoursChar); - lv_label_set_text_fmt(labelMinutes, "%s", minutesChar); - } + lv_label_set_text_fmt(labelHour, "%02d", hour); + lv_label_set_text_fmt(labelMinutes, "%02d", minute); if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - lv_label_set_text(labelTimeAmPm, ampmChar); lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 10); lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); } - if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { - lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(), day); + currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get()); + if (currentDate.IsUpdated()) { + uint8_t day = dateTimeController.Day(); + Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek(); + lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day); lv_obj_realign(labelDate); - - currentYear = year; - currentMonth = month; - currentDayOfWeek = dayOfWeek; - currentDay = day; } } batteryPercentRemaining = batteryController.PercentRemaining(); isCharging = batteryController.IsCharging(); - if (batteryController.IsCharging()) { // Charging battery animation - chargingBatteryPercent += 1; + // Charging battery animation + if (batteryController.IsCharging() && (xTaskGetTickCount() - chargingAnimationTick > pdMS_TO_TICKS(150))) { + // Dividing 100 by the height gives the battery percentage required to shift the animation by 1 pixel + chargingBatteryPercent += 100 / lv_obj_get_height(logoPine); if (chargingBatteryPercent > 100) { chargingBatteryPercent = batteryPercentRemaining.Get(); } SetBatteryLevel(chargingBatteryPercent); + chargingAnimationTick = xTaskGetTickCount(); } else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) { chargingBatteryPercent = batteryPercentRemaining.Get(); SetBatteryLevel(chargingBatteryPercent); @@ -478,8 +456,7 @@ void WatchFaceInfineat::Refresh() { } stepCount = motionController.NbSteps(); - motionSensorOk = motionController.IsSensorOk(); - if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { + if (stepCount.IsUpdated()) { lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0); lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 26973efe..78d020f1 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -4,8 +4,11 @@ #include <chrono> #include <cstdint> #include <memory> +#include <displayapp/Controllers.h> #include "displayapp/screens/Screen.h" #include "components/datetime/DateTimeController.h" +#include "utility/DirtyValue.h" +#include "displayapp/apps/Apps.h" namespace Pinetime { namespace Controllers { @@ -42,23 +45,18 @@ namespace Pinetime { 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; uint32_t savedTick = 0; uint8_t chargingBatteryPercent = 101; // not a mistake ;) + TickType_t chargingAnimationTick = 0; - DirtyValue<uint8_t> batteryPercentRemaining {}; - DirtyValue<bool> isCharging {}; - DirtyValue<bool> bleState {}; - DirtyValue<bool> bleRadioEnabled {}; - DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {}; - DirtyValue<bool> motionSensorOk {}; - DirtyValue<uint32_t> stepCount {}; - DirtyValue<bool> notificationState {}; + Utility::DirtyValue<uint8_t> batteryPercentRemaining {}; + Utility::DirtyValue<bool> isCharging {}; + Utility::DirtyValue<bool> bleState {}; + Utility::DirtyValue<bool> bleRadioEnabled {}; + Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {}; + Utility::DirtyValue<uint32_t> stepCount {}; + Utility::DirtyValue<bool> notificationState {}; + Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate; // Lines making up the side cover lv_obj_t* lineBattery; @@ -102,5 +100,25 @@ namespace Pinetime { lv_font_t* font_bebas = nullptr; }; } + + template <> + struct WatchFaceTraits<WatchFace::Infineat> { + static constexpr WatchFace watchFace = WatchFace::Infineat; + static constexpr const char* name = "Infineat face"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceInfineat(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.notificationManager, + controllers.settingsController, + controllers.motionController, + controllers.filesystem); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { + return Screens::WatchFaceInfineat::IsAvailable(filesystem); + } + }; } } diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.cpp b/src/displayapp/screens/WatchFacePineTimeStyle.cpp index 85505a63..22ccefc7 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.cpp +++ b/src/displayapp/screens/WatchFacePineTimeStyle.cpp @@ -22,17 +22,19 @@ #include "displayapp/screens/WatchFacePineTimeStyle.h" #include <lvgl/lvgl.h> #include <cstdio> -#include <displayapp/Colors.h> +#include "displayapp/Colors.h" #include "displayapp/screens/BatteryIcon.h" #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/motion/MotionController.h" #include "components/settings/Settings.h" #include "displayapp/DisplayApp.h" +#include "components/ble/SimpleWeatherService.h" using namespace Pinetime::Applications::Screens; @@ -48,7 +50,8 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo const Controllers::Ble& bleController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, - Controllers::MotionController& motionController) + Controllers::MotionController& motionController, + Controllers::SimpleWeatherService& weatherService) : currentDateTime {{}}, batteryIcon(false), dateTimeController {dateTimeController}, @@ -56,7 +59,8 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo bleController {bleController}, notificationManager {notificationManager}, settingsController {settingsController}, - motionController {motionController} { + motionController {motionController}, + weatherService {weatherService} { // Create a 200px wide background rectangle timebar = lv_obj_create(lv_scr_act(), nullptr); @@ -94,27 +98,53 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo // Display icons batteryIcon.Create(sidebar); batteryIcon.SetColor(LV_COLOR_BLACK); - lv_obj_align(batteryIcon.GetObject(), nullptr, LV_ALIGN_IN_TOP_MID, 0, 2); + lv_obj_align(batteryIcon.GetObject(), nullptr, LV_ALIGN_IN_TOP_MID, 10, 2); plugIcon = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(plugIcon, Symbols::plug); lv_obj_set_style_local_text_color(plugIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); - lv_obj_align(plugIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 2); + lv_obj_align(plugIcon, sidebar, LV_ALIGN_IN_TOP_MID, 10, 2); 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, ""); + lv_obj_align(bleIcon, sidebar, LV_ALIGN_IN_TOP_MID, -10, 2); 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, ""); + lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Convert(settingsController.GetPTSColorTime())); + lv_obj_align(notificationIcon, timebar, LV_ALIGN_IN_TOP_LEFT, 5, 5); + + weatherIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); + lv_label_set_text(weatherIcon, Symbols::ban); + lv_obj_align(weatherIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 35); + lv_obj_set_auto_realign(weatherIcon, true); + if (settingsController.GetPTSWeather() == Pinetime::Controllers::Settings::PTSWeather::On) { + lv_obj_set_hidden(weatherIcon, false); + } else { + lv_obj_set_hidden(weatherIcon, true); + } + + temperature = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_label_set_text(temperature, "--"); + lv_obj_align(temperature, sidebar, LV_ALIGN_IN_TOP_MID, 0, 65); + if (settingsController.GetPTSWeather() == Pinetime::Controllers::Settings::PTSWeather::On) { + lv_obj_set_hidden(temperature, false); + } else { + lv_obj_set_hidden(temperature, true); + } // Calendar icon calendarOuter = lv_obj_create(lv_scr_act(), nullptr); lv_obj_set_style_local_bg_color(calendarOuter, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); lv_obj_set_style_local_radius(calendarOuter, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 0); lv_obj_set_size(calendarOuter, 34, 34); - lv_obj_align(calendarOuter, sidebar, LV_ALIGN_CENTER, 0, 0); + if (settingsController.GetPTSWeather() == Pinetime::Controllers::Settings::PTSWeather::On) { + lv_obj_align(calendarOuter, sidebar, LV_ALIGN_CENTER, 0, 20); + } else { + lv_obj_align(calendarOuter, sidebar, LV_ALIGN_CENTER, 0, 0); + } calendarInner = lv_obj_create(lv_scr_act(), nullptr); lv_obj_set_style_local_bg_color(calendarInner, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); @@ -150,17 +180,17 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo 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_BLACK); lv_label_set_text_static(dateDayOfWeek, "THU"); - lv_obj_align(dateDayOfWeek, sidebar, LV_ALIGN_CENTER, 0, -34); + lv_obj_align(dateDayOfWeek, calendarOuter, LV_ALIGN_CENTER, 0, -32); 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_BLACK); lv_label_set_text_static(dateDay, "25"); - lv_obj_align(dateDay, sidebar, LV_ALIGN_CENTER, 0, 3); + lv_obj_align(dateDay, calendarOuter, LV_ALIGN_CENTER, 0, 3); dateMonth = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(dateMonth, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); lv_label_set_text_static(dateMonth, "MAR"); - lv_obj_align(dateMonth, sidebar, LV_ALIGN_CENTER, 0, 32); + lv_obj_align(dateMonth, calendarOuter, LV_ALIGN_CENTER, 0, 32); // Step count gauge if (settingsController.GetPTSColorBar() == Pinetime::Controllers::Settings::Colors::White) { @@ -323,13 +353,23 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo btnSteps = lv_btn_create(lv_scr_act(), nullptr); btnSteps->user_data = this; lv_obj_set_size(btnSteps, 160, 60); - lv_obj_align(btnSteps, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + lv_obj_align(btnSteps, lv_scr_act(), LV_ALIGN_CENTER, 0, -10); lv_obj_set_style_local_bg_opa(btnSteps, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); lv_obj_t* lblSteps = lv_label_create(btnSteps, nullptr); lv_label_set_text_static(lblSteps, "Steps style"); lv_obj_set_event_cb(btnSteps, event_handler); lv_obj_set_hidden(btnSteps, true); + btnWeather = lv_btn_create(lv_scr_act(), nullptr); + btnWeather->user_data = this; + lv_obj_set_size(btnWeather, 160, 60); + lv_obj_align(btnWeather, lv_scr_act(), LV_ALIGN_CENTER, 0, 60); + lv_obj_set_style_local_bg_opa(btnWeather, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); + lv_obj_t* lblWeather = lv_label_create(btnWeather, nullptr); + lv_label_set_text_static(lblWeather, "Weather"); + lv_obj_set_event_cb(btnWeather, event_handler); + lv_obj_set_hidden(btnWeather, true); + btnSetColor = lv_btn_create(lv_scr_act(), nullptr); btnSetColor->user_data = this; lv_obj_set_size(btnSetColor, 150, 60); @@ -337,9 +377,9 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo lv_obj_set_style_local_radius(btnSetColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_set_style_local_bg_opa(btnSetColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); lv_obj_set_event_cb(btnSetColor, event_handler); - lbl_btnSetColor = lv_label_create(btnSetColor, nullptr); - lv_obj_set_style_local_text_font(lbl_btnSetColor, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); - lv_label_set_text_static(lbl_btnSetColor, Symbols::paintbrushLg); + lv_obj_t* lblSetColor = lv_label_create(btnSetColor, nullptr); + lv_obj_set_style_local_text_font(lblSetColor, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); + lv_label_set_text_static(lblSetColor, Symbols::paintbrushLg); lv_obj_set_hidden(btnSetColor, true); btnSetOpts = lv_btn_create(lv_scr_act(), nullptr); @@ -349,9 +389,9 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo lv_obj_set_style_local_radius(btnSetOpts, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_set_style_local_bg_opa(btnSetOpts, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); lv_obj_set_event_cb(btnSetOpts, event_handler); - lbl_btnSetOpts = lv_label_create(btnSetOpts, nullptr); - lv_obj_set_style_local_text_font(lbl_btnSetOpts, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); - lv_label_set_text_static(lbl_btnSetOpts, Symbols::settings); + lv_obj_t* lblSetOpts = lv_label_create(btnSetOpts, nullptr); + lv_obj_set_style_local_text_font(lblSetOpts, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); + lv_label_set_text_static(lblSetOpts, Symbols::settings); lv_obj_set_hidden(btnSetOpts, true); taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); @@ -388,6 +428,7 @@ void WatchFacePineTimeStyle::CloseMenu() { lv_obj_set_hidden(btnRandom, true); lv_obj_set_hidden(btnClose, true); lv_obj_set_hidden(btnSteps, true); + lv_obj_set_hidden(btnWeather, true); } bool WatchFacePineTimeStyle::OnButtonPushed() { @@ -403,17 +444,6 @@ void WatchFacePineTimeStyle::SetBatteryIcon() { batteryIcon.SetBatteryPercentage(batteryPercent); } -void WatchFacePineTimeStyle::AlignIcons() { - if (notificationState.Get() && bleState.Get()) { - lv_obj_align(bleIcon, sidebar, LV_ALIGN_IN_TOP_MID, 8, 25); - lv_obj_align(notificationIcon, sidebar, LV_ALIGN_IN_TOP_MID, -8, 25); - } else if (notificationState.Get() && !bleState.Get()) { - lv_obj_align(notificationIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 25); - } else { - lv_obj_align(bleIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 25); - } -} - void WatchFacePineTimeStyle::Refresh() { isCharging = batteryController.IsCharging(); if (isCharging.IsUpdated()) { @@ -437,13 +467,12 @@ void WatchFacePineTimeStyle::Refresh() { bleRadioEnabled = bleController.IsRadioEnabled(); if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) { lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); - AlignIcons(); + lv_obj_realign(bleIcon); } notificationState = notificationManager.AreNewNotificationsAvailable(); if (notificationState.IsUpdated()) { lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get())); - AlignIcons(); } currentDateTime = dateTimeController.CurrentDateTime(); @@ -499,8 +528,7 @@ void WatchFacePineTimeStyle::Refresh() { } stepCount = motionController.NbSteps(); - motionSensorOk = motionController.IsSensorOk(); - if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { + if (stepCount.IsUpdated()) { lv_gauge_set_value(stepGauge, 0, (stepCount.Get() / (settingsController.GetStepsGoal() / 100)) % 100); lv_obj_realign(stepGauge); lv_label_set_text_fmt(stepValue, "%luK", (stepCount.Get() / 1000)); @@ -510,6 +538,25 @@ void WatchFacePineTimeStyle::Refresh() { lv_obj_set_style_local_scale_grad_color(stepGauge, LV_GAUGE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); } } + + currentWeather = weatherService.Current(); + if (currentWeather.IsUpdated()) { + auto optCurrentWeather = currentWeather.Get(); + if (optCurrentWeather) { + int16_t temp = optCurrentWeather->temperature.Celsius(); + if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { + temp = optCurrentWeather->temperature.Fahrenheit(); + } + lv_label_set_text_fmt(temperature, "%d°", temp); + lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId)); + } else { + lv_label_set_text(temperature, "--"); + lv_label_set_text(weatherIcon, Symbols::ban); + } + lv_obj_realign(temperature); + lv_obj_realign(weatherIcon); + } + if (!lv_obj_get_hidden(btnSetColor)) { if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { lv_obj_set_hidden(btnSetColor, true); @@ -655,6 +702,37 @@ void WatchFacePineTimeStyle::UpdateSelected(lv_obj_t* object, lv_event_t event) settingsController.SetPTSGaugeStyle(Controllers::Settings::PTSGaugeStyle::Full); } } + if (object == btnWeather) { + if (lv_obj_get_hidden(weatherIcon)) { + // show weather icon and temperature + lv_obj_set_hidden(weatherIcon, false); + lv_obj_set_hidden(temperature, false); + lv_obj_align(calendarOuter, sidebar, LV_ALIGN_CENTER, 0, 20); + lv_obj_realign(calendarInner); + lv_obj_realign(calendarBar1); + lv_obj_realign(calendarBar2); + lv_obj_realign(calendarCrossBar1); + lv_obj_realign(calendarCrossBar2); + lv_obj_realign(dateDayOfWeek); + lv_obj_realign(dateDay); + lv_obj_realign(dateMonth); + settingsController.SetPTSWeather(Controllers::Settings::PTSWeather::On); + } else { + // hide weather + lv_obj_set_hidden(weatherIcon, true); + lv_obj_set_hidden(temperature, true); + lv_obj_align(calendarOuter, sidebar, LV_ALIGN_CENTER, 0, 0); + lv_obj_realign(calendarInner); + lv_obj_realign(calendarBar1); + lv_obj_realign(calendarBar2); + lv_obj_realign(calendarCrossBar1); + lv_obj_realign(calendarCrossBar2); + lv_obj_realign(dateDayOfWeek); + lv_obj_realign(dateDay); + lv_obj_realign(dateMonth); + settingsController.SetPTSWeather(Controllers::Settings::PTSWeather::Off); + } + } if (object == btnSetColor) { lv_obj_set_hidden(btnSetColor, true); lv_obj_set_hidden(btnSetOpts, true); @@ -672,6 +750,7 @@ void WatchFacePineTimeStyle::UpdateSelected(lv_obj_t* object, lv_event_t event) lv_obj_set_hidden(btnSetColor, true); lv_obj_set_hidden(btnSetOpts, true); lv_obj_set_hidden(btnSteps, false); + lv_obj_set_hidden(btnWeather, false); lv_obj_set_hidden(btnClose, false); } } diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.h b/src/displayapp/screens/WatchFacePineTimeStyle.h index bccb224a..72537095 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.h +++ b/src/displayapp/screens/WatchFacePineTimeStyle.h @@ -4,11 +4,14 @@ #include <chrono> #include <cstdint> #include <memory> +#include <displayapp/Controllers.h> #include "displayapp/screens/Screen.h" #include "displayapp/screens/BatteryIcon.h" #include "displayapp/Colors.h" #include "components/datetime/DateTimeController.h" +#include "components/ble/SimpleWeatherService.h" #include "components/ble/BleController.h" +#include "utility/DirtyValue.h" namespace Pinetime { namespace Controllers { @@ -29,7 +32,8 @@ namespace Pinetime { const Controllers::Ble& bleController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, - Controllers::MotionController& motionController); + Controllers::MotionController& motionController, + Controllers::SimpleWeatherService& weather); ~WatchFacePineTimeStyle() override; bool OnTouchEvent(TouchEvents event) override; @@ -50,14 +54,14 @@ namespace Pinetime { uint8_t currentDay = 0; uint32_t savedTick = 0; - DirtyValue<uint8_t> batteryPercentRemaining {}; - DirtyValue<bool> isCharging {}; - DirtyValue<bool> bleState {}; - DirtyValue<bool> bleRadioEnabled {}; - DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {}; - DirtyValue<bool> motionSensorOk {}; - DirtyValue<uint32_t> stepCount {}; - DirtyValue<bool> notificationState {}; + Utility::DirtyValue<uint8_t> batteryPercentRemaining {}; + Utility::DirtyValue<bool> isCharging {}; + Utility::DirtyValue<bool> bleState {}; + Utility::DirtyValue<bool> bleRadioEnabled {}; + Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {}; + Utility::DirtyValue<uint32_t> stepCount {}; + Utility::DirtyValue<bool> notificationState {}; + Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {}; static Pinetime::Controllers::Settings::Colors GetNext(Controllers::Settings::Colors color); static Pinetime::Controllers::Settings::Colors GetPrevious(Controllers::Settings::Colors color); @@ -72,6 +76,7 @@ namespace Pinetime { lv_obj_t* btnRandom; lv_obj_t* btnClose; lv_obj_t* btnSteps; + lv_obj_t* btnWeather; lv_obj_t* timebar; lv_obj_t* sidebar; lv_obj_t* timeDD1; @@ -81,6 +86,8 @@ namespace Pinetime { lv_obj_t* dateDayOfWeek; lv_obj_t* dateDay; lv_obj_t* dateMonth; + lv_obj_t* weatherIcon; + lv_obj_t* temperature; lv_obj_t* plugIcon; lv_obj_t* bleIcon; lv_obj_t* calendarOuter; @@ -93,8 +100,6 @@ namespace Pinetime { lv_obj_t* stepGauge; lv_obj_t* btnSetColor; lv_obj_t* btnSetOpts; - lv_obj_t* lbl_btnSetColor; - lv_obj_t* lbl_btnSetOpts; lv_obj_t* stepIcon; lv_obj_t* stepValue; lv_color_t needle_colors[1]; @@ -107,13 +112,33 @@ namespace Pinetime { Controllers::NotificationManager& notificationManager; Controllers::Settings& settingsController; Controllers::MotionController& motionController; + Controllers::SimpleWeatherService& weatherService; void SetBatteryIcon(); void CloseMenu(); - void AlignIcons(); lv_task_t* taskRefresh; }; } + + template <> + struct WatchFaceTraits<WatchFace::PineTimeStyle> { + static constexpr WatchFace watchFace = WatchFace::PineTimeStyle; + static constexpr const char* name = "PineTimeStyle"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFacePineTimeStyle(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.notificationManager, + controllers.settingsController, + controllers.motionController, + *controllers.weatherController); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + } + }; } } diff --git a/src/displayapp/screens/WatchFaceTerminal.cpp b/src/displayapp/screens/WatchFaceTerminal.cpp index e5ff195e..96d77741 100644 --- a/src/displayapp/screens/WatchFaceTerminal.cpp +++ b/src/displayapp/screens/WatchFaceTerminal.cpp @@ -104,45 +104,33 @@ void WatchFaceTerminal::Refresh() { } } - currentDateTime = dateTimeController.CurrentDateTime(); - + currentDateTime = std::chrono::time_point_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime()); if (currentDateTime.IsUpdated()) { - auto hour = dateTimeController.Hours(); - auto minute = dateTimeController.Minutes(); - auto second = dateTimeController.Seconds(); - auto year = dateTimeController.Year(); - auto month = dateTimeController.Month(); - auto dayOfWeek = dateTimeController.DayOfWeek(); - auto day = dateTimeController.Day(); - - if (displayedHour != hour || displayedMinute != minute || displayedSecond != second) { - displayedHour = hour; - displayedMinute = minute; - displayedSecond = second; + uint8_t hour = dateTimeController.Hours(); + uint8_t minute = dateTimeController.Minutes(); + uint8_t second = dateTimeController.Seconds(); - 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_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d %s#", hour, minute, second, ampmChar); - } else { - lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second); + 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_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d %s#", hour, minute, second, ampmChar); + } else { + lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second); } - if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { + currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get()); + if (currentDate.IsUpdated()) { + uint16_t year = dateTimeController.Year(); + Controllers::DateTime::Months month = dateTimeController.Month(); + uint8_t day = dateTimeController.Day(); lv_label_set_text_fmt(label_date, "[DATE]#007fff %04d-%02d-%02d#", short(year), char(month), char(day)); - - currentYear = year; - currentMonth = month; - currentDayOfWeek = dayOfWeek; - currentDay = day; } } @@ -157,8 +145,7 @@ void WatchFaceTerminal::Refresh() { } stepCount = motionController.NbSteps(); - motionSensorOk = motionController.IsSensorOk(); - if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { + if (stepCount.IsUpdated()) { lv_label_set_text_fmt(stepValue, "[STEP]#ee3377 %lu steps#", stepCount.Get()); } } diff --git a/src/displayapp/screens/WatchFaceTerminal.h b/src/displayapp/screens/WatchFaceTerminal.h index 67156a50..bf460866 100644 --- a/src/displayapp/screens/WatchFaceTerminal.h +++ b/src/displayapp/screens/WatchFaceTerminal.h @@ -4,8 +4,10 @@ #include <chrono> #include <cstdint> #include <memory> +#include <displayapp/Controllers.h> #include "displayapp/screens/Screen.h" #include "components/datetime/DateTimeController.h" +#include "utility/DirtyValue.h" namespace Pinetime { namespace Controllers { @@ -34,25 +36,16 @@ namespace Pinetime { void Refresh() override; private: - uint8_t displayedHour = -1; - uint8_t displayedMinute = -1; - uint8_t displayedSecond = -1; - - 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<int> batteryPercentRemaining {}; - DirtyValue<bool> powerPresent {}; - DirtyValue<bool> bleState {}; - DirtyValue<bool> bleRadioEnabled {}; - DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {}; - DirtyValue<bool> motionSensorOk {}; - DirtyValue<uint32_t> stepCount {}; - DirtyValue<uint8_t> heartbeat {}; - DirtyValue<bool> heartbeatRunning {}; - DirtyValue<bool> notificationState {}; + Utility::DirtyValue<int> batteryPercentRemaining {}; + Utility::DirtyValue<bool> powerPresent {}; + Utility::DirtyValue<bool> bleState {}; + Utility::DirtyValue<bool> bleRadioEnabled {}; + Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>> currentDateTime {}; + Utility::DirtyValue<uint32_t> stepCount {}; + Utility::DirtyValue<uint8_t> heartbeat {}; + Utility::DirtyValue<bool> heartbeatRunning {}; + Utility::DirtyValue<bool> notificationState {}; + Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate; lv_obj_t* label_time; lv_obj_t* label_date; @@ -75,5 +68,25 @@ namespace Pinetime { lv_task_t* taskRefresh; }; } + + template <> + struct WatchFaceTraits<WatchFace::Terminal> { + static constexpr WatchFace watchFace = WatchFace::Terminal; + static constexpr const char* name = "Terminal"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceTerminal(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.notificationManager, + controllers.settingsController, + controllers.heartRateController, + controllers.motionController); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + } + }; } } diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp index 4921174c..25464c70 100644 --- a/src/displayapp/screens/Weather.cpp +++ b/src/displayapp/screens/Weather.cpp @@ -1,221 +1,197 @@ -/* Copyright (C) 2021 Avamander +#include "displayapp/screens/Weather.h" - This file is part of InfiniTime. - - InfiniTime is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - InfiniTime is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ -#include "Weather.h" #include <lvgl/lvgl.h> -#include <components/ble/weather/WeatherService.h> -#include "Label.h" -#include "components/battery/BatteryController.h" -#include "components/ble/BleController.h" -#include "components/ble/weather/WeatherData.h" -using namespace Pinetime::Applications::Screens; +#include "components/ble/SimpleWeatherService.h" +#include "components/datetime/DateTimeController.h" +#include "components/settings/Settings.h" +#include "displayapp/DisplayApp.h" +#include "displayapp/screens/WeatherSymbols.h" +#include "displayapp/InfiniTimeTheme.h" -Weather::Weather(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::WeatherService& weather) - : app {app}, - weatherService(weather), - screens {app, - 0, - {[this]() -> std::unique_ptr<Screen> { - return CreateScreenTemperature(); - }, - [this]() -> std::unique_ptr<Screen> { - return CreateScreenAir(); - }, - [this]() -> std::unique_ptr<Screen> { - return CreateScreenClouds(); - }, - [this]() -> std::unique_ptr<Screen> { - return CreateScreenPrecipitation(); - }, - [this]() -> std::unique_ptr<Screen> { - return CreateScreenHumidity(); - }}, - Screens::ScreenListModes::UpDown} { -} +using namespace Pinetime::Applications::Screens; -Weather::~Weather() { - lv_obj_clean(lv_scr_act()); -} +namespace { + lv_color_t TemperatureColor(Pinetime::Controllers::SimpleWeatherService::Temperature temp) { + if (temp.Celsius() <= 0) { // freezing + return Colors::blue; + } else if (temp.Celsius() <= 4) { // ice + return LV_COLOR_CYAN; + } else if (temp.Celsius() >= 27) { // hot + return Colors::deepOrange; + } + return Colors::orange; // normal + } -void Weather::Refresh() { - if (running) { - // screens.Refresh(); + uint8_t TemperatureStyle(Pinetime::Controllers::SimpleWeatherService::Temperature temp) { + if (temp.Celsius() <= 0) { // freezing + return LV_TABLE_PART_CELL3; + } else if (temp.Celsius() <= 4) { // ice + return LV_TABLE_PART_CELL4; + } else if (temp.Celsius() >= 27) { // hot + return LV_TABLE_PART_CELL6; + } + return LV_TABLE_PART_CELL5; // normal } } -bool Weather::OnButtonPushed() { - running = false; - return true; -} +Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService) + : settingsController {settingsController}, weatherService {weatherService} { -bool Weather::OnTouchEvent(Pinetime::Applications::TouchEvents event) { - return screens.OnTouchEvent(event); -} + temperature = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_font(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); + lv_label_set_text(temperature, "---"); + lv_obj_align(temperature, nullptr, LV_ALIGN_CENTER, 0, -30); + lv_obj_set_auto_realign(temperature, true); -std::unique_ptr<Screen> Weather::CreateScreenTemperature() { - lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_recolor(label, true); - std::unique_ptr<Controllers::WeatherData::Temperature>& current = weatherService.GetCurrentTemperature(); - if (current->timestamp == 0) { - // Do not use the data, it's invalid - lv_label_set_text_fmt(label, - "#FFFF00 Temperature#\n\n" - "#444444 %d#°C \n\n" - "#444444 %d#\n\n" - "%d\n" - "%d\n", - 0, - 0, - 0, - 0); - } else { - lv_label_set_text_fmt(label, - "#FFFF00 Temperature#\n\n" - "#444444 %d#°C \n\n" - "#444444 %hd#\n\n" - "%llu\n" - "%lu\n", - current->temperature / 100, - current->dewPoint, - current->timestamp, - current->expires); - } - lv_label_set_align(label, LV_LABEL_ALIGN_CENTER); - lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); - return std::unique_ptr<Screen>(new Screens::Label(0, 5, label)); -} + minTemperature = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(minTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg); + lv_label_set_text(minTemperature, ""); + lv_obj_align(minTemperature, temperature, LV_ALIGN_OUT_LEFT_MID, -10, 0); + lv_obj_set_auto_realign(minTemperature, true); + + maxTemperature = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(maxTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg); + lv_label_set_text(maxTemperature, ""); + lv_obj_align(maxTemperature, temperature, LV_ALIGN_OUT_RIGHT_MID, 10, 0); + lv_obj_set_auto_realign(maxTemperature, true); + + condition = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(condition, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + lv_label_set_text(condition, ""); + lv_obj_align(condition, temperature, LV_ALIGN_OUT_TOP_MID, 0, -10); + lv_obj_set_auto_realign(condition, true); + + icon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_font(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); + lv_label_set_text(icon, ""); + lv_obj_align(icon, condition, LV_ALIGN_OUT_TOP_MID, 0, 0); + lv_obj_set_auto_realign(icon, true); -std::unique_ptr<Screen> Weather::CreateScreenAir() { - lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_recolor(label, true); - std::unique_ptr<Controllers::WeatherData::AirQuality>& current = weatherService.GetCurrentQuality(); - if (current->timestamp == 0) { - // Do not use the data, it's invalid - lv_label_set_text_fmt(label, - "#FFFF00 Air quality#\n\n" - "#444444 %s#\n" - "#444444 %d#\n\n" - "%d\n" - "%d\n", - "", - 0, - 0, - 0); - } else { - lv_label_set_text_fmt(label, - "#FFFF00 Air quality#\n\n" - "#444444 %s#\n" - "#444444 %lu#\n\n" - "%llu\n" - "%lu\n", - current->polluter.c_str(), - (current->amount / 100), - current->timestamp, - current->expires); + forecast = lv_table_create(lv_scr_act(), nullptr); + lv_table_set_col_cnt(forecast, Controllers::SimpleWeatherService::MaxNbForecastDays); + lv_table_set_row_cnt(forecast, 4); + // LV_TABLE_PART_CELL1: Default table style + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, Colors::lightGray); + // LV_TABLE_PART_CELL2: Condition icon + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_font(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, &fontawesome_weathericons); + // LV_TABLE_PART_CELL3: Freezing + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, Colors::blue); + // LV_TABLE_PART_CELL4: Ice + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_CYAN); + // LV_TABLE_PART_CELL5: Normal + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, Colors::orange); + // LV_TABLE_PART_CELL6: Hot + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, Colors::deepOrange); + + lv_obj_align(forecast, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); + + for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) { + lv_table_set_col_width(forecast, i, 48); + lv_table_set_cell_type(forecast, 1, i, LV_TABLE_PART_CELL2); + lv_table_set_cell_align(forecast, 0, i, LV_LABEL_ALIGN_CENTER); + lv_table_set_cell_align(forecast, 1, i, LV_LABEL_ALIGN_CENTER); + lv_table_set_cell_align(forecast, 2, i, LV_LABEL_ALIGN_CENTER); + lv_table_set_cell_align(forecast, 3, i, LV_LABEL_ALIGN_CENTER); } - lv_label_set_align(label, LV_LABEL_ALIGN_CENTER); - lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); - return std::unique_ptr<Screen>(new Screens::Label(0, 5, label)); + + taskRefresh = lv_task_create(RefreshTaskCallback, 1000, LV_TASK_PRIO_MID, this); + Refresh(); } -std::unique_ptr<Screen> Weather::CreateScreenClouds() { - lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_recolor(label, true); - std::unique_ptr<Controllers::WeatherData::Clouds>& current = weatherService.GetCurrentClouds(); - if (current->timestamp == 0) { - // Do not use the data, it's invalid - lv_label_set_text_fmt(label, - "#FFFF00 Clouds#\n\n" - "#444444 %d%%#\n\n" - "%d\n" - "%d\n", - 0, - 0, - 0); - } else { - lv_label_set_text_fmt(label, - "#FFFF00 Clouds#\n\n" - "#444444 %hhu%%#\n\n" - "%llu\n" - "%lu\n", - current->amount, - current->timestamp, - current->expires); - } - lv_label_set_align(label, LV_LABEL_ALIGN_CENTER); - lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); - return std::unique_ptr<Screen>(new Screens::Label(0, 5, label)); +Weather::~Weather() { + lv_task_del(taskRefresh); + lv_obj_clean(lv_scr_act()); } -std::unique_ptr<Screen> Weather::CreateScreenPrecipitation() { - lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_recolor(label, true); - std::unique_ptr<Controllers::WeatherData::Precipitation>& current = weatherService.GetCurrentPrecipitation(); - if (current->timestamp == 0) { - // Do not use the data, it's invalid - lv_label_set_text_fmt(label, - "#FFFF00 Precipitation#\n\n" - "#444444 %d%%#\n\n" - "%d\n" - "%d\n", - 0, - 0, - 0); - } else { - lv_label_set_text_fmt(label, - "#FFFF00 Precipitation#\n\n" - "#444444 %hhu%%#\n\n" - "%llu\n" - "%lu\n", - current->amount, - current->timestamp, - current->expires); +void Weather::Refresh() { + currentWeather = weatherService.Current(); + if (currentWeather.IsUpdated()) { + auto optCurrentWeather = currentWeather.Get(); + if (optCurrentWeather) { + int16_t temp = optCurrentWeather->temperature.Celsius(); + int16_t minTemp = optCurrentWeather->minTemperature.Celsius(); + int16_t maxTemp = optCurrentWeather->maxTemperature.Celsius(); + char tempUnit = 'C'; + if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { + temp = optCurrentWeather->temperature.Fahrenheit(); + minTemp = optCurrentWeather->minTemperature.Fahrenheit(); + maxTemp = optCurrentWeather->maxTemperature.Fahrenheit(); + tempUnit = 'F'; + } + lv_obj_set_style_local_text_color(temperature, + LV_LABEL_PART_MAIN, + LV_STATE_DEFAULT, + TemperatureColor(optCurrentWeather->temperature)); + lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId)); + lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId)); + lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); + lv_label_set_text_fmt(minTemperature, "%d°", minTemp); + lv_label_set_text_fmt(maxTemperature, "%d°", maxTemp); + } else { + lv_label_set_text(icon, ""); + lv_label_set_text(condition, ""); + lv_label_set_text(temperature, "---"); + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_label_set_text(minTemperature, ""); + lv_label_set_text(maxTemperature, ""); + } } - lv_label_set_align(label, LV_LABEL_ALIGN_CENTER); - lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); - return std::unique_ptr<Screen>(new Screens::Label(0, 5, label)); -} -std::unique_ptr<Screen> Weather::CreateScreenHumidity() { - lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_recolor(label, true); - std::unique_ptr<Controllers::WeatherData::Humidity>& current = weatherService.GetCurrentHumidity(); - if (current->timestamp == 0) { - // Do not use the data, it's invalid - lv_label_set_text_fmt(label, - "#FFFF00 Humidity#\n\n" - "#444444 %d%%#\n\n" - "%d\n" - "%d\n", - 0, - 0, - 0); - } else { - lv_label_set_text_fmt(label, - "#FFFF00 Humidity#\n\n" - "#444444 %hhu%%#\n\n" - "%llu\n" - "%lu\n", - current->humidity, - current->timestamp, - current->expires); + currentForecast = weatherService.GetForecast(); + if (currentForecast.IsUpdated()) { + auto optCurrentForecast = currentForecast.Get(); + if (optCurrentForecast) { + std::tm localTime = *std::localtime(reinterpret_cast<const time_t*>(&optCurrentForecast->timestamp)); + + for (int i = 0; i < optCurrentForecast->nbDays; i++) { + int16_t maxTemp = optCurrentForecast->days[i]->maxTemperature.Celsius(); + int16_t minTemp = optCurrentForecast->days[i]->minTemperature.Celsius(); + if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { + maxTemp = optCurrentForecast->days[i]->maxTemperature.Fahrenheit(); + minTemp = optCurrentForecast->days[i]->minTemperature.Fahrenheit(); + } + lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(optCurrentForecast->days[i]->maxTemperature)); + lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(optCurrentForecast->days[i]->minTemperature)); + uint8_t wday = localTime.tm_wday + i + 1; + if (wday > 7) { + wday -= 7; + } + const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(wday)); + lv_table_set_cell_value(forecast, 0, i, dayOfWeek); + lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i]->iconId)); + // Pad cells based on the largest number of digits on each column + char maxPadding[3] = " "; + char minPadding[3] = " "; + int diff = snprintf(nullptr, 0, "%d", maxTemp) - snprintf(nullptr, 0, "%d", minTemp); + if (diff <= 0) { + maxPadding[-diff] = '\0'; + minPadding[0] = '\0'; + } else { + maxPadding[0] = '\0'; + minPadding[diff] = '\0'; + } + lv_table_set_cell_value_fmt(forecast, 2, i, "%s%d", maxPadding, maxTemp); + lv_table_set_cell_value_fmt(forecast, 3, i, "%s%d", minPadding, minTemp); + } + } else { + for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) { + lv_table_set_cell_value(forecast, 0, i, ""); + lv_table_set_cell_value(forecast, 1, i, ""); + lv_table_set_cell_value(forecast, 2, i, ""); + lv_table_set_cell_value(forecast, 3, i, ""); + lv_table_set_cell_type(forecast, 2, i, LV_TABLE_PART_CELL1); + lv_table_set_cell_type(forecast, 3, i, LV_TABLE_PART_CELL1); + } + } } - lv_label_set_align(label, LV_LABEL_ALIGN_CENTER); - lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); - return std::unique_ptr<Screen>(new Screens::Label(0, 5, label)); } diff --git a/src/displayapp/screens/Weather.h b/src/displayapp/screens/Weather.h index 459534aa..6975311e 100644 --- a/src/displayapp/screens/Weather.h +++ b/src/displayapp/screens/Weather.h @@ -1,45 +1,56 @@ #pragma once -#include <memory> -#include <components/ble/weather/WeatherService.h> -#include "Screen.h" -#include "ScreenList.h" +#include <cstdint> +#include <lvgl/lvgl.h> +#include "displayapp/screens/Screen.h" +#include "components/ble/SimpleWeatherService.h" +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" +#include "utility/DirtyValue.h" namespace Pinetime { - namespace Applications { - class DisplayApp; + namespace Controllers { + class Settings; + } + + namespace Applications { namespace Screens { + class Weather : public Screen { public: - explicit Weather(DisplayApp* app, Pinetime::Controllers::WeatherService& weather); - + Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService); ~Weather() override; void Refresh() override; - bool OnButtonPushed() override; - - bool OnTouchEvent(TouchEvents event) override; - private: - DisplayApp* app; - bool running = true; - - Controllers::WeatherService& weatherService; - - ScreenList<5> screens; + Controllers::Settings& settingsController; + Controllers::SimpleWeatherService& weatherService; - std::unique_ptr<Screen> CreateScreenTemperature(); + Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {}; + Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::Forecast>> currentForecast {}; - std::unique_ptr<Screen> CreateScreenAir(); + lv_obj_t* icon; + lv_obj_t* condition; + lv_obj_t* temperature; + lv_obj_t* minTemperature; + lv_obj_t* maxTemperature; + lv_obj_t* forecast; - std::unique_ptr<Screen> CreateScreenClouds(); + lv_task_t* taskRefresh; + }; + } - std::unique_ptr<Screen> CreateScreenPrecipitation(); + template <> + struct AppTraits<Apps::Weather> { + static constexpr Apps app = Apps::Weather; + static constexpr const char* icon = Screens::Symbols::cloudSunRain; - std::unique_ptr<Screen> CreateScreenHumidity(); + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Weather(controllers.settingsController, *controllers.weatherController); }; - } + }; } } diff --git a/src/displayapp/screens/WeatherSymbols.cpp b/src/displayapp/screens/WeatherSymbols.cpp new file mode 100644 index 00000000..de66312f --- /dev/null +++ b/src/displayapp/screens/WeatherSymbols.cpp @@ -0,0 +1,61 @@ +#include "displayapp/screens/WeatherSymbols.h" + +const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon) { + switch (icon) { + case Pinetime::Controllers::SimpleWeatherService::Icons::Sun: + return Symbols::sun; + break; + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun: + return Symbols::cloudSun; + break; + case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds: + return Symbols::cloud; + break; + case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds: + return Symbols::cloudMeatball; + break; + case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm: + return Symbols::bolt; + break; + case Pinetime::Controllers::SimpleWeatherService::Icons::Snow: + return Symbols::snowflake; + break; + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy: + return Symbols::cloudShowersHeavy; + break; + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain: + return Symbols::cloudSunRain; + break; + case Pinetime::Controllers::SimpleWeatherService::Icons::Smog: + return Symbols::smog; + break; + default: + return Symbols::ban; + break; + } +} + +const char* Pinetime::Applications::Screens::Symbols::GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon) { + switch (icon) { + case Pinetime::Controllers::SimpleWeatherService::Icons::Sun: + return "Clear sky"; + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun: + return "Few clouds"; + case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds: + return "Scattered clouds"; + case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds: + return "Broken clouds"; + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy: + return "Shower rain"; + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain: + return "Rain"; + case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm: + return "Thunderstorm"; + case Pinetime::Controllers::SimpleWeatherService::Icons::Snow: + return "Snow"; + case Pinetime::Controllers::SimpleWeatherService::Icons::Smog: + return "Mist"; + default: + return ""; + } +} diff --git a/src/displayapp/screens/WeatherSymbols.h b/src/displayapp/screens/WeatherSymbols.h new file mode 100644 index 00000000..f3eeed55 --- /dev/null +++ b/src/displayapp/screens/WeatherSymbols.h @@ -0,0 +1,14 @@ +#pragma once +#include "components/ble/SimpleWeatherService.h" +#include "displayapp/screens/Symbols.h" + +namespace Pinetime { + namespace Applications { + namespace Screens { + namespace Symbols { + const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon); + const char* GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon); + } + } + } +} diff --git a/src/displayapp/screens/settings/QuickSettings.cpp b/src/displayapp/screens/settings/QuickSettings.cpp index 05484888..c5c3071a 100644 --- a/src/displayapp/screens/settings/QuickSettings.cpp +++ b/src/displayapp/screens/settings/QuickSettings.cpp @@ -33,13 +33,14 @@ QuickSettings::QuickSettings(Pinetime::Applications::DisplayApp* app, Controllers::BrightnessController& brightness, Controllers::MotorController& motorController, Pinetime::Controllers::Settings& settingsController, - const Controllers::Ble& bleController) + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController) : app {app}, dateTimeController {dateTimeController}, brightness {brightness}, motorController {motorController}, settingsController {settingsController}, - statusIcons(batteryController, bleController) { + statusIcons(batteryController, bleController, alarmController) { statusIcons.Create(); diff --git a/src/displayapp/screens/settings/QuickSettings.h b/src/displayapp/screens/settings/QuickSettings.h index 55da6176..87c126b7 100644 --- a/src/displayapp/screens/settings/QuickSettings.h +++ b/src/displayapp/screens/settings/QuickSettings.h @@ -23,7 +23,8 @@ namespace Pinetime { Controllers::BrightnessController& brightness, Controllers::MotorController& motorController, Pinetime::Controllers::Settings& settingsController, - const Controllers::Ble& bleController); + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController); ~QuickSettings() override; diff --git a/src/displayapp/screens/settings/SettingBluetooth.cpp b/src/displayapp/screens/settings/SettingBluetooth.cpp index 82c3dee1..e4dc695c 100644 --- a/src/displayapp/screens/settings/SettingBluetooth.cpp +++ b/src/displayapp/screens/settings/SettingBluetooth.cpp @@ -36,17 +36,19 @@ namespace { SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) : app {app}, + settings {settingsController}, checkboxList( 0, 1, "Bluetooth", Symbols::bluetooth, settingsController.GetBleRadioEnabled() ? 0 : 1, - [&settings = settingsController](uint32_t index) { + [this](uint32_t index) { const bool priorMode = settings.GetBleRadioEnabled(); const bool newMode = options[index].radioEnabled; if (newMode != priorMode) { settings.SetBleRadioEnabled(newMode); + this->app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle); } }, CreateOptionArray()) { @@ -54,6 +56,4 @@ SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pine SettingBluetooth::~SettingBluetooth() { lv_obj_clean(lv_scr_act()); - // Pushing the message in the OnValueChanged function causes a freeze? - app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle); } diff --git a/src/displayapp/screens/settings/SettingBluetooth.h b/src/displayapp/screens/settings/SettingBluetooth.h index 1e3f9b81..0cf014f5 100644 --- a/src/displayapp/screens/settings/SettingBluetooth.h +++ b/src/displayapp/screens/settings/SettingBluetooth.h @@ -20,6 +20,7 @@ namespace Pinetime { private: DisplayApp* app; + Pinetime::Controllers::Settings& settings; CheckboxList checkboxList; }; } diff --git a/src/displayapp/screens/settings/SettingDisplay.cpp b/src/displayapp/screens/settings/SettingDisplay.cpp index a9476432..bbc188a9 100644 --- a/src/displayapp/screens/settings/SettingDisplay.cpp +++ b/src/displayapp/screens/settings/SettingDisplay.cpp @@ -9,16 +9,22 @@ using namespace Pinetime::Applications::Screens; namespace { - void event_handler(lv_obj_t* obj, lv_event_t event) { + void TimeoutEventHandler(lv_obj_t* obj, lv_event_t event) { auto* screen = static_cast<SettingDisplay*>(obj->user_data); screen->UpdateSelected(obj, event); } + + void AlwaysOnEventHandler(lv_obj_t* obj, lv_event_t event) { + if (event == LV_EVENT_VALUE_CHANGED) { + auto* screen = static_cast<SettingDisplay*>(obj->user_data); + screen->ToggleAlwaysOn(); + } + } } constexpr std::array<uint16_t, 6> SettingDisplay::options; -SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) - : app {app}, settingsController {settingsController} { +SettingDisplay::SettingDisplay(Pinetime::Controllers::Settings& settingsController) : settingsController {settingsController} { lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr); @@ -43,19 +49,26 @@ SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER); lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); - char buffer[12]; + char buffer[4]; for (unsigned int i = 0; i < options.size(); i++) { cbOption[i] = lv_checkbox_create(container1, nullptr); - sprintf(buffer, "%2ds", options[i] / 1000); + snprintf(buffer, sizeof(buffer), "%2" PRIu16 "s", options[i] / 1000); lv_checkbox_set_text(cbOption[i], buffer); cbOption[i]->user_data = this; - lv_obj_set_event_cb(cbOption[i], event_handler); + lv_obj_set_event_cb(cbOption[i], TimeoutEventHandler); SetRadioButtonStyle(cbOption[i]); if (settingsController.GetScreenTimeOut() == options[i]) { lv_checkbox_set_checked(cbOption[i], true); } } + + alwaysOnCheckbox = lv_checkbox_create(container1, nullptr); + lv_checkbox_set_text(alwaysOnCheckbox, "Always On"); + lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting()); + lv_obj_add_state(alwaysOnCheckbox, LV_STATE_DEFAULT); + alwaysOnCheckbox->user_data = this; + lv_obj_set_event_cb(alwaysOnCheckbox, AlwaysOnEventHandler); } SettingDisplay::~SettingDisplay() { @@ -63,13 +76,17 @@ SettingDisplay::~SettingDisplay() { settingsController.SaveSettings(); } +void SettingDisplay::ToggleAlwaysOn() { + settingsController.SetAlwaysOnDisplaySetting(!settingsController.GetAlwaysOnDisplaySetting()); + lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting()); +} + void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) { if (event == LV_EVENT_CLICKED) { for (unsigned int i = 0; i < options.size(); i++) { if (object == cbOption[i]) { lv_checkbox_set_checked(cbOption[i], true); settingsController.SetScreenTimeOut(options[i]); - app->PushMessage(Applications::Display::Messages::UpdateTimeOut); } else { lv_checkbox_set_checked(cbOption[i], false); } diff --git a/src/displayapp/screens/settings/SettingDisplay.h b/src/displayapp/screens/settings/SettingDisplay.h index 64212c02..3bd10a62 100644 --- a/src/displayapp/screens/settings/SettingDisplay.h +++ b/src/displayapp/screens/settings/SettingDisplay.h @@ -14,17 +14,18 @@ namespace Pinetime { class SettingDisplay : public Screen { public: - SettingDisplay(DisplayApp* app, Pinetime::Controllers::Settings& settingsController); + SettingDisplay(Pinetime::Controllers::Settings& settingsController); ~SettingDisplay() override; void UpdateSelected(lv_obj_t* object, lv_event_t event); + void ToggleAlwaysOn(); private: - DisplayApp* app; static constexpr std::array<uint16_t, 6> options = {5000, 7000, 10000, 15000, 20000, 30000}; Controllers::Settings& settingsController; lv_obj_t* cbOption[options.size()]; + lv_obj_t* alwaysOnCheckbox; }; } } diff --git a/src/displayapp/screens/settings/SettingSetDateTime.cpp b/src/displayapp/screens/settings/SettingSetDateTime.cpp index cf9b0638..8926ff31 100644 --- a/src/displayapp/screens/settings/SettingSetDateTime.cpp +++ b/src/displayapp/screens/settings/SettingSetDateTime.cpp @@ -15,8 +15,7 @@ bool SettingSetDateTime::OnTouchEvent(Pinetime::Applications::TouchEvents event) SettingSetDateTime::SettingSetDateTime(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::DateTime& dateTimeController, Pinetime::Controllers::Settings& settingsController) - : app {app}, - dateTimeController {dateTimeController}, + : dateTimeController {dateTimeController}, settingsController {settingsController}, screens {app, 0, diff --git a/src/displayapp/screens/settings/SettingSetDateTime.h b/src/displayapp/screens/settings/SettingSetDateTime.h index 051b1abe..dea283f8 100644 --- a/src/displayapp/screens/settings/SettingSetDateTime.h +++ b/src/displayapp/screens/settings/SettingSetDateTime.h @@ -20,7 +20,6 @@ namespace Pinetime { void Quit(); private: - DisplayApp* app; Controllers::DateTime& dateTimeController; Controllers::Settings& settingsController; diff --git a/src/displayapp/screens/settings/SettingWakeUp.cpp b/src/displayapp/screens/settings/SettingWakeUp.cpp index 8df34c20..4649dc82 100644 --- a/src/displayapp/screens/settings/SettingWakeUp.cpp +++ b/src/displayapp/screens/settings/SettingWakeUp.cpp @@ -8,7 +8,7 @@ using namespace Pinetime::Applications::Screens; -constexpr std::array<SettingWakeUp::Option, 4> SettingWakeUp::options; +constexpr std::array<SettingWakeUp::Option, 5> SettingWakeUp::options; namespace { void event_handler(lv_obj_t* obj, lv_event_t event) { @@ -27,9 +27,9 @@ SettingWakeUp::SettingWakeUp(Pinetime::Controllers::Settings& settingsController lv_obj_set_style_local_pad_inner(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5); lv_obj_set_style_local_border_width(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0); - lv_obj_set_pos(container1, 10, 60); + lv_obj_set_pos(container1, 10, 35); lv_obj_set_width(container1, LV_HOR_RES - 20); - lv_obj_set_height(container1, LV_VER_RES - 50); + lv_obj_set_height(container1, LV_VER_RES - 20); lv_cont_set_layout(container1, LV_LAYOUT_COLUMN_LEFT); lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr); diff --git a/src/displayapp/screens/settings/SettingWakeUp.h b/src/displayapp/screens/settings/SettingWakeUp.h index 28219ca1..61edabce 100644 --- a/src/displayapp/screens/settings/SettingWakeUp.h +++ b/src/displayapp/screens/settings/SettingWakeUp.h @@ -25,11 +25,12 @@ namespace Pinetime { }; Controllers::Settings& settingsController; - static constexpr std::array<Option, 4> options = {{ + static constexpr std::array<Option, 5> options = {{ {Controllers::Settings::WakeUpMode::SingleTap, "Single Tap"}, {Controllers::Settings::WakeUpMode::DoubleTap, "Double Tap"}, {Controllers::Settings::WakeUpMode::RaiseWrist, "Raise Wrist"}, {Controllers::Settings::WakeUpMode::Shake, "Shake Wake"}, + {Controllers::Settings::WakeUpMode::LowerWrist, "Lower Wrist"}, }}; lv_obj_t* cbOption[options.size()]; diff --git a/src/displayapp/screens/settings/SettingWatchFace.cpp b/src/displayapp/screens/settings/SettingWatchFace.cpp index 285efa72..0d5168d2 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.cpp +++ b/src/displayapp/screens/settings/SettingWatchFace.cpp @@ -9,6 +9,37 @@ using namespace Pinetime::Applications::Screens; constexpr const char* SettingWatchFace::title; constexpr const char* SettingWatchFace::symbol; +namespace { + uint32_t IndexOf(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item, + Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces, + Pinetime::Applications::WatchFace watchface) { + size_t index = 0; + auto found = std::find_if(watchfaces.begin(), + watchfaces.end(), + [&index, &watchface](const Pinetime::Applications::Screens::SettingWatchFace::Item& item) { + const bool result = item.watchface == watchface; + if (!result) { + index++; + } + return result; + }); + if (found == watchfaces.end()) { + index = 0; + } + + return index; + } + + Pinetime::Applications::WatchFace IndexToWatchFace(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item, + Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces, + size_t index) { + if (index >= watchfaces.size()) { + return watchfaces[0].watchface; + } + return watchfaces[index].watchface; + } +} + auto SettingWatchFace::CreateScreenList() const { std::array<std::function<std::unique_ptr<Screen>()>, nScreens> screens; for (size_t i = 0; i < screens.size(); i++) { @@ -20,9 +51,10 @@ auto SettingWatchFace::CreateScreenList() const { } SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app, + std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count>&& watchfaceItems, Pinetime::Controllers::Settings& settingsController, Pinetime::Controllers::FS& filesystem) - : app {app}, + : watchfaceItems {std::move(watchfaceItems)}, settingsController {settingsController}, filesystem {filesystem}, screens {app, 0, CreateScreenList(), Screens::ScreenListModes::UpDown} { @@ -39,7 +71,12 @@ bool SettingWatchFace::OnTouchEvent(Pinetime::Applications::TouchEvents event) { std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) const { std::array<Screens::CheckboxList::Item, settingsPerScreen> watchfacesOnThisScreen; for (int i = 0; i < settingsPerScreen; i++) { - watchfacesOnThisScreen[i] = watchfaces[screenNum * settingsPerScreen + i]; + if (i + (screenNum * settingsPerScreen) >= watchfaceItems.size()) { + watchfacesOnThisScreen[i] = {"", false}; + } else { + auto& item = watchfaceItems[i + (screenNum * settingsPerScreen)]; + watchfacesOnThisScreen[i] = Screens::CheckboxList::Item {item.name, item.enabled}; + } } return std::make_unique<Screens::CheckboxList>( @@ -47,9 +84,9 @@ std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) c nScreens, title, symbol, - settingsController.GetClockFace(), - [&settings = settingsController](uint32_t clockFace) { - settings.SetClockFace(clockFace); + static_cast<uint32_t>(IndexOf(watchfaceItems, settingsController.GetWatchFace())), + [this, &settings = settingsController](uint32_t index) { + settings.SetWatchFace(IndexToWatchFace(watchfaceItems, index)); settings.SaveSettings(); }, watchfacesOnThisScreen); diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 45a50e3d..9edc1f7a 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -19,36 +19,34 @@ namespace Pinetime { class SettingWatchFace : public Screen { public: - SettingWatchFace(DisplayApp* app, Pinetime::Controllers::Settings& settingsController, Pinetime::Controllers::FS& filesystem); + struct Item { + const char* name; + WatchFace watchface; + bool enabled; + }; + + SettingWatchFace(DisplayApp* app, + std::array<Item, UserWatchFaceTypes::Count>&& watchfaceItems, + Pinetime::Controllers::Settings& settingsController, + Pinetime::Controllers::FS& filesystem); ~SettingWatchFace() override; bool OnTouchEvent(TouchEvents event) override; private: - DisplayApp* app; auto CreateScreenList() const; std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const; + static constexpr int settingsPerScreen = 4; + std::array<Item, UserWatchFaceTypes::Count> watchfaceItems; + static constexpr int nScreens = UserWatchFaceTypes::Count > 0 ? (UserWatchFaceTypes ::Count - 1) / settingsPerScreen + 1 : 1; + Controllers::Settings& settingsController; Pinetime::Controllers::FS& filesystem; static constexpr const char* title = "Watch face"; static constexpr const char* symbol = Symbols::home; - static constexpr int settingsPerScreen = 4; - - // Increment this when more space is needed - static constexpr int nScreens = 2; - - std::array<Screens::CheckboxList::Item, settingsPerScreen * nScreens> watchfaces { - {{"Digital face", true}, - {"Analog face", true}, - {"PineTimeStyle", true}, - {"Terminal", true}, - {"Infineat face", Applications::Screens::WatchFaceInfineat::IsAvailable(filesystem)}, - {"Casio G7710", Applications::Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem)}, - {"", false}, - {"", false}}}; ScreenList<nScreens> screens; }; } diff --git a/src/displayapp/screens/settings/SettingWeatherFormat.cpp b/src/displayapp/screens/settings/SettingWeatherFormat.cpp new file mode 100644 index 00000000..22d281b2 --- /dev/null +++ b/src/displayapp/screens/settings/SettingWeatherFormat.cpp @@ -0,0 +1,63 @@ +#include "displayapp/screens/settings/SettingWeatherFormat.h" + +#include <lvgl/lvgl.h> + +#include "displayapp/DisplayApp.h" +#include "displayapp/screens/Styles.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/Symbols.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + struct Option { + Pinetime::Controllers::Settings::WeatherFormat weatherFormat; + const char* name; + }; + + constexpr std::array<Option, 2> options = {{ + {Pinetime::Controllers::Settings::WeatherFormat::Metric, "Metric"}, + {Pinetime::Controllers::Settings::WeatherFormat::Imperial, "Imperial"}, + }}; + + std::array<CheckboxList::Item, CheckboxList::MaxItems> CreateOptionArray() { + std::array<Pinetime::Applications::Screens::CheckboxList::Item, CheckboxList::MaxItems> optionArray; + for (size_t i = 0; i < CheckboxList::MaxItems; i++) { + if (i >= options.size()) { + optionArray[i].name = ""; + optionArray[i].enabled = false; + } else { + optionArray[i].name = options[i].name; + optionArray[i].enabled = true; + } + } + return optionArray; + } + + uint32_t GetDefaultOption(Pinetime::Controllers::Settings::WeatherFormat currentOption) { + for (size_t i = 0; i < options.size(); i++) { + if (options[i].weatherFormat == currentOption) { + return i; + } + } + return 0; + } +} + +SettingWeatherFormat::SettingWeatherFormat(Pinetime::Controllers::Settings& settingsController) + : checkboxList( + 0, + 1, + "Weather format", + Symbols::cloudSunRain, + GetDefaultOption(settingsController.GetWeatherFormat()), + [&settings = settingsController](uint32_t index) { + settings.SetWeatherFormat(options[index].weatherFormat); + settings.SaveSettings(); + }, + CreateOptionArray()) { +} + +SettingWeatherFormat::~SettingWeatherFormat() { + lv_obj_clean(lv_scr_act()); +} diff --git a/src/displayapp/screens/settings/SettingWeatherFormat.h b/src/displayapp/screens/settings/SettingWeatherFormat.h new file mode 100644 index 00000000..a3d2bf4b --- /dev/null +++ b/src/displayapp/screens/settings/SettingWeatherFormat.h @@ -0,0 +1,26 @@ +#pragma once + +#include <array> +#include <cstdint> +#include <lvgl/lvgl.h> + +#include "components/settings/Settings.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/CheckboxList.h" + +namespace Pinetime { + + namespace Applications { + namespace Screens { + + class SettingWeatherFormat : public Screen { + public: + explicit SettingWeatherFormat(Pinetime::Controllers::Settings& settingsController); + ~SettingWeatherFormat() override; + + private: + CheckboxList checkboxList; + }; + } + } +} diff --git a/src/displayapp/screens/settings/Settings.cpp b/src/displayapp/screens/settings/Settings.cpp index 065417fa..cb5ba413 100644 --- a/src/displayapp/screens/settings/Settings.cpp +++ b/src/displayapp/screens/settings/Settings.cpp @@ -1,7 +1,7 @@ #include "displayapp/screens/settings/Settings.h" #include <lvgl/lvgl.h> #include <functional> -#include "displayapp/Apps.h" +#include "displayapp/apps/Apps.h" #include "displayapp/DisplayApp.h" using namespace Pinetime::Applications::Screens; diff --git a/src/displayapp/screens/settings/Settings.h b/src/displayapp/screens/settings/Settings.h index 3f809753..3722c2be 100644 --- a/src/displayapp/screens/settings/Settings.h +++ b/src/displayapp/screens/settings/Settings.h @@ -29,7 +29,7 @@ namespace Pinetime { static constexpr int entriesPerScreen = 4; // Increment this when more space is needed - static constexpr int nScreens = 3; + static constexpr int nScreens = 4; static constexpr std::array<List::Applications, entriesPerScreen * nScreens> entries {{ {Symbols::sun, "Display", Apps::SettingDisplay}, @@ -38,13 +38,15 @@ namespace Pinetime { {Symbols::home, "Watch face", Apps::SettingWatchFace}, {Symbols::shoe, "Steps", Apps::SettingSteps}, - {Symbols::clock, "Date&Time", Apps::SettingSetDateTime}, + {Symbols::clock, "Date & Time", Apps::SettingSetDateTime}, + {Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat}, {Symbols::batteryHalf, "Battery", Apps::BatteryInfo}, - {Symbols::clock, "Chimes", Apps::SettingChimes}, + {Symbols::clock, "Chimes", Apps::SettingChimes}, {Symbols::tachometer, "Shake Calib.", Apps::SettingShakeThreshold}, {Symbols::check, "Firmware", Apps::FirmwareValidation}, {Symbols::bluetooth, "Bluetooth", Apps::SettingBluetooth}, + {Symbols::list, "About", Apps::SysInfo}, // {Symbols::none, "None", Apps::None}, |
