diff --git a/src/displayapp/screens/StopWatch.cpp b/src/displayapp/screens/StopWatch.cpp index 8749839f7d..ab2f2e6f35 100644 --- a/src/displayapp/screens/StopWatch.cpp +++ b/src/displayapp/screens/StopWatch.cpp @@ -1,56 +1,28 @@ #include "displayapp/screens/StopWatch.h" - -#include "displayapp/screens/Screen.h" #include "displayapp/screens/Symbols.h" -#include -#include -#include using namespace Pinetime::Applications::Screens; -// Anonymous namespace for local functions namespace { TimeSeparated_t convertTicksToTimeSegments(const TickType_t timeElapsed) { - const int timeElapsedMillis = (static_cast(timeElapsed) / static_cast(configTICK_RATE_HZ)) * 1000; - - const int hundredths = (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; + const uint8_t hundredths = timeElapsed * 100 / configTICK_RATE_HZ % 100; + const uint8_t secs = (timeElapsed / configTICK_RATE_HZ) % 60; + const uint32_t mins = (timeElapsed / configTICK_RATE_HZ) / 60; return TimeSeparated_t {mins, secs, hundredths}; } - 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; + void play_pause_event_handler(lv_obj_t* obj, lv_event_t event) { + auto* stopWatch = static_cast(obj->user_data); + stopWatch->PlayPauseBtnEventHandler(event); } -} -static void play_pause_event_handler(lv_obj_t* obj, lv_event_t event) { - auto stopWatch = static_cast(obj->user_data); - stopWatch->playPauseBtnEventHandler(event); -} - -static void stop_lap_event_handler(lv_obj_t* obj, lv_event_t event) { - auto stopWatch = static_cast(obj->user_data); - stopWatch->stopLapBtnEventHandler(event); + void stop_lap_event_handler(lv_obj_t* obj, lv_event_t event) { + auto* stopWatch = static_cast(obj->user_data); + stopWatch->StopLapBtnEventHandler(event); + } } -StopWatch::StopWatch(DisplayApp* app, System::SystemTask& systemTask) - : Screen(app), - systemTask {systemTask}, - currentState {States::Init}, - startTime {}, - oldTimeElapsed {}, - currentTimeSeparated {}, - lapBuffer {}, - lapNr {} { +StopWatch::StopWatch(DisplayApp* app, System::SystemTask& systemTask) : Screen(app), systemTask {systemTask} { 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_76); @@ -59,7 +31,6 @@ StopWatch::StopWatch(DisplayApp* app, System::SystemTask& systemTask) lv_obj_align(time, lv_scr_act(), LV_ALIGN_CENTER, 0, -45); 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_set_style_local_text_color(msecTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY); lv_label_set_text(msecTime, "00"); lv_obj_align(msecTime, lv_scr_act(), LV_ALIGN_CENTER, 0, 3); @@ -67,36 +38,25 @@ StopWatch::StopWatch(DisplayApp* app, System::SystemTask& systemTask) btnPlayPause = lv_btn_create(lv_scr_act(), nullptr); btnPlayPause->user_data = this; lv_obj_set_event_cb(btnPlayPause, play_pause_event_handler); - lv_obj_set_height(btnPlayPause, 50); - lv_obj_set_width(btnPlayPause, 115); + lv_obj_set_size(btnPlayPause, 115, 50); lv_obj_align(btnPlayPause, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); - txtPlayPause = lv_label_create(btnPlayPause, nullptr); - lv_label_set_text(txtPlayPause, Symbols::play); + lv_obj_set_style_local_value_str(btnPlayPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::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_set_height(btnStopLap, 50); - lv_obj_set_width(btnStopLap, 115); + lv_obj_set_size(btnStopLap, 115, 50); lv_obj_align(btnStopLap, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); lv_obj_set_style_local_bg_color(btnStopLap, LV_BTN_PART_MAIN, LV_STATE_DISABLED, lv_color_hex(0x080808)); - txtStopLap = lv_label_create(btnStopLap, nullptr); - lv_obj_set_style_local_text_color(txtStopLap, LV_BTN_PART_MAIN, LV_STATE_DISABLED, lv_color_hex(0x888888)); - lv_label_set_text(txtStopLap, Symbols::stop); + lv_obj_set_style_local_text_color(btnStopLap, LV_BTN_PART_MAIN, LV_STATE_DISABLED, lv_color_hex(0x888888)); + lv_obj_set_style_local_value_str(btnStopLap, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::stop); + lv_obj_set_style_local_value_str(btnStopLap, LV_BTN_PART_MAIN, LV_STATE_DISABLED, Symbols::stop); lv_obj_set_state(btnStopLap, LV_STATE_DISABLED); - lv_obj_set_state(txtStopLap, LV_STATE_DISABLED); - - 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_set_style_local_text_color(lapOneText, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_YELLOW); - 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_set_style_local_text_color(lapTwoText, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_YELLOW); - lv_obj_align(lapTwoText, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 50, 55); - lv_label_set_text(lapTwoText, ""); + lapText = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(lapText, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_SILVER); + lv_obj_align(lapText, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 0, 72); + lv_label_set_text(lapText, ""); taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); } @@ -107,94 +67,149 @@ StopWatch::~StopWatch() { lv_obj_clean(lv_scr_act()); } -void StopWatch::reset() { +void StopWatch::Reset() { currentState = States::Init; oldTimeElapsed = 0; + lv_obj_set_hidden(msecTime, false); lv_obj_set_style_local_text_color(time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY); lv_obj_set_style_local_text_color(msecTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY); - 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; + lv_label_set_text(lapText, ""); + savedLapsCount = 0; + displayedLapsStart = 0; lv_obj_set_state(btnStopLap, LV_STATE_DISABLED); - lv_obj_set_state(txtStopLap, LV_STATE_DISABLED); + + lv_obj_align(time, lv_scr_act(), LV_ALIGN_CENTER, 0, -45); + SetTime(0); } -void StopWatch::start() { +void StopWatch::Start() { lv_obj_set_state(btnStopLap, LV_STATE_DEFAULT); - lv_obj_set_state(txtStopLap, LV_STATE_DEFAULT); + lv_obj_set_style_local_value_str(btnPlayPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::pause); + lv_obj_set_style_local_value_str(btnStopLap, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::lapsFlag); + lv_obj_set_style_local_text_color(time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN); lv_obj_set_style_local_text_color(msecTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN); - lv_label_set_text(txtPlayPause, Symbols::pause); - lv_label_set_text(txtStopLap, Symbols::lapsFlag); + startTime = xTaskGetTickCount(); currentState = States::Running; systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); + + displayedLapsStart = std::max(savedLapsCount - 5, 0); + DisplayLaps(displayedLapsStart); } -void StopWatch::pause() { +void StopWatch::Pause() { startTime = 0; // Store the current time elapsed in cache - oldTimeElapsed += timeElapsed; + oldTimeElapsed = lapTimes[savedLapsCount]; currentState = States::Halted; - lv_label_set_text(txtPlayPause, Symbols::play); - lv_label_set_text(txtStopLap, Symbols::stop); + + lv_obj_set_style_local_value_str(btnPlayPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::play); + lv_obj_set_style_local_value_str(btnStopLap, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::stop); + lv_obj_set_style_local_text_color(time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_YELLOW); lv_obj_set_style_local_text_color(msecTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_YELLOW); + systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); } +void StopWatch::DisplayLaps(uint8_t startLap) { + char buffer[22]; + std::string allLapsText = ""; + TimeSeparated_t lapTime = {0}; + TimeSeparated_t runTime = {0}; + for (uint8_t i = startLap; i < savedLapsCount && i < startLap + 5 && i < maxLapCount; i++) { + runTime = convertTicksToTimeSegments(lapTimes[i]); + if (i > 0) { + lapTime = convertTicksToTimeSegments(lapTimes[i] - lapTimes[i - 1]); + } else { + lapTime = runTime; + } + sprintf(buffer, + "#%-2d %2d:%02d.%1d %2d:%02d.%1d\n", + i + 1, + static_cast(std::min(lapTime.mins, static_cast(99))), + lapTime.secs, + lapTime.hundredths / 10, + static_cast(std::min(runTime.mins, static_cast(99))), + runTime.secs, + runTime.hundredths / 10); + + allLapsText.append(buffer); + } + lv_label_set_text(lapText, allLapsText.c_str()); +} + +void StopWatch::SetTime(TickType_t timeToSet) { + TimeSeparated_t currentTimeSeparated = convertTicksToTimeSegments(timeToSet); + + lv_label_set_text_fmt(time, "%02d:%02d", currentTimeSeparated.mins, currentTimeSeparated.secs); + lv_label_set_text_fmt(msecTime, "%02d", currentTimeSeparated.hundredths); +} + void StopWatch::Refresh() { if (currentState == States::Running) { - 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.hundredths); + lapTimes[savedLapsCount] = oldTimeElapsed + xTaskGetTickCount() - startTime; + SetTime(lapTimes[savedLapsCount]); } } -void StopWatch::playPauseBtnEventHandler(lv_event_t event) { +void StopWatch::PlayPauseBtnEventHandler(lv_event_t event) { if (event != LV_EVENT_CLICKED) { return; } if (currentState == States::Init) { - start(); + Start(); } else if (currentState == States::Running) { - pause(); + Pause(); } else if (currentState == States::Halted) { - start(); + Start(); } } -void StopWatch::stopLapBtnEventHandler(lv_event_t event) { +void StopWatch::StopLapBtnEventHandler(lv_event_t event) { if (event != LV_EVENT_CLICKED) { return; } // If running, then this button is used to save laps if (currentState == States::Running) { - lapBuffer.addLaps(currentTimeSeparated); - lapNr++; - if (lapBuffer[1]) { - lv_label_set_text_fmt( - lapOneText, "#%2d %2d:%02d.%02d", (lapNr - 1), lapBuffer[1]->mins, lapBuffer[1]->secs, lapBuffer[1]->hundredths); - } - if (lapBuffer[0]) { - lv_label_set_text_fmt(lapTwoText, "#%2d %2d:%02d.%02d", lapNr, lapBuffer[0]->mins, lapBuffer[0]->secs, lapBuffer[0]->hundredths); + if (savedLapsCount < maxLapCount) { + savedLapsCount++; } + lv_obj_align(time, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 0); + lv_obj_set_hidden(msecTime, true); + displayedLapsStart = std::max(savedLapsCount - 5, 0); + DisplayLaps(displayedLapsStart); } else if (currentState == States::Halted) { - reset(); + Reset(); } } bool StopWatch::OnButtonPushed() { if (currentState == States::Running) { - pause(); + Pause(); return true; } return false; } + +bool StopWatch::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + if (currentState != States::Halted) { + return false; + } + + switch (event) { + case TouchEvents::SwipeUp: + displayedLapsStart = std::min({displayedLapsStart + 5, maxLapCount - 5, savedLapsCount - 5}); + break; + case TouchEvents::SwipeDown: + displayedLapsStart = std::max(displayedLapsStart - 5, 0); + break; + default: + return false; + } + DisplayLaps(displayedLapsStart); + + return true; +} diff --git a/src/displayapp/screens/StopWatch.h b/src/displayapp/screens/StopWatch.h index 06193f6886..53a6bb1e73 100644 --- a/src/displayapp/screens/StopWatch.h +++ b/src/displayapp/screens/StopWatch.h @@ -1,91 +1,55 @@ #pragma once #include "displayapp/screens/Screen.h" -#include "components/datetime/DateTimeController.h" -#include "displayapp/LittleVgl.h" - -#include -#include "portmacro_cmsis.h" - -#include +#include "lvgl/lvgl.h" #include "systemtask/SystemTask.h" -namespace Pinetime::Applications::Screens { - - enum class States { Init, Running, Halted }; - - struct TimeSeparated_t { - int mins; - int secs; - int hundredths; - }; - - // A simple buffer to hold the latest two laps - template struct LapTextBuffer_t { - LapTextBuffer_t() : buffer {}, currentSize {}, capacity {N}, head {-1} { - } +namespace Pinetime { + namespace Applications { + namespace Screens { - void addLaps(const TimeSeparated_t& timeVal) { - head++; - head %= capacity; - buffer[head] = timeVal; + enum class States { Init, Running, Halted }; - if (currentSize < capacity) { - currentSize++; - } - } + struct TimeSeparated_t { + uint32_t mins; + uint8_t secs; + uint8_t hundredths; + }; - void clearBuffer() { - buffer = {}; - currentSize = 0; - head = -1; - } + class StopWatch : public Screen { + public: + StopWatch(DisplayApp* app, System::SystemTask& systemTask); + ~StopWatch() override; + void Refresh() override; - 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; - } + void PlayPauseBtnEventHandler(lv_event_t event); + void StopLapBtnEventHandler(lv_event_t event); + bool OnButtonPushed() override; + bool OnTouchEvent(Pinetime::Applications::TouchEvents event) override; - private: - std::array buffer; - uint8_t currentSize; - uint8_t capacity; - int8_t head; - }; + private: + void DisplayLaps(uint8_t startLap); + void SetTime(TickType_t timeToSet); - class StopWatch : public Screen { - public: - StopWatch(DisplayApp* app, System::SystemTask& systemTask); - ~StopWatch() override; - void Refresh() override; + void Reset(); + void Start(); + void Pause(); - void playPauseBtnEventHandler(lv_event_t event); - void stopLapBtnEventHandler(lv_event_t event); - bool OnButtonPushed() override; + Pinetime::System::SystemTask& systemTask; + States currentState = States::Init; + TickType_t startTime = 0; + TickType_t oldTimeElapsed = 0; - void reset(); - void start(); - void pause(); + static constexpr uint8_t maxLapCount = 20; + TickType_t lapTimes[maxLapCount + 1] = {0}; - private: - Pinetime::System::SystemTask& systemTask; - TickType_t timeElapsed; - States currentState; - TickType_t startTime; - TickType_t oldTimeElapsed; - TimeSeparated_t currentTimeSeparated; // Holds Mins, Secs, millisecs - LapTextBuffer_t<2> lapBuffer; - int lapNr = 0; - lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap, *txtPlayPause, *txtStopLap; - lv_obj_t *lapOneText, *lapTwoText; + uint8_t savedLapsCount = 0; + uint8_t displayedLapsStart = 0; - lv_task_t* taskRefresh; - }; + lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap; + lv_obj_t* lapText; + lv_task_t* taskRefresh; + }; + } + } }