diff options
Diffstat (limited to 'src/displayapp/screens')
| -rw-r--r-- | src/displayapp/screens/ApplicationList.cpp | 14 | ||||
| -rw-r--r-- | src/displayapp/screens/Clock.cpp | 8 | ||||
| -rw-r--r-- | src/displayapp/screens/Notifications.cpp | 16 | ||||
| -rw-r--r-- | src/displayapp/screens/StopWatch.cpp | 210 | ||||
| -rw-r--r-- | src/displayapp/screens/StopWatch.h | 86 | ||||
| -rw-r--r-- | src/displayapp/screens/Symbols.h | 3 | ||||
| -rw-r--r-- | src/displayapp/screens/SystemInfo.cpp | 6 | ||||
| -rw-r--r-- | src/displayapp/screens/WatchFaceAnalog.cpp | 2 |
8 files changed, 323 insertions, 22 deletions
diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp index 0f3286df..60039045 100644 --- a/src/displayapp/screens/ApplicationList.cpp +++ b/src/displayapp/screens/ApplicationList.cpp @@ -56,21 +56,21 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen1() { }; - return std::unique_ptr<Screen>(new Screens::Tile(0, app, settingsController, applications)); + return std::make_unique<Screens::Tile>(0, app, settingsController, applications); } std::unique_ptr<Screen> ApplicationList::CreateScreen2() { std::array<Screens::Tile::Applications, 6> applications { {{Symbols::map, Apps::Navigation}, - {Symbols::asterisk, Apps::Meter}, + {Symbols::stopWatch, Apps::StopWatch}, {Symbols::paintbrush, Apps::Paint}, - {Symbols::info, Apps::Notifications}, - {Symbols::paddle, Apps::Paddle}, - {"2", Apps::Twos} + {Symbols::info, Apps::Notifications}, + {Symbols::paddle, Apps::Paddle}, + {"2", Apps::Twos} } }; - return std::unique_ptr<Screen>(new Screens::Tile(1, app, settingsController, applications)); + return std::make_unique<Screens::Tile>(1, app, settingsController, applications); } std::unique_ptr<Screen> ApplicationList::CreateScreen3() { @@ -84,6 +84,6 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen3() { } }; - return std::unique_ptr<Screen>(new Screens::Tile(2, app, settingsController, applications)); + return std::make_unique<Screens::Tile>(2, app, settingsController, applications); } diff --git a/src/displayapp/screens/Clock.cpp b/src/displayapp/screens/Clock.cpp index 342dd222..69180370 100644 --- a/src/displayapp/screens/Clock.cpp +++ b/src/displayapp/screens/Clock.cpp @@ -64,20 +64,20 @@ bool Clock::OnTouchEvent(Pinetime::Applications::TouchEvents event) { } std::unique_ptr<Screen> Clock::WatchFaceDigitalScreen() { - return std::unique_ptr<Screen>(new Screens::WatchFaceDigital(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController, heartRateController)); + return std::make_unique<Screens::WatchFaceDigital>(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController, heartRateController); } std::unique_ptr<Screen> Clock::WatchFaceAnalogScreen() { - return std::unique_ptr<Screen>(new Screens::WatchFaceAnalog(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController)); + return std::make_unique<Screens::WatchFaceAnalog>(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController); } /* // Examples for more watch faces std::unique_ptr<Screen> Clock::WatchFaceMinimalScreen() { - return std::unique_ptr<Screen>(new Screens::WatchFaceMinimal(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController)); + return std::make_unique<Screens::WatchFaceMinimal>(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController); } std::unique_ptr<Screen> Clock::WatchFaceCustomScreen() { - return std::unique_ptr<Screen>(new Screens::WatchFaceCustom(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController)); + return std::make_unique<Screens::WatchFaceCustom>(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController); } */
\ No newline at end of file diff --git a/src/displayapp/screens/Notifications.cpp b/src/displayapp/screens/Notifications.cpp index 4219bac7..c903ed0f 100644 --- a/src/displayapp/screens/Notifications.cpp +++ b/src/displayapp/screens/Notifications.cpp @@ -17,22 +17,22 @@ Notifications::Notifications(DisplayApp *app, auto notification = notificationManager.GetLastNotification(); if(notification.valid) { currentId = notification.id; - currentItem.reset(new NotificationItem("\nNotification", + currentItem = std::make_unique<NotificationItem>("\nNotification", notification.message.data(), notification.index, notification.category, notificationManager.NbNotifications(), mode, - alertNotificationService)); + alertNotificationService); validDisplay = true; } else { - currentItem.reset(new NotificationItem("\nNotification", + currentItem = std::make_unique<NotificationItem>("\nNotification", "No notification to display", 0, notification.category, notificationManager.NbNotifications(), Modes::Preview, - alertNotificationService)); + alertNotificationService); } if(mode == Modes::Preview) { @@ -87,13 +87,13 @@ bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) { currentId = previousNotification.id; currentItem.reset(nullptr); app->SetFullRefresh(DisplayApp::FullRefreshDirections::Up); - currentItem.reset(new NotificationItem("\nNotification", + currentItem = std::make_unique<NotificationItem>("\nNotification", previousNotification.message.data(), previousNotification.index, previousNotification.category, notificationManager.NbNotifications(), mode, - alertNotificationService)); + alertNotificationService); } return true; case Pinetime::Applications::TouchEvents::SwipeDown: { @@ -109,13 +109,13 @@ bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) { currentId = nextNotification.id; currentItem.reset(nullptr); app->SetFullRefresh(DisplayApp::FullRefreshDirections::Down); - currentItem.reset(new NotificationItem("\nNotification", + currentItem = std::make_unique<NotificationItem>("\nNotification", nextNotification.message.data(), nextNotification.index, nextNotification.category, notificationManager.NbNotifications(), mode, - alertNotificationService)); + alertNotificationService); } return true; case Pinetime::Applications::TouchEvents::LongTap: { diff --git a/src/displayapp/screens/StopWatch.cpp b/src/displayapp/screens/StopWatch.cpp new file mode 100644 index 00000000..63f18d4b --- /dev/null +++ b/src/displayapp/screens/StopWatch.cpp @@ -0,0 +1,210 @@ +#include "StopWatch.h" + +#include "Screen.h" +#include "Symbols.h" +#include "lvgl/lvgl.h" +#include "projdefs.h" +#include "FreeRTOSConfig.h" +#include "task.h" + +#include <tuple> + +using namespace Pinetime::Applications::Screens; + +// Anonymous namespace for local functions +namespace { + TimeSeparated_t convertTicksToTimeSegments(const TickType_t timeElapsed) { + const int timeElapsedMillis = (static_cast<float>(timeElapsed) / static_cast<float>(configTICK_RATE_HZ)) * 1000; + + const int milliSecs = (timeElapsedMillis % 1000) / 10; // Get only the first two digits and ignore the last + const int secs = (timeElapsedMillis / 1000) % 60; + const int mins = (timeElapsedMillis / 1000) / 60; + return TimeSeparated_t {mins, secs, milliSecs}; + } + + TickType_t calculateDelta(const TickType_t startTime, const TickType_t currentTime) { + TickType_t delta = 0; + // Take care of overflow + if (startTime > currentTime) { + delta = 0xffffffff - startTime; + delta += (currentTime + 1); + } else { + delta = currentTime - startTime; + } + return delta; + } +} + +static void play_pause_event_handler(lv_obj_t* obj, lv_event_t event) { + auto stopWatch = static_cast<StopWatch*>(obj->user_data); + stopWatch->playPauseBtnEventHandler(event); +} + +static void stop_lap_event_handler(lv_obj_t* obj, lv_event_t event) { + auto stopWatch = static_cast<StopWatch*>(obj->user_data); + stopWatch->stopLapBtnEventHandler(event); +} + +StopWatch::StopWatch(DisplayApp* app) + : Screen(app), running {true}, currentState {States::Init}, currentEvent {Events::Stop}, startTime {}, oldTimeElapsed {}, + currentTimeSeparated {}, lapBuffer {}, lapNr {}, lapPressed {false} { + + time = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed); + lv_obj_align(time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -45); + lv_label_set_text(time, "00:00"); + + msecTime = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(msecTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_20); + lv_obj_align(msecTime, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 108, 3); + lv_label_set_text(msecTime, "00"); + + btnPlayPause = lv_btn_create(lv_scr_act(), nullptr); + btnPlayPause->user_data = this; + lv_obj_set_event_cb(btnPlayPause, play_pause_event_handler); + lv_obj_align(btnPlayPause, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, -10); + lv_obj_set_height(btnPlayPause, 40); + txtPlayPause = lv_label_create(btnPlayPause, nullptr); + lv_label_set_text(txtPlayPause, Symbols::play); + + lapOneText = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(lapOneText, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_20); + lv_obj_align(lapOneText, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 50, 30); + lv_label_set_text(lapOneText, ""); + + lapTwoText = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(lapTwoText, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_20); + lv_obj_align(lapTwoText, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 50, 55); + lv_label_set_text(lapTwoText, ""); + + // We don't want this button in the init state + btnStopLap = nullptr; +} + +StopWatch::~StopWatch() { + lv_obj_clean(lv_scr_act()); +} + +bool StopWatch::Refresh() { + // @startuml CHIP8_state + // State "Init" as init + // State "Running" as run + // State "Halted" as halt + + // [*] --> init + // init -> run : press play + // run -> run : press lap + // run --> halt : press pause + // halt --> run : press play + // halt --> init : press stop + // @enduml + // Copy paste the above plantuml text to visualize the state diagram + switch (currentState) { + // Init state when an user first opens the app + // and when a stop/reset button is pressed + case States::Init: { + if (btnStopLap) { + lv_obj_del(btnStopLap); + } + // The initial default value + lv_label_set_text(time, "00:00"); + lv_label_set_text(msecTime, "00"); + + lv_label_set_text(lapOneText, ""); + lv_label_set_text(lapTwoText, ""); + lapBuffer.clearBuffer(); + lapNr = 0; + + if (currentEvent == Events::Play) { + btnStopLap = lv_btn_create(lv_scr_act(), nullptr); + btnStopLap->user_data = this; + lv_obj_set_event_cb(btnStopLap, stop_lap_event_handler); + lv_obj_align(btnStopLap, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 0); + lv_obj_set_height(btnStopLap, 40); + txtStopLap = lv_label_create(btnStopLap, nullptr); + lv_label_set_text(txtStopLap, Symbols::lapsFlag); + + startTime = xTaskGetTickCount(); + currentState = States::Running; + } + break; + } + case States::Running: { + lv_label_set_text(txtPlayPause, Symbols::pause); + lv_label_set_text(txtStopLap, Symbols::lapsFlag); + + const auto timeElapsed = calculateDelta(startTime, xTaskGetTickCount()); + currentTimeSeparated = convertTicksToTimeSegments((oldTimeElapsed + timeElapsed)); + + lv_label_set_text_fmt(time, "%02d:%02d", currentTimeSeparated.mins, currentTimeSeparated.secs); + lv_label_set_text_fmt(msecTime, "%02d", currentTimeSeparated.msecs); + + if (lapPressed == true) { + if (lapBuffer[1]) { + lv_label_set_text_fmt(lapOneText, "#%d %d:%d:%d", (lapNr - 1), lapBuffer[1]->mins, lapBuffer[1]->secs, lapBuffer[1]->msecs); + } + if (lapBuffer[0]) { + lv_label_set_text_fmt(lapTwoText, "#%d %d:%d:%d", lapNr, lapBuffer[0]->mins, lapBuffer[0]->secs, lapBuffer[0]->msecs); + } + // Reset the bool to avoid setting the text in each cycle until there is a change + lapPressed = false; + } + + if (currentEvent == Events::Pause) { + // Reset the start time + startTime = 0; + // Store the current time elapsed in cache + oldTimeElapsed += timeElapsed; + currentState = States::Halted; + } + break; + } + case States::Halted: { + lv_label_set_text(txtPlayPause, Symbols::play); + lv_label_set_text(txtStopLap, Symbols::stop); + + if (currentEvent == Events::Play) { + startTime = xTaskGetTickCount(); + currentState = States::Running; + } + if (currentEvent == Events::Stop) { + currentState = States::Init; + oldTimeElapsed = 0; + } + break; + } + } + return running; +} + +bool StopWatch::OnButtonPushed() { + running = false; + return true; +} + +void StopWatch::playPauseBtnEventHandler(lv_event_t event) { + if (event == LV_EVENT_CLICKED) { + if (currentState == States::Init) { + currentEvent = Events::Play; + } else { + // Simple Toggle for play/pause + currentEvent = (currentEvent == Events::Play ? Events::Pause : Events::Play); + } + } +} + +void StopWatch::stopLapBtnEventHandler(lv_event_t event) { + if (event == LV_EVENT_CLICKED) { + // If running, then this button is used to save laps + if (currentState == States::Running) { + lapBuffer.addLaps(currentTimeSeparated); + lapNr++; + lapPressed = true; + + } else if (currentState == States::Halted) { + currentEvent = Events::Stop; + } else { + // Not possible to reach here. Do nothing. + } + } +} diff --git a/src/displayapp/screens/StopWatch.h b/src/displayapp/screens/StopWatch.h new file mode 100644 index 00000000..f9dd5c76 --- /dev/null +++ b/src/displayapp/screens/StopWatch.h @@ -0,0 +1,86 @@ +#pragma once + +#include "Screen.h" +#include "components/datetime/DateTimeController.h" +#include "../LittleVgl.h" + +#include "FreeRTOS.h" +#include "portmacro_cmsis.h" + +#include <array> + +namespace Pinetime::Applications::Screens { + + enum class States { Init, Running, Halted }; + + enum class Events { Play, Pause, Stop }; + + struct TimeSeparated_t { + int mins; + int secs; + int msecs; + }; + + // A simple buffer to hold the latest two laps + template <int N> struct LapTextBuffer_t { + LapTextBuffer_t() : buffer {}, currentSize {}, capacity {N}, head {-1} { + } + + void addLaps(const TimeSeparated_t& timeVal) { + head++; + head %= capacity; + buffer[head] = timeVal; + + if (currentSize < capacity) { + currentSize++; + } + } + + void clearBuffer() { + buffer = {}; + currentSize = 0; + head = -1; + } + + TimeSeparated_t* operator[](std::size_t idx) { + // Sanity check for out-of-bounds + if (idx >= 0 && idx < capacity) { + if (idx < currentSize) { + // This transformation is to ensure that head is always pointing to index 0. + const auto transformed_idx = (head - idx) % capacity; + return (&buffer[transformed_idx]); + } + } + return nullptr; + } + + private: + std::array<TimeSeparated_t, N> buffer; + uint8_t currentSize; + uint8_t capacity; + int8_t head; + }; + + class StopWatch : public Screen { + public: + StopWatch(DisplayApp* app); + ~StopWatch() override; + bool Refresh() override; + bool OnButtonPushed() override; + void playPauseBtnEventHandler(lv_event_t event); + void stopLapBtnEventHandler(lv_event_t event); + + private: + bool running; + States currentState; + Events currentEvent; + TickType_t startTime; + TickType_t oldTimeElapsed; + TimeSeparated_t currentTimeSeparated; // Holds Mins, Secs, millisecs + LapTextBuffer_t<2> lapBuffer; + int lapNr; + bool lapPressed; + lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap, *txtPlayPause, *txtStopLap; + lv_obj_t *lapOneText, *lapTwoText; + }; +} diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 1a6bbd7f..9a13a755 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -36,6 +36,9 @@ namespace Pinetime { static constexpr const char* stepBackward = "\xEF\x81\x88"; static constexpr const char* play = "\xEF\x81\x8B"; static constexpr const char* pause = "\xEF\x81\x8C"; + static constexpr const char* stop = "\xEF\x81\x8D"; + static constexpr const char* stopWatch = "\xEF\x8B\xB2"; + static constexpr const char* lapsFlag = "\xEF\x80\xA4"; } } } diff --git a/src/displayapp/screens/SystemInfo.cpp b/src/displayapp/screens/SystemInfo.cpp index 0d6f8e5a..949fd345 100644 --- a/src/displayapp/screens/SystemInfo.cpp +++ b/src/displayapp/screens/SystemInfo.cpp @@ -104,14 +104,14 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen1() { uptimeDays, uptimeHours, uptimeMinutes, uptimeSeconds, batteryPercent, brightness, resetReason); - return std::unique_ptr<Screen>(new Screens::Label(app, t1)); + return std::make_unique<Screens::Label>(app, t1); } std::unique_ptr<Screen> SystemInfo::CreateScreen2() { auto& bleAddr = bleController.Address(); sprintf(t2, "BLE MAC: \n %02x:%02x:%02x:%02x:%02x:%02x", bleAddr[5], bleAddr[4], bleAddr[3], bleAddr[2], bleAddr[1], bleAddr[0]); - return std::unique_ptr<Screen>(new Screens::Label(app, t2)); + return std::make_unique<Screens::Label>(app, t2); } std::unique_ptr<Screen> SystemInfo::CreateScreen3() { @@ -123,5 +123,5 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() { "Source code:\n" "https://github.com/\n" " JF002/InfiniTime"); - return std::unique_ptr<Screen>(new Screens::Label(app, t3)); + return std::make_unique<Screens::Label>(app, t3); } diff --git a/src/displayapp/screens/WatchFaceAnalog.cpp b/src/displayapp/screens/WatchFaceAnalog.cpp index b51d48c7..66af584a 100644 --- a/src/displayapp/screens/WatchFaceAnalog.cpp +++ b/src/displayapp/screens/WatchFaceAnalog.cpp @@ -5,6 +5,8 @@ #include "Symbols.h" #include "NotificationIcon.h" +#include <cmath> + LV_IMG_DECLARE(bg_clock); using namespace Pinetime::Applications::Screens; |
