From 0aac353f636126a809a4b3c521d64e576682019c Mon Sep 17 00:00:00 2001 From: Reiner Herrmann Date: Sat, 10 Jan 2026 18:45:29 +0100 Subject: initial commit --- src/c/watchface.c | 289 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 src/c/watchface.c (limited to 'src/c/watchface.c') diff --git a/src/c/watchface.c b/src/c/watchface.c new file mode 100644 index 0000000..0c12d70 --- /dev/null +++ b/src/c/watchface.c @@ -0,0 +1,289 @@ +/* + * 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(); +} -- cgit v1.2.3