#include "gbitmap_color_palette_manipulator.h"
#include <pebble.h>
+// Persistent storage key
+#define SETTINGS_KEY 1
+
+// Define our settings struct
+typedef struct ClaySettings {
+ GColor BackgroundColor;
+ GColor ForegroundColor;
+ GColor ClockColor;
+ bool SecondTick;
+ int BusFrequency;
+} ClaySettings;
+
+static ClaySettings settings;
+
static Window *s_main_window;
+static GRect window_bounds;
static Layer *s_hands_layer;
static TextLayer *s_time_layer;
static BitmapLayer *s_background_layer;
static GBitmap *s_background_bitmap;
+// bus layer and bitmap
+static BitmapLayer *s_bus_layer;
+static GBitmap *s_bus_bitmap;
+static GRect bus_bitmap_bounds;
+
static GPoint center = {
.x = (int16_t)70,
.y = (int16_t)68,
};
+
+void draw_centered_ray(int32_t tick_angle, int16_t start, int16_t end, int8_t stroke, Layer *layer, GContext *ctx){
+ graphics_context_set_stroke_color(ctx, settings.ClockColor);
+ // from the the starting point (predetermined angle with starting_tick_length)..
+ GPoint starting_point = {
+ .x = (int16_t)(sin_lookup(tick_angle) * (int32_t)start/TRIG_MAX_RATIO) + center.x,
+ .y = (int16_t)(-cos_lookup(tick_angle) * (int32_t)start/TRIG_MAX_RATIO) + center.y,
+ };
+ // .. to the ending point (same angle, slightly more length)
+ GPoint ending_point = {
+ .x = (int16_t)(sin_lookup(tick_angle) * (int32_t)end/TRIG_MAX_RATIO) + center.x,
+ .y = (int16_t)(-cos_lookup(tick_angle) * (int32_t)end/TRIG_MAX_RATIO) + center.y,
+ };
+ // draw it, then wrap around to next
+ graphics_context_set_stroke_width(ctx, stroke);
+ graphics_draw_line(ctx, starting_point, ending_point);
+
+}
+
static void draw_ticks(Layer *layer, GContext *ctx){
const int16_t starting_tick_length = 66;
const int16_t ending_tick_length = 68;
// for each tick
for (int i = 0; i < 12; i++){
int32_t tick_angle = TRIG_MAX_ANGLE * i/12;
- // from the the starting point (predetermined angle with starting_tick_length)..
- GPoint starting_point = {
- .x = (int16_t)(sin_lookup(tick_angle) * (int32_t)starting_tick_length/TRIG_MAX_RATIO) + center.x,
- .y = (int16_t)(-cos_lookup(tick_angle) * (int32_t)starting_tick_length/TRIG_MAX_RATIO) + center.y,
- };
- // .. to the ending point (same angle, slightly more length)
- GPoint ending_point = {
- .x = (int16_t)(sin_lookup(tick_angle) * (int32_t)ending_tick_length/TRIG_MAX_RATIO) + center.x,
- .y = (int16_t)(-cos_lookup(tick_angle) * (int32_t)ending_tick_length/TRIG_MAX_RATIO) + center.y,
- };
-
- // draw it, then wrap around to next
- graphics_context_set_stroke_width(ctx, 2);
- graphics_draw_line(ctx, starting_point, ending_point);
+ draw_centered_ray(tick_angle, starting_tick_length, ending_tick_length, 2, layer, ctx);
}
-
}
static void hands_update_proc(Layer *layer, GContext *ctx) {
-
const int16_t second_hand_length = 64;
const int16_t minute_hand_length = 64;
const int16_t hour_hand_length = 32;
time_t now = time(NULL);
struct tm *t = localtime(&now);
- int32_t second_angle = TRIG_MAX_ANGLE * t->tm_sec / 60;
- GPoint second_hand = {
- .x = (int16_t)(sin_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.x,
- .y = (int16_t)(-cos_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.y,
- };
-
- // second hand
- graphics_context_set_stroke_color(ctx, GColorWhite);
- graphics_context_set_stroke_width(ctx, 1);
- graphics_draw_line(ctx, second_hand, center);
+ if (settings.SecondTick){
+ int32_t second_angle = TRIG_MAX_ANGLE * t->tm_sec / 60;
+ draw_centered_ray(second_angle, 0, second_hand_length, 1, layer, ctx);
+ }
// minute hand drawing
int32_t minute_angle = TRIG_MAX_ANGLE * t->tm_min/60;
- GPoint minute_hand = {
- .x = (int16_t)(sin_lookup(minute_angle) * (int32_t)minute_hand_length / TRIG_MAX_RATIO) + center.x,
- .y = (int16_t)(-cos_lookup(minute_angle) * (int32_t)minute_hand_length / TRIG_MAX_RATIO) + center.y,
- };
- graphics_context_set_stroke_width(ctx, minute_stroke_width);
-
- graphics_draw_line(ctx, minute_hand, center);
+ draw_centered_ray(minute_angle, 0, minute_hand_length, minute_stroke_width, layer, ctx);
// hour hand drawing
int32_t hour_angle = TRIG_MAX_ANGLE * (((t->tm_hour % 12 ) * 6) + (t->tm_min/10)) / (12*6);
- GPoint hour_hand = {
- .x = (int16_t)(sin_lookup(hour_angle) * (int32_t)hour_hand_length / TRIG_MAX_RATIO) + center.x,
- .y = (int16_t)(-cos_lookup(hour_angle) * (int32_t)hour_hand_length / TRIG_MAX_RATIO) + center.y,
- };
- graphics_context_set_stroke_width(ctx, hour_stroke_width);
+ draw_centered_ray(hour_angle, 0, hour_hand_length, hour_stroke_width, layer, ctx);
+}
- graphics_draw_line(ctx, hour_hand, center);
-
- // dot in the middle
- //graphics_context_set_fill_color(ctx, GColorBlack);
- //graphics_fill_rect(ctx, GRect(bounds.size.w / 2 - 1, bounds.size.h / 2 - 1, 3, 3), 0, GCornerNone);
+
+static void refresh_bitmap(){
+ if (s_background_bitmap){
+ // Destroy GBitmap
+ gbitmap_destroy(s_background_bitmap);
+ }
+
+ // Create GBitmap
+ s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_METRO);
+ // color replace, only if color pebble
+#ifdef PBL_COLOR
+ // foreground, black -> user-defined color
+ // set it to clear first to "save" it (otherwise you might set to e.g. white and replace it ALL with the next move)
+ replace_gbitmap_color(GColorBlack, GColorPastelYellow, s_background_bitmap, NULL);
+ // background, white -> user-defined color
+ replace_gbitmap_color(GColorWhite, settings.BackgroundColor, s_background_bitmap, NULL);
+ // now do the final clear -> user defined color
+ replace_gbitmap_color(GColorPastelYellow, settings.ForegroundColor, s_background_bitmap, NULL);
+#endif
+ // Set the bitmap onto the layer and add to the window
+ bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
}
+static void create_bus_layer(){
+ // Load the image data
+ s_bus_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_XDE);
+ // Get the bounds of the image
+ bus_bitmap_bounds = gbitmap_get_bounds(s_bus_bitmap);
+ bitmap_layer_set_compositing_mode(s_bus_layer, GCompOpSet);
+
+ bitmap_layer_set_bitmap(s_bus_layer, s_bus_bitmap);
+}
static void main_window_load(Window *window) {
// Get information about the Window
Layer *window_layer = window_get_root_layer(window);
- GRect bounds = layer_get_bounds(window_layer);
-
- // Create GBitmap
- s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_METRO);
- // color replace, only if color pebble
-#ifdef PBL_COLOR
- // foreground, black -> user-defined color
- replace_gbitmap_color(GColorBlack, GColorChromeYellow, s_background_bitmap, NULL);
- // background, white -> user-defined color
- replace_gbitmap_color(GColorWhite, GColorDarkCandyAppleRed, s_background_bitmap, NULL);
-#endif
+ window_bounds = layer_get_bounds(window_layer);
// Create BitmapLayer to display the GBitmap
- s_background_layer = bitmap_layer_create(bounds);
+ s_background_layer = bitmap_layer_create(window_bounds);
+
+ refresh_bitmap();
- // Set the bitmap onto the layer and add to the window
- bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
layer_add_child(window_layer, bitmap_layer_get_layer(s_background_layer));
/*
// Create the TextLayer with specific bounds
*/
- s_hands_layer = layer_create(bounds);
+ s_hands_layer = layer_create(window_bounds);
layer_set_update_proc(s_hands_layer, hands_update_proc);
layer_add_child(window_layer, s_hands_layer);
+
+ s_bus_layer = bitmap_layer_create(window_bounds);
+ create_bus_layer();
+ layer_set_hidden(bitmap_layer_get_layer(s_bus_layer), true);
+ layer_add_child(window_layer, bitmap_layer_get_layer(s_bus_layer));
}
static void main_window_unload(Window *window) {
text_layer_destroy(s_time_layer);
// Destroy hands layer
layer_destroy(s_hands_layer);
+ gbitmap_destroy(s_bus_bitmap);
+ bitmap_layer_destroy(s_bus_layer);
}
static void update_time() {
//text_layer_set_text(s_time_layer, s_buffer);
}
-static void handle_second_tick(struct tm *tick_time, TimeUnits units_changed) {
- layer_mark_dirty(window_get_root_layer(s_main_window));
+static void anim_started_handler(Animation *animation, void *context) {
+ layer_set_hidden(bitmap_layer_get_layer(s_bus_layer), false);
+ APP_LOG(APP_LOG_LEVEL_DEBUG, "Animation started!");
+}
+
+static void anim_stopped_handler(Animation *animation, bool finished, void *context) {
+ layer_set_hidden(bitmap_layer_get_layer(s_bus_layer), true);
+ APP_LOG(APP_LOG_LEVEL_DEBUG, "Animation stopped!");
}
+
+static void start_bus_animation(){
+ // The start and end frames - move the Layer 40 pixels to the right
+ GRect start = GRect(-bus_bitmap_bounds.size.w, bus_bitmap_bounds.size.h/3, bus_bitmap_bounds.size.w, bus_bitmap_bounds.size.h);
+ GRect finish = GRect(window_bounds.size.w, bus_bitmap_bounds.size.h/3, bus_bitmap_bounds.size.w, bus_bitmap_bounds.size.h);
+ // Animate the Layer
+ PropertyAnimation *prop_anim = property_animation_create_layer_frame(bitmap_layer_get_layer(s_bus_layer), &start, &finish);
+ // Get the Animation
+ Animation *anim = property_animation_get_animation(prop_anim);
+
+ // Choose parameters
+ const int delay_ms = 0;
+ const int duration_ms = 5000;
+
+ // Configure the Animation's curve, delay, and duration
+ //animation_set_curve(anim, AnimationCurveEaseOut);
+ animation_set_delay(anim, delay_ms);
+ animation_set_duration(anim, duration_ms);
+ animation_set_handlers(anim, (AnimationHandlers) {
+ .started = anim_started_handler,
+ .stopped = anim_stopped_handler
+ }, NULL);
+ // Play the animation
+ animation_schedule(anim);
+}
+
+static void handle_clock_tick(struct tm *tick_time, TimeUnits units_changed) {
+ //layer_mark_dirty(window_get_root_layer(s_main_window));
+
+ if (units_changed & MINUTE_UNIT){
+ APP_LOG(APP_LOG_LEVEL_DEBUG, "minute rollover, updating minute stuff");
+ layer_mark_dirty(window_get_root_layer(s_main_window));
+ // we want the bus to show up
+ if (settings.BusFrequency != 0 ){
+ // number of minutes past the hour, will NOT line up evenly if you choose a frequency like e.g. 13, it will advance:
+ // 0, 13, 26, 39, 52 ..(8 mins).. 0, 13
+ // this makes more conceptual sense to me, but if you want it since start of day or whatever feel free to change
+ //APP_LOG(APP_LOG_LEVEL_DEBUG, "tick_time = %d start_of_today = %d", (int)mktime(tick_time), (int)time_start_of_today());
+ int minutes_past_the_hour = mktime(tick_time)/60 % 60;
+ // minutes since start of day == bus frequency?
+ if (minutes_past_the_hour % settings.BusFrequency == 0 ){
+ start_bus_animation();
+ }
+ }
+ } else if (settings.SecondTick){
+ layer_mark_dirty(window_get_root_layer(s_main_window));
+ }
+}
+
+
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
update_time();
}
+// Initialize the default settings
+static void prv_default_settings() {
+ settings.BackgroundColor = GColorWhite;
+ settings.ForegroundColor = GColorBlack;
+ settings.ClockColor = GColorWhite;
+ settings.SecondTick = false;
+ settings.BusFrequency = 0;
+}
+
+void refresh_procs(){
+ tick_timer_service_unsubscribe();
+ // choose minute or second refresh depending on hands
+ if (settings.SecondTick){
+ tick_timer_service_subscribe(SECOND_UNIT, handle_clock_tick);
+ } else {
+ tick_timer_service_subscribe(MINUTE_UNIT, handle_clock_tick);
+ }
+}
+
+
+// Save the settings to persistent storage
+static void prv_save_settings() {
+ persist_write_data(SETTINGS_KEY, &settings, sizeof(settings));
+}
+
+static void prv_inbox_received_handler(DictionaryIterator *iter, void *context) {
+ APP_LOG(APP_LOG_LEVEL_DEBUG, "received message");
+ // Read color preferences
+ Tuple *bg_color_t = dict_find(iter, MESSAGE_KEY_BackgroundColor);
+ if(bg_color_t) {
+ APP_LOG(APP_LOG_LEVEL_DEBUG, "set background color to %d", (int)bg_color_t->value->int32);
+ settings.BackgroundColor = GColorFromHEX(bg_color_t->value->int32);
+ }
+
+ Tuple *fg_color_t = dict_find(iter, MESSAGE_KEY_ForegroundColor);
+ if(fg_color_t) {
+ APP_LOG(APP_LOG_LEVEL_DEBUG, "set foreground color to %d", (int)fg_color_t->value->int32);
+ settings.ForegroundColor = GColorFromHEX(fg_color_t->value->int32);
+ }
+
+ Tuple *clock_color_t = dict_find(iter, MESSAGE_KEY_ClockColor);
+ if(clock_color_t) {
+ APP_LOG(APP_LOG_LEVEL_DEBUG, "set clock color to %d", (int)clock_color_t->value->int32);
+ settings.ClockColor = GColorFromHEX(clock_color_t->value->int32);
+ }
+
+ // Read boolean preferences
+ Tuple *second_tick_t = dict_find(iter, MESSAGE_KEY_SecondTick);
+ if(second_tick_t) {
+ settings.SecondTick = second_tick_t->value->int32 == 1;
+ }
+
+ Tuple *bus_freq_t = dict_find(iter, MESSAGE_KEY_BusFrequency);
+ if(bus_freq_t) {
+ settings.BusFrequency = bus_freq_t->value->int32;
+ }
+ prv_save_settings();
+ refresh_bitmap();
+ refresh_procs();
+}
+
+// Read settings from persistent storage
+static void prv_load_settings() {
+ // Load the default settings
+ prv_default_settings();
+ // Read settings from persistent storage, if they exist
+ persist_read_data(SETTINGS_KEY, &settings, sizeof(settings));
+}
+
+void prv_init() {
+ prv_load_settings();
+ // Open AppMessage connection
+ app_message_register_inbox_received(prv_inbox_received_handler);
+ app_message_open(128, 128);
+}
+
static void init() {
+ prv_init();
// Create main Window element and assign to pointer
s_main_window = window_create();
// Show the Window on the watch, with animated=true
window_stack_push(s_main_window, true);
- // Make sure the time is displayed from the start
- tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
- tick_timer_service_subscribe(SECOND_UNIT, handle_second_tick);
+ refresh_procs();
update_time();
}