mirror of
https://github.com/google/pebble
synced 2026-10-16 00:04:12 +02:00
276 lines
9.5 KiB
C
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;
|
|
}
|