Files
Maan-On-My-Wrist-XL/src/c/main.c

472 lines
19 KiB
C

#include "palette_manip.h"
#include <pebble.h>
// DEBUG memory usage layer
static TextLayer *s_memory_layer;
// declare general statics
static Window *s_main_window;
static GFont s_custom_font;
static Layer *s_time_bar_layer;
static TextLayer *s_time_layer;
static TextLayer *s_date_layer;
static BitmapLayer *s_bt_layer;
static BitmapLayer *s_guy_head_layer;
static BitmapLayer *s_guy_butt_layer;
// declare silly guy parts
static GColor8 s_random_color_current;
static GColor8 s_random_color_next;
static GBitmap *s_head_current;
static GBitmap *s_butt_current;
static GBitmap *s_head_next;
static GBitmap *s_butt_next;
static GBitmap *s_bt_icon;
// declare positions for animation stages
static GRect s_guy_head_grect_on_screen_raised;
static GRect s_guy_head_grect_off_screen_raised;
static GRect s_guy_head_grect_on_screen_resting;
static GRect s_guy_butt_grect_on_screen_raised;
static GRect s_guy_butt_grect_off_screen_raised;
static GRect s_guy_butt_grect_on_screen_resting;
#if PBL_DISPLAY_WIDTH == 180
// 8x+20(4-x)+20,
// where x=one_count,
// 8=width of "1",
// first 20=width of other,
// second 20=combined width of spaces+colon
static const uint8_t s_max_time_width = 100;
#else
// 10x+25(4-x)+21,
// where x=one_count,
// 10=width of "1",
// 25=width of other,
// 21=combined width of spaces+colon
static const uint8_t s_max_time_width = 121;
#endif
static const uint8_t s_date_pos_x = 0;
static const uint8_t s_date_height = 14;
static const uint8_t s_bt_height = 24;
static uint8_t s_date_pos_y;
static uint8_t s_date_bt_width;
static GRect s_date_grect_a;
static uint8_t s_bt_pos_x;
static uint8_t s_bt_pos_y;
static GRect s_bt_grect_a;
static GRect s_date_grect_b;
static GRect s_bt_grect_b;
// declare animation templates
static uint8_t s_animation_duration = 255;
static Animation *s_head_raise_template;
static Animation *s_head_out_template;
static Animation *s_butt_raise_template;
static Animation *s_butt_out_template;
// declare lookup tables
#if PBL_DISPLAY_WIDTH == 180
static const uint8_t s_ones_offset[] = {0, 3, 6, 9, 12};
#else
static const uint8_t s_ones_offset[] = {0, 4, 7, 11, 15};
#endif
static const uint8_t s_head_count = 25;
static const uint32_t s_random_heads[] = {
RESOURCE_ID_H001, RESOURCE_ID_H002, RESOURCE_ID_H003, RESOURCE_ID_H004,
RESOURCE_ID_H005, RESOURCE_ID_H006, RESOURCE_ID_H007, RESOURCE_ID_H008,
RESOURCE_ID_H009, RESOURCE_ID_H010, RESOURCE_ID_H011, RESOURCE_ID_H012,
RESOURCE_ID_H013, RESOURCE_ID_H014, RESOURCE_ID_H015, RESOURCE_ID_H016,
RESOURCE_ID_H017, RESOURCE_ID_H018, RESOURCE_ID_H019, RESOURCE_ID_H020,
RESOURCE_ID_H021, RESOURCE_ID_H022, RESOURCE_ID_H023, RESOURCE_ID_H024,
RESOURCE_ID_H025};
static const uint8_t s_butt_count = 26;
static const uint32_t s_random_butts[] = {
RESOURCE_ID_B001, RESOURCE_ID_B002, RESOURCE_ID_B003, RESOURCE_ID_B004,
RESOURCE_ID_B005, RESOURCE_ID_B006, RESOURCE_ID_B007, RESOURCE_ID_B008,
RESOURCE_ID_B009, RESOURCE_ID_B010, RESOURCE_ID_B011, RESOURCE_ID_B012,
RESOURCE_ID_B013, RESOURCE_ID_B014, RESOURCE_ID_B015, RESOURCE_ID_B016,
RESOURCE_ID_B017, RESOURCE_ID_B018, RESOURCE_ID_B019, RESOURCE_ID_B020,
RESOURCE_ID_B021, RESOURCE_ID_B022, RESOURCE_ID_B023, RESOURCE_ID_B024,
RESOURCE_ID_B025, RESOURCE_ID_XB001};
static const uint8_t s_color_count = 20;
static const GColor8 s_dark_bg_colors[] = {
// Red
GColorFolly, GColorRed,
// Orange
GColorOrange, GColorRajah,
// Yellow
GColorIcterine, GColorPastelYellow, GColorYellow,
// Green
GColorInchworm, GColorJaegerGreen, GColorKellyGreen, GColorMintGreen,
// Blue
GColorElectricBlue, GColorTiffanyBlue, GColorVividCerulean,
// Purple
GColorLavenderIndigo, GColorPurpureus,
// Pink
GColorBrilliantRose, GColorMelon, GColorShockingPink,
// Brown
GColorRoseVale};
static void destroy_animation_handler(Animation *animation, bool finished, void *context) {
animation_destroy(animation);
}
static bool first_minute_update = true;
static void update_minute_1() {
// get a tm structure
time_t temp = time(NULL);
struct tm *tick_time = localtime(&temp);
// write the current hours and minutes into a buffer & format
static char s_time_buffer[8];
strftime(s_time_buffer, sizeof(s_time_buffer), clock_is_24h_style() ? "%H:%M" : "%I:%M", tick_time);
// write the current date into a buffer & format
static char s_date_buffer[16];
strftime(s_date_buffer, sizeof(s_date_buffer), "%m.%d", tick_time);
// update layers
text_layer_set_text(s_time_layer, s_time_buffer);
text_layer_set_text(s_date_layer, s_date_buffer);
// create date/bt centering animations
//// count "1"s in s_time_buffer
static uint8_t ones;
uint8_t i, new_ones;
for (i = 0, new_ones = 0; s_time_buffer[i]; i++) {
new_ones += (s_time_buffer[i] == '1');
}
if (new_ones != ones) { // only animate if necessary
ones = new_ones;
//// calculate new positions
s_date_grect_b = GRect(s_ones_offset[ones], s_date_pos_y, s_date_bt_width, s_date_height);
s_bt_grect_b = GRect(s_bt_pos_x - s_ones_offset[ones], s_bt_pos_y, s_date_bt_width, s_bt_height);
if (!first_minute_update) {
//// configure animations
////// date
PropertyAnimation *date_center_prop = property_animation_create_layer_frame(text_layer_get_layer(s_date_layer), &s_date_grect_a, &s_date_grect_b);
Animation *date_center_anim = property_animation_get_animation(date_center_prop);
animation_set_curve(date_center_anim, AnimationCurveEaseOut);
animation_set_duration(date_center_anim, s_animation_duration);
////// bt
PropertyAnimation *s_bt_center_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_bt_layer), &s_bt_grect_a, &s_bt_grect_b);
Animation *bt_center_anim = property_animation_get_animation(s_bt_center_prop);
animation_set_curve(bt_center_anim, AnimationCurveEaseOut);
animation_set_duration(bt_center_anim, s_animation_duration);
////// create spawn animation (animate centering of both elements at the same time)
Animation *centering_spawn_anim = animation_spawn_create(date_center_anim, bt_center_anim, NULL);
////// set the handler to destroy the spawn animation (and its children) when finished
animation_set_handlers(centering_spawn_anim, (AnimationHandlers){.stopped = destroy_animation_handler}, NULL);
////// run centering spawn animation
animation_schedule(centering_spawn_anim);
}
////// update current date/bt positions
s_date_grect_a = s_date_grect_b;
s_bt_grect_a = s_bt_grect_b;
}
first_minute_update = false;
// DEBUG memory usage layer
static char s_memory_buffer[32];
snprintf(s_memory_buffer, sizeof(s_memory_buffer), "%d/%d", (int)heap_bytes_used(), (int)heap_bytes_free() + (int)heap_bytes_used());
text_layer_set_text(s_memory_layer, s_memory_buffer);
}
static void schedule_guy_animation(Animation *raise_tmpl, Animation *out_tmpl, AnimationStoppedHandler out_handler) {
Animation *raise = animation_clone(raise_tmpl);
Animation *out = animation_clone(out_tmpl);
animation_set_handlers(out, (AnimationHandlers){.stopped = out_handler}, NULL);
Animation *in = animation_clone(out_tmpl);
animation_set_reverse(in, true);
Animation *rest = animation_clone(raise_tmpl);
animation_set_reverse(rest, true);
Animation *seq = animation_sequence_create(raise, out, in, rest, NULL);
// set the handler to destroy the sequence (and its children) when finished
animation_set_handlers(seq, (AnimationHandlers){.stopped = destroy_animation_handler}, NULL);
// run it
animation_schedule(seq);
}
static void update_minute_30_out_handler(Animation *animation, bool finished, void *context) {
// destroy and reassign current
gbitmap_destroy(s_butt_current);
s_butt_current = s_butt_next;
// swap colors
replace_gbitmap_color(GColorGreen, s_random_color_next, s_butt_current, NULL);
replace_gbitmap_color(s_random_color_current, s_random_color_next, s_head_current, NULL);
s_random_color_current = s_random_color_next; // must be referenced for later replacement
// force update bitmap layers
bitmap_layer_set_bitmap(s_guy_butt_layer, s_butt_current);
bitmap_layer_set_bitmap(s_guy_head_layer, s_head_current);
// calc next values
s_butt_next = gbitmap_create_with_resource(s_random_butts[rand() % s_butt_count]);
s_random_color_next = s_dark_bg_colors[rand() % s_color_count]; // next color
}
static void update_minute_30() {
update_minute_1();
schedule_guy_animation(s_butt_raise_template, s_butt_out_template, update_minute_30_out_handler);
}
static void update_minute_60_out_handler(Animation *animation, bool finished, void *context) {
// destroy and reassign current
gbitmap_destroy(s_head_current);
s_head_current = s_head_next;
// swap colors
replace_gbitmap_color(GColorGreen, s_random_color_next, s_head_current, NULL);
replace_gbitmap_color(s_random_color_current, s_random_color_next, s_butt_current, NULL);
s_random_color_current = s_random_color_next;
// force update bitmap layers
bitmap_layer_set_bitmap(s_guy_head_layer, s_head_current);
bitmap_layer_set_bitmap(s_guy_butt_layer, s_butt_current);
// calc next values
s_head_next = gbitmap_create_with_resource(s_random_heads[rand() % s_head_count]);
s_random_color_next = s_dark_bg_colors[rand() % s_color_count]; // next color
}
static void update_minute_60() {
update_minute_1();
schedule_guy_animation(s_head_raise_template, s_head_out_template, update_minute_60_out_handler);
}
static void minute_handler(struct tm *tick_time, TimeUnits units_changed) {
if (tick_time->tm_min % 60 == 0) {
update_minute_60();
} else if (tick_time->tm_min % 30 == 0) {
update_minute_30();
} else {
update_minute_1();
}
}
static void time_bar_update_proc(Layer *layer, GContext *ctx) {
graphics_context_set_fill_color(ctx, GColorWhite);
graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone);
}
// define contents of the Window upon load
static void main_window_load(Window *window) {
// dynamically calculate coordinates for silly guy animations
int8_t part_x_on_screen = (260 - PBL_DISPLAY_WIDTH) / -2;
int8_t head_y_resting = (260 - PBL_DISPLAY_HEIGHT) / -2;
int8_t head_y_rasied = head_y_resting - 15;
s_guy_head_grect_on_screen_raised = GRect(part_x_on_screen, head_y_rasied, 260, 115);
s_guy_head_grect_off_screen_raised = GRect(-PBL_DISPLAY_WIDTH + part_x_on_screen, head_y_rasied, 260, 115);
s_guy_head_grect_on_screen_resting = GRect(part_x_on_screen, head_y_resting, 260, 115);
s_guy_butt_grect_on_screen_raised = GRect(part_x_on_screen, head_y_resting + 180, 260, 115);
s_guy_butt_grect_off_screen_raised = GRect(PBL_DISPLAY_WIDTH + part_x_on_screen, head_y_resting + 160, 260, 115);
s_guy_butt_grect_on_screen_resting = GRect(part_x_on_screen, head_y_resting + 145, 260, 115);
// format silly guy layers
s_guy_head_layer = bitmap_layer_create(s_guy_head_grect_on_screen_resting);
s_guy_butt_layer = bitmap_layer_create(s_guy_butt_grect_on_screen_resting);
bitmap_layer_set_compositing_mode(s_guy_head_layer, GCompOpSet);
bitmap_layer_set_alignment(s_guy_head_layer, GAlignLeft);
bitmap_layer_set_bitmap(s_guy_head_layer, s_head_current);
bitmap_layer_set_compositing_mode(s_guy_butt_layer, GCompOpSet);
bitmap_layer_set_alignment(s_guy_butt_layer, GAlignTopLeft);
bitmap_layer_set_bitmap(s_guy_butt_layer, s_butt_current);
// create time bar layer
s_time_bar_layer = layer_create(GRect(0, PBL_DISPLAY_HEIGHT / 2 - 15, PBL_DISPLAY_WIDTH, 30));
layer_set_update_proc(s_time_bar_layer, time_bar_update_proc);
text_layer_set_text_alignment(s_date_layer, GTextAlignmentCenter);
// add layers as children to window
Layer *window_layer = window_get_root_layer(window);
layer_add_child(window_layer, bitmap_layer_get_layer(s_guy_head_layer));
layer_add_child(window_layer, bitmap_layer_get_layer(s_guy_butt_layer));
layer_add_child(window_layer, s_time_bar_layer);
layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
layer_add_child(window_layer, text_layer_get_layer(s_date_layer));
layer_add_child(window_layer, bitmap_layer_get_layer(s_bt_layer));
// DEBUG memory usage layer
layer_add_child(window_layer, text_layer_get_layer(s_memory_layer));
// create animation templates
// butt Raise
PropertyAnimation *butt_raise_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_guy_butt_layer), &s_guy_butt_grect_on_screen_resting, &s_guy_butt_grect_on_screen_raised);
s_butt_raise_template = property_animation_get_animation(butt_raise_prop);
animation_set_curve(s_butt_raise_template, AnimationCurveEaseOut);
animation_set_duration(s_butt_raise_template, s_animation_duration);
// butt Out
PropertyAnimation *butt_out_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_guy_butt_layer), &s_guy_butt_grect_on_screen_raised, &s_guy_butt_grect_off_screen_raised);
s_butt_out_template = property_animation_get_animation(butt_out_prop);
animation_set_curve(s_butt_out_template, AnimationCurveEaseOut);
animation_set_duration(s_butt_out_template, s_animation_duration);
// head Raise
PropertyAnimation *head_raise_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_guy_head_layer), &s_guy_head_grect_on_screen_resting, &s_guy_head_grect_on_screen_raised);
s_head_raise_template = property_animation_get_animation(head_raise_prop);
animation_set_curve(s_head_raise_template, AnimationCurveEaseOut);
animation_set_duration(s_head_raise_template, s_animation_duration);
// head Out
PropertyAnimation *head_out_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_guy_head_layer), &s_guy_head_grect_on_screen_raised, &s_guy_head_grect_off_screen_raised);
s_head_out_template = property_animation_get_animation(head_out_prop);
animation_set_curve(s_head_out_template, AnimationCurveEaseOut);
animation_set_duration(s_head_out_template, s_animation_duration);
}
// free memory on Window close
static void main_window_unload(Window *window) {
// destroy layers
bitmap_layer_destroy(s_guy_head_layer);
bitmap_layer_destroy(s_guy_butt_layer);
layer_destroy(s_time_bar_layer);
// Destroy animation templates
animation_destroy(s_butt_raise_template);
animation_destroy(s_butt_out_template);
animation_destroy(s_head_raise_template);
animation_destroy(s_head_out_template);
}
// DEBUG buttons
static void up_single_click_handler(ClickRecognizerRef recognizer, void *context) {
Window *window = (Window *)context;
update_minute_60();
}
// DEBUG buttons
static void down_single_click_handler(ClickRecognizerRef recognizer, void *context) {
Window *window = (Window *)context;
update_minute_30();
}
// DEBUG buttons
static void click_config_provider(Window *window) {
window_single_click_subscribe(BUTTON_ID_UP, up_single_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, down_single_click_handler);
}
// set up the app on launch (don't put app logic in here);
static void init() {
// create main Window element and assign to pointer
s_main_window = window_create();
// set handlers to manage the elements inside the Window
window_set_window_handlers(s_main_window, (WindowHandlers){
.load = main_window_load,
.unload = main_window_unload});
// set app background color
window_set_background_color(s_main_window, GColorBlack);
// load initial batch of silly guy parts into memory
s_head_current = gbitmap_create_with_resource(s_random_heads[rand() % s_head_count]);
s_butt_current = gbitmap_create_with_resource(s_random_butts[rand() % s_butt_count]);
s_head_next = gbitmap_create_with_resource(s_random_heads[rand() % s_head_count]);
s_butt_next = gbitmap_create_with_resource(s_random_butts[rand() % s_butt_count]);
// pick starting colors
s_random_color_current = s_dark_bg_colors[rand() % s_color_count];
replace_gbitmap_color(GColorGreen, s_random_color_current, s_head_current, NULL);
replace_gbitmap_color(GColorGreen, s_random_color_current, s_butt_current, NULL);
// don't overwrite current color, as it will need to be replaced
s_random_color_next = s_dark_bg_colors[rand() % s_color_count];
// load custom font
#if PBL_DISPLAY_WIDTH == 180
s_custom_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_RETRO_COMPUTER_28));
#else
s_custom_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_RETRO_COMPUTER_34));
#endif
// load bluetooth indicator
s_bt_icon = gbitmap_create_with_resource(RESOURCE_ID_BT);
// dynamically calculate coordinates for date/bt indicators
s_date_pos_y = PBL_DISPLAY_HEIGHT / 2 - 9;
s_date_bt_width = (PBL_DISPLAY_WIDTH - s_max_time_width) / 2;
s_date_grect_a = GRect(s_date_pos_x, s_date_pos_y, s_date_bt_width, s_date_height);
#if PBL_DISPLAY_WIDTH == 180
s_bt_pos_x = s_date_bt_width + s_max_time_width;
#else
s_bt_pos_x = s_date_bt_width + s_max_time_width + 1;
#endif
s_bt_pos_y = (PBL_DISPLAY_HEIGHT / 2) - 12;
s_bt_grect_a = GRect(s_bt_pos_x, s_bt_pos_y, s_date_bt_width, s_bt_height);
// pre-create layers used in update_minute_1
//// time
#if PBL_DISPLAY_WIDTH == 180
s_time_layer = text_layer_create(GRect(0, (PBL_DISPLAY_HEIGHT / 2) - 18, PBL_DISPLAY_WIDTH, 28));
#else
s_time_layer = text_layer_create(GRect(0, (PBL_DISPLAY_HEIGHT / 2) - 21, PBL_DISPLAY_WIDTH, 34));
#endif
text_layer_set_background_color(s_time_layer, GColorClear);
text_layer_set_font(s_time_layer, s_custom_font);
text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
//// date
s_date_layer = text_layer_create(s_date_grect_a);
text_layer_set_background_color(s_date_layer, GColorClear);
text_layer_set_font(s_date_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD));
text_layer_set_text_alignment(s_date_layer, GTextAlignmentCenter);
//// bt
s_bt_layer = bitmap_layer_create(s_bt_grect_a);
bitmap_layer_set_compositing_mode(s_bt_layer, GCompOpSet);
bitmap_layer_set_alignment(s_bt_layer, GAlignCenter);
bitmap_layer_set_bitmap(s_bt_layer, s_bt_icon);
//// DEBUG memory usage layer
s_memory_layer = text_layer_create(GRect(0, PBL_DISPLAY_HEIGHT - 24, PBL_DISPLAY_WIDTH, 14));
text_layer_set_background_color(s_memory_layer, GColorClear);
text_layer_set_font(s_memory_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14));
text_layer_set_text_color(s_memory_layer, GColorWhite);
PBL_IF_ROUND_ELSE(text_layer_set_text_alignment(s_memory_layer, GTextAlignmentCenter), text_layer_set_text_alignment(s_memory_layer, GTextAlignmentLeft));
// make sure the time is displayed from the start
// and that the date/bt indicators are centered
update_minute_1();
layer_set_frame(text_layer_get_layer(s_date_layer), s_date_grect_a);
layer_set_frame(bitmap_layer_get_layer(s_bt_layer), s_bt_grect_a);
// show the Window on the watch, with animated=true
window_stack_push(s_main_window, true);
// register with TickTimerService
tick_timer_service_subscribe(MINUTE_UNIT, minute_handler);
// DEBUG buttons
window_set_click_config_provider(s_main_window, (ClickConfigProvider)click_config_provider);
}
// free memory on app exit
static void deinit() {
// unsubscribe
tick_timer_service_unsubscribe();
// destroy layers
text_layer_destroy(s_time_layer);
text_layer_destroy(s_date_layer);
bitmap_layer_destroy(s_bt_layer);
// DEBUG memory usage layer
text_layer_destroy(s_memory_layer);
// unload font
fonts_unload_custom_font(s_custom_font);
// destroy gbitmaps
gbitmap_destroy(s_head_current);
gbitmap_destroy(s_butt_current);
gbitmap_destroy(s_head_next);
gbitmap_destroy(s_butt_next);
gbitmap_destroy(s_bt_icon);
// destroy window
window_destroy(s_main_window);
}
int main(void) {
init();
app_event_loop();
deinit();
}