Upload files to "src/c"
This commit is contained in:
217
src/c/num2words.c
Normal file
217
src/c/num2words.c
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
#include "num2words.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static const char* const ONES[] = {
|
||||||
|
"zero",
|
||||||
|
"one",
|
||||||
|
"two",
|
||||||
|
"three",
|
||||||
|
"four",
|
||||||
|
"five",
|
||||||
|
"six",
|
||||||
|
"seven",
|
||||||
|
"eight",
|
||||||
|
"nine"
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* const TEENS[] ={
|
||||||
|
"",
|
||||||
|
"eleven",
|
||||||
|
"twelve",
|
||||||
|
"thirteen",
|
||||||
|
"fourteen",
|
||||||
|
"fifteen",
|
||||||
|
"sixteen",
|
||||||
|
"seventeen",
|
||||||
|
"eighteen",
|
||||||
|
"nineteen"
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* const TEENS_SPLIT[][2] = {
|
||||||
|
{"", ""},
|
||||||
|
{"eleven",""},
|
||||||
|
{"twelve",""},
|
||||||
|
{"thirteen",""},
|
||||||
|
{"four","teen"},
|
||||||
|
{"fifteen",""},
|
||||||
|
{"sixteen",""},
|
||||||
|
{"seven","teen"},
|
||||||
|
{"eight","teen"},
|
||||||
|
{"nine","teen"}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* const TENS[] = {
|
||||||
|
"",
|
||||||
|
"ten",
|
||||||
|
"twenty",
|
||||||
|
"thirty",
|
||||||
|
"forty",
|
||||||
|
"fifty",
|
||||||
|
"sixty",
|
||||||
|
"seventy",
|
||||||
|
"eighty",
|
||||||
|
"ninety"
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* STR_OH_TICK = "o'";
|
||||||
|
static const char* STR_O_TICK = "o";
|
||||||
|
static const char* STR_CLOCK = "clock";
|
||||||
|
static const char* STR_NOON = "noon";
|
||||||
|
static const char* STR_MIDNIGHT = "midnight";
|
||||||
|
static const char* STR_QUARTER = "quarter";
|
||||||
|
static const char* STR_TO = "to";
|
||||||
|
static const char* STR_PAST = "past";
|
||||||
|
static const char* STR_HALF = "half";
|
||||||
|
static const char* STR_AFTER = "after";
|
||||||
|
|
||||||
|
static size_t append_number(char* words, int num) {
|
||||||
|
int tens_val = num / 10 % 10;
|
||||||
|
int ones_val = num % 10;
|
||||||
|
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
if (tens_val > 0) {
|
||||||
|
if (tens_val == 1 && num != 10) {
|
||||||
|
strcat(words, TEENS[ones_val]);
|
||||||
|
return strlen(TEENS[ones_val]);
|
||||||
|
}
|
||||||
|
strcat(words, TENS[tens_val]);
|
||||||
|
len += strlen(TENS[tens_val]);
|
||||||
|
if (ones_val > 0) {
|
||||||
|
strcat(words, " ");
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ones_val > 0 || num == 0) {
|
||||||
|
strcat(words, ONES[ones_val]);
|
||||||
|
len += strlen(ONES[ones_val]);
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fuzzy_time_to_words(int hours, int minutes, char* words) {
|
||||||
|
int fuzzy_hours = hours;
|
||||||
|
int fuzzy_minutes = ((minutes + 2) / 5) * 5;
|
||||||
|
|
||||||
|
// Handle hour & minute roll-over.
|
||||||
|
if (fuzzy_minutes > 55) {
|
||||||
|
fuzzy_minutes = 0;
|
||||||
|
fuzzy_hours += 1;
|
||||||
|
if (fuzzy_hours > 23) {
|
||||||
|
fuzzy_hours = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time_to_common_words(fuzzy_hours, fuzzy_minutes, words);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns number of lines written
|
||||||
|
void time_to_common_words(int hours, int minutes, char *words) {
|
||||||
|
// TODO: make an app-safe assert
|
||||||
|
// PBL_ASSERT(hours >= 0 && hours < 24, "Invalid number of hours");
|
||||||
|
// PBL_ASSERT(minutes >= 0 && minutes < 60, "Invalid number of minutes");
|
||||||
|
|
||||||
|
size_t written = 0;
|
||||||
|
strcpy(words, "");
|
||||||
|
|
||||||
|
if (minutes != 0 && (minutes >= 10 || minutes == 5 || hours == 0 || hours == 12)) {
|
||||||
|
if (minutes == 15) {
|
||||||
|
written += sprintf(words, "%s %s ", STR_QUARTER, STR_AFTER);
|
||||||
|
} else if (minutes == 45) {
|
||||||
|
written += sprintf(words, "%s %s ", STR_QUARTER, STR_TO);
|
||||||
|
hours = (hours + 1) % 24;
|
||||||
|
} else if (minutes == 30) {
|
||||||
|
written += sprintf(words, "%s %s ", STR_HALF, STR_PAST);
|
||||||
|
} else if (minutes < 30) {
|
||||||
|
written += append_number(words, minutes);
|
||||||
|
written += sprintf(words + written, " %s ", STR_AFTER);
|
||||||
|
} else {
|
||||||
|
written += append_number(words, 60 - minutes);
|
||||||
|
written += sprintf(words + written, " %s ", STR_TO);
|
||||||
|
hours = (hours + 1) % 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours == 0) {
|
||||||
|
written += sprintf(words + written, "%s", STR_MIDNIGHT);
|
||||||
|
} else if (hours == 12) {
|
||||||
|
strcat(words, STR_NOON);
|
||||||
|
written += sprintf(words + written, "%s", STR_NOON);
|
||||||
|
} else {
|
||||||
|
written += append_number(words, hours % 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes < 10 && minutes != 5 && !(hours == 0 || hours == 12)) {
|
||||||
|
if (minutes == 0) {
|
||||||
|
sprintf(words + written, " %s%s", STR_OH_TICK, STR_CLOCK);
|
||||||
|
} else {
|
||||||
|
sprintf(words + written, " %s%s", STR_OH_TICK, ONES[minutes]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// o'clock (0) and plain number words (10..)
|
||||||
|
void minute_to_formal_words(int minutes, int displayPrefix, char *first_word, char *second_word) {
|
||||||
|
// PBL_ASSERT(minutes >= 0 && minutes < 60, "Invalid number of minutes");
|
||||||
|
|
||||||
|
strcpy(first_word, "");
|
||||||
|
strcpy(second_word, "");
|
||||||
|
|
||||||
|
|
||||||
|
if (minutes == 0) {
|
||||||
|
strcat(first_word, STR_OH_TICK);
|
||||||
|
strcat(first_word, STR_CLOCK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (minutes < 10) {
|
||||||
|
if (displayPrefix) {
|
||||||
|
strcat(first_word, STR_O_TICK); //add "o"
|
||||||
|
strcat(second_word, ONES[minutes%10]); //new
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
strcat(first_word, ONES[minutes%10]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (minutes > 10 && minutes < 20) {
|
||||||
|
strcat(first_word, TEENS_SPLIT[(minutes - 10) % 10][0]);
|
||||||
|
strcat(second_word, TEENS_SPLIT[(minutes - 10) % 10][1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
strcat(first_word, TENS[minutes / 10 % 10]);
|
||||||
|
|
||||||
|
int minute_ones = minutes % 10;
|
||||||
|
if (minute_ones) {
|
||||||
|
strcat(second_word, ONES[minute_ones]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hour_to_12h_word(int hours, char *word) {
|
||||||
|
// PBL_ASSERT(hours >= 0 && hours < 24, "Invalid number of hours");
|
||||||
|
hours = hours % 12;
|
||||||
|
|
||||||
|
if (hours == 0) {
|
||||||
|
hours = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy(word, "");
|
||||||
|
|
||||||
|
append_number(word, hours);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void hour_to_24h_word(int hours, char *words) {
|
||||||
|
// PBL_ASSERT(hours >= 0 && hours < 24, "Invalid number of hours");
|
||||||
|
|
||||||
|
hours = hours % 24;
|
||||||
|
|
||||||
|
strcpy(words, "");
|
||||||
|
|
||||||
|
append_number(words, hours);
|
||||||
|
}
|
||||||
7
src/c/num2words.h
Normal file
7
src/c/num2words.h
Normal 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);
|
||||||
692
src/c/sliding_text.c
Normal file
692
src/c/sliding_text.c
Normal file
@@ -0,0 +1,692 @@
|
|||||||
|
#include <pebble.h>
|
||||||
|
#include "num2words.h"
|
||||||
|
|
||||||
|
#define KEY_TEMPERATURE_IN_C 1
|
||||||
|
#define KEY_CONDITIONS 2
|
||||||
|
#define KEY_BACKGROUND_COLOR 3
|
||||||
|
#define KEY_TEXT_COLOR 4
|
||||||
|
#define KEY_WEATHER_FREQUENCY 5
|
||||||
|
#define KEY_USE_CELSIUS 6
|
||||||
|
#define KEY_DISPLAY_O_PREFIX 7
|
||||||
|
#define KEY_DISPLAY_WEATHER 8
|
||||||
|
#define KEY_MIN_SINCE_WEATHER_UPDATE 9
|
||||||
|
#define KEY_WEATHERDATE_ALIGNMENT 10
|
||||||
|
#define KEY_HOURMINUTES_ALIGNMENT 11
|
||||||
|
#define KEY_GET_WEATHER 12
|
||||||
|
#define KEY_TEMPERATURE 13
|
||||||
|
#define KEY_OPTIONS 14
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int tempF;
|
||||||
|
int tempC;
|
||||||
|
int weatherDateAlignment;
|
||||||
|
int hourMinuteAlignment;
|
||||||
|
int display_weather;
|
||||||
|
int use_celsius;
|
||||||
|
int weather_frequency;
|
||||||
|
int min_since_last_forecast;
|
||||||
|
int display_prefix;
|
||||||
|
int text_color;
|
||||||
|
int background_color;
|
||||||
|
char conditions[32];
|
||||||
|
} Options;
|
||||||
|
|
||||||
|
Options options;
|
||||||
|
|
||||||
|
TextLayer *weather_layer, *date_layer;
|
||||||
|
GFont weatherdate_text;
|
||||||
|
|
||||||
|
const int DEFAULT_TEMPF = 0;
|
||||||
|
const int DEFAULT_TEMPC = 0;
|
||||||
|
const int DEFAULT_MIN_SINCE_WEATHER_UPDATE = 0; //weather just updated
|
||||||
|
const int DEFAULT_DISPLAY_WEATHER = 1; //true
|
||||||
|
const int DEFAULT_WEATHER_FREQUENCY = 30; //minutes
|
||||||
|
const int DEFAULT_USE_CELSIUS = 0; //false
|
||||||
|
const int DEFAULT_DISPLAY_O_PREFIX = 0; //false
|
||||||
|
const int DEFAULT_WEATHERDATE_ALIGNMENT = 1; //center
|
||||||
|
const int DEFAULT_HOURMINUTES_ALIGNMENT = 0; //left
|
||||||
|
const int DEFAULT_TEXT_COLOR = 0xFFFFFF; //white
|
||||||
|
const int DEFAULT_BACKGROUND_COLOR = 0x000000; //black
|
||||||
|
|
||||||
|
const int HOUR_ROW = 0;
|
||||||
|
const int MINUTE_ROW1 = 1;
|
||||||
|
const int MINUTE_ROW2 = 2;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
SlidingTextData *s_data;
|
||||||
|
|
||||||
|
GTextAlignment row_alignment(int alignment) {
|
||||||
|
switch (alignment) {
|
||||||
|
case 0: return GTextAlignmentLeft;
|
||||||
|
case 1: return GTextAlignmentCenter;
|
||||||
|
case 2: return GTextAlignmentRight;
|
||||||
|
default: return GTextAlignmentLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send(int key, int value){
|
||||||
|
DictionaryIterator *iterator;
|
||||||
|
app_message_outbox_begin(&iterator);
|
||||||
|
dict_write_int(iterator, key, &value, sizeof(int), true);
|
||||||
|
|
||||||
|
app_message_outbox_send();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_text_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
text_layer_set_text_color(weather_layer, textColor);
|
||||||
|
text_layer_set_text_color(date_layer, textColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_options() {
|
||||||
|
|
||||||
|
if (persist_exists(KEY_OPTIONS)) {
|
||||||
|
persist_read_data(KEY_OPTIONS, &options, sizeof(options));
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.tempF: %d", options.tempF);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.tempC: %d", options.tempC);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.weatherDateAlignment: %d", options.weatherDateAlignment);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.hourMinuteAlignment: %d", options.hourMinuteAlignment);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.display_weather: %d", options.display_weather);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.use_celsius: %d", options.use_celsius);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.weather_frequency: %d", options.weather_frequency);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.min_since_last_forecast: %d", options.min_since_last_forecast);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.display_prefix: %d", options.display_prefix);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.text_color: %d", options.text_color);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.background_color: %d", options.background_color);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.conditions: %s", options.conditions);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options sizeof(options) %d", sizeof(options));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
options.tempF = DEFAULT_TEMPF;
|
||||||
|
options.tempC = DEFAULT_TEMPC;
|
||||||
|
options.weatherDateAlignment = DEFAULT_WEATHERDATE_ALIGNMENT;
|
||||||
|
options.hourMinuteAlignment = DEFAULT_HOURMINUTES_ALIGNMENT;
|
||||||
|
options.display_weather = DEFAULT_DISPLAY_WEATHER;
|
||||||
|
options.use_celsius = DEFAULT_USE_CELSIUS;
|
||||||
|
options.weather_frequency = DEFAULT_WEATHER_FREQUENCY;
|
||||||
|
options.min_since_last_forecast = DEFAULT_MIN_SINCE_WEATHER_UPDATE;
|
||||||
|
options.display_prefix = DEFAULT_DISPLAY_O_PREFIX;
|
||||||
|
options.text_color = DEFAULT_TEXT_COLOR;
|
||||||
|
options.background_color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
memset(options.conditions, 0,sizeof(options.conditions));
|
||||||
|
persist_write_data(KEY_OPTIONS, &options, sizeof(options));
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_write_data options sizeof(options) %d", sizeof(options));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_sliding_row(SlidingTextData *data, SlidingRow *row, GRect pos, GFont font,
|
||||||
|
int delay) {
|
||||||
|
|
||||||
|
row->label = text_layer_create(pos);
|
||||||
|
|
||||||
|
//set row alignment
|
||||||
|
text_layer_set_text_alignment(row->label, row_alignment(options.hourMinuteAlignment));
|
||||||
|
|
||||||
|
text_layer_set_background_color(row->label, GColorClear);
|
||||||
|
text_layer_set_text_color(row->label, GColorWhite);
|
||||||
|
|
||||||
|
#ifdef PBL_COLOR // If on basalt
|
||||||
|
text_layer_set_text_color(row->label, GColorFromHEX(options.text_color));
|
||||||
|
#else
|
||||||
|
if (options.text_color == 0xFFFFFF) {//white
|
||||||
|
text_layer_set_text_color(row->label, GColorWhite);
|
||||||
|
}
|
||||||
|
else if (options.text_color == 0x000000) {
|
||||||
|
text_layer_set_text_color(row->label, GColorBlack);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_layer_set_text_color(row->label, DEFAULT_TEXT_COLOR);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_static_row(TextLayer *label, GFont font) {
|
||||||
|
|
||||||
|
//set row alignment
|
||||||
|
text_layer_set_text_alignment(label, row_alignment(options.weatherDateAlignment));
|
||||||
|
|
||||||
|
text_layer_set_background_color(label, GColorClear);
|
||||||
|
text_layer_set_text_color(label, GColorWhite);
|
||||||
|
|
||||||
|
#ifdef PBL_COLOR // If on basalt
|
||||||
|
text_layer_set_text_color(label, GColorFromHEX(options.text_color));
|
||||||
|
#else
|
||||||
|
if (options.text_color == 0xFFFFFF) {//white
|
||||||
|
text_layer_set_text_color(label, GColorWhite);
|
||||||
|
}
|
||||||
|
else if (options.text_color == 0x000000) {
|
||||||
|
text_layer_set_text_color(label, GColorBlack);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_layer_set_text_color(label, DEFAULT_TEXT_COLOR);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_date_layer() {
|
||||||
|
|
||||||
|
static char date_buffer[16];
|
||||||
|
const char *current_date_layer;
|
||||||
|
|
||||||
|
//get current date_layer text
|
||||||
|
current_date_layer = text_layer_get_text(date_layer);
|
||||||
|
|
||||||
|
//get current date
|
||||||
|
time_t temp = time(NULL);
|
||||||
|
struct tm *tick_time = localtime(&temp);
|
||||||
|
strftime(date_buffer, sizeof(date_buffer), "%a %d %b", tick_time);
|
||||||
|
|
||||||
|
if (current_date_layer != date_buffer) {
|
||||||
|
text_layer_set_text(date_layer, date_buffer);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "update_date_layer: text_layer_set_text date_layer :%s", date_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_weather_layer() {
|
||||||
|
|
||||||
|
static char weather_layer_buffer[32];
|
||||||
|
|
||||||
|
// Get a tm structure
|
||||||
|
time_t temp = time(NULL);
|
||||||
|
struct tm *tick_time = localtime(&temp);
|
||||||
|
static char time_buffer[] = "00:00";
|
||||||
|
strftime(time_buffer, sizeof("00:00"), "%I:%M", tick_time);
|
||||||
|
|
||||||
|
if (options.use_celsius){
|
||||||
|
snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%dC - %s", options.tempC, options.conditions);
|
||||||
|
//snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%dC - %s (%s)", options.tempC, options.conditions, time_buffer);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "C: Time %s set weather_layer_buffer: %s", time_buffer, weather_layer_buffer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%dF - %s", options.tempF, options.conditions);
|
||||||
|
//snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%dF - %s (%s)", options.tempF, options.conditions, time_buffer);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "F: Time %s set weather_layer_buffer: %s", time_buffer, weather_layer_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
//write weather text row
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "update_weather_layer: sizeof(options.conditions) %d. ", strlen(options.conditions));
|
||||||
|
if (strlen(options.conditions) != 0) {
|
||||||
|
//if (strlen(weather_layer_buffer) > 5) {
|
||||||
|
text_layer_set_text(weather_layer, weather_layer_buffer);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "update_weather_layer: Successful weather row information written at %s. ", time_buffer);
|
||||||
|
}
|
||||||
|
else { //empty temp & conditions
|
||||||
|
text_layer_set_text(weather_layer, "");
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "update_weather_layer: Error displaying weather %s. ", time_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 handle_minute_tick(struct tm *tick_time, TimeUnits units_changed) {
|
||||||
|
|
||||||
|
if (units_changed & DAY_UNIT) {
|
||||||
|
//vibes_double_pulse(); //hourly vibration (future option)
|
||||||
|
}
|
||||||
|
|
||||||
|
make_animation(); //animate mew time
|
||||||
|
update_date_layer(); //check/update date layer
|
||||||
|
|
||||||
|
//if min_since_last_forecast is 0 or greater
|
||||||
|
if(options.min_since_last_forecast >= 0) {
|
||||||
|
|
||||||
|
//add one to min_since_last_forecast
|
||||||
|
++options.min_since_last_forecast;
|
||||||
|
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "handle_minute_tick options.min_since_last_forecast : options.weather_frequency %d : %d", options.min_since_last_forecast, options.weather_frequency);
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "handle_minute_tick strlen(options.conditions) : %d", strlen(options.conditions));
|
||||||
|
//if min_since_last_forecast greater than or equal to weatherUpdateFrequency
|
||||||
|
if(options.min_since_last_forecast >= options.weather_frequency ||
|
||||||
|
strlen(options.conditions) == 0) {
|
||||||
|
|
||||||
|
options.min_since_last_forecast = 0;
|
||||||
|
|
||||||
|
//get updated weather
|
||||||
|
send(KEY_GET_WEATHER, 1);
|
||||||
|
|
||||||
|
//update weather layer
|
||||||
|
update_weather_layer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
|
||||||
|
SlidingTextData *data = s_data;
|
||||||
|
SlidingRow *row;
|
||||||
|
|
||||||
|
// Read first item
|
||||||
|
Tuple *t = dict_read_first(iterator);
|
||||||
|
|
||||||
|
// For all items
|
||||||
|
while(t != NULL) {
|
||||||
|
// Which key was received?
|
||||||
|
switch(t->key) {
|
||||||
|
|
||||||
|
case KEY_TEMPERATURE:
|
||||||
|
options.tempF = (int)t->value->int32;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY_TEMPERATURE_IN_C:
|
||||||
|
options.tempC = (int)t->value->int32;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY_CONDITIONS:
|
||||||
|
snprintf(options.conditions, sizeof(options.conditions), "%s", t->value->cstring);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY_DISPLAY_O_PREFIX:
|
||||||
|
options.display_prefix = (int)t->value->int32;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY_USE_CELSIUS:
|
||||||
|
options.use_celsius = (int)t->value->int32;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY_WEATHER_FREQUENCY:
|
||||||
|
options.weather_frequency = (int)t->value->int32;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY_BACKGROUND_COLOR:
|
||||||
|
options.background_color = (int)t->value->int32;
|
||||||
|
#ifdef PBL_COLOR // If on basalt
|
||||||
|
window_set_background_color(data->window, GColorFromHEX(options.background_color));
|
||||||
|
#else
|
||||||
|
if (options.background_color == 0xFFFFFF) {
|
||||||
|
window_set_background_color(data->window, GColorWhite);//white
|
||||||
|
}
|
||||||
|
else if (options.background_color == 0x000000) {//black
|
||||||
|
window_set_background_color(data->window, GColorBlack);
|
||||||
|
}
|
||||||
|
else { //didn't select white or black, default to black
|
||||||
|
window_set_background_color(data->window, GColorBlack);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY_WEATHERDATE_ALIGNMENT:
|
||||||
|
options.weatherDateAlignment = (int)t->value->int32;
|
||||||
|
text_layer_set_text_alignment(weather_layer, row_alignment(options.weatherDateAlignment));
|
||||||
|
text_layer_set_text_alignment(date_layer, row_alignment(options.weatherDateAlignment));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY_HOURMINUTES_ALIGNMENT:
|
||||||
|
row = &data->rows[HOUR_ROW];
|
||||||
|
options.hourMinuteAlignment = (int)t->value->int32;
|
||||||
|
text_layer_set_text_alignment(row->label, row_alignment(options.hourMinuteAlignment));
|
||||||
|
row = &data->rows[MINUTE_ROW1];
|
||||||
|
text_layer_set_text_alignment(row->label, row_alignment(options.hourMinuteAlignment));
|
||||||
|
row = &data->rows[MINUTE_ROW2];
|
||||||
|
text_layer_set_text_alignment(row->label, row_alignment(options.hourMinuteAlignment));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY_TEXT_COLOR:
|
||||||
|
options.text_color = (int)t->value->int32;
|
||||||
|
#ifdef PBL_COLOR // If on basalt
|
||||||
|
for (size_t i = 0; i < ARRAY_LENGTH(data->rows); ++i) {
|
||||||
|
row = &data->rows[i];
|
||||||
|
text_layer_set_text_color(row->label, GColorFromHEX(options.text_color));
|
||||||
|
}
|
||||||
|
text_layer_set_text_color(weather_layer, GColorFromHEX(options.text_color));
|
||||||
|
text_layer_set_text_color(date_layer, GColorFromHEX(options.text_color));
|
||||||
|
#else
|
||||||
|
if (options.text_color == 0xFFFFFF) {//white
|
||||||
|
set_text_layers_color(GColorWhite);
|
||||||
|
}
|
||||||
|
else if (options.text_color == 0x000000) { //black
|
||||||
|
set_text_layers_color(GColorBlack);
|
||||||
|
}
|
||||||
|
else { //didn't select white or black, default to white
|
||||||
|
set_text_layers_color(GColorWhite);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Look for next item
|
||||||
|
t = dict_read_next(iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
//weather layer
|
||||||
|
update_weather_layer();
|
||||||
|
}
|
||||||
|
|
||||||
|
char *translate_error(AppMessageResult result) {
|
||||||
|
switch (result) {
|
||||||
|
case APP_MSG_OK: return "APP_MSG_OK";
|
||||||
|
case APP_MSG_SEND_TIMEOUT: return "APP_MSG_SEND_TIMEOUT";
|
||||||
|
case APP_MSG_SEND_REJECTED: return "APP_MSG_SEND_REJECTED";
|
||||||
|
case APP_MSG_NOT_CONNECTED: return "APP_MSG_NOT_CONNECTED";
|
||||||
|
case APP_MSG_APP_NOT_RUNNING: return "APP_MSG_APP_NOT_RUNNING";
|
||||||
|
case APP_MSG_INVALID_ARGS: return "APP_MSG_INVALID_ARGS";
|
||||||
|
case APP_MSG_BUSY: return "APP_MSG_BUSY";
|
||||||
|
case APP_MSG_BUFFER_OVERFLOW: return "APP_MSG_BUFFER_OVERFLOW";
|
||||||
|
case APP_MSG_ALREADY_RELEASED: return "APP_MSG_ALREADY_RELEASED";
|
||||||
|
case APP_MSG_CALLBACK_ALREADY_REGISTERED: return "APP_MSG_CALLBACK_ALREADY_REGISTERED";
|
||||||
|
case APP_MSG_CALLBACK_NOT_REGISTERED: return "APP_MSG_CALLBACK_NOT_REGISTERED";
|
||||||
|
case APP_MSG_OUT_OF_MEMORY: return "APP_MSG_OUT_OF_MEMORY";
|
||||||
|
case APP_MSG_CLOSED: return "APP_MSG_CLOSED";
|
||||||
|
case APP_MSG_INTERNAL_ERROR: return "APP_MSG_INTERNAL_ERROR";
|
||||||
|
default: return "UNKNOWN ERROR";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void message_dropped(AppMessageResult reason, void *context) {
|
||||||
|
APP_LOG(APP_LOG_LEVEL_ERROR, "Dropped message! Reason given: %s", translate_error(reason));
|
||||||
|
}
|
||||||
|
|
||||||
|
void message_out_success(DictionaryIterator *iter, void *context) {
|
||||||
|
APP_LOG(APP_LOG_LEVEL_DEBUG, "Message sent.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void message_out_failed(DictionaryIterator *iter, AppMessageResult reason, void *context) {
|
||||||
|
APP_LOG(APP_LOG_LEVEL_DEBUG, "Failed to send message. Reason = %s", translate_error(reason));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_deinit(void) {
|
||||||
|
|
||||||
|
//save watchface options
|
||||||
|
persist_write_data(KEY_OPTIONS, &options, sizeof(options));
|
||||||
|
APP_LOG(APP_LOG_LEVEL_INFO, "handle_deinit: persist_write_data options sizeof(options) %d", sizeof(options));
|
||||||
|
|
||||||
|
text_layer_destroy(weather_layer);
|
||||||
|
text_layer_destroy(date_layer);
|
||||||
|
|
||||||
|
tick_timer_service_unsubscribe();
|
||||||
|
free(s_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_init() {
|
||||||
|
SlidingTextData *data = (SlidingTextData*)malloc(sizeof(SlidingTextData));
|
||||||
|
s_data = data;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
//set-retrieve watchface options
|
||||||
|
init_options();
|
||||||
|
|
||||||
|
window_set_background_color(data->window, GColorBlack);
|
||||||
|
#ifdef PBL_COLOR // If on basalt
|
||||||
|
window_set_background_color(data->window, GColorFromHEX(options.background_color));
|
||||||
|
#else
|
||||||
|
if (options.background_color == 0xFFFFFF) {
|
||||||
|
window_set_background_color(data->window, GColorWhite);//white
|
||||||
|
}
|
||||||
|
else if (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, DEFAULT_BACKGROUND_COLOR);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
data->hour_text = fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD);
|
||||||
|
data->minute_text = fonts_get_system_font(FONT_KEY_BITHAM_42_LIGHT);
|
||||||
|
weatherdate_text = fonts_get_system_font(FONT_KEY_GOTHIC_18);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
init_sliding_row(data, &data->rows[HOUR_ROW], GRect(0, 20, width, 60), data->hour_text, 6);
|
||||||
|
layer_add_child(window_layer, text_layer_get_layer(data->rows[HOUR_ROW].label));
|
||||||
|
|
||||||
|
init_sliding_row(data, &data->rows[MINUTE_ROW1], GRect(0, 56, width, 96), data->minute_text, 3);
|
||||||
|
layer_add_child(window_layer, text_layer_get_layer(data->rows[MINUTE_ROW1].label));
|
||||||
|
|
||||||
|
init_sliding_row(data, &data->rows[MINUTE_ROW2], GRect(0, 92, width, 132), data->minute_text, 0);
|
||||||
|
layer_add_child(window_layer, text_layer_get_layer(data->rows[MINUTE_ROW2].label));
|
||||||
|
|
||||||
|
//weather layer
|
||||||
|
weather_layer = text_layer_create(GRect(0, 0, width, 24));
|
||||||
|
init_static_row(weather_layer, weatherdate_text);
|
||||||
|
layer_add_child(window_layer, text_layer_get_layer(weather_layer));
|
||||||
|
|
||||||
|
//date layer
|
||||||
|
date_layer = text_layer_create(GRect(0, 145, width, 198));
|
||||||
|
init_static_row(date_layer, weatherdate_text);
|
||||||
|
layer_add_child(window_layer, text_layer_get_layer(date_layer));
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());
|
||||||
|
|
||||||
|
update_date_layer();
|
||||||
|
|
||||||
|
APP_LOG(APP_LOG_LEVEL_DEBUG, "strlen conditions %d", strlen(options.conditions));
|
||||||
|
if (strlen(options.conditions) == 0) {
|
||||||
|
//no previous conditions, get updated weather
|
||||||
|
send(KEY_GET_WEATHER, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
update_weather_layer(); //check/update date layer
|
||||||
|
make_animation(); //animate new time layers
|
||||||
|
|
||||||
|
tick_timer_service_subscribe(MINUTE_UNIT, handle_minute_tick);
|
||||||
|
|
||||||
|
const bool animated = true;
|
||||||
|
window_stack_push(data->window, animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
handle_init();
|
||||||
|
|
||||||
|
app_event_loop();
|
||||||
|
|
||||||
|
handle_deinit();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user