#include #define ITEM_COUNT 33 static Window *s_main_window; static Window *s_cidr_window; static Window *s_mask_window; static Window *s_result_window; static MenuLayer *s_main_menu_layer; static MenuLayer *s_cidr_menu_layer; static MenuLayer *s_mask_menu_layer; static TextLayer *s_cidr_text_layer; static TextLayer *s_mask_text_layer; static TextLayer *s_usable_hosts_text_layer; static TextLayer *s_bitwidth_text_layer; static char s_cidr_text[24]; static char s_mask_text[32]; static char s_usable_hosts_text[40]; #if PBL_DISPLAY_WIDTH > 180 static char s_bitwidth_text[37]; #else static char s_bitwidth_text[36]; #endif static uint8_t s_current_prefix = 24; static bool s_up_boundary_haptic_sent = false; static bool s_down_boundary_haptic_sent = false; static void trigger_soft_boundary_haptic(void) { static const uint32_t segments[] = {45}; VibePattern pattern = { .durations = segments, .num_segments = ARRAY_LENGTH(segments), }; vibes_enqueue_custom_pattern(pattern); } static uint32_t cidr_to_mask_uint8(int prefix) { if (prefix <= 0) { return 0; } if (prefix >= 32) { return 0xFFFFFFFFu; } return 0xFFFFFFFFu << (32 - prefix); } static void mask_to_dotted(uint32_t mask, char *out, size_t out_len) { uint8_t o1 = (uint8_t)((mask >> 24) & 0xFF); uint8_t o2 = (uint8_t)((mask >> 16) & 0xFF); uint8_t o3 = (uint8_t)((mask >> 8) & 0xFF); uint8_t o4 = (uint8_t)(mask & 0xFF); snprintf(out, out_len, "%u.%u.%u.%u", o1, o2, o3, o4); } static int index_to_prefix(int index) { return 32 - index; } static uint64_t prefix_to_usable_hosts(int prefix) { if (prefix == 31) { return 2; } if (prefix == 32) { return 0; } uint64_t total = 1ULL << (32 - prefix); return total - 2ULL; } static void layout_result_text_layer(void) { if (!s_cidr_text_layer || !s_mask_text_layer || !s_usable_hosts_text_layer) { return; } Layer *root = window_get_root_layer(s_result_window); GRect bounds = layer_get_bounds(root); #if PBL_RECT && PBL_DISPLAY_WIDTH > 144 const int16_t side_margin = 18; #else const int16_t side_margin = 6; #endif const int16_t width = bounds.size.w - (2 * side_margin); const int16_t gap = 2; GSize cidr_size = text_layer_get_content_size(s_cidr_text_layer); GSize mask_size = text_layer_get_content_size(s_mask_text_layer); GSize usable_size = text_layer_get_content_size(s_usable_hosts_text_layer); GSize bit_size = s_bitwidth_text_layer ? text_layer_get_content_size(s_bitwidth_text_layer) : GSize(0, 0); int16_t cidr_h = cidr_size.h > 0 ? cidr_size.h : 1; int16_t mask_h = mask_size.h > 0 ? mask_size.h : 1; int16_t usable_h = usable_size.h > 0 ? usable_size.h : 1; int16_t bit_h = bit_size.h > 0 ? bit_size.h : 1; int16_t total_h = cidr_h + gap + mask_h + gap + usable_h; if (s_bitwidth_text_layer) { total_h += gap + bit_h; } if (total_h > bounds.size.h - 10) { total_h = bounds.size.h - 10; } int16_t y = (bounds.size.h - total_h) / 2; layer_set_frame(text_layer_get_layer(s_cidr_text_layer), GRect(side_margin, y, width, cidr_h)); y += cidr_h + gap; layer_set_frame(text_layer_get_layer(s_mask_text_layer), GRect(side_margin, y, width, mask_h)); y += mask_h + gap; layer_set_frame(text_layer_get_layer(s_usable_hosts_text_layer), GRect(side_margin, y, width, usable_h)); if (s_bitwidth_text_layer) { y += usable_h + gap; layer_set_frame(text_layer_get_layer(s_bitwidth_text_layer), GRect(side_margin, y, width, bit_h)); } } static void update_result_text(void) { uint32_t mask = cidr_to_mask_uint8(s_current_prefix); char mask_text[20]; uint64_t usable_hosts = prefix_to_usable_hosts(s_current_prefix); int selected_row = 32 - s_current_prefix; mask_to_dotted(mask, mask_text, sizeof(mask_text)); snprintf(s_cidr_text, sizeof(s_cidr_text), "CIDR: /%d", s_current_prefix); #if PBL_RECT snprintf(s_mask_text, sizeof(s_mask_text), "\nMask:\n%s", mask_text); snprintf(s_usable_hosts_text, sizeof(s_usable_hosts_text), "\nUsable Hosts:\n%llu", usable_hosts); #else snprintf(s_mask_text, sizeof(s_mask_text), "Mask: %s", mask_text); #if PBL_PLATFORM_CHALK snprintf(s_usable_hosts_text, sizeof(s_usable_hosts_text), "Usable Hosts:\n%llu", usable_hosts); #else snprintf(s_usable_hosts_text, sizeof(s_usable_hosts_text), "Usable Hosts: %llu", usable_hosts); #endif #endif #if PBL_DISPLAY_WIDTH > 180 s_bitwidth_text[0] = '\n'; int pos = 1; #else int pos = 0; #endif for (int i = 0; i < 32; i++) { s_bitwidth_text[pos++] = (i < s_current_prefix) ? '1' : '0'; if (i == 7 || i == 15 || i == 23) { s_bitwidth_text[pos++] = '.'; } } s_bitwidth_text[pos] = '\0'; if (s_cidr_text_layer && s_mask_text_layer && s_usable_hosts_text_layer) { text_layer_set_text(s_cidr_text_layer, s_cidr_text); text_layer_set_text(s_mask_text_layer, s_mask_text); text_layer_set_text(s_usable_hosts_text_layer, s_usable_hosts_text); if (s_bitwidth_text_layer) { text_layer_set_text(s_bitwidth_text_layer, s_bitwidth_text); } layout_result_text_layer(); } if (s_cidr_menu_layer) { menu_layer_set_selected_index(s_cidr_menu_layer, (MenuIndex){.section = 0, .row = (uint16_t)selected_row}, MenuRowAlignCenter, false); } if (s_mask_menu_layer) { menu_layer_set_selected_index(s_mask_menu_layer, (MenuIndex){.section = 0, .row = (uint16_t)selected_row}, MenuRowAlignCenter, false); } } static void show_result_for_prefix(int prefix) { if (prefix < 0) { prefix = 0; } if (prefix > 32) { prefix = 32; } s_current_prefix = prefix; s_up_boundary_haptic_sent = false; s_down_boundary_haptic_sent = false; // Push first so the result window/text layer is guaranteed to be loaded. window_stack_push(s_result_window, true); update_result_text(); } static void result_up_click_handler(ClickRecognizerRef recognizer, void *context) { (void)recognizer; (void)context; if (s_current_prefix < 32) { s_current_prefix++; update_result_text(); s_down_boundary_haptic_sent = false; if (s_current_prefix == 32 && !s_up_boundary_haptic_sent) { trigger_soft_boundary_haptic(); s_up_boundary_haptic_sent = true; } else if (s_current_prefix < 32) { s_up_boundary_haptic_sent = false; } } else if (!s_up_boundary_haptic_sent) { trigger_soft_boundary_haptic(); s_up_boundary_haptic_sent = true; } } static void result_down_click_handler(ClickRecognizerRef recognizer, void *context) { (void)recognizer; (void)context; if (s_current_prefix > 0) { s_current_prefix--; update_result_text(); s_up_boundary_haptic_sent = false; if (s_current_prefix == 0 && !s_down_boundary_haptic_sent) { trigger_soft_boundary_haptic(); s_down_boundary_haptic_sent = true; } else if (s_current_prefix > 0) { s_down_boundary_haptic_sent = false; } } else if (!s_down_boundary_haptic_sent) { trigger_soft_boundary_haptic(); s_down_boundary_haptic_sent = true; } } static void result_click_config_provider(void *context) { (void)context; window_single_repeating_click_subscribe(BUTTON_ID_UP, 120, result_up_click_handler); window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 120, result_down_click_handler); } static uint16_t main_get_num_rows(MenuLayer *menu_layer, uint16_t section_index, void *context) { (void)menu_layer; (void)section_index; (void)context; return 2; } static void main_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { (void)context; switch (cell_index->row) { case 0: menu_cell_basic_draw(ctx, cell_layer, "CIDR", NULL, NULL); break; case 1: menu_cell_basic_draw(ctx, cell_layer, "Mask", NULL, NULL); break; } } static void main_select(MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { (void)menu_layer; (void)context; if (cell_index->row == 0) { window_stack_push(s_cidr_window, true); } else { window_stack_push(s_mask_window, true); } } static uint16_t value_get_num_rows(MenuLayer *menu_layer, uint16_t section_index, void *context) { (void)menu_layer; (void)section_index; (void)context; return ITEM_COUNT; } static void cidr_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { (void)context; int prefix = index_to_prefix(cell_index->row); static char cidr_label[8]; snprintf(cidr_label, sizeof(cidr_label), "/%d", prefix); menu_cell_basic_draw(ctx, cell_layer, cidr_label, NULL, NULL); } static void mask_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { (void)context; int prefix = index_to_prefix(cell_index->row); uint32_t mask = cidr_to_mask_uint8(prefix); static char mask_label[20]; mask_to_dotted(mask, mask_label, sizeof(mask_label)); menu_cell_basic_draw(ctx, cell_layer, mask_label, NULL, NULL); } static void cidr_select(MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { (void)menu_layer; (void)context; int prefix = index_to_prefix(cell_index->row); show_result_for_prefix(prefix); } static void mask_select(MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { (void)menu_layer; (void)context; int prefix = index_to_prefix(cell_index->row); show_result_for_prefix(prefix); } static void main_window_load(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); s_main_menu_layer = menu_layer_create(bounds); menu_layer_set_callbacks(s_main_menu_layer, NULL, (MenuLayerCallbacks){ .get_num_rows = main_get_num_rows, .draw_row = main_draw_row, .select_click = main_select, }); menu_layer_set_click_config_onto_window(s_main_menu_layer, window); layer_add_child(window_layer, menu_layer_get_layer(s_main_menu_layer)); } static void main_window_unload(Window *window) { (void)window; menu_layer_destroy(s_main_menu_layer); s_main_menu_layer = NULL; } static void cidr_window_load(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); s_cidr_menu_layer = menu_layer_create(bounds); menu_layer_set_callbacks(s_cidr_menu_layer, NULL, (MenuLayerCallbacks){ .get_num_rows = value_get_num_rows, .draw_row = cidr_draw_row, .select_click = cidr_select, }); menu_layer_set_click_config_onto_window(s_cidr_menu_layer, window); menu_layer_set_selected_index(s_cidr_menu_layer, (MenuIndex){.section = 0, .row = (uint16_t)(32 - 24)}, MenuRowAlignCenter, false); layer_add_child(window_layer, menu_layer_get_layer(s_cidr_menu_layer)); } static void cidr_window_unload(Window *window) { (void)window; menu_layer_destroy(s_cidr_menu_layer); s_cidr_menu_layer = NULL; } static void mask_window_load(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); s_mask_menu_layer = menu_layer_create(bounds); menu_layer_set_callbacks(s_mask_menu_layer, NULL, (MenuLayerCallbacks){ .get_num_rows = value_get_num_rows, .draw_row = mask_draw_row, .select_click = mask_select, }); menu_layer_set_click_config_onto_window(s_mask_menu_layer, window); menu_layer_set_selected_index(s_mask_menu_layer, (MenuIndex){.section = 0, .row = (uint16_t)(32 - 24)}, MenuRowAlignCenter, false); layer_add_child(window_layer, menu_layer_get_layer(s_mask_menu_layer)); } static void mask_window_unload(Window *window) { (void)window; menu_layer_destroy(s_mask_menu_layer); s_mask_menu_layer = NULL; } static void result_window_load(Window *window) { s_cidr_text_layer = text_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, 24)); text_layer_set_background_color(s_cidr_text_layer, GColorClear); text_layer_set_text_color(s_cidr_text_layer, GColorWhite); text_layer_set_font(s_cidr_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); text_layer_set_overflow_mode(s_cidr_text_layer, GTextOverflowModeTrailingEllipsis); text_layer_set_text(s_cidr_text_layer, s_cidr_text); s_mask_text_layer = text_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, 56)); text_layer_set_background_color(s_mask_text_layer, GColorClear); text_layer_set_text_color(s_mask_text_layer, GColorWhite); text_layer_set_font(s_mask_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); text_layer_set_overflow_mode(s_mask_text_layer, GTextOverflowModeTrailingEllipsis); text_layer_set_text(s_mask_text_layer, s_mask_text); s_usable_hosts_text_layer = text_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, 56)); text_layer_set_background_color(s_usable_hosts_text_layer, GColorClear); text_layer_set_text_color(s_usable_hosts_text_layer, GColorWhite); text_layer_set_font(s_usable_hosts_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); text_layer_set_overflow_mode(s_usable_hosts_text_layer, GTextOverflowModeTrailingEllipsis); text_layer_set_text(s_usable_hosts_text_layer, s_usable_hosts_text); Layer *window_layer = window_get_root_layer(window); #if PBL_DISPLAY_WIDTH > 144 #if PBL_PLATFORM_CHALK s_bitwidth_text_layer = text_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, 14)); #else s_bitwidth_text_layer = text_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, 56)); #endif text_layer_set_background_color(s_bitwidth_text_layer, GColorClear); text_layer_set_text_color(s_bitwidth_text_layer, GColorLightGray); text_layer_set_font(s_bitwidth_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_09)); text_layer_set_text(s_bitwidth_text_layer, s_bitwidth_text); #endif #if PBL_ROUND text_layer_set_text_alignment(s_cidr_text_layer, GTextAlignmentCenter); text_layer_set_text_alignment(s_mask_text_layer, GTextAlignmentCenter); text_layer_set_text_alignment(s_usable_hosts_text_layer, GTextAlignmentCenter); text_layer_set_text_alignment(s_bitwidth_text_layer, GTextAlignmentCenter); #endif layer_add_child(window_layer, text_layer_get_layer(s_cidr_text_layer)); layer_add_child(window_layer, text_layer_get_layer(s_mask_text_layer)); layer_add_child(window_layer, text_layer_get_layer(s_usable_hosts_text_layer)); if (s_bitwidth_text_layer) { layer_add_child(window_layer, text_layer_get_layer(s_bitwidth_text_layer)); } update_result_text(); } static void result_window_unload(Window *window) { (void)window; text_layer_destroy(s_cidr_text_layer); text_layer_destroy(s_mask_text_layer); text_layer_destroy(s_usable_hosts_text_layer); if (s_bitwidth_text_layer) { text_layer_destroy(s_bitwidth_text_layer); } s_cidr_text_layer = NULL; s_mask_text_layer = NULL; s_usable_hosts_text_layer = NULL; s_bitwidth_text_layer = NULL; } static void init(void) { app_touch_navigation_enable(true); s_main_window = window_create(); window_set_window_handlers(s_main_window, (WindowHandlers){ .load = main_window_load, .unload = main_window_unload, }); window_set_background_color(s_main_window, GColorBlack); s_cidr_window = window_create(); window_set_window_handlers(s_cidr_window, (WindowHandlers){ .load = cidr_window_load, .unload = cidr_window_unload, }); window_set_background_color(s_cidr_window, GColorBlack); s_mask_window = window_create(); window_set_window_handlers(s_mask_window, (WindowHandlers){ .load = mask_window_load, .unload = mask_window_unload, }); window_set_background_color(s_mask_window, GColorBlack); s_result_window = window_create(); window_set_window_handlers(s_result_window, (WindowHandlers){ .load = result_window_load, .unload = result_window_unload, }); window_set_background_color(s_result_window, GColorBlack); window_set_click_config_provider(s_result_window, result_click_config_provider); window_stack_push(s_main_window, true); } static void deinit(void) { window_destroy(s_result_window); window_destroy(s_mask_window); window_destroy(s_cidr_window); window_destroy(s_main_window); } int main(void) { init(); app_event_loop(); deinit(); }