Upload files to "src/c"

This commit is contained in:
2024-10-06 21:24:05 +00:00
parent 0c51b688eb
commit 5db5da362e
5 changed files with 658 additions and 0 deletions

7
src/c/num2words.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
void time_to_common_words(int hours, int minutes, char *words);
void fuzzy_time_to_words(int hours, int minutes, char* words);
void minute_to_formal_words(int minutes, int displayPrefix, char *first_word, char *second_word);
void hour_to_12h_word(int hours, char *word);
void hour_to_24h_word(int hours, char *words);

72
src/c/options.c Normal file
View File

@@ -0,0 +1,72 @@
#include <pebble.h>
#include "options.h"
#include "constants.h"
#include "sliding_time_wd.h"
extern Options s_options;
void init_options() {
if (persist_exists(KEY_OPTIONS)) {
persist_read_data(KEY_OPTIONS, &s_options, sizeof(s_options));
}
else {
s_options.shake_for_lohi = DEFAULT_SHAKE_FOR_LOHI;
s_options.weatherdate_alignment = DEFAULT_WEATHERDATE_ALIGNMENT;
s_options.hourminute_alignment = DEFAULT_HOURMINUTES_ALIGNMENT;
s_options.display_weather = DEFAULT_DISPLAY_WEATHER;
s_options.use_celsius = DEFAULT_USE_CELSIUS;
s_options.weather_use_GPS = DEFAULT_WEATHER_USE_GPS;
memset(s_options.weather_location, 0,sizeof(s_options.weather_location));
s_options.weather_frequency = DEFAULT_WEATHER_FREQUENCY;
s_options.min_since_last_forecast = DEFAULT_MIN_SINCE_WEATHER_UPDATE;
s_options.display_prefix = DEFAULT_DISPLAY_O_PREFIX;
s_options.time_color = DEFAULT_TIME_COLOR;
s_options.hr_color = DEFAULT_HR_COLOR;
s_options.min_color = DEFAULT_MIN_COLOR;
s_options.wd_color = DEFAULT_WD_COLOR;
s_options.background_color = DEFAULT_BACKGROUND_COLOR;
s_options.weatherdate_readability = DEFAULT_WEATHERDATE_READABILITY;
s_options.minutes_readability = DEFAULT_MINUTES_READABILITY;
s_options.condition_code = DEFAULT_CONDITION_CODE;
s_options.vibrate_bt_status = DEFAULT_VIBRATE_BT_STATUS;
s_options.display_date = DEFAULT_DISPLAY_DATE;
memset(s_options.conditions, 0,sizeof(s_options.conditions));
snprintf(s_options.last_weather_update12hr, sizeof(s_options.last_weather_update12hr), DEFAULT_LAST_WEATHER_UPDATE);
snprintf(s_options.last_weather_update24hr, sizeof(s_options.last_weather_update24hr), DEFAULT_LAST_WEATHER_UPDATE);
//persist_write_data(KEY_OPTIONS, &s_options, sizeof(s_options));
}
#if DEBUG
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.shake_for_lohi: %d", s_options.shake_for_lohi);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempF: %d", s_options.tempF);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempFLo: %d", s_options.tempFLo);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempFHi: %d", s_options.tempFHi);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempC: %d", s_options.tempC);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempCLo: %d", s_options.tempCLo);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.tempCHi: %d", s_options.tempCHi);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.weatherdate_alignment: %d", s_options.weatherdate_alignment);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.hourminute_alignment: %d", s_options.hourminute_alignment);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.display_weather: %d", s_options.display_weather);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.display_date: %d", s_options.display_date);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.use_celsius: %d", s_options.use_celsius);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.weather_use_GPS: %d", s_options.weather_use_GPS);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.weather_location: %s", s_options.weather_location);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.weather_frequency: %d", s_options.weather_frequency);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.min_since_last_forecast: %d", s_options.min_since_last_forecast);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.display_prefix: %d", s_options.display_prefix);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.time_color: %d", s_options.time_color);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.hr_color: %d", s_options.hr_color);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.min_color: %d", s_options.min_color);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.wd_color: %d", s_options.wd_color);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.background_color: %d", s_options.background_color);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.weatherdate_readability: %d", s_options.weatherdate_readability);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.minutes_readability: %d", s_options.minutes_readability);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.condition_code: %d", s_options.condition_code);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.bluetooth_state: %d", s_options.bluetooth_state);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.vibrate_bt_status: %d", s_options.vibrate_bt_status);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.conditions: %s", s_options.conditions);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.last_weather_update12hr: %s", s_options.last_weather_update12hr);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: s_options.last_weather_update24hr: %s", s_options.last_weather_update24hr);
APP_LOG(APP_LOG_LEVEL_DEBUG, "init_options: options sizeof(s_options) %zu", sizeof(s_options));
#endif
}

5
src/c/options.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#define KEY_OPTIONS 99
void init_options();

477
src/c/sliding_time_wd.c Normal file
View File

@@ -0,0 +1,477 @@
#include <pebble.h>
#include "sliding_time_wd.h"
#include "constants.h"
#include "num2words.h"
#include "options.h"
#include "weather_date.h"
#include "messaging.h"
Options s_options;
SlidingTextData *s_data;
//stores weather retry counts (incremented in second tick handler).
//Stop retry count at DEFAULT_MAX_WEATHER_RETRY_COUNT
int g_weather_retry_count = 0;
GTextAlignment row_alignment(int alignment) {
switch (alignment) {
case 0: return GTextAlignmentLeft;
case 1: return GTextAlignmentCenter;
case 2: return GTextAlignmentRight;
default: return GTextAlignmentLeft;
}
}
void set_time_layers_color(GColor textColor) {
SlidingTextData *data = s_data;
SlidingRow *row;
for (size_t i = 0; i < ARRAY_LENGTH(data->rows); ++i) {
row = &data->rows[i];
text_layer_set_text_color(row->label, textColor);
}
}
void set_hr_layer_color(GColor textColor) {
SlidingTextData *data = s_data;
SlidingRow *row;
row = &data->rows[HOUR_ROW];
text_layer_set_text_color(row->label, textColor);
}
void set_min_layers_color(GColor textColor) {
SlidingTextData *data = s_data;
SlidingRow *row;
row = &data->rows[MINUTE_ROW1];
text_layer_set_text_color(row->label, textColor);
row = &data->rows[MINUTE_ROW2];
text_layer_set_text_color(row->label, textColor);
}
void set_wd_layers_color(GColor textColor) {
text_layer_set_text_color(weather_layer1, textColor);
#if defined(PBL_ROUND)
text_layer_set_text_color(weather_layer2, textColor);
text_layer_set_text_color(weather_layer_center, textColor);
#endif
text_layer_set_text_color(date_layer, textColor);
}
static void init_window_background_color(){
SlidingTextData *data = s_data;
window_set_background_color(data->window, GColorBlack);
#ifdef PBL_COLOR // If on basalt
window_set_background_color(data->window, GColorFromHEX(s_options.background_color));
#else
if (s_options.background_color == 0xFFFFFF) {
window_set_background_color(data->window, GColorWhite);//white
}
else if (s_options.background_color == 0x000000) {//black
window_set_background_color(data->window, GColorBlack);
}
else { //didn't select white or black, default to white
window_set_background_color(data->window, GColorBlack);
}
#endif
}
static void init_sliding_row(SlidingTextData *data, SlidingRow *row, GRect pos, GFont font,
int delay, int layerColor) {
row->label = text_layer_create(pos);
//set row alignment
text_layer_set_text_alignment(row->label, row_alignment(s_options.hourminute_alignment));
text_layer_set_background_color(row->label, GColorClear);
text_layer_set_text_color(row->label, GColorWhite);
text_layer_set_text_color(row->label, GColorFromHEX(layerColor));
if (font) {
text_layer_set_font(row->label, font);
row->unchanged_font = true;
} else {
row->unchanged_font = false;
}
row->state = IN_FRAME;
row->next_string = NULL;
row->left_pos = -pos.size.w;
row->right_pos = pos.size.w;
row->still_pos = pos.origin.x;
row->movement_delay = delay;
row->delay_count = 0;
data->last_hour = -1;
data->last_minute = -1;
}
void init_static_row(TextLayer *label, GFont font) {
//set row alignment
#if defined(PBL_RECT)
text_layer_set_text_alignment(label, row_alignment(s_options.weatherdate_alignment));
#elif defined(PBL_ROUND)
text_layer_set_text_alignment(label, row_alignment(DEFAULT_CHALK_ALIGNMENT));
#endif
text_layer_set_background_color(label, GColorClear);
text_layer_set_text_color(label, GColorWhite);
text_layer_set_text_color(label, GColorFromHEX(s_options.wd_color));
if (font) {
text_layer_set_font(label, font);
}
}
static void slide_in_text(SlidingTextData *data, SlidingRow *row, char* new_text) {
(void) data;
const char *old_text = text_layer_get_text(row->label);
if (old_text) {
row->next_string = new_text;
row->state = PREPARE_TO_MOVE;
} else {
text_layer_set_text(row->label, new_text);
GRect frame = layer_get_frame(text_layer_get_layer(row->label));
frame.origin.x = row->right_pos;
layer_set_frame(text_layer_get_layer(row->label), frame);
row->state = MOVING_IN;
}
}
static bool update_sliding_row(SlidingTextData *data, SlidingRow *row) {
(void) data;
GRect frame = layer_get_frame(text_layer_get_layer(row->label));
bool something_changed = true;
switch (row->state) {
case PREPARE_TO_MOVE:
frame.origin.x = row->still_pos;
row->delay_count++;
if (row->delay_count > row->movement_delay) {
row->state = MOVING_OUT;
row->delay_count = 0;
}
break;
case MOVING_IN: {
int speed = abs(frame.origin.x - row->still_pos) / 3 + 1;
frame.origin.x -= speed;
if (frame.origin.x <= row->still_pos) {
frame.origin.x = row->still_pos;
row->state = IN_FRAME;
}
}
break;
case MOVING_OUT: {
int speed = abs(frame.origin.x - row->still_pos) / 3 + 1;
frame.origin.x -= speed;
if (frame.origin.x <= row->left_pos) {
frame.origin.x = row->right_pos;
row->state = MOVING_IN;
text_layer_set_text(row->label, row->next_string);
row->next_string = NULL;
}
}
break;
case IN_FRAME:
default:
something_changed = false;
break;
}
if (something_changed) {
layer_set_frame(text_layer_get_layer(row->label), frame);
}
return something_changed;
}
static void animation_update(struct Animation *animation, const AnimationProgress time_normalized) {
SlidingTextData *data = s_data;
struct SlidingTextRenderState *rs = &data->render_state;
time_t now = time(NULL);
struct tm t = *localtime(&now);
bool something_changed = false;
if (data->last_minute != t.tm_min) {
something_changed = true;
minute_to_formal_words(t.tm_min, s_options.display_prefix, rs->first_minutes[rs->next_minutes], rs->second_minutes[rs->next_minutes]);
if(data->last_hour != t.tm_hour || t.tm_min <= 20
|| t.tm_min/10 != data->last_minute/10) {
slide_in_text(data, &data->rows[MINUTE_ROW1], rs->first_minutes[rs->next_minutes]);
} else {
// The tens line didn't change, so swap to the correct buffer but don't animate
text_layer_set_text(data->rows[MINUTE_ROW1].label, rs->first_minutes[rs->next_minutes]);
}
slide_in_text(data, &data->rows[MINUTE_ROW2], rs->second_minutes[rs->next_minutes]);
rs->next_minutes = rs->next_minutes ? 0 : 1;
data->last_minute = t.tm_min;
}
if (data->last_hour != t.tm_hour) {
hour_to_12h_word(t.tm_hour, rs->hours[rs->next_hours]);
slide_in_text(data, &data->rows[HOUR_ROW], rs->hours[rs->next_hours]);
rs->next_hours = rs->next_hours ? 0 : 1;
data->last_hour = t.tm_hour;
}
for (size_t i = 0; i < ARRAY_LENGTH(data->rows); ++i) {
something_changed = update_sliding_row(data, &data->rows[i]) || something_changed;
}
if (!something_changed) {
animation_unschedule(data->animation);
#ifdef PBL_SDK_2
animation_destroy(s_data->animation);
#endif
}
}
static void make_animation() {
s_data->animation = animation_create();
animation_set_duration(s_data->animation, ANIMATION_DURATION_INFINITE);
// the animation will stop itself
static const struct AnimationImplementation s_animation_implementation = {
.update = animation_update,
};
animation_set_implementation(s_data->animation, &s_animation_implementation);
animation_schedule(s_data->animation);
}
static void bt_handler(bool isConnected) {
int current_bluetooth_state = 0; //default bluetooth disconnected
if (isConnected)
current_bluetooth_state = 1; //bluetooth connected
//if bluetooth was previously connected and is now disconnected
if (s_options.bluetooth_state == 1 &&
current_bluetooth_state == 0) {
if (s_options.vibrate_bt_status) {
vibes_short_pulse(); //vibrate on bt disconnect, if option enabled
}
}
//if bluetooth was previously disconnected and is now connected
if (s_options.bluetooth_state == 0 &&
current_bluetooth_state == 1) {
//clear weather conditions and reset weather retry count to force weather update
memset(s_options.conditions, 0,sizeof(s_options.conditions));
s_options.condition_code = DEFAULT_CONDITION_CODE;
g_weather_retry_count = 0;
}
//store bluetooth state
s_options.bluetooth_state = current_bluetooth_state;
}
static void tap_handler(AccelAxisType axis, int32_t direction) {
if (s_options.shake_for_lohi == 1) {
//Display Lo Hi temp info;
display_lohi_weather_info();
}
}
//every five seconds
static void handle_5second_tick(struct tm *tick_time, TimeUnits units_changed) {
//if conditions blank (no current weather info)
if(strlen(s_options.conditions) == 0 &&
g_weather_retry_count < DEFAULT_MAX_WEATHER_RETRY_COUNT) {
//add one to weather retry count - stop trying this app session at DEFAULT_MAX_WEATHER_RETRY_COUNT
++g_weather_retry_count;
//reset time since last forecast timer
s_options.min_since_last_forecast = 0;
//get updated weather
send(KEY_GET_WEATHER, 1, KEY_WEATHER_USE_GPS, s_options.weather_use_GPS, KEY_WEATHER_LOCATION, s_options.weather_location);
//update weather layer
update_weather_layer();
}
//APP_LOG(APP_LOG_LEVEL_DEBUG, "heap_bytes_free:heap_bytes_used %zu: %zu", heap_bytes_free(), heap_bytes_used());
}
//every minute
static void handle_minute_tick(struct tm *tick_time, TimeUnits units_changed) {
if (units_changed & DAY_UNIT) {
//vibes_double_pulse(); //hourly vibration
}
make_animation(); //animate mew time
update_date_layer(); //check/update date layer
//get minutes since last weather update
time_t t = time(NULL);
int min_since_last_update = (difftime(t, s_options.last_update) / 60) + 1;
APP_LOG(APP_LOG_LEVEL_DEBUG, " (Last update %s) handle_minute_tick min_since_last_update:weather_frequency - %d:%d", s_options.last_weather_update24hr, min_since_last_update, s_options.weather_frequency);
//APP_LOG(APP_LOG_LEVEL_DEBUG, "handle_minute_tick strlen(s_options.conditions): %zu", strlen(s_options.conditions));
//if min_since_last_forecast greater than or equal to weatherUpdateFrequency
if(min_since_last_update >= s_options.weather_frequency ||
strlen(s_options.conditions) == 0) {
s_options.min_since_last_forecast = 0;
//get updated weather
send(KEY_GET_WEATHER, 1, KEY_WEATHER_USE_GPS, s_options.weather_use_GPS, KEY_WEATHER_LOCATION, s_options.weather_location);
//update weather layer
update_weather_layer();
}
}
void handle_tick(struct tm *tick_time, TimeUnits units_changed) {
if (units_changed & SECOND_UNIT) {
if (tick_time->tm_sec % 5 == 0) {
handle_5second_tick(tick_time, units_changed);
}
}
if (units_changed & MINUTE_UNIT) {
handle_minute_tick(tick_time, units_changed);
}
}
static void handle_deinit(void) {
//save watchface options
persist_write_data(KEY_OPTIONS, &s_options, sizeof(s_options));
text_layer_destroy(weather_layer1);
#if defined(PBL_ROUND)
text_layer_destroy(weather_layer_center);
text_layer_destroy(weather_layer2);
#endif
text_layer_destroy(date_layer);
#ifdef PBL_SDK_2
bluetooth_connection_service_unsubscribe();
#elif PBL_SDK_3
connection_service_unsubscribe();
#endif
accel_tap_service_unsubscribe();
tick_timer_service_unsubscribe();
free(s_data);
}
static void handle_init() {
SlidingTextData *data = (SlidingTextData*)malloc(sizeof(SlidingTextData));
s_data = data;
g_weather_retry_count = 0;
int chalk_time_X_offset = 0;
int chalk_time_Y_offset = 0;
#if defined(PBL_RECT)
chalk_time_X_offset = 0;
chalk_time_Y_offset = 0;
#elif defined(PBL_ROUND)
chalk_time_X_offset = DEFAULT_CHALK_TIME_X_OFFSET;
chalk_time_Y_offset = DEFAULT_CHALK_TIME_Y_OFFSET;
#endif
data->render_state.next_hours = 0;
data->render_state.next_minutes = 0;
data->render_state.demo_time.secs = 0;
data->render_state.demo_time.mins = 0;
data->render_state.demo_time.hour = 0;
data->window = window_create();
Layer *window_layer = window_get_root_layer(data->window);
GRect layer_frame = layer_get_frame(window_layer);
const int16_t width = layer_frame.size.w;
//set-retrieve watchface options
init_options();
init_window_background_color();
data->hour_text = fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD);
if (s_options.minutes_readability) {
data->minute_text = fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD);
}
else {
data->minute_text = fonts_get_system_font(FONT_KEY_BITHAM_42_LIGHT);
}
//init hour/minutes layers
init_sliding_row(data, &data->rows[HOUR_ROW], GRect(0 + chalk_time_X_offset, 20 + chalk_time_Y_offset, width - (chalk_time_X_offset * 2), 60), data->hour_text, 6, s_options.hr_color);
layer_add_child(window_layer, text_layer_get_layer(data->rows[HOUR_ROW].label));
init_sliding_row(data, &data->rows[MINUTE_ROW1], GRect(0 + chalk_time_X_offset, 56 + chalk_time_Y_offset, width - (chalk_time_X_offset * 2), 96), data->minute_text, 3, s_options.min_color);
layer_add_child(window_layer, text_layer_get_layer(data->rows[MINUTE_ROW1].label));
init_sliding_row(data, &data->rows[MINUTE_ROW2], GRect(0 + chalk_time_X_offset, 92 + chalk_time_Y_offset, width - (chalk_time_X_offset * 2), 132), data->minute_text, 0, s_options.min_color);
layer_add_child(window_layer, text_layer_get_layer(data->rows[MINUTE_ROW2].label));
//add weather and date layers
add_weatherdate_layers(window_layer, width);
GFont norm14 = fonts_get_system_font(FONT_KEY_GOTHIC_14);
data->demo_label = text_layer_create(GRect(0, -3, 100, 20));
text_layer_set_background_color(data->demo_label, GColorClear);
text_layer_set_text_color(data->demo_label, GColorWhite);
text_layer_set_font(data->demo_label, norm14);
text_layer_set_text(data->demo_label, "demo mode");
layer_add_child(window_layer, text_layer_get_layer(data->demo_label));
layer_set_hidden(text_layer_get_layer(data->demo_label), true);
layer_mark_dirty(window_layer);
app_message_register_inbox_received(inbox_received_callback);
app_message_register_inbox_dropped(message_dropped);
app_message_register_outbox_sent(message_out_success);
app_message_register_outbox_failed(message_out_failed);
#if defined(PBL_BW)
app_message_open(175, 250);
#else
app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());
#endif
update_date_layer(); //update date_layer
update_weather_layer(); //update weather layer
make_animation(); //animate new time layers
tick_timer_service_subscribe(MINUTE_UNIT | SECOND_UNIT, handle_tick);
accel_tap_service_subscribe(tap_handler);
// Subscribe to Bluetooth updates
connection_service_subscribe((ConnectionHandlers) {
.pebble_app_connection_handler = bt_handler
});
// Check current bluetooth connection state
bt_handler(connection_service_peek_pebble_app_connection());
const bool animated = true;
window_stack_push(data->window, animated);
}
int main(void) {
handle_init();
app_event_loop();
handle_deinit();
}

97
src/c/sliding_time_wd.h Normal file
View File

@@ -0,0 +1,97 @@
#include <pebble.h>
TextLayer *weather_layer1, *weather_layer2, *weather_layer_center, *date_layer;
AppTimer *lohi_display_timer;
void init_static_row(TextLayer *label, GFont font); //creates/configures static (weather/date) display lines
GTextAlignment row_alignment(int alignment); //accepts int alignement and sets row GTextAlignment (0 left, 1 center, 2 right)
void set_time_layers_color(GColor textColor);
void set_hr_layer_color(GColor textColor);
void set_min_layers_color(GColor textColor);
void set_wd_layers_color(GColor textColor);
typedef struct {
int shake_for_lohi;
int tempF;
int tempFLo;
int tempFHi;
int tempC;
int tempCLo;
int tempCHi;
int weatherdate_alignment;
int hourminute_alignment;
int display_weather;
int weather_use_GPS;
char weather_location[64];
int use_celsius;
int weather_frequency;
int min_since_last_forecast;
int display_prefix;
int time_color;
int hr_color;
int min_color;
int wd_color;
int background_color;
int weatherdate_readability;
int minutes_readability;
int condition_code;
int vibrate_bt_status;
int pebble_js_ready;
int display_date;
bool bluetooth_state;
time_t last_update;
char conditions[32];
char last_weather_update12hr[16];
char last_weather_update24hr[16];
} Options;
typedef enum {
MOVING_IN,
IN_FRAME,
PREPARE_TO_MOVE,
MOVING_OUT
} SlideState;
typedef struct {
TextLayer *label;
SlideState state; // animation state
char *next_string; // what to say in the next phase of animation
bool unchanged_font;
int left_pos;
int right_pos;
int still_pos;
int movement_delay;
int delay_count;
} SlidingRow;
typedef struct {
TextLayer *demo_label;
SlidingRow rows[3];
int last_hour;
int last_minute;
GFont hour_text;
GFont minute_text;
Window *window;
Animation *animation;
struct SlidingTextRenderState {
// double buffered string storage
char hours[2][32];
uint8_t next_hours;
char first_minutes[2][32];
char second_minutes[2][32];
uint8_t next_minutes;
struct SlidingTextRenderDemoTime {
int secs;
int mins;
int hour;
} demo_time;
} render_state;
} SlidingTextData;