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
This commit is contained in:
Lorenzo Prosseda 2025-08-03 17:22:18 +00:00 committed by GitHub
parent 9121c0cfb8
commit 4eee544762
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 101 additions and 40 deletions

View File

@ -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"

View File

@ -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 \

View File

@ -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);