Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved StopWatch UI (iOS-inspired) #2251

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
92 changes: 69 additions & 23 deletions src/displayapp/screens/StopWatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace {
StopWatch::StopWatch(System::SystemTask& systemTask) : wakeLock(systemTask) {
static constexpr uint8_t btnWidth = 115;
static constexpr uint8_t btnHeight = 80;
static constexpr uint8_t lapLineHeight = 30;
btnPlayPause = lv_btn_create(lv_scr_act(), nullptr);
btnPlayPause->user_data = this;
lv_obj_set_event_cb(btnPlayPause, play_pause_event_handler);
Expand All @@ -59,18 +60,18 @@ StopWatch::StopWatch(System::SystemTask& systemTask) : wakeLock(systemTask) {
lv_label_set_long_mode(lapText, LV_LABEL_LONG_BREAK);
lv_label_set_align(lapText, LV_LABEL_ALIGN_CENTER);
lv_obj_set_width(lapText, LV_HOR_RES_MAX);
lv_obj_align(lapText, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, -btnHeight);

msecTime = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(msecTime, "00");
lv_obj_set_style_local_text_color(msecTime, LV_LABEL_PART_MAIN, LV_STATE_DISABLED, Colors::lightGray);
lv_obj_align(msecTime, lapText, LV_ALIGN_OUT_TOP_MID, 0, 0);
lv_obj_align(lapText, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, -(btnHeight + lapLineHeight));

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);
lv_label_set_text_static(time, "00:00");
lv_obj_set_style_local_text_color(time, LV_LABEL_PART_MAIN, LV_STATE_DISABLED, Colors::lightGray);
lv_obj_align(time, msecTime, LV_ALIGN_OUT_TOP_MID, 0, 0);
lv_obj_align(time, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 10);

msecTime = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(msecTime, "00");
lv_obj_set_style_local_text_color(msecTime, LV_LABEL_PART_MAIN, LV_STATE_DISABLED, Colors::lightGray);
lv_obj_align(msecTime, time, LV_ALIGN_IN_TOP_MID, 0, -3);

SetInterfaceStopped();

Expand Down Expand Up @@ -135,6 +136,60 @@ void StopWatch::Start() {
startTime = xTaskGetTickCount();
currentState = States::Running;
wakeLock.Lock();

// Add first lap automatically when starting
if (lapsDone == 0) {
lapsDone = 1;
laps[0] = 0; // Current lap starts at 0
updateLapDisplay();
}
}

void StopWatch::updateLapDisplay() {
std::string displayText;
displayText.reserve(maxLapCount * 20);

std::array<char, 32> buffer{};

// Reverse loop to show newest laps first
for (int i = lapsDone - 1; i >= std::max(0, lapsDone - displayedLaps); i--) {
TimeSeparated_t times;
if (i == lapsDone - 1 && currentState == States::Running) {
// For current lap, calculate time since last lap
TickType_t currentLapTime = xTaskGetTickCount() - startTime;
if (i > 0) {
currentLapTime += oldTimeElapsed - laps[i - 1];
}
times = convertTicksToTimeSegments(currentLapTime);
} else {
// For completed laps, show the lap duration
TickType_t lapDuration = (i == 0) ? laps[i] : laps[i] - laps[i - 1];
times = convertTicksToTimeSegments(lapDuration);
}

int written = (times.hours == 0)
? snprintf(buffer.data(),
buffer.size(),
"Lap %d %2d:%02d.%02d\n",
i + 1,
times.mins,
times.secs,
times.hundredths)
: snprintf(buffer.data(),
buffer.size(),
"Lap %d %2d:%02d:%02d.%02d\n",
i + 1,
times.hours,
times.mins,
times.secs,
times.hundredths);

if (written > 0 && written < static_cast<int>(buffer.size())) {
displayText += buffer.data();
}
}

lv_label_set_text(lapText, displayText.c_str());
}

void StopWatch::Pause() {
Expand Down Expand Up @@ -163,6 +218,9 @@ void StopWatch::Refresh() {
}
}
lv_label_set_text_fmt(msecTime, "%02d", currentTimeSeparated.hundredths);

// Update the lap display
updateLapDisplay();
} else if (currentState == States::Halted) {
const TickType_t currentTime = xTaskGetTickCount();
if (currentTime > blinkTime) {
Expand All @@ -189,22 +247,10 @@ void StopWatch::playPauseBtnEventHandler() {
void StopWatch::stopLapBtnEventHandler() {
// If running, then this button is used to save laps
if (currentState == States::Running) {
lv_label_set_text(lapText, "");
// Store the current lap time
laps[lapsDone-1] = oldTimeElapsed + xTaskGetTickCount() - startTime;
lapsDone = std::min(lapsDone + 1, maxLapCount);
for (int i = lapsDone - displayedLaps; i < lapsDone; i++) {
if (i < 0) {
lv_label_ins_text(lapText, LV_LABEL_POS_LAST, "\n");
continue;
}
TimeSeparated_t times = convertTicksToTimeSegments(laps[i]);
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);
}
updateLapDisplay();
} else if (currentState == States::Halted) {
Reset();
}
Expand All @@ -216,4 +262,4 @@ bool StopWatch::OnButtonPushed() {
return true;
}
return false;
}
}
3 changes: 2 additions & 1 deletion src/displayapp/screens/StopWatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ namespace Pinetime {
bool OnButtonPushed() override;

private:
void updateLapDisplay();
void SetInterfacePaused();
void SetInterfaceRunning();
void SetInterfaceStopped();
Expand All @@ -51,7 +52,7 @@ namespace Pinetime {
TickType_t blinkTime = 0;
static constexpr int maxLapCount = 20;
TickType_t laps[maxLapCount + 1];
static constexpr int displayedLaps = 2;
static constexpr int displayedLaps = 3;
int lapsDone = 0;
lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap, *txtPlayPause, *txtStopLap;
lv_obj_t* lapText;
Expand Down
Loading