From 4eee544762c661d6b43aed34b07cead6e1010dc6 Mon Sep 17 00:00:00 2001 From: Lorenzo Prosseda Date: Sun, 3 Aug 2025 17:22:18 +0000 Subject: [PATCH] Port the interval_face complication to Second Movement (#66) * Port the interval_face complication to Second Movement - Compile inside Second Movement - Support custom display * Refactor display buffer name, enlarge index buffer Also removed now unused _blink_idx array * Fix Clear setting not showing, and its formatting * Rename work interval label to make it unique * Skip empty interval timers while cycling through --- movement_faces.h | 1 + watch-faces.mk | 1 + .../complication/interval_face.c | 139 +++++++++++++----- .../complication/interval_face.h | 0 4 files changed, 101 insertions(+), 40 deletions(-) rename {legacy/watch_faces => watch-faces}/complication/interval_face.c (82%) rename {legacy/watch_faces => watch-faces}/complication/interval_face.h (100%) diff --git a/movement_faces.h b/movement_faces.h index 542a0667..1e14d235 100644 --- a/movement_faces.h +++ b/movement_faces.h @@ -63,6 +63,7 @@ #include "tally_face.h" #include "probability_face.h" #include "ke_decimal_time_face.h" +#include "interval_face.h" #include "timer_face.h" #include "simple_coin_flip_face.h" #include "lis2dw_monitor_face.h" diff --git a/watch-faces.mk b/watch-faces.mk index bf3c54de..2b728e06 100644 --- a/watch-faces.mk +++ b/watch-faces.mk @@ -38,6 +38,7 @@ SRCS += \ ./watch-faces/complication/kitchen_conversions_face.c \ ./watch-faces/complication/periodic_table_face.c \ ./watch-faces/clock/ke_decimal_time_face.c \ + ./watch-faces/complication/interval_face.c \ ./watch-faces/complication/timer_face.c \ ./watch-faces/complication/simple_coin_flip_face.c \ ./watch-faces/sensor/lis2dw_monitor_face.c \ diff --git a/legacy/watch_faces/complication/interval_face.c b/watch-faces/complication/interval_face.c similarity index 82% rename from legacy/watch_faces/complication/interval_face.c rename to watch-faces/complication/interval_face.c index c354e722..15593567 100644 --- a/legacy/watch_faces/complication/interval_face.c +++ b/watch-faces/complication/interval_face.c @@ -28,8 +28,6 @@ #include "interval_face.h" #include "watch.h" #include "watch_utility.h" -#include "watch_private_display.h" -#include "watch_buzzer.h" typedef enum { interval_setting_0_timer_idx, @@ -48,10 +46,15 @@ typedef enum { } interval_setting_idx_t; #define INTERVAL_FACE_STATE_DEFAULT "IT" // Interval Timer +#define INTERVAL_FACE_STATE_DEFAULT_CD "INT" #define INTERVAL_FACE_STATE_WARMUP "PR" // PRepare / warm up +#define INTERVAL_FACE_STATE_WARMUP_CD "PRE" #define INTERVAL_FACE_STATE_WORK "WO" // WOrk +#define INTERVAL_FACE_STATE_WORK_CD "WOR" #define INTERVAL_FACE_STATE_BREAK "BR" // BReak +#define INTERVAL_FACE_STATE_BREAK_CD "BRK" #define INTERVAL_FACE_STATE_COOLDOWN "CD" // CoolDown +#define INTERVAL_FACE_STATE_COOLDOWN_CD "CLD" // Define some default timer settings. Each timer is described in an array like this: // 1. warm-up seconds, @@ -68,7 +71,7 @@ static const int8_t _default_timers[6][5] = {{0, 40, 20, 0, 0}, {0, -20, -5, 0, 0}}; static const uint8_t _intro_segdata[4][2] = {{1, 8}, {0, 8}, {0, 7}, {1, 7}}; -static const uint8_t _blink_idx[] = {3, 9, 4, 6, 4, 6, 8, 4, 6, 8, 4, 6}; +static const uint8_t _intro_segdata_cd[4][2] = {{1, 8}, {1, 9}, {0, 9}, {0, 8}}; static const uint8_t _setting_page_idx[] = {1, 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4}; static const int8_t _sound_seq_warmup[] = {BUZZER_NOTE_F6, 8, BUZZER_NOTE_REST, 1, -2, 3, 0}; static const int8_t _sound_seq_work[] = {BUZZER_NOTE_F6, 8, BUZZER_NOTE_REST, 1, -2, 2, BUZZER_NOTE_C7, 24, 0}; @@ -102,41 +105,56 @@ static inline void _button_beep() { if (movement_button_should_sound()) watch_buzzer_play_note(BUZZER_NOTE_C7, 50); } -static void _timer_write_info(interval_face_state_t *state, char *buf, uint8_t timer_page) { +static void _timer_write_info(interval_face_state_t *state, char* bottom_row, char state_str[2][4], char* index_str, uint8_t timer_page) { // fill display string with requested timer information switch (timer_page) { case 0: // clear timer? - sprintf(buf, "%2s %1dCLEARn", INTERVAL_FACE_STATE_DEFAULT, state->timer_idx + 1); - if (_erase_timer_flag) buf[9] = 'y'; + sprintf(bottom_row, "CLEARn"); + sprintf(state_str[0], "%2s", INTERVAL_FACE_STATE_DEFAULT); + sprintf(state_str[1], "%3s", INTERVAL_FACE_STATE_DEFAULT_CD); + sprintf(index_str, " %1d", state->timer_idx + 1); + if (_erase_timer_flag) bottom_row[5] = 'y'; watch_clear_colon(); break; case 1: // warmup time info - sprintf(buf, "%2s %1d%02d%02d ", INTERVAL_FACE_STATE_WARMUP, state->timer_idx + 1, + sprintf(bottom_row, "%02d%02d ", state->timer[state->timer_idx].warmup_minutes, state->timer[state->timer_idx].warmup_seconds); + sprintf(state_str[0], "%2s", INTERVAL_FACE_STATE_WARMUP); + sprintf(state_str[1], "%3s", INTERVAL_FACE_STATE_WARMUP_CD); + sprintf(index_str, " %1d", state->timer_idx + 1); break; case 2: // work interval info - sprintf(buf, "%2s %1d%02d%02d%2d", INTERVAL_FACE_STATE_WORK, state->timer_idx + 1, + sprintf(bottom_row, "%02d%02d%2d", state->timer[state->timer_idx].work_minutes, state->timer[state->timer_idx].work_seconds, state->timer[state->timer_idx].work_rounds); + sprintf(state_str[0], "%2s", INTERVAL_FACE_STATE_WORK); + sprintf(state_str[1], "%3s", INTERVAL_FACE_STATE_WORK_CD); + sprintf(index_str, " %1d", state->timer_idx + 1); break; case 3: // break interval info - sprintf(buf, "%2s %1d%02d%02d%2d", INTERVAL_FACE_STATE_BREAK, state->timer_idx + 1, + sprintf(bottom_row, "%02d%02d%2d", state->timer[state->timer_idx].break_minutes, state->timer[state->timer_idx].break_seconds, state->timer[state->timer_idx].full_rounds); - if (!state->timer[state->timer_idx].full_rounds) buf[9] = '-'; + if (!state->timer[state->timer_idx].full_rounds) bottom_row[5] = '-'; + sprintf(state_str[0], "%2s", INTERVAL_FACE_STATE_BREAK); + sprintf(state_str[1], "%3s", INTERVAL_FACE_STATE_BREAK_CD); + sprintf(index_str, " %1d", state->timer_idx + 1); break; case 4: // cooldown time info - sprintf(buf, "%2s %1d%02d%02d ", INTERVAL_FACE_STATE_COOLDOWN ,state->timer_idx + 1, + sprintf(bottom_row, "%02d%02d ", state->timer[state->timer_idx].cooldown_minutes, state->timer[state->timer_idx].cooldown_seconds); + sprintf(state_str[0], "%2s", INTERVAL_FACE_STATE_COOLDOWN); + sprintf(state_str[1], "%3s", INTERVAL_FACE_STATE_COOLDOWN_CD); + sprintf(index_str, " %1d", state->timer_idx + 1); break; default: break; @@ -146,8 +164,10 @@ static void _timer_write_info(interval_face_state_t *state, char *buf, uint8_t t static void _face_draw(interval_face_state_t *state, uint8_t subsecond) { // draws current face state if (!state->is_active) return; - char buf[14]; - buf[0] = 0; + char bottom_row[10]; + char int_state_str[2][4]; + char int_index_str[5]; + bottom_row[0] = 0; uint8_t tmp; if (state->face_state == interval_state_waiting && _ticks >= 0) { // play info slideshow for current timer @@ -160,9 +180,9 @@ static void _face_draw(interval_face_state_t *state, uint8_t subsecond) { } } tmp = ticks / 3 + 1; - _timer_write_info(state, buf, tmp); + _timer_write_info(state, bottom_row, int_state_str, int_index_str, tmp); // don't show '1 round' when displaying workout time to avoid detail overload - if (tmp == 2 && state->timer[state->timer_idx].work_rounds == 1) buf[9] = ' '; + if (tmp == 2 && state->timer[state->timer_idx].work_rounds == 1) bottom_row[5] = ' '; // blink colon if (subsecond % 2 == 0 && _ticks < 24) watch_clear_colon(); else watch_set_colon(); @@ -175,38 +195,66 @@ static void _face_draw(interval_face_state_t *state, uint8_t subsecond) { } else { tmp = _setting_page_idx[_setting_idx]; } - _timer_write_info(state, buf, tmp); + _timer_write_info(state, bottom_row, int_state_str, int_index_str, tmp); // blink at cursor position if (subsecond % 2 && _ticks != -2) { - buf[_blink_idx[_setting_idx]] = ' '; - if (_blink_idx[_setting_idx] % 2 == 0) buf[_blink_idx[_setting_idx] + 1] = ' '; + switch (_setting_idx) { + case interval_setting_0_timer_idx: + int_index_str[0] = int_index_str[1] = ' '; + break; + case interval_setting_1_clear_yn: + bottom_row[5] = ' '; + break; + case interval_setting_2_warmup_minutes: + case interval_setting_4_work_minutes: + case interval_setting_7_break_minutes: + case interval_setting_10_cooldown_minutes: + bottom_row[0] = bottom_row[1] = ' '; + break; + case interval_setting_3_warmup_seconds: + case interval_setting_5_work_seconds: + case interval_setting_8_break_seconds: + case interval_setting_11_cooldown_seconds: + bottom_row[2] = bottom_row[3] = ' '; + break; + case interval_setting_6_work_rounds: + case interval_setting_9_full_rounds: + bottom_row[4] = bottom_row[5] = ' '; + break; + default: + break; + } } // show lap indicator only when rounds are set if (_setting_idx == interval_setting_6_work_rounds || _setting_idx == interval_setting_9_full_rounds) watch_set_indicator(WATCH_INDICATOR_LAP); - else + else watch_clear_indicator(WATCH_INDICATOR_LAP); } else if (state->face_state == interval_state_running || state->face_state == interval_state_pausing) { tmp = _timer_full_round; switch (_timer_run_state) { case 0: - sprintf(buf, INTERVAL_FACE_STATE_WARMUP); + sprintf(int_state_str[0], "%2s", INTERVAL_FACE_STATE_WARMUP); + sprintf(int_state_str[1], "%3s", INTERVAL_FACE_STATE_WARMUP_CD); break; case 1: - sprintf(buf, INTERVAL_FACE_STATE_WORK); + sprintf(int_state_str[0], "%2s", INTERVAL_FACE_STATE_WORK); + sprintf(int_state_str[1], "%3s", INTERVAL_FACE_STATE_WORK_CD); if (state->timer[state->timer_idx].work_rounds > 1) tmp = _timer_work_round; break; case 2: - sprintf(buf, INTERVAL_FACE_STATE_BREAK); + sprintf(int_state_str[0], "%2s", INTERVAL_FACE_STATE_BREAK); + sprintf(int_state_str[1], "%3s", INTERVAL_FACE_STATE_BREAK_CD); break; case 3: - sprintf(buf, INTERVAL_FACE_STATE_COOLDOWN); + sprintf(int_state_str[0], "%2s", INTERVAL_FACE_STATE_COOLDOWN); + sprintf(int_state_str[1], "%3s", INTERVAL_FACE_STATE_COOLDOWN_CD); break; default: break; } div_t delta; - + if (state->face_state == interval_state_pausing) { // pausing delta = div(_target_ts - _paused_ts, 60); @@ -216,16 +264,21 @@ static void _face_draw(interval_face_state_t *state, uint8_t subsecond) { } else // running delta = div(_target_ts - _now_ts, 60); - sprintf(&buf[2], " %1d%02d%02d%2d", state->timer_idx + 1, delta.quot, delta.rem, tmp + 1); + sprintf(bottom_row, "%02d%02d%2d", delta.quot, delta.rem, tmp + 1); + sprintf(int_index_str, " %1d", state->timer_idx + 1); } // write out to lcd - if (buf[0]) { - watch_display_character(buf[0], 0); - watch_display_character(buf[1], 1); + if (int_state_str[0][0]) { + watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, int_state_str[1], int_state_str[0]); + watch_display_text_with_fallback(WATCH_POSITION_TOP_RIGHT, int_index_str, int_index_str); // set the bar for the i-like symbol on position 2 - watch_set_pixel(2, 9); + if (watch_get_lcd_type() == WATCH_LCD_TYPE_CLASSIC) { + watch_set_pixel(2, 9); + } else { + watch_set_pixel(2, 10); + } // display the rest of the string - watch_display_string(&buf[3], 3); + watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, bottom_row, bottom_row); } } @@ -427,8 +480,12 @@ bool interval_face_loop(movement_event_t event, void *context) { _init_timer_info(state); _face_draw(state, event.subsecond); break; - } - watch_set_pixel(_intro_segdata[_ticks][0], _intro_segdata[_ticks][1]); + } + if (watch_get_lcd_type() == WATCH_LCD_TYPE_CLASSIC) { + watch_set_pixel(_intro_segdata[_ticks][0], _intro_segdata[_ticks][1]); + } else { + watch_set_pixel(_intro_segdata_cd[_ticks][0], _intro_segdata_cd[_ticks][1]); + } _ticks++; } else if (state->face_state == interval_state_waiting && _ticks >= 0) { // play information slideshow for current interval timer @@ -448,7 +505,7 @@ bool interval_face_loop(movement_event_t event, void *context) { } break; case EVENT_ACTIVATE: - watch_display_string(INTERVAL_FACE_STATE_DEFAULT, 0); + watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, INTERVAL_FACE_STATE_DEFAULT_CD, INTERVAL_FACE_STATE_DEFAULT); if (state->face_state) _face_draw(state, event.subsecond); break; case EVENT_LIGHT_BUTTON_UP: @@ -479,7 +536,7 @@ bool interval_face_loop(movement_event_t event, void *context) { } break; case EVENT_LIGHT_LONG_PRESS: - _button_beep(settings); + _button_beep(); if (state->face_state == interval_state_setting) { _resume_setting(state, event.subsecond); } else { @@ -490,8 +547,10 @@ bool interval_face_loop(movement_event_t event, void *context) { case EVENT_ALARM_BUTTON_UP: switch (state->face_state) { case interval_state_waiting: - // cycle through timers - _inc_uint8(&state->timer_idx, 1, INTERVAL_TIMERS); + // cycle through timers, skipping empty ones + do { + _inc_uint8(&state->timer_idx, 1, INTERVAL_TIMERS); + } while (_is_timer_empty(&state->timer[state->timer_idx]) && state->timer_idx != 0); _ticks = 0; _face_draw(state, event.subsecond); break; @@ -502,7 +561,7 @@ bool interval_face_loop(movement_event_t event, void *context) { break; case interval_state_running: // pause timer - _button_beep(settings); + _button_beep(); _paused_ts = _get_now_ts(); state->face_state = interval_state_pausing; movement_cancel_background_task(); @@ -510,7 +569,7 @@ bool interval_face_loop(movement_event_t event, void *context) { break; case interval_state_pausing: // resume paused timer - _button_beep(settings); + _button_beep(); _resume_paused_timer(state); _face_draw(state, event.subsecond); break; @@ -527,7 +586,7 @@ bool interval_face_loop(movement_event_t event, void *context) { } else if (state->face_state <= interval_state_waiting) { if (_is_timer_empty(timer)) { // jump back to timer #1 - _button_beep(settings); + _button_beep(); state->timer_idx = 0; _init_timer_info(state); } else { @@ -551,7 +610,7 @@ bool interval_face_loop(movement_event_t event, void *context) { _init_timer_info(state); } else if (state->face_state == interval_state_pausing) { // resume paused timer - _button_beep(settings); + _button_beep(); _resume_paused_timer(state); } _face_draw(state, event.subsecond); diff --git a/legacy/watch_faces/complication/interval_face.h b/watch-faces/complication/interval_face.h similarity index 100% rename from legacy/watch_faces/complication/interval_face.h rename to watch-faces/complication/interval_face.h