/* * Copyright (C) 2025 Reiner Herrmann * SPDX-License-Identifier: GPL-3.0-or-later */ #include static Window *main_window; static TextLayer *clock_text; static TextLayer *date_text; static TextLayer *header_text; static TextLayer *mem_text; static TextLayer *content_text; static TextLayer *batt_text; static GFont font; static GFont font_small; static Layer *background_layer; #ifdef PBL_HEALTH static TextLayer *steps_text; static bool health_available = false; #endif #if PBL_DISPLAY_HEIGHT == 228 #define FONT_SIZE_LARGE 58 #define FONT_SIZE_SMALL 16 #define BORDER_PERCENT 5 #define HEADER "*** COMMODORE 64 ***" #define DATE_FMT "%a %d. %b %Y" #define STEPS_FMT "Steps:%d" #elif PBL_DISPLAY_HEIGHT == 168 #define FONT_SIZE_LARGE 48 #define FONT_SIZE_SMALL 15 #define BORDER_PERCENT 3 #define HEADER "* COMMODORE 64 *" #define DATE_FMT "%a %d.%b %y" #define STEPS_FMT "S:%d" #endif #define FONT2(SIZE) (RESOURCE_ID_FONT_COMMODORE_ ## SIZE) #define FONT(SIZE) FONT2(SIZE) #define FONT_SMALL FONT(FONT_SIZE_SMALL) #define FONT_LARGE FONT(FONT_SIZE_LARGE) #define PALETTE_COLODORE #if defined(PALETTE_COLODORE) // https://www.colodore.com/ #define GColor_Red GColorFromHEX(0x813338) #define GColor_Blue GColorFromHEX(0x2e2c9b) #define GColor_Orange GColorFromHEX(0x8e5029) #define GColor_Grey1 GColorFromHEX(0x4a4a4a) #define GColor_Grey2 GColorFromHEX(0x7b7b7b) #define GColor_LightBlue GColorFromHEX(0x6f6deb) #define GColor_LightGrey GColorFromHEX(0xb2b2b2) #elif defined(PALETTE_PEPTO) // /usr/share/vice/C64/pepto-pal.vpl #define GColor_Red GColorFromHEX(0x68372b) #define GColor_Blue GColorFromHEX(0x352879) #define GColor_Orange GColorFromHEX(0x6f4f25) #define GColor_Grey1 GColorFromHEX(0x444444) #define GColor_Grey2 GColorFromHEX(0x6c6c6c) #define GColor_LightBlue GColorFromHEX(0x6c5eb5) #define GColor_LightGrey GColorFromHEX(0x959595) #elif defined(PALETTE_WIKIPEDIA) // https://en.wikipedia.org/wiki/Commodore_64#Graphics #define GColor_Red GColorFromHEX(0x9F4E44) #define GColor_Blue GColorFromHEX(0x50459B) #define GColor_Orange GColorFromHEX(0xA1683C) #define GColor_Grey1 GColorFromHEX(0x626262) #define GColor_Grey2 GColorFromHEX(0x898989) #define GColor_LightBlue GColorFromHEX(0x887ECB) #define GColor_LightGrey GColorFromHEX(0xADADAD) #endif #if defined(PBL_COLOR) #define GColor_Border GColor_LightBlue #define GColor_Background GColor_Blue #define GColor_Text GColor_LightBlue #define GColor_Clock GColor_LightGrey #define GColor_Bat_Med GColor_Orange #define GColor_Bat_Low GColor_Red #elif defined(PBL_BW) #define GColor_Border GColorWhite #define GColor_Background GColorBlack #define GColor_Text GColorWhite #define GColor_Clock GColorWhite #define GColor_Bat_Med GColorWhite #define GColor_Bat_Low GColorWhite #endif static void background_update_proc(Layer *layer, GContext *ctx) { GRect bounds = layer_get_bounds(layer); graphics_context_set_fill_color(ctx, GColor_Background); graphics_fill_rect(ctx, bounds, 0, GCornerNone); } static void main_window_load(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_unobstructed_bounds(window_layer); font = fonts_load_custom_font(resource_get_handle(FONT_LARGE)); font_small = fonts_load_custom_font(resource_get_handle(FONT_SMALL)); /* inner background */ int16_t border_width = bounds.size.w * BORDER_PERCENT / 100; GRect bg_bounds = GRect(border_width, border_width, bounds.size.w - 2*border_width, bounds.size.h - 2*border_width); background_layer = layer_create(bg_bounds); layer_set_update_proc(background_layer, background_update_proc); layer_add_child(window_layer, background_layer); /* header text */ header_text = text_layer_create(GRect(bg_bounds.origin.x, bg_bounds.origin.y, bg_bounds.size.w, FONT_SIZE_SMALL+2)); text_layer_set_background_color(header_text, GColorClear); text_layer_set_text_color(header_text, GColor_Text); text_layer_set_text_alignment(header_text, GTextAlignmentCenter); text_layer_set_font(header_text, font_small); text_layer_set_text(header_text, HEADER); layer_add_child(window_layer, text_layer_get_layer(header_text)); /* free memory text */ mem_text = text_layer_create(GRect(bg_bounds.origin.x, bg_bounds.origin.y + FONT_SIZE_SMALL, bg_bounds.size.w, FONT_SIZE_SMALL+2)); text_layer_set_background_color(mem_text, GColorClear); text_layer_set_text_color(mem_text, GColor_Text); text_layer_set_text_alignment(mem_text, GTextAlignmentCenter); text_layer_set_font(mem_text, font_small); static char mem_buffer[20]; snprintf(mem_buffer, sizeof(mem_buffer), "%d bytes free", heap_bytes_free()); text_layer_set_text(mem_text, mem_buffer); layer_add_child(window_layer, text_layer_get_layer(mem_text)); /* content text */ content_text = text_layer_create(GRect(bg_bounds.origin.x, bg_bounds.origin.y + 3*FONT_SIZE_SMALL, bg_bounds.size.w, FONT_SIZE_SMALL+2)); text_layer_set_background_color(content_text, GColorClear); text_layer_set_text_color(content_text, GColor_Text); text_layer_set_text_alignment(content_text, GTextAlignmentLeft); text_layer_set_font(content_text, font_small); text_layer_set_text(content_text, "READY."); layer_add_child(window_layer, text_layer_get_layer(content_text)); /* clock text */ clock_text = text_layer_create(GRect(bg_bounds.origin.x, bounds.size.h/2 - FONT_SIZE_LARGE/2, bg_bounds.size.w, FONT_SIZE_LARGE+2)); text_layer_set_background_color(clock_text, GColorClear); text_layer_set_text_color(clock_text, GColor_Clock); text_layer_set_text_alignment(clock_text, GTextAlignmentCenter); text_layer_set_font(clock_text, font); layer_add_child(window_layer, text_layer_get_layer(clock_text)); /* date text */ date_text = text_layer_create(GRect(bg_bounds.origin.x, bounds.size.h/2 + FONT_SIZE_LARGE/2 + FONT_SIZE_SMALL, bg_bounds.size.w, FONT_SIZE_SMALL+2)); text_layer_set_background_color(date_text, GColorClear); text_layer_set_text_color(date_text, GColor_Text); text_layer_set_text_alignment(date_text, GTextAlignmentCenter); text_layer_set_font(date_text, font_small); layer_add_child(window_layer, text_layer_get_layer(date_text)); #ifdef PBL_HEALTH /* step counter text */ steps_text = text_layer_create(GRect(bg_bounds.origin.x, bg_bounds.origin.y + bg_bounds.size.h - FONT_SIZE_SMALL - 2, bg_bounds.size.w, FONT_SIZE_SMALL+2)); text_layer_set_background_color(steps_text, GColorClear); text_layer_set_text_color(steps_text, GColor_Text); text_layer_set_text_alignment(steps_text, GTextAlignmentLeft); text_layer_set_font(steps_text, font_small); layer_add_child(window_layer, text_layer_get_layer(steps_text)); #endif /* battery text */ batt_text = text_layer_create(GRect(bg_bounds.origin.x, bg_bounds.origin.y + bg_bounds.size.h - FONT_SIZE_SMALL - 2, bg_bounds.size.w, FONT_SIZE_SMALL+2)); text_layer_set_background_color(batt_text, GColorClear); text_layer_set_text_color(batt_text, GColor_Text); text_layer_set_text_alignment(batt_text, GTextAlignmentRight); text_layer_set_font(batt_text, font_small); layer_add_child(window_layer, text_layer_get_layer(batt_text)); } static void main_window_unload(Window *window) { text_layer_destroy(content_text); text_layer_destroy(header_text); text_layer_destroy(mem_text); text_layer_destroy(clock_text); text_layer_destroy(date_text); text_layer_destroy(batt_text); #ifdef PBL_HEALTH text_layer_destroy(steps_text); #endif layer_destroy(background_layer); fonts_unload_custom_font(font); fonts_unload_custom_font(font_small); } static void update_time() { time_t temp = time(NULL); struct tm *tick_time = localtime(&temp); static char buffer[8]; if (clock_is_24h_style()) { strftime(buffer, sizeof(buffer), "%H:%M", tick_time); } else { strftime(buffer, sizeof(buffer), "%I:%M", tick_time); } text_layer_set_text(clock_text, buffer); } static void update_date() { time_t temp = time(NULL); struct tm *tick_time = localtime(&temp); static char buffer[20]; strftime(buffer, sizeof(buffer), DATE_FMT, tick_time); text_layer_set_text(date_text, buffer); } static void tick_handler(struct tm *tick_time, TimeUnits units_changed) { if (units_changed & MINUTE_UNIT) { update_time(); } if (units_changed & DAY_UNIT) { update_date(); } } static void battery_handler(BatteryChargeState charge) { static char buffer[12]; const char *charge_indicator = charge.is_charging ? "^" : ""; snprintf(buffer, sizeof(buffer), "%sBat:%d%%", charge_indicator, charge.charge_percent); if (charge.charge_percent <= 20) { text_layer_set_text_color(batt_text, GColor_Bat_Med); } else if (charge.charge_percent <= 10) { text_layer_set_text_color(batt_text, GColor_Bat_Low); } else { text_layer_set_text_color(batt_text, GColor_Text); } text_layer_set_text(batt_text, buffer); } #ifdef PBL_HEALTH static void health_handler(HealthEventType event, void *context) { static char buffer[24]; HealthValue steps; switch (event) { case HealthEventSignificantUpdate: case HealthEventMovementUpdate: steps = health_service_sum_today(HealthMetricStepCount); snprintf(buffer, sizeof(buffer), STEPS_FMT, (int) steps); text_layer_set_text(steps_text, buffer); break; default: break; } } #endif static void init() { main_window = window_create(); window_set_window_handlers(main_window, (WindowHandlers) { .load = main_window_load, .unload = main_window_unload, }); window_set_background_color(main_window, GColor_Border); window_stack_push(main_window, true); tick_timer_service_subscribe(MINUTE_UNIT | DAY_UNIT, tick_handler); battery_state_service_subscribe(battery_handler); #ifdef PBL_HEALTH health_available = health_service_events_subscribe(health_handler, NULL); #endif /* initialize dynamic texts */ update_time(); update_date(); battery_handler(battery_state_service_peek()); } static void deinit() { #ifdef PBL_HEALTH health_service_events_unsubscribe(); #endif battery_state_service_unsubscribe(); tick_timer_service_unsubscribe(); window_destroy(main_window); } int main() { init(); app_event_loop(); deinit(); }