From 48f336a2eb91f5bd7ff396c0e55be3b815f126d7 Mon Sep 17 00:00:00 2001 From: synzr Date: Sat, 18 Oct 2025 21:23:17 +0500 Subject: [PATCH] feat(layers): information layer --- package.json | 27 ++++++++ resources/icon_battery_empty.png | Bin 0 -> 130 bytes resources/icon_battery_full.png | Bin 0 -> 143 bytes resources/icon_battery_half.png | Bin 0 -> 143 bytes resources/icon_paws.png | Bin 0 -> 213 bytes src/c/clock_layer.c | 4 ++ src/c/clock_layer.h | 3 +- src/c/information_layer.c | 115 +++++++++++++++++++++++++++++++ src/c/information_layer.h | 10 +++ src/c/main_window.c | 9 ++- src/c/resources_service.c | 32 ++++++++- src/c/resources_service.h | 8 +++ 12 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 resources/icon_battery_empty.png create mode 100644 resources/icon_battery_full.png create mode 100644 resources/icon_battery_half.png create mode 100644 resources/icon_paws.png create mode 100644 src/c/information_layer.c create mode 100644 src/c/information_layer.h diff --git a/package.json b/package.json index 5573c89..beea0fa 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,9 @@ "messageKeys": [ "dummy" ], + "capabilities": [ + "health" + ], "resources": { "media": [{ "type": "bitmap", @@ -36,6 +39,30 @@ "file": "character_odd.png", "memoryFormat": "Smallest", "spaceOptimization": "memory" + }, { + "type": "bitmap", + "name": "ICON_BATTERY_EMPTY", + "file": "icon_battery_empty.png", + "memoryFormat": "Smallest", + "spaceOptimization": "memory" + }, { + "type": "bitmap", + "name": "ICON_BATTERY_HALF", + "file": "icon_battery_half.png", + "memoryFormat": "Smallest", + "spaceOptimization": "memory" + }, { + "type": "bitmap", + "name": "ICON_BATTERY_FULL", + "file": "icon_battery_full.png", + "memoryFormat": "Smallest", + "spaceOptimization": "memory" + }, { + "type": "bitmap", + "name": "ICON_PAWS", + "file": "icon_paws.png", + "memoryFormat": "Smallest", + "spaceOptimization": "memory" }, { "type": "font", "name": "FONT_TORO_36", diff --git a/resources/icon_battery_empty.png b/resources/icon_battery_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..4ee026acba85db5b7dc8ab83976069f4480c8096 GIT binary patch literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^LO{&H!3HG1G_PL;q&N#aB8wRq^pruEv0|xx8Box~ z)5S4FBRDxFA>jxAkta-UGu+gQd>yW{8woyB+VRpwqVFO1s+}1hBwJ+MTv&M9gkBw& ZVi20c!t3*Ek_1pcgQu&X%Q~loCIHT&Bi#T1 literal 0 HcmV?d00001 diff --git a/resources/icon_battery_full.png b/resources/icon_battery_full.png new file mode 100644 index 0000000000000000000000000000000000000000..05b318088bed42bf920d528a84ab5b09ecf20812 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^LO{&H!3HG1G_PL;q&N#aB8wRq^pruEv0|xx8BoyC z)5S4FBRDxFA>jxAkta-UGu+gQd>yW{8woyB+VRpwqVFNMn2GO^3gOkQA)lpob$fi2 n>q?(+xT9mi!2qYf5zGuFk648|#odg6hB0`$`njxgN@xNAD!wZK literal 0 HcmV?d00001 diff --git a/resources/icon_battery_half.png b/resources/icon_battery_half.png new file mode 100644 index 0000000000000000000000000000000000000000..841aca0ae65d6db5b16a7bb2a4b9e28add55128b GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^LO{&H!3HG1G_PL;q&N#aB8wRq^pruEv0|xx8BoyC z)5S4FBRDxFA>jxAkta-UGu+gQd>yW{8woyB+VRpwqVFNMn2GO=J)TooLq1FG>h9Pi nJNx>?!yO$94hA^=jbLUd`NS%8&&};F&@cv1S3j3^P6-Tg@vT@J2nz2dV{k3+$se6^PeD)RQ9dd6KHTgTwa8s3u za>cVC@!ryTzmoR6-g=hh(1yabYD#A_b}bdyx#xcJ%~}bj<;T9|%rLs>GQ}*qbAv+k zz7n%HPFYOfC;W-mdK II;Vst0O?9keE void clock_layer_init(Layer *layer, int y); +GRect clock_layer_get_bounds(); void clock_layer_tick(void); void clock_layer_deinit(void); -#endif +#endif // CLOCK_LAYER_H_ diff --git a/src/c/information_layer.c b/src/c/information_layer.c new file mode 100644 index 0000000..ec0b09e --- /dev/null +++ b/src/c/information_layer.c @@ -0,0 +1,115 @@ +#include "information_layer.h" + +#include "resources_service.h" + +#define INFO_LAYER_ICON_WIDTH 18 +#define INFO_LAYER_PADDING 4 +#define INFO_LAYER_HEIGHT 24 + +typedef struct InformationLayerData { + int level; + int steps; +} InformationLayerData; + +static Layer *s_information_layer; +static char s_information_layer_level_text[3]; +static char s_information_layer_count_text[6]; + +static void information_layer_update_proc(Layer *layer, GContext *ctx) { + GRect rect = layer_get_unobstructed_bounds(layer); + GFont font = resources_service_get_custom_font(CustomFontKonekoToro); + InformationLayerData *data = layer_get_data(s_information_layer); + + int content_w = INFO_LAYER_ICON_WIDTH + INFO_LAYER_PADDING; + + snprintf(s_information_layer_level_text, sizeof(s_information_layer_level_text), "%d", + data->level); + GSize text_size = graphics_text_layout_get_content_size( + s_information_layer_level_text, font, rect, GTextOverflowModeFill, GTextAlignmentLeft); + content_w += text_size.w; + +#ifdef PBL_HEALTH + snprintf(s_information_layer_count_text, sizeof(s_information_layer_count_text), "%d", + data->steps); + text_size = graphics_text_layout_get_content_size(s_information_layer_count_text, font, rect, + GTextOverflowModeFill, GTextAlignmentLeft); + content_w += INFO_LAYER_ICON_WIDTH + INFO_LAYER_PADDING + text_size.w; +#endif + + int content_x = (rect.size.w - content_w) / 2; + + /* Draw the battery icon based on level percentage. */ + graphics_context_set_compositing_mode(ctx, GCompOpSet); + + GBitmap *battery_icon; + if (data->level < 20) { + battery_icon = resources_service_get_custom_icon(CustomIconBatteryEmpty); + } else if (data->level < 80) { + battery_icon = resources_service_get_custom_icon(CustomIconBatteryHalf); + } else { + battery_icon = resources_service_get_custom_icon(CustomIconBatteryFull); + } + + GRect battery_rect_inside = GRect(0, 0, INFO_LAYER_ICON_WIDTH, rect.size.h); + GRect battery_rect = gbitmap_get_bounds(battery_icon); + grect_align(&battery_rect, &battery_rect_inside, GAlignCenter, true); + battery_rect.origin.x += content_x; + graphics_draw_bitmap_in_rect(ctx, battery_icon, battery_rect); + + /* Draw the battery level in text. */ + GRect text_rect = + GRect(content_x + INFO_LAYER_ICON_WIDTH + INFO_LAYER_PADDING, -4, text_size.w, text_size.h); + + graphics_context_set_text_color(ctx, GColorBlack); + graphics_draw_text(ctx, s_information_layer_level_text, font, text_rect, GTextOverflowModeFill, + GTextAlignmentLeft, NULL); + +#ifdef PBL_HEALTH + /* Draw the paws icon. */ + GBitmap *paws_icon = resources_service_get_custom_icon(CustomIconPaws); + GRect paws_rect = gbitmap_get_bounds(paws_icon); + paws_rect.origin.x = content_x + text_rect.size.w; + graphics_draw_bitmap_in_rect(ctx, paws_icon, paws_rect); + + /* Draw the step count in text. */ + text_rect = GRect(paws_rect.origin.x + INFO_LAYER_ICON_WIDTH + INFO_LAYER_PADDING, -4, + text_size.w, text_size.h); + graphics_context_set_text_color(ctx, GColorBlack); + graphics_draw_text(ctx, s_information_layer_count_text, font, text_rect, GTextOverflowModeFill, + GTextAlignmentLeft, NULL); +#endif // PBL_HEALTH +} + +static void battery_state_service_callback(BatteryChargeState charge) { + InformationLayerData *data = layer_get_data(s_information_layer); + data->level = charge.charge_percent; + layer_mark_dirty(s_information_layer); +} + +void information_layer_init(Layer *layer, int y) { + GRect rect = layer_get_unobstructed_bounds(layer); + + s_information_layer = layer_create_with_data(GRect(0, y, rect.size.w, INFO_LAYER_HEIGHT), + sizeof(InformationLayerData)); + + InformationLayerData *data = layer_get_data(s_information_layer); + data->level = battery_state_service_peek().charge_percent; + data->steps = PBL_IF_HEALTH_ELSE(health_service_sum_today(HealthMetricStepCount), 0); + + layer_set_update_proc(s_information_layer, information_layer_update_proc); + layer_add_child(layer, s_information_layer); + + battery_state_service_subscribe(battery_state_service_callback); +} + +void information_layer_tick(void) { +#ifdef PBL_HEALTH + InformationLayerData *data = layer_get_data(s_information_layer); + data->steps = health_service_sum_today(HealthMetricStepCount); + layer_mark_dirty(s_information_layer); +#endif // PBL_HEALTH +} + +void information_layer_deinit(void) { + layer_destroy(s_information_layer); +} diff --git a/src/c/information_layer.h b/src/c/information_layer.h new file mode 100644 index 0000000..75216ca --- /dev/null +++ b/src/c/information_layer.h @@ -0,0 +1,10 @@ +#ifndef INFORMATION_LAYER_H_ +#define INFORMATION_LAYER_H_ + +#include + +void information_layer_init(Layer *layer, int y); +void information_layer_tick(void); +void information_layer_deinit(void); + +#endif // INFORMATION_LAYER_H_ diff --git a/src/c/main_window.c b/src/c/main_window.c index fd9089d..f7acd41 100644 --- a/src/c/main_window.c +++ b/src/c/main_window.c @@ -2,12 +2,14 @@ #include "date_layer.h" #include "character_layer.h" #include "clock_layer.h" +#include "information_layer.h" static Window *s_main_window; static void main_window_tick(struct tm *time, TimeUnits units) { date_layer_tick(); clock_layer_tick(); + information_layer_tick(); character_layer_tick(); } @@ -15,7 +17,11 @@ static void main_window_load(Window *window) { Layer *layer = window_get_root_layer(window); date_layer_init(layer, 0); - clock_layer_init(layer, 40); + clock_layer_init(layer, 30); + + GRect clock_layer_rect = clock_layer_get_bounds(); + information_layer_init(layer, 30 + clock_layer_rect.size.h + 4); + character_layer_init(layer); tick_timer_service_subscribe(SECOND_UNIT, main_window_tick); @@ -26,6 +32,7 @@ static void main_window_unload(Window *window) { clock_layer_deinit(); date_layer_deinit(); + information_layer_deinit(); character_layer_deinit(); } diff --git a/src/c/resources_service.c b/src/c/resources_service.c index 4c851af..744ca37 100644 --- a/src/c/resources_service.c +++ b/src/c/resources_service.c @@ -8,25 +8,41 @@ typedef struct ResourcesService { GFont font_toro; GBitmap *character_even; GBitmap *character_odd; + GBitmap *icon_battery_empty; + GBitmap *icon_battery_half; + GBitmap *icon_battery_full; + GBitmap *icon_paws; } ResourcesService; static ResourcesService *s_resources_service; void resources_service_init(void) { s_resources_service = malloc(sizeof(ResourcesService)); + s_resources_service->font_koneko_toro = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_KONEKO_TORO)); s_resources_service->font_toro = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_TORO)); + s_resources_service->character_even = gbitmap_create_with_resource(RESOURCE_ID_CHARACTER_EVEN); s_resources_service->character_odd = gbitmap_create_with_resource(RESOURCE_ID_CHARACTER_ODD); + + s_resources_service->icon_battery_empty = + gbitmap_create_with_resource(RESOURCE_ID_ICON_BATTERY_EMPTY); + s_resources_service->icon_battery_full = + gbitmap_create_with_resource(RESOURCE_ID_ICON_BATTERY_FULL); + s_resources_service->icon_battery_half = + gbitmap_create_with_resource(RESOURCE_ID_ICON_BATTERY_HALF); + s_resources_service->icon_paws = gbitmap_create_with_resource(RESOURCE_ID_ICON_PAWS); } void resources_service_deinit(void) { fonts_unload_custom_font(s_resources_service->font_koneko_toro); fonts_unload_custom_font(s_resources_service->font_toro); + gbitmap_destroy(s_resources_service->character_even); gbitmap_destroy(s_resources_service->character_odd); + free(s_resources_service); } @@ -37,6 +53,20 @@ GBitmap *resources_service_get_character(int ticks) { return s_resources_service->character_odd; } +GBitmap *resources_service_get_custom_icon(CustomIcon icon) { + switch (icon) { + case CustomIconBatteryEmpty: + return s_resources_service->icon_battery_empty; + case CustomIconBatteryFull: + return s_resources_service->icon_battery_full; + case CustomIconBatteryHalf: + return s_resources_service->icon_battery_half; + case CustomIconPaws: + return s_resources_service->icon_paws; + } + return NULL; +} + GFont resources_service_get_custom_font(CustomFont font) { switch (font) { case CustomFontKonekoToro: @@ -44,5 +74,5 @@ GFont resources_service_get_custom_font(CustomFont font) { case CustomFontToro: return s_resources_service->font_toro; } - return NULL; + return fonts_get_system_font(FONT_KEY_FONT_FALLBACK); } diff --git a/src/c/resources_service.h b/src/c/resources_service.h index b5217e9..d5c0f91 100644 --- a/src/c/resources_service.h +++ b/src/c/resources_service.h @@ -8,10 +8,18 @@ typedef enum CustomFont { CustomFontToro } CustomFont; +typedef enum CustomIcon { + CustomIconBatteryEmpty, + CustomIconBatteryFull, + CustomIconBatteryHalf, + CustomIconPaws +} CustomIcon; + void resources_service_init(void); void resources_service_deinit(void); GBitmap *resources_service_get_character(int ticks); +GBitmap *resources_service_get_custom_icon(CustomIcon icon); GFont resources_service_get_custom_font(CustomFont font); #endif