/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "applib/app.h" #include "applib/app_timer.h" #include "applib/fonts/fonts.h" #include "applib/graphics/graphics.h" #include "applib/graphics/text.h" #include "applib/ui/window.h" #include "applib/ui/app_window_stack.h" #include "kernel/event_loop.h" #include "kernel/pbl_malloc.h" #include "mfg/mfg_serials.h" #include "mfg/mfg_info.h" #include "services/common/light.h" #include "process_management/app_manager.h" #include "process_management/pebble_process_md.h" #include "process_state/app_state/app_state.h" #include "resource/system_resource.h" #include #define TICK_LENGTH 20 #define LINE_HEIGHT 20 typedef enum { X_AdjustState, Y_AdjustState, NumAdjustStates } AppState; typedef struct { Window window; AppTimer *exit_timer; AppState app_state; int8_t axis_offsets[NumAdjustStates]; char text_buffer[16]; char device_serial[MFG_SERIAL_NUMBER_SIZE + 1]; bool is_saving; } AppData; static void prv_draw_solid(Layer *layer, GContext *ctx, GColor color) { graphics_context_set_fill_color(ctx, color); graphics_fill_rect(ctx, &layer->bounds); } static void prv_display_offsets(Layer *layer, GContext *ctx) { AppData *data = app_state_get_user_data(); graphics_context_set_text_color(ctx, GColorWhite); const GRect *bounds = &data->window.layer.bounds; int pixel_max = bounds->origin.x + bounds->size.w - 1; GFont font; for (int i = 0; i < NumAdjustStates; i++) { font = fonts_get_system_font((i == data->app_state) ? FONT_KEY_GOTHIC_24_BOLD : FONT_KEY_GOTHIC_24); snprintf(data->text_buffer, sizeof(data->text_buffer), "%c: %"PRIi8, 'X' + i, data->axis_offsets[X_AdjustState + i]); graphics_draw_text(ctx, data->text_buffer, font, GRect(bounds->origin.x, bounds->origin.y + 35 + LINE_HEIGHT * i, pixel_max, pixel_max), GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL); } } static void prv_display_serial_number(Layer *layer, GContext *ctx) { AppData *data = app_state_get_user_data(); const GRect *bounds = &data->window.layer.bounds; graphics_context_set_text_color(ctx, GColorWhite); graphics_draw_text(ctx, data->device_serial, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD), GRect(bounds->origin.x, (bounds->origin.x + bounds->size.w) / 2, bounds->size.w, bounds->size.h), GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL); } static void prv_display_saving_message(Layer *layer, GContext *ctx) { AppData *data = app_state_get_user_data(); const GRect *bounds = &data->window.layer.bounds; graphics_context_set_text_color(ctx, GColorWhite); graphics_draw_text(ctx, "Saving...", fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD), GRect(bounds->origin.x, (bounds->origin.y + bounds->size.h) / 2 + LINE_HEIGHT, bounds->size.w, bounds->size.h), GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL); } static void prv_draw_crosshair(Layer *layer, GContext *ctx) { AppData *data = app_state_get_user_data(); const GRect *bounds = &data->window.layer.bounds; const int mid_pixel_minus_one = (bounds->origin.x + bounds->size.w - 1) / 2; const int pixel_min = bounds->origin.x; const int pixel_max = bounds->origin.x + bounds->size.w - 1; for (int i = 0; i < 2; i++) { graphics_context_set_stroke_color(ctx, i ? GColorWhite : GColorWhite); graphics_draw_line(ctx, (GPoint) {.x = mid_pixel_minus_one + i, .y = pixel_min}, (GPoint) {.x = mid_pixel_minus_one + i, .y = pixel_min + TICK_LENGTH}); graphics_draw_line(ctx, (GPoint) {.x = mid_pixel_minus_one + i, .y = pixel_max - TICK_LENGTH}, (GPoint) {.x = mid_pixel_minus_one + i, .y = pixel_max}); graphics_draw_line(ctx, (GPoint) {.x = pixel_min, .y = mid_pixel_minus_one + i}, (GPoint) {.x = pixel_min + TICK_LENGTH, .y = mid_pixel_minus_one + i}); graphics_draw_line(ctx, (GPoint) {.x = pixel_max - TICK_LENGTH, .y = mid_pixel_minus_one + i}, (GPoint) {.x = pixel_max, .y = mid_pixel_minus_one + i}); } } static void prv_draw_border_stripes(Layer *layer, GContext *ctx) { AppData *data = app_state_get_user_data(); const GRect *bounds = &data->window.layer.bounds; const int pixel_min = bounds->origin.x; const int pixel_max = bounds->origin.x + bounds->size.w - 1; for (int j = 0; j < 2; j++) { graphics_context_set_stroke_color(ctx, j ? GColorGreen : GColorRed); for (int i = 0 + j; i < 5; i += 2) { graphics_draw_line(ctx, (GPoint) {.x = i, .y = pixel_min}, (GPoint) {.x = i, .y = pixel_max}); graphics_draw_line(ctx, (GPoint) {.x = pixel_max - i, .y = pixel_min}, (GPoint) {.x = pixel_max - i, .y = pixel_max}); graphics_draw_line(ctx, (GPoint) {.x = pixel_min, .y = i}, (GPoint) {.x = pixel_max, .y = i}); graphics_draw_line(ctx, (GPoint) {.x = pixel_min, .y = pixel_max - i}, (GPoint) {.x = pixel_max, .y = pixel_max - i}); } } } static void prv_layer_update_proc(Layer *layer, GContext *ctx) { AppData *data = app_state_get_user_data(); prv_draw_solid(layer, ctx, GColorBlack); ctx->draw_state.drawing_box.origin.x += data->axis_offsets[X_AdjustState]; ctx->draw_state.drawing_box.origin.y += data->axis_offsets[Y_AdjustState]; prv_draw_border_stripes(layer, ctx); prv_draw_crosshair(layer, ctx); prv_display_offsets(layer, ctx); prv_display_serial_number(layer, ctx); if (data->is_saving) { prv_display_saving_message(layer, ctx); } } static void prv_select_click_handler(ClickRecognizerRef recognizer, void *data) { AppData *app_data = app_state_get_user_data(); app_data->app_state = (app_data->app_state + 1) % (NumAdjustStates); layer_mark_dirty(&app_data->window.layer); } static void prv_up_click_handler(ClickRecognizerRef recognizer, void *data) { AppData *app_data = app_state_get_user_data(); --(app_data->axis_offsets[app_data->app_state]); layer_mark_dirty(&app_data->window.layer); } static void prv_down_click_handler(ClickRecognizerRef recognizer, void *data) { AppData *app_data = app_state_get_user_data(); ++(app_data->axis_offsets[app_data->app_state]); layer_mark_dirty(&app_data->window.layer); } static void prv_save_offsets_callback(void *data) { AppData *app_data = app_state_get_user_data(); mfg_info_set_disp_offsets((GPoint) { .x = app_data->axis_offsets[X_AdjustState], .y = app_data->axis_offsets[Y_AdjustState] }); app_window_stack_pop_all(false); } static void prv_back_click_handler(ClickRecognizerRef recognizer, void *data) { AppData *app_data = app_state_get_user_data(); if (app_data->axis_offsets[X_AdjustState] == mfg_info_get_disp_offsets().x && app_data->axis_offsets[Y_AdjustState] == mfg_info_get_disp_offsets().y) { app_window_stack_pop_all(true); return; } app_data->is_saving = true; layer_mark_dirty(&app_data->window.layer); app_data->exit_timer = app_timer_register(200, prv_save_offsets_callback, NULL); } static void prv_config_provider(void *data) { window_single_click_subscribe(BUTTON_ID_SELECT, prv_select_click_handler); window_single_click_subscribe(BUTTON_ID_UP, prv_up_click_handler); window_single_click_subscribe(BUTTON_ID_DOWN, prv_down_click_handler); window_single_click_subscribe(BUTTON_ID_BACK, prv_back_click_handler); } static void prv_handle_init(void) { AppData *data = app_malloc_check(sizeof(AppData)); *data = (AppData) { .app_state = (AppState) app_manager_get_task_context()->args, .axis_offsets[X_AdjustState] = mfg_info_get_disp_offsets().x, .axis_offsets[Y_AdjustState] = mfg_info_get_disp_offsets().y }; app_state_set_user_data(data); Window *window = &data->window; window_init(window, "MfgDisplayCalibration"); window_set_fullscreen(window, true); window_set_click_config_provider(window, prv_config_provider); Layer *layer = window_get_root_layer(window); layer_set_update_proc(layer, prv_layer_update_proc); mfg_info_get_serialnumber(data->device_serial, sizeof(data->device_serial)); light_enable(true); app_window_stack_push(window, true /* Animated */); } static void prv_handle_deinit(void) { light_enable(false); AppData *data = app_state_get_user_data(); app_timer_cancel(data->exit_timer); app_free(data); } static void s_main(void) { prv_handle_init(); app_event_loop(); prv_handle_deinit(); } const PebbleProcessMd* mfg_display_calibration_app_get_info(void) { static const PebbleProcessMdSystem s_app_info = { .common.main_func = &s_main, // UUID: d0582042-5beb-410f-9fed-76eccd31821e .common.uuid = { 0xd0, 0x58, 0x20, 0x42, 0x5b, 0xeb, 0x41, 0x0f, 0x9f, 0xed, 0x76, 0xec, 0xcd, 0x31, 0x82, 0x1e }, .name = "MfgDisplayCalibration", }; return (const PebbleProcessMd*) &s_app_info; }