Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 113 additions & 98 deletions src/displayapp/screens/StopWatch.cpp
Original file line number Diff line number Diff line change
@@ -1,56 +1,28 @@
#include "displayapp/screens/StopWatch.h"

#include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.h"
#include <lvgl/lvgl.h>
#include <FreeRTOS.h>
#include <task.h>

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 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<StopWatch*>(obj->user_data);
stopWatch->PlayPauseBtnEventHandler(event);
}
}

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);
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, 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);
Expand All @@ -59,44 +31,32 @@ 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);

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);
}
Expand All @@ -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<uint8_t>(std::min(lapTime.mins, static_cast<uint32_t>(99))),
lapTime.secs,
lapTime.hundredths / 10,
static_cast<uint8_t>(std::min(runTime.mins, static_cast<uint32_t>(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;
}
Loading