void destroy_chrono_objects() { app_log(APP_LOG_LEVEL_INFO, __FILE__, __LINE__, "destroy_chrono_objects"); layer_destroy(chrono_dial_layer); bwd_destroy(&chrono_dial_white); bwd_destroy(&chrono_dial_black); if (chrono_digital_window != NULL) { window_destroy(chrono_digital_window); chrono_digital_window = NULL; } #ifdef ENABLE_CHRONO_MINUTE_HAND layer_destroy(chrono_minute_layer); #endif #ifdef ENABLE_CHRONO_SECOND_HAND layer_destroy(chrono_second_layer); #endif #ifdef ENABLE_CHRONO_TENTH_HAND layer_destroy(chrono_tenth_layer); #endif hand_cache_destroy(&chrono_minute_cache); hand_cache_destroy(&chrono_second_cache); hand_cache_destroy(&chrono_tenth_cache); }
void draw_chrono_dial(GContext *ctx) { // app_log(APP_LOG_LEVEL_INFO, __FILE__, __LINE__, "draw_chrono_dial"); if (config.chrono_dial != CDM_off) { #ifdef PBL_PLATFORM_APLITE BitmapWithData chrono_dial_black; if (chrono_dial_shows_tenths) { chrono_dial_black = rle_bwd_create(RESOURCE_ID_CHRONO_DIAL_TENTHS_BLACK); } else { chrono_dial_black = rle_bwd_create(RESOURCE_ID_CHRONO_DIAL_HOURS_BLACK); } if (chrono_dial_black.bitmap == NULL) { bwd_destroy(&chrono_dial_black); trigger_memory_panic(__LINE__); } #endif // PBL_PLATFORM_APLITE // In Basalt, we only load the "white" image. if (chrono_dial_white.bitmap == NULL) { if (chrono_dial_shows_tenths) { chrono_dial_white = rle_bwd_create(RESOURCE_ID_CHRONO_DIAL_TENTHS_WHITE); } else { chrono_dial_white = rle_bwd_create(RESOURCE_ID_CHRONO_DIAL_HOURS_WHITE); } if (chrono_dial_white.bitmap == NULL) { trigger_memory_panic(__LINE__); return; } // We apply the color scheme as needed. remap_colors_clock(&chrono_dial_white); } int x = chrono_tenth_hand_def.place_x - chrono_dial_size.w / 2; int y = chrono_tenth_hand_def.place_y - chrono_dial_size.h / 2; GRect destination = GRect(x, y, chrono_dial_size.w, chrono_dial_size.h); #ifdef PBL_PLATFORM_APLITE graphics_context_set_compositing_mode(ctx, draw_mode_table[config.draw_mode ^ APLITE_INVERT].paint_fg); graphics_draw_bitmap_in_rect(ctx, chrono_dial_black.bitmap, destination); graphics_context_set_compositing_mode(ctx, draw_mode_table[config.draw_mode ^ APLITE_INVERT].paint_bg); graphics_draw_bitmap_in_rect(ctx, chrono_dial_white.bitmap, destination); #else // PBL_PLATFORM_APLITE graphics_context_set_compositing_mode(ctx, GCompOpSet); graphics_draw_bitmap_in_rect(ctx, chrono_dial_white.bitmap, destination); #endif // PBL_PLATFORM_APLITE if (!keep_assets) { bwd_destroy(&chrono_dial_white); } #ifdef PBL_PLATFORM_APLITE bwd_destroy(&chrono_dial_black); #endif // PBL_PLATFORM_APLITE } }
void destroy_bluetooth_bitmaps() { bwd_destroy(&bluetooth_disconnected); bwd_destroy(&bluetooth_connected); bwd_destroy(&bluetooth_mask); #ifndef PBL_PLATFORM_APLITE bwd_destroy(&quiet_time); bwd_destroy(&quiet_time_mask); #endif // PBL_PLATFORM_APLITE }
// Release any memory held within a HandCache structure. void hand_cache_destroy(struct HandCache *hand_cache) { bwd_destroy(&hand_cache->image); bwd_destroy(&hand_cache->mask); int gi; for (gi = 0; gi < HAND_CACHE_MAX_GROUPS; ++gi) { if (hand_cache->path[gi] != NULL) { gpath_destroy(hand_cache->path[gi]); hand_cache->path[gi] = NULL; } } }
// Updates any runtime settings as needed when the config changes. void apply_config() { app_log(APP_LOG_LEVEL_INFO, __FILE__, __LINE__, "apply_config"); // Reset the memory panic count when we get a new config setting. // Maybe the user knows what he's doing. memory_panic_count = 0; if (face_index != config.face_index) { // Update the face bitmap if it's changed. face_index = config.face_index; bwd_destroy(&clock_face); // Also move any layers to their new position on this face. for (int i = 0; i < NUM_DATE_WINDOWS; ++i) { const struct IndicatorTable *window = &date_windows[i][config.face_index]; layer_set_frame((Layer *)date_window_layers[i], GRect(window->x - 19, window->y - 8, 39, 19)); } { const struct IndicatorTable *window = &battery_table[config.face_index]; move_battery_gauge(window->x, window->y, window->invert, window->opaque); } { const struct IndicatorTable *window = &bluetooth_table[config.face_index]; move_bluetooth_indicator(window->x, window->y, window->invert, window->opaque); } } if (display_lang != config.display_lang) { // Unload the day font if it changes with the language. if (date_lang_font != NULL && (display_lang == -1 || lang_table[display_lang].font_index != lang_table[config.display_lang].font_index)) { app_log(APP_LOG_LEVEL_INFO, __FILE__, __LINE__, "apply_config unload date_lang_font %p", date_lang_font); safe_unload_custom_font(&date_lang_font); } // Reload the weekday, month, and ampm names from the appropriate // language resource. fill_date_names(date_names, NUM_DATE_NAMES, date_names_buffer, DATE_NAMES_MAX_BUFFER, lang_table[config.display_lang].date_name_id); display_lang = config.display_lang; } #ifdef SUPPORT_MOON // Reload the moon bitmap just for good measure. Maybe the user // changed the draw mode or the lunar direction. bwd_destroy(&moon_bitmap); #endif // SUPPORT_MOON layer_mark_dirty(clock_face_layer); reset_tick_timer(); }
// Draws the frame and optionally fills the background of the current date window. void draw_date_window_background(GContext *ctx, unsigned int fg_draw_mode, unsigned int bg_draw_mode, bool opaque_layer) { if (opaque_layer || bg_draw_mode != fg_draw_mode) { if (date_window_mask.bitmap == NULL) { date_window_mask = rle_bwd_create(RESOURCE_ID_DATE_WINDOW_MASK); if (date_window_mask.bitmap == NULL) { trigger_memory_panic(__LINE__); return; } } graphics_context_set_compositing_mode(ctx, draw_mode_table[bg_draw_mode].paint_mask); graphics_draw_bitmap_in_rect(ctx, date_window_mask.bitmap, date_window_box); } if (date_window.bitmap == NULL) { date_window = rle_bwd_create(RESOURCE_ID_DATE_WINDOW); if (date_window.bitmap == NULL) { bwd_destroy(&date_window_mask); trigger_memory_panic(__LINE__); return; } } graphics_context_set_compositing_mode(ctx, draw_mode_table[fg_draw_mode].paint_fg); graphics_draw_bitmap_in_rect(ctx, date_window.bitmap, date_window_box); }
// Destroys the objects created by create_objects(). void destroy_objects() { app_log(APP_LOG_LEVEL_INFO, __FILE__, __LINE__, "destroy_objects"); window_stack_pop_all(false); layer_destroy(clock_face_layer); clock_face_layer = NULL; bwd_destroy(&clock_face); face_index = -1; #ifdef MAKE_CHRONOGRAPH destroy_chrono_objects(); #endif // MAKE_CHRONOGRAPH deinit_battery_gauge(); deinit_bluetooth_indicator(); for (int i = 0; i < NUM_DATE_WINDOWS; ++i) { layer_destroy(date_window_layers[i]); date_window_layers[i] = NULL; } bwd_destroy(&date_window); bwd_destroy(&date_window_mask); #ifdef SUPPORT_MOON bwd_destroy(&moon_bitmap); #endif // SUPPORT_MOON layer_destroy(minute_layer); layer_destroy(hour_layer); layer_destroy(second_layer); hand_cache_destroy(&hour_cache); hand_cache_destroy(&minute_cache); hand_cache_destroy(&second_cache); if (date_lang_font != NULL) { safe_unload_custom_font(&date_lang_font); } display_lang = -1; window_destroy(window); window = NULL; }
void load_chrono_dial() { #ifdef PBL_PLATFORM_APLITE bwd_destroy(&chrono_dial_white); bwd_destroy(&chrono_dial_black); if (chrono_dial_shows_tenths) { chrono_dial_white = rle_bwd_create(RESOURCE_ID_CHRONO_DIAL_TENTHS_WHITE); chrono_dial_black = rle_bwd_create(RESOURCE_ID_CHRONO_DIAL_TENTHS_BLACK); } else { chrono_dial_white = rle_bwd_create(RESOURCE_ID_CHRONO_DIAL_HOURS_WHITE); chrono_dial_black = rle_bwd_create(RESOURCE_ID_CHRONO_DIAL_HOURS_BLACK); } if (chrono_dial_white.bitmap == NULL || chrono_dial_black.bitmap == NULL) { bwd_destroy(&chrono_dial_white); bwd_destroy(&chrono_dial_black); trigger_memory_panic(__LINE__); } #else // PBL_PLATFORM_APLITE // In Basalt, we only load the "white" image. bwd_destroy(&chrono_dial_white); if (chrono_dial_shows_tenths) { chrono_dial_white = rle_bwd_create(RESOURCE_ID_CHRONO_DIAL_TENTHS_WHITE); } else { chrono_dial_white = rle_bwd_create(RESOURCE_ID_CHRONO_DIAL_HOURS_WHITE); } if (chrono_dial_white.bitmap == NULL) { bwd_destroy(&chrono_dial_white); trigger_memory_panic(__LINE__); return; } // We apply the color-inverting mode if necessary. uint8_t xor_argb8 = config.draw_mode ? 0x3f : 0x00; bwd_adjust_colors(&chrono_dial_white, 0xff, 0x00, xor_argb8); #endif // PBL_PLATFORM_APLITE }
void destroy_chrono_objects() { app_log(APP_LOG_LEVEL_INFO, __FILE__, __LINE__, "destroy_chrono_objects"); if (chrono_digital_window != NULL) { window_destroy(chrono_digital_window); chrono_digital_window = NULL; } hand_cache_destroy(&chrono_minute_cache); hand_cache_destroy(&chrono_second_cache); hand_cache_destroy(&chrono_tenth_cache); bwd_destroy(&chrono_dial_white); }
void compute_chrono_hands(unsigned int ms, struct HandPlacement *placement) { unsigned int chrono_ms = get_chrono_ms(ms); bool chrono_dial_wants_tenths = true; switch (config.chrono_dial) { case CDM_off: break; case CDM_tenths: chrono_dial_wants_tenths = true; break; case CDM_hours: chrono_dial_wants_tenths = false; break; case CDM_dual: // In dual mode, we show either tenths or hours, depending on the // amount of elapsed time. Less than 30 minutes shows tenths. chrono_dial_wants_tenths = (chrono_ms < 30 * 60 * 1000); break; } if (chrono_dial_shows_tenths != chrono_dial_wants_tenths) { // The dial has changed states; reload and redraw it. chrono_dial_shows_tenths = chrono_dial_wants_tenths; bwd_destroy(&chrono_dial_white); bwd_destroy(&chrono_dial_black); if (chrono_dial_layer != NULL) { layer_mark_dirty(chrono_dial_layer); } } #ifdef ENABLE_CHRONO_MINUTE_HAND // The chronograph minute hand rolls completely around in 30 // minutes (not 60). { unsigned int use_ms = chrono_ms % (1800 * 1000); placement->chrono_minute_hand_index = ((NUM_STEPS_CHRONO_MINUTE * use_ms) / (1800 * 1000)) % NUM_STEPS_CHRONO_MINUTE; } #endif // ENABLE_CHRONO_MINUTE_HAND #ifdef ENABLE_CHRONO_SECOND_HAND { // Avoid overflowing the integer arithmetic by pre-constraining // the ms value to the appropriate range. unsigned int use_ms = chrono_ms % (60 * 1000); if (!config.sweep_seconds) { // Also constrain to an integer second if we've not enabled sweep-second resolution. use_ms = (use_ms / 1000) * 1000; } placement->chrono_second_hand_index = ((NUM_STEPS_CHRONO_SECOND * use_ms) / (60 * 1000)); } #endif // ENABLE_CHRONO_SECOND_HAND #ifdef ENABLE_CHRONO_TENTH_HAND if (config.chrono_dial == CDM_off) { // Don't keep updating this hand if we're not showing it anyway. placement->chrono_tenth_hand_index = 0; } else { if (chrono_dial_shows_tenths) { // Drawing tenths-of-a-second. if (chrono_data.running && !chrono_data.lap_paused) { // We don't actually show the tenths time while the chrono is running. placement->chrono_tenth_hand_index = 0; } else { // We show the tenths time when the chrono is stopped or showing // the lap time. unsigned int use_ms = chrono_ms % 1000; // Truncate to the previous 0.1 seconds (100 ms), just to // make the dial easier to read. use_ms = 100 * (use_ms / 100); placement->chrono_tenth_hand_index = ((NUM_STEPS_CHRONO_TENTH * use_ms) / (1000)) % NUM_STEPS_CHRONO_TENTH; } } else { // Drawing hours. 12-hour scale. unsigned int use_ms = chrono_ms % (12 * SECONDS_PER_HOUR * 1000); placement->chrono_tenth_hand_index = ((NUM_STEPS_CHRONO_TENTH * use_ms) / (12 * SECONDS_PER_HOUR * 1000)) % NUM_STEPS_CHRONO_TENTH; } } #endif // ENABLE_CHRONO_TENTH_HAND }
// Called once per epoch (e.g. once per second, or once per minute) to // compute the new positions for all of the hands on the watch based // on the current time. This does not actually draw the hands; it // only computes which position each hand should hold, and it marks // the appropriate layers dirty, to eventually redraw the hands that // have moved since the last call. void update_hands(struct tm *time) { struct HandPlacement new_placement = current_placement; compute_hands(time, &new_placement); if (new_placement.hour_hand_index != current_placement.hour_hand_index) { current_placement.hour_hand_index = new_placement.hour_hand_index; layer_mark_dirty(hour_layer); } if (new_placement.minute_hand_index != current_placement.minute_hand_index) { current_placement.minute_hand_index = new_placement.minute_hand_index; layer_mark_dirty(minute_layer); } if (new_placement.second_hand_index != current_placement.second_hand_index) { current_placement.second_hand_index = new_placement.second_hand_index; layer_mark_dirty(second_layer); } if (new_placement.hour_buzzer != current_placement.hour_buzzer) { current_placement.hour_buzzer = new_placement.hour_buzzer; if (config.hour_buzzer) { // The hour has changed; ring the buzzer if it's enabled. vibes_short_pulse(); } } // Make sure the sweep timer is fast enough to capture the second // hand. sweep_timer_ms = 1000; if (config.sweep_seconds) { sweep_timer_ms = sweep_seconds_ms; } #ifdef MAKE_CHRONOGRAPH update_chrono_hands(&new_placement); #endif // MAKE_CHRONOGRAPH // If any of the date window properties changes, update all of the // date windows. (We cheat and only check the fastest changing // element, and the date value just in case someone's playing games // with the clock.) if (new_placement.ampm_value != current_placement.ampm_value || new_placement.date_value != current_placement.date_value) { current_placement.day_index = new_placement.day_index; current_placement.month_index = new_placement.month_index; current_placement.date_value = new_placement.date_value; current_placement.year_value = new_placement.year_value; current_placement.ampm_value = new_placement.ampm_value; // Shorthand for the below for loop. This achieves the same thing. layer_mark_dirty(clock_face_layer); /* for (int i = 0; i < NUM_DATE_WINDOWS; ++i) { layer_mark_dirty(date_window_layers[i]); } */ } #ifdef SUPPORT_MOON // Also check the lunar phase, in a separate check from the other // date windows, so it doesn't necessarily have to wait till // midnight to flip over to the next phase. if (new_placement.lunar_phase != current_placement.lunar_phase) { current_placement.lunar_phase = new_placement.lunar_phase; bwd_destroy(&moon_bitmap); layer_mark_dirty(clock_face_layer); } #endif // SUPPORT_MOON }
// Draws a given hand on the face, using the bitmap structures. void draw_bitmap_hand(struct HandCache *hand_cache, struct HandDef *hand_def, int hand_index, GContext *ctx) { if (hand_cache->bitmap_hand_index != hand_index) { // Force a new bitmap. if (hand_cache->image.bitmap != NULL) { bwd_destroy(&hand_cache->image); } if (hand_cache->mask.bitmap != NULL) { bwd_destroy(&hand_cache->mask); } hand_cache->bitmap_hand_index = hand_index; } struct BitmapHandTableRow *hand = &hand_def->bitmap_table[hand_index]; int bitmap_index = hand->bitmap_index; struct BitmapHandCenterRow *lookup = &hand_def->bitmap_centers[bitmap_index]; int hand_resource_id = hand_def->resource_id + bitmap_index; int hand_resource_mask_id = hand_def->resource_mask_id + bitmap_index; if (hand_def->resource_id == hand_def->resource_mask_id) { // The hand does not have a mask. Draw the hand on top of the scene. if (hand_cache->image.bitmap == NULL) { if (hand_def->use_rle) { hand_cache->image = rle_bwd_create(hand_resource_id); } else { hand_cache->image = png_bwd_create(hand_resource_id); } if (hand_cache->image.bitmap == NULL) { hand_cache_destroy(hand_cache); trigger_memory_panic(__LINE__); return; } hand_cache->cx = lookup->cx; hand_cache->cy = lookup->cy; if (hand->flip_x) { // To minimize wasteful resource usage, if the hand is symmetric // we can store only the bitmaps for the right half of the clock // face, and flip them for the left half. flip_bitmap_x(hand_cache->image.bitmap, &hand_cache->cx); } if (hand->flip_y) { // We can also do this vertically. flip_bitmap_y(hand_cache->image.bitmap, &hand_cache->cy); } } // We make sure the dimensions of the GRect to draw into // are equal to the size of the bitmap--otherwise the image // will automatically tile. GRect destination = hand_cache->image.bitmap->bounds; // Place the hand's center point at place_x, place_y. destination.origin.x = hand_def->place_x - hand_cache->cx; destination.origin.y = hand_def->place_y - hand_cache->cy; // Specify a compositing mode to make the hands overlay on top of // each other, instead of the background parts of the bitmaps // blocking each other. if (hand_def->paint_black) { // Painting foreground ("white") pixels as black. graphics_context_set_compositing_mode(ctx, draw_mode_table[config.draw_mode].paint_black); } else { // Painting foreground ("white") pixels as white. graphics_context_set_compositing_mode(ctx, draw_mode_table[config.draw_mode].paint_white); } graphics_draw_bitmap_in_rect(ctx, hand_cache->image.bitmap, destination); } else { // The hand has a mask, so use it to draw the hand opaquely. if (hand_cache->image.bitmap == NULL) { if (hand_def->use_rle) { hand_cache->image = rle_bwd_create(hand_resource_id); hand_cache->mask = rle_bwd_create(hand_resource_mask_id); } else { hand_cache->image = png_bwd_create(hand_resource_id); hand_cache->mask = png_bwd_create(hand_resource_mask_id); } if (hand_cache->image.bitmap == NULL || hand_cache->mask.bitmap == NULL) { hand_cache_destroy(hand_cache); trigger_memory_panic(__LINE__); return; } hand_cache->cx = lookup->cx; hand_cache->cy = lookup->cy; if (hand->flip_x) { // To minimize wasteful resource usage, if the hand is symmetric // we can store only the bitmaps for the right half of the clock // face, and flip them for the left half. flip_bitmap_x(hand_cache->image.bitmap, &hand_cache->cx); flip_bitmap_x(hand_cache->mask.bitmap, NULL); } if (hand->flip_y) { // We can also do this vertically. flip_bitmap_y(hand_cache->image.bitmap, &hand_cache->cy); flip_bitmap_y(hand_cache->mask.bitmap, NULL); } } GRect destination = hand_cache->image.bitmap->bounds; destination.origin.x = hand_def->place_x - hand_cache->cx; destination.origin.y = hand_def->place_y - hand_cache->cy; graphics_context_set_compositing_mode(ctx, draw_mode_table[config.draw_mode].paint_white); graphics_draw_bitmap_in_rect(ctx, hand_cache->mask.bitmap, destination); graphics_context_set_compositing_mode(ctx, draw_mode_table[config.draw_mode].paint_black); graphics_draw_bitmap_in_rect(ctx, hand_cache->image.bitmap, destination); } }