aboutsummaryrefslogtreecommitdiff
path: root/src/c/watchface.c
blob: 0c12d70a9ccda2b346756e908bfd756a858357ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/*
 * Copyright (C) 2025 Reiner Herrmann
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include <pebble.h>

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();
}