418 lines
14 KiB
C
418 lines
14 KiB
C
/**
|
|
* Copyright (c) 2015 Tomek Wójcik <tomek@bthlabs.pl>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
#include <pebble.h>
|
|
|
|
static Window *s_main_window;
|
|
static GBitmap *s_background_bitmap;
|
|
static GBitmap *s_battery_bitmap;
|
|
static GFont s_text_font;
|
|
static GPath *s_minute_hand;
|
|
static GPath *s_hour_hand;
|
|
static BitmapLayer *s_background_layer;
|
|
static TextLayer *s_title_layer;
|
|
static Layer *s_hands_layer;
|
|
static TextLayer *s_date_layer;
|
|
static TextLayer *s_ampm_layer;
|
|
static BitmapLayer *s_battery_icon_layer;
|
|
static Layer *s_battery_indicator_layer;
|
|
static struct tm *t = NULL;
|
|
static uint8_t battery_charge = 0;
|
|
static bool hide_seconds_hand = false;
|
|
static bool hide_battery_indicator = false;
|
|
static char date_format[2];
|
|
static bool use_wide_layout = false;
|
|
static bool use_xen_style = false;
|
|
|
|
static const GPathInfo MINUTE_HAND_POINTS = {
|
|
4,
|
|
(GPoint []) {
|
|
{ 0, 0 },
|
|
{ -4.5, -26 },
|
|
{ 0, -38 },
|
|
{ 4.5, -26 }
|
|
}
|
|
};
|
|
|
|
static const GPathInfo HOUR_HAND_POINTS = {
|
|
4,
|
|
(GPoint []) {
|
|
{ 0, 0 },
|
|
{ -4.5, -22 },
|
|
{ 0, -29 },
|
|
{ 4.5, -22 }
|
|
}
|
|
};
|
|
|
|
#define AMPM_TEXT_SIZE 3
|
|
#define DUMMY_AMPM_TEXT ""
|
|
|
|
#define DATE_TEXT_SIZE 12
|
|
#define DUMMY_DATE_TEXT "23 Jul 1985"
|
|
|
|
#define KEY_SECONDS_HAND 0
|
|
#define KEY_BATTERY_INDICATOR 1
|
|
#define KEY_DATE_FORMAT 2
|
|
#define KEY_WIDE_LAYOUT 3
|
|
#define KEY_XEN_STYLE 4
|
|
|
|
static void update_date_text_layer() {
|
|
static char date_text[DATE_TEXT_SIZE];
|
|
size_t result = 0;
|
|
|
|
if (strcmp(date_format, "d") == 0) {
|
|
result = strftime(date_text, DATE_TEXT_SIZE, "%d/%m/%y", t);
|
|
} else if (strcmp(date_format, "m") == 0) {
|
|
result = strftime(date_text, DATE_TEXT_SIZE, "%m/%d/%y", t);
|
|
} else {
|
|
result = strftime(date_text, DATE_TEXT_SIZE, "%e %b %Y", t);
|
|
}
|
|
|
|
if (result != 0) {
|
|
text_layer_set_text(s_date_layer, date_text);
|
|
} else {
|
|
text_layer_set_text(s_date_layer, DUMMY_DATE_TEXT);
|
|
}
|
|
}
|
|
|
|
static void update_ampm_text_layer() {
|
|
static char ampm_text[AMPM_TEXT_SIZE];
|
|
|
|
if (clock_is_24h_style()) {
|
|
text_layer_set_text(s_ampm_layer, DUMMY_AMPM_TEXT);
|
|
} else {
|
|
size_t result = strftime(ampm_text, AMPM_TEXT_SIZE, "%p", t);
|
|
if (result != 0) {
|
|
text_layer_set_text(s_ampm_layer, ampm_text);
|
|
} else {
|
|
text_layer_set_text(s_ampm_layer, DUMMY_AMPM_TEXT);
|
|
}
|
|
}
|
|
|
|
text_layer_set_text(s_ampm_layer, ampm_text);
|
|
}
|
|
|
|
static void tick_handler(struct tm *tick_time, TimeUnits changed_units) {
|
|
// Getting the current time.
|
|
time_t now = time(NULL);
|
|
t = localtime(&now);
|
|
|
|
// Updating views.
|
|
layer_mark_dirty(s_hands_layer);
|
|
update_date_text_layer();
|
|
update_ampm_text_layer();
|
|
}
|
|
|
|
static void s_hands_layer_update(Layer *layer, GContext *ctx) {
|
|
GRect bounds = layer_get_bounds(layer);
|
|
GPoint center = grect_center_point(&bounds);
|
|
|
|
// Drawing the minute hand.
|
|
graphics_context_set_fill_color(ctx, GColorBlack);
|
|
graphics_context_set_stroke_color(ctx, GColorBlack);
|
|
gpath_move_to(s_minute_hand, (GPoint){39.5, 39});
|
|
gpath_rotate_to(s_minute_hand, TRIG_MAX_ANGLE * t->tm_min / 60);
|
|
gpath_draw_outline(ctx, s_minute_hand);
|
|
gpath_draw_filled(ctx, s_minute_hand);
|
|
|
|
// Drawing the hour hand.
|
|
graphics_context_set_fill_color(ctx, GColorBlack);
|
|
graphics_context_set_stroke_color(ctx, GColorBlack);
|
|
gpath_move_to(s_hour_hand, (GPoint){39.5, 39});
|
|
gpath_rotate_to(s_hour_hand, (TRIG_MAX_ANGLE * (((t->tm_hour % 12) * 6) + (t->tm_min / 10))) / (12 * 6));
|
|
gpath_draw_outline(ctx, s_hour_hand);
|
|
gpath_draw_filled(ctx, s_hour_hand);
|
|
|
|
// Drawing the second hand.
|
|
if (hide_seconds_hand == false) {
|
|
graphics_context_set_antialiased(ctx, false);
|
|
int32_t second_angle = TRIG_MAX_ANGLE * t->tm_sec / 60;
|
|
int16_t second_hand_length = (bounds.size.w / 2) - 1;
|
|
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,
|
|
};
|
|
|
|
graphics_context_set_stroke_color(ctx, GColorPictonBlue);
|
|
graphics_draw_line(ctx, second_hand, center);
|
|
graphics_context_set_antialiased(ctx, true);
|
|
}
|
|
}
|
|
|
|
static void s_battery_indicator_layer_update(Layer *layer, GContext *ctx) {
|
|
GRect bounds = layer_get_bounds(layer);
|
|
|
|
graphics_context_set_fill_color(ctx, GColorGreen);
|
|
if (battery_charge <= 10) {
|
|
graphics_context_set_fill_color(ctx, GColorRed);
|
|
} else if (battery_charge <= 60) {
|
|
graphics_context_set_fill_color(ctx, GColorYellow);
|
|
}
|
|
|
|
graphics_fill_rect(ctx, bounds, 0, GCornerNone);
|
|
}
|
|
|
|
static void battery_state_handler(BatteryChargeState charge) {
|
|
if (charge.charge_percent != battery_charge) {
|
|
battery_charge = charge.charge_percent;
|
|
layer_mark_dirty(s_battery_indicator_layer);
|
|
}
|
|
}
|
|
|
|
static void start_time_tracking() {
|
|
TimeUnits unit = MINUTE_UNIT;
|
|
|
|
if (hide_seconds_hand == false) {
|
|
unit = SECOND_UNIT;
|
|
}
|
|
|
|
tick_handler(NULL, unit);
|
|
tick_timer_service_subscribe(unit, tick_handler);
|
|
}
|
|
|
|
static void start_battery_tracking() {
|
|
if (hide_battery_indicator == false) {
|
|
BatteryChargeState charge = battery_state_service_peek();
|
|
battery_state_handler(charge);
|
|
battery_state_service_subscribe(battery_state_handler);
|
|
} else {
|
|
battery_state_service_unsubscribe();
|
|
}
|
|
|
|
layer_set_hidden(bitmap_layer_get_layer(s_battery_icon_layer), hide_battery_indicator);
|
|
layer_set_hidden(s_battery_indicator_layer, hide_battery_indicator);
|
|
}
|
|
|
|
static void update_geometry() {
|
|
if (s_background_bitmap) {
|
|
gbitmap_destroy(s_background_bitmap);
|
|
}
|
|
|
|
if (use_wide_layout == false) {
|
|
if (use_xen_style == false) {
|
|
s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMG_BKG_CLASSIC_NARROW);
|
|
} else {
|
|
s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMG_BKG_XEN_NARROW);
|
|
}
|
|
|
|
layer_set_frame(text_layer_get_layer(s_title_layer), GRect(36, 2, 46, 18));
|
|
layer_set_frame(text_layer_get_layer(s_date_layer), GRect(17, 130, 109, 16));
|
|
layer_set_frame(text_layer_get_layer(s_ampm_layer), GRect(108, 26, 16, 16));
|
|
layer_set_frame(bitmap_layer_get_layer(s_battery_icon_layer), GRect(20, 26, 16, 7));
|
|
layer_set_frame(s_battery_indicator_layer, GRect(21, 27, 13, 5));
|
|
} else {
|
|
if (use_xen_style == false) {
|
|
s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMG_BKG_CLASSIC_WIDE);
|
|
} else {
|
|
s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMG_BKG_XEN_WIDE);
|
|
}
|
|
|
|
layer_set_frame(text_layer_get_layer(s_title_layer), GRect(24, 2, 71, 18));
|
|
layer_set_frame(text_layer_get_layer(s_date_layer), GRect(4, 130, 136, 16));
|
|
layer_set_frame(text_layer_get_layer(s_ampm_layer), GRect(123, 25, 16, 16));
|
|
layer_set_frame(bitmap_layer_get_layer(s_battery_icon_layer), GRect(7, 26, 16, 7));
|
|
layer_set_frame(s_battery_indicator_layer, GRect(8, 27, 13, 5));
|
|
}
|
|
|
|
bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
|
|
}
|
|
|
|
static void inbox_received_handler(DictionaryIterator *iter, void *context) {
|
|
// Handling seconds_hand setting.
|
|
Tuple *seconds_hand_t = dict_find(iter, KEY_SECONDS_HAND);
|
|
if (seconds_hand_t && seconds_hand_t->value->int32 > 0) {
|
|
hide_seconds_hand = false;
|
|
} else {
|
|
hide_seconds_hand = true;
|
|
}
|
|
|
|
persist_write_bool(KEY_SECONDS_HAND, hide_seconds_hand);
|
|
start_time_tracking();
|
|
|
|
// Handling battery_indicator setting.
|
|
Tuple *battery_indicator_t = dict_find(iter, KEY_BATTERY_INDICATOR);
|
|
if (battery_indicator_t && battery_indicator_t->value->int32 > 0) {
|
|
hide_battery_indicator = false;
|
|
} else {
|
|
hide_battery_indicator = true;
|
|
}
|
|
|
|
persist_write_bool(KEY_BATTERY_INDICATOR, hide_battery_indicator);
|
|
start_battery_tracking();
|
|
|
|
// Handling date_format setting.
|
|
Tuple *date_format_t = dict_find(iter, KEY_DATE_FORMAT);
|
|
if (date_format_t) {
|
|
strcpy(date_format, date_format_t->value->cstring);
|
|
}
|
|
|
|
persist_write_string(KEY_DATE_FORMAT, date_format);
|
|
update_date_text_layer();
|
|
|
|
// Handling wide_layout setting.
|
|
Tuple *wide_layout_t = dict_find(iter, KEY_WIDE_LAYOUT);
|
|
if (wide_layout_t && wide_layout_t->value->int32 > 0) {
|
|
use_wide_layout = true;
|
|
} else {
|
|
use_wide_layout = false;
|
|
}
|
|
|
|
persist_write_bool(KEY_WIDE_LAYOUT, use_wide_layout);
|
|
|
|
// Handling xen_style setting.
|
|
Tuple *xen_style_t = dict_find(iter, KEY_XEN_STYLE);
|
|
if (xen_style_t && xen_style_t->value->int32 > 0) {
|
|
use_xen_style = true;
|
|
} else {
|
|
use_xen_style = false;
|
|
}
|
|
|
|
persist_write_bool(KEY_XEN_STYLE, use_xen_style);
|
|
|
|
// Updating geometry according to settings.
|
|
update_geometry();
|
|
}
|
|
|
|
static void main_window_load(Window *window) {
|
|
// Loading resources.
|
|
s_battery_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMG_BATTERY);
|
|
s_text_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_TOPAZ_16));
|
|
|
|
// Creating hand paths.
|
|
s_minute_hand = gpath_create(&MINUTE_HAND_POINTS);
|
|
s_hour_hand = gpath_create(&HOUR_HAND_POINTS);
|
|
|
|
Layer *window_layer = window_get_root_layer(window);
|
|
GRect bounds = layer_get_bounds(window_layer);
|
|
|
|
// Creating background image layer.
|
|
s_background_layer = bitmap_layer_create(bounds);
|
|
bitmap_layer_set_compositing_mode(s_background_layer, GCompOpSet);
|
|
layer_add_child(window_layer, bitmap_layer_get_layer(s_background_layer));
|
|
|
|
// Creating title text layer.
|
|
s_title_layer = text_layer_create(GRect(0, 0, 71, 18));
|
|
text_layer_set_background_color(s_title_layer, GColorClear);
|
|
text_layer_set_text(s_title_layer, "Clock");
|
|
text_layer_set_font(s_title_layer, s_text_font);
|
|
layer_add_child(window_layer, text_layer_get_layer(s_title_layer));
|
|
|
|
// Creating hands layer.
|
|
s_hands_layer = layer_create(GRect(32, 38, 78, 78));
|
|
layer_set_update_proc(s_hands_layer, s_hands_layer_update);
|
|
layer_add_child(window_layer, s_hands_layer);
|
|
|
|
// Creating date layer.
|
|
s_date_layer = text_layer_create(GRect(0, 0, 136, 16));
|
|
text_layer_set_text_alignment(s_date_layer, GTextAlignmentCenter);
|
|
text_layer_set_background_color(s_date_layer, GColorClear);
|
|
text_layer_set_font(s_date_layer, s_text_font);
|
|
layer_add_child(window_layer, text_layer_get_layer(s_date_layer));
|
|
|
|
// Creating AM/PM layer.
|
|
s_ampm_layer = text_layer_create(GRect(0, 0, 16, 16));
|
|
text_layer_set_text_alignment(s_ampm_layer, GTextAlignmentCenter);
|
|
text_layer_set_background_color(s_ampm_layer, GColorClear);
|
|
text_layer_set_text_color(s_ampm_layer, GColorWhite);
|
|
text_layer_set_font(s_ampm_layer, s_text_font);
|
|
layer_add_child(window_layer, text_layer_get_layer(s_ampm_layer));
|
|
|
|
// Creating battery icon layer.
|
|
s_battery_icon_layer = bitmap_layer_create(GRect(0, 0, 16, 7));
|
|
bitmap_layer_set_bitmap(s_battery_icon_layer, s_battery_bitmap);
|
|
bitmap_layer_set_compositing_mode(s_battery_icon_layer, GCompOpSet);
|
|
layer_add_child(window_layer, bitmap_layer_get_layer(s_battery_icon_layer));
|
|
|
|
// Creating battery indicator layer.
|
|
s_battery_indicator_layer = layer_create(GRect(0, 0, 13, 5));
|
|
layer_set_update_proc(s_battery_indicator_layer, s_battery_indicator_layer_update);
|
|
layer_add_child(window_layer, s_battery_indicator_layer);
|
|
|
|
// Update geometry according to wide_layout and xen_style settings.
|
|
use_wide_layout = persist_read_bool(KEY_WIDE_LAYOUT);
|
|
use_xen_style = persist_read_bool(KEY_XEN_STYLE);
|
|
update_geometry();
|
|
|
|
// Read date_format setting from persistent storage.
|
|
int result = persist_read_string(KEY_DATE_FORMAT, date_format, 2);
|
|
if (result == E_DOES_NOT_EXIST) {
|
|
strcpy(date_format, "t");
|
|
}
|
|
|
|
// Starting time tracking.
|
|
hide_seconds_hand = persist_read_bool(KEY_SECONDS_HAND);
|
|
start_time_tracking();
|
|
|
|
// Starting battery tracking.
|
|
hide_battery_indicator = persist_read_bool(KEY_BATTERY_INDICATOR);
|
|
start_battery_tracking();
|
|
}
|
|
|
|
static void main_window_unload(Window *window) {
|
|
battery_state_service_unsubscribe();
|
|
tick_timer_service_unsubscribe();
|
|
|
|
// Apparently, freeing this pointer causes the watchface to crash on
|
|
// v3.3. Weird.
|
|
// if (t) {
|
|
// free(t);
|
|
// }
|
|
|
|
layer_destroy(s_battery_indicator_layer);
|
|
bitmap_layer_destroy(s_battery_icon_layer);
|
|
text_layer_destroy(s_ampm_layer);
|
|
text_layer_destroy(s_date_layer);
|
|
layer_destroy(s_hands_layer);
|
|
text_layer_destroy(s_title_layer);
|
|
bitmap_layer_destroy(s_background_layer);
|
|
|
|
gpath_destroy(s_hour_hand);
|
|
gpath_destroy(s_minute_hand);
|
|
|
|
fonts_unload_custom_font(s_text_font);
|
|
gbitmap_destroy(s_battery_bitmap);
|
|
gbitmap_destroy(s_background_bitmap);
|
|
}
|
|
|
|
static void init() {
|
|
s_main_window = window_create();
|
|
|
|
window_set_window_handlers(s_main_window, (WindowHandlers) {
|
|
.load = main_window_load,
|
|
.unload = main_window_unload
|
|
});
|
|
|
|
window_stack_push(s_main_window, true);
|
|
|
|
app_message_register_inbox_received(inbox_received_handler);
|
|
app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());
|
|
}
|
|
|
|
static void deinit() {
|
|
window_destroy(s_main_window);
|
|
}
|
|
|
|
int main(void) {
|
|
init();
|
|
app_event_loop();
|
|
deinit();
|
|
}
|