Files
pebble/src/fw/apps/prf_apps/mfg_display_calibration_app.c
T

276 lines
9.5 KiB
C

/*
* 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 <stdio.h>
#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;
}