Skip to content
Open
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
172 changes: 172 additions & 0 deletions movement/watch_faces/clock/simple_clock_face.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@
#include "watch_utility.h"
#include "watch_private_display.h"

// Defining the EASTER_EGG means that very long presses on 'ALARM' on this face
// show these words on the screen, and once the end is reached the watch display
// inverts. If EASTER_EGG is not defined, all of this code path is not included.
// If there's more than one string in the easter egg array, after completion
// the display will 'flip' all the characters. To undo this, start the easter
// egg again (but don't let it complete).
#define EASTER_EGG {" SENSOR ", " HACKED"}

#ifdef EASTER_EGG
static bool easter_egg_loop(movement_event_type_t event_type, uint8_t watch_face_index);
#endif

static void _update_alarm_indicator(bool settings_alarm_enabled, simple_clock_state_t *state) {
state->alarm_enabled = settings_alarm_enabled;
if (state->alarm_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL);
Expand Down Expand Up @@ -71,6 +83,17 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting
char buf[11];
uint8_t pos;

#ifdef EASTER_EGG
// If easter egg mode is enabled, then first see if the easter
// egg wants to do anything before our normal processing.
if (easter_egg_loop(event.event_type, state->watch_face_index)) {
// It's returned true. This means it's currently doing something weird
// on the screen, so (a) we shouldn't pass to the normal logic and (b) the watch
// shouldn't enter standby.
return false;
}
#endif

watch_date_time date_time;
uint32_t previous_date_time;
switch (event.event_type) {
Expand Down Expand Up @@ -159,3 +182,152 @@ bool simple_clock_face_wants_background_task(movement_settings_t *settings, void

return date_time.unit.minute == 0;
}

#ifdef EASTER_EGG

#define DISPLAY_WIDTH 10

static bool easter_egg_display_string_morph(char *old_string_arg, char *new_string_arg) {
static char *old_string = NULL;
static char *new_string = NULL;
static uint8_t old_segdata[DISPLAY_WIDTH];
static uint8_t new_segdata[DISPLAY_WIDTH];
static size_t position = 0;
size_t i;

if (old_string_arg == NULL && new_string_arg == NULL) {
old_string = new_string = NULL;
return false;
}

if (old_string != old_string_arg) {
position = 0;
old_string = old_string_arg;
for (i = 0; old_string[i] && i < DISPLAY_WIDTH; ++i) {
old_segdata[i] = watch_convert_char_to_segdata(old_string[i], i);
}
for (; i < DISPLAY_WIDTH; ++i) {
old_segdata[i] = watch_convert_char_to_segdata(' ', i);
}
}
if (new_string != new_string_arg) {
position = 0;
new_string = new_string_arg;
for (i = 0; new_string[i] && i < DISPLAY_WIDTH; ++i) {
new_segdata[i] = watch_convert_char_to_segdata(new_string[i], i);
}
for (; i < DISPLAY_WIDTH; ++i) {
new_segdata[i] = watch_convert_char_to_segdata(' ', i);
}
}

// Make old_segdata approach new_segdata by the first bit we find.
for (; position < DISPLAY_WIDTH; ++position) {
if (old_segdata[position] == new_segdata[position]) {
continue;
}

for (int bit_pos = 0; bit_pos < 8; ++bit_pos) {
int bit = 1 << bit_pos;

if ((old_segdata[position] & bit) != (new_segdata[position] & bit)) {
old_segdata[position] ^= bit;
watch_display_segment(bit_pos, position, old_segdata[position] & bit);
// We only do one segment change. Call again to keep morphing.
return true;
}
}
}

// This cleans up the extra tweaks that wouldn't have been done in the
// segment map.
watch_display_string(new_string, 0);
return false;
}

static char *egg_states[] = EASTER_EGG;
static const size_t egg_states_length = sizeof(egg_states) / sizeof(egg_states[0]);
static const int32_t easter_egg_delay = 3;

static bool easter_egg_loop(movement_event_type_t event_type, uint8_t watch_face_index) {
static int32_t egg_state_index = -1;
static uint32_t alarm_button_down_time = 0;
static bool morphing = false;

if (alarm_button_down_time == 0 && event_type != EVENT_ALARM_BUTTON_DOWN) {
// Nothing to do here folks. Avoid any unnecessary processing.
return false;
}

watch_date_time date_time = watch_rtc_get_date_time();
int32_t time_since_alarm_button = date_time.reg - alarm_button_down_time;

// Sometimes, events are missed. This is particularly bad in this face
// since the alarm release flips us back to the normal face. Since we should
// never get a 'tick' if the alarm button is already up, detect this
// and terminate the fake face.
if (event_type == EVENT_TICK && !watch_get_pin_level(BTN_ALARM)) {
event_type = EVENT_ALARM_BUTTON_UP;
}

switch (event_type) {
case EVENT_ACTIVATE:
case EVENT_LOW_ENERGY_UPDATE:
case EVENT_ALARM_BUTTON_UP:
case EVENT_ALARM_LONG_UP:
// Either a button press ended, or we've somehow still got a button down
// time after more interesting things have happened. Clean stuff up.
if (egg_state_index != -1) {
movement_move_to_face(watch_face_index);
easter_egg_display_string_morph(NULL, NULL);
egg_state_index = -1;
morphing = false;
movement_request_tick_frequency(1);
}
alarm_button_down_time = 0;
break;
case EVENT_ALARM_BUTTON_DOWN:
alarm_button_down_time = date_time.reg;
break;
case EVENT_TICK:
if (egg_state_index == -1) {
if (time_since_alarm_button > easter_egg_delay) {
egg_state_index = 0;
watch_display_invert(false);
watch_clear_colon();
watch_clear_all_indicators();
watch_display_string(egg_states[egg_state_index], 0);
}
} else if (egg_state_index >= 0) {
if (morphing) {
morphing = easter_egg_display_string_morph(egg_states[egg_state_index - 1], egg_states[egg_state_index]);
if (!morphing) { // Finished changing to new string.
movement_request_tick_frequency(1);

if (egg_state_index + 1 >= (int32_t)egg_states_length) {
// We've finished our animation after the final morph. Flip!
watch_display_invert(true);
// And display so people are immediately confused.
watch_display_string(egg_states[egg_state_index], 0);
}
}
}

if (egg_state_index + 1 < (int32_t)egg_states_length && time_since_alarm_button > easter_egg_delay * (egg_state_index + 2)) {
// In case we didn't finish the morph, set it here.
if (morphing) {
watch_display_string(egg_states[egg_state_index], 0);
}
egg_state_index += 1;
movement_request_tick_frequency(16);
morphing = true;
}
}
break;
default:
break;
}

return egg_state_index != -1;
}
#endif // EASTER_EGG