mirror of
https://github.com/google/pebble.git
synced 2025-03-24 12:39:07 +00:00
417 lines
16 KiB
C
417 lines
16 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/graphics/framebuffer.h"
|
|
#include "applib/graphics/graphics.h"
|
|
#include "applib/ui/menu_layer.h"
|
|
#include "applib/ui/status_bar_layer.h"
|
|
#include "resource/resource.h"
|
|
#include "resource/resource_ids.auto.h"
|
|
#include "util/graphics.h"
|
|
#include "util/size.h"
|
|
|
|
#include "clar.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
// Fakes
|
|
/////////////////////
|
|
|
|
#include "fake_resource_syscalls.h"
|
|
#include "fake_spi_flash.h"
|
|
#include "fixtures/load_test_resources.h"
|
|
|
|
static bool s_cell_is_highlighted = false;
|
|
|
|
// TODO PBL-23041: When round MenuLayer animations are enabled, we need a "is_selected" function
|
|
bool menu_cell_layer_is_highlighted(const Layer *cell_layer) {
|
|
return s_cell_is_highlighted;
|
|
}
|
|
|
|
// Stubs
|
|
/////////////////////
|
|
|
|
#include "stubs_analytics.h"
|
|
#include "stubs_app_state.h"
|
|
#include "stubs_bootbits.h"
|
|
#include "stubs_compiled_with_legacy2_sdk.h"
|
|
#include "stubs_content_indicator.h"
|
|
#include "stubs_heap.h"
|
|
#include "stubs_logging.h"
|
|
#include "stubs_memory_layout.h"
|
|
#include "stubs_mutex.h"
|
|
#include "stubs_passert.h"
|
|
#include "stubs_pbl_malloc.h"
|
|
#include "stubs_pebble_tasks.h"
|
|
#include "stubs_print.h"
|
|
#include "stubs_process_manager.h"
|
|
#include "stubs_prompt.h"
|
|
#include "stubs_serial.h"
|
|
#include "stubs_shell_prefs.h"
|
|
#include "stubs_sleep.h"
|
|
#include "stubs_syscalls.h"
|
|
#include "stubs_task_watchdog.h"
|
|
#include "stubs_ui_window.h"
|
|
#include "stubs_unobstructed_area.h"
|
|
|
|
void window_long_click_subscribe(ButtonId button_id, uint16_t delay_ms, ClickHandler down_handler,
|
|
ClickHandler up_handler) {}
|
|
|
|
void window_set_click_config_provider_with_context(Window *window,
|
|
ClickConfigProvider click_config_provider,
|
|
void *context) {}
|
|
|
|
void window_set_click_context(ButtonId button_id, void *context) {}
|
|
|
|
void window_single_click_subscribe(ButtonId button_id, ClickHandler handler) {}
|
|
|
|
void window_single_repeating_click_subscribe(ButtonId button_id, uint16_t repeat_interval_ms,
|
|
ClickHandler handler) {}
|
|
|
|
// Helper Functions
|
|
/////////////////////
|
|
#include "../graphics/test_graphics.h"
|
|
#include "../graphics/util.h"
|
|
|
|
// Setup and Teardown
|
|
////////////////////////////////////
|
|
|
|
static FrameBuffer *fb;
|
|
static GContext s_ctx;
|
|
|
|
static GBitmap *s_dest_bitmap;
|
|
|
|
static GBitmap s_tictoc_icon_bitmap;
|
|
static GBitmap s_smart_alarm_icon_bitmap;
|
|
|
|
static void prv_initialize_icons(void) {
|
|
gbitmap_init_with_resource(&s_tictoc_icon_bitmap, RESOURCE_ID_MENU_ICON_TICTOC_WATCH);
|
|
gbitmap_init_with_resource(&s_smart_alarm_icon_bitmap, RESOURCE_ID_SMART_ALARM_ICON_BLACK);
|
|
}
|
|
|
|
void test_menu_layer_system_cells__initialize(void) {
|
|
process_manager_set_compiled_with_legacy2_sdk(false);
|
|
|
|
fb = malloc(sizeof(FrameBuffer));
|
|
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
|
|
|
|
test_graphics_context_init(&s_ctx, fb);
|
|
framebuffer_clear(fb);
|
|
|
|
// Setup resources
|
|
fake_spi_flash_init(0, 0x1000000);
|
|
pfs_init(false);
|
|
pfs_format(true /* write erase headers */);
|
|
load_resource_fixture_in_flash(RESOURCES_FIXTURE_PATH, SYSTEM_RESOURCES_FIXTURE_NAME, false /* is_next */);
|
|
|
|
resource_init();
|
|
|
|
prv_initialize_icons();
|
|
}
|
|
|
|
void test_menu_layer_system_cells__cleanup(void) {
|
|
free(fb);
|
|
fb = NULL;
|
|
|
|
gbitmap_deinit(&s_tictoc_icon_bitmap);
|
|
gbitmap_deinit(&s_smart_alarm_icon_bitmap);
|
|
|
|
gbitmap_destroy(s_dest_bitmap);
|
|
s_dest_bitmap = NULL;
|
|
}
|
|
|
|
// Helpers
|
|
//////////////////////
|
|
|
|
// This struct represents the data for a column of the grid of our resulting test image
|
|
typedef struct {
|
|
const char *title_font;
|
|
const char *subtitle_font;
|
|
const char *value_font;
|
|
} MenuLayerSystemCellTestColumnData;
|
|
|
|
static const MenuLayerSystemCellTestColumnData s_menu_system_basic_cell_test_column_data[] = {
|
|
{ NULL, NULL, NULL }, // Use the default fonts
|
|
};
|
|
|
|
static const MenuLayerSystemCellTestColumnData s_menu_system_cell_layer_test_column_data[] = {
|
|
{ FONT_KEY_GOTHIC_24_BOLD, FONT_KEY_GOTHIC_14, FONT_KEY_GOTHIC_24_BOLD },
|
|
{ FONT_KEY_GOTHIC_14, FONT_KEY_GOTHIC_24_BOLD, FONT_KEY_GOTHIC_14 },
|
|
};
|
|
|
|
// This struct represents the data for a row of the grid of our resulting test image
|
|
typedef struct {
|
|
char *title;
|
|
char *subtitle;
|
|
char *value;
|
|
GBitmap *icon;
|
|
MenuCellLayerIconAlign icon_align;
|
|
bool icon_subbitmap;
|
|
GBoxModel *icon_box_model;
|
|
int horizontal_inset;
|
|
bool icon_form_fit;
|
|
} MenuLayerSystemCellTestRowData;
|
|
|
|
#define DEFAULT_ICON_ALIGN PBL_IF_RECT_ELSE(MenuCellLayerIconAlign_Left, MenuCellLayerIconAlign_Top)
|
|
|
|
static const MenuLayerSystemCellTestRowData s_menu_system_cell_test_row_data[] = {
|
|
{"Star Wars", NULL, NULL, NULL, DEFAULT_ICON_ALIGN},
|
|
{"The Lord of the Rings", "The Fellowship of the Ring", NULL, NULL, DEFAULT_ICON_ALIGN},
|
|
{"The Lord of the Rings", NULL, NULL, &s_tictoc_icon_bitmap, DEFAULT_ICON_ALIGN},
|
|
{"The Matrix", "Revolutions", NULL, &s_tictoc_icon_bitmap, DEFAULT_ICON_ALIGN},
|
|
{"8:00 AM", "Weekdays", "OFF", NULL, DEFAULT_ICON_ALIGN},
|
|
{"8:00 AM", "Weekdays", NULL, &s_tictoc_icon_bitmap, MenuCellLayerIconAlign_Right},
|
|
{"8:00 AM", "Weekdays", "OFF", &s_smart_alarm_icon_bitmap, MenuCellLayerIconAlign_TopLeft,
|
|
false, &(GBoxModel){ .offset = { 0, 5 }, .margin = { 6, 0 }}, PBL_IF_ROUND_ELSE(-6, 0), true},
|
|
{"The Lord of the Rings", NULL, NULL, &s_tictoc_icon_bitmap, DEFAULT_ICON_ALIGN, true},
|
|
{"The Matrix", "Revolutions", NULL, &s_tictoc_icon_bitmap, DEFAULT_ICON_ALIGN, true},
|
|
};
|
|
|
|
// We render all of the row data's for each of the following heights
|
|
static const int16_t s_menu_system_cell_test_row_heights[] = {
|
|
#if PBL_ROUND
|
|
MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT,
|
|
MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT,
|
|
#endif
|
|
0, //!< Will be interpreted as "use menu_cell_basic_cell_height()"
|
|
MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT,
|
|
MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT,
|
|
};
|
|
|
|
#define GRID_CELL_PADDING 5
|
|
|
|
static int16_t prv_get_row_height_for_index(int i) {
|
|
return (s_menu_system_cell_test_row_heights[i] == 0) ? menu_cell_basic_cell_height() :
|
|
s_menu_system_cell_test_row_heights[i];
|
|
}
|
|
|
|
static int16_t prv_calculate_overall_grid_height(void) {
|
|
int16_t sum = 0;
|
|
const unsigned int num_distinct_rows = ARRAY_LENGTH(s_menu_system_cell_test_row_data);
|
|
const unsigned int num_row_heights = ARRAY_LENGTH(s_menu_system_cell_test_row_heights);
|
|
for (int i = 0; i < num_row_heights; i++) {
|
|
sum += GRID_CELL_PADDING + prv_get_row_height_for_index(i);
|
|
}
|
|
return (sum * num_distinct_rows) + GRID_CELL_PADDING;
|
|
}
|
|
|
|
typedef enum MenuCellType {
|
|
MenuCellType_Basic,
|
|
MenuCellType_BasicCustom,
|
|
MenuCellType_CellLayer,
|
|
} MenuCellType;
|
|
|
|
//! Allows testing other cell drawing functions using MenuCellLayerConfig
|
|
static void prv_menu_cell_draw_dispatch(GContext *ctx, Layer *cell_layer, MenuCellType cell_type,
|
|
const MenuCellLayerConfig *config, bool icon_subbitmap) {
|
|
GRect old_icon_bounds = GRectZero;
|
|
if (config->icon) {
|
|
old_icon_bounds = config->icon->bounds;
|
|
if (icon_subbitmap) {
|
|
gpoint_add_eq(&config->icon->bounds.origin, GPoint(4, 4));
|
|
config->icon->bounds.size.w -= 8;
|
|
config->icon->bounds.size.h -= 8;
|
|
}
|
|
}
|
|
switch (cell_type) {
|
|
case MenuCellType_Basic:
|
|
// These should be ignored, we want to make sure they are!
|
|
menu_cell_basic_draw(ctx, cell_layer, config->title, config->subtitle, config->icon);
|
|
break;
|
|
case MenuCellType_BasicCustom:
|
|
menu_cell_basic_draw_custom(
|
|
ctx, cell_layer, config->title_font, config->title, config->value_font, config->value,
|
|
config->subtitle_font, config->subtitle, config->icon,
|
|
(config->icon_align == MenuCellLayerIconAlign_Right), config->overflow_mode);
|
|
break;
|
|
case MenuCellType_CellLayer:
|
|
menu_cell_layer_draw(ctx, cell_layer, config);
|
|
break;
|
|
}
|
|
|
|
if (config->icon) {
|
|
config->icon->bounds = old_icon_bounds;
|
|
}
|
|
}
|
|
|
|
static void prv_draw_cell(MenuCellType cell_type, const GRect *cell_bounds,
|
|
const MenuLayerSystemCellTestRowData *row_data,
|
|
const MenuLayerSystemCellTestColumnData *column_data, bool is_selected) {
|
|
s_ctx.draw_state.drawing_box.origin = cell_bounds->origin;
|
|
const GRect cell_frame = GRect(0, 0, cell_bounds->size.w, cell_bounds->size.h);
|
|
|
|
const GColor background_color = is_selected ? GColorCobaltBlue : GColorWhite;
|
|
graphics_context_set_fill_color(&s_ctx, background_color);
|
|
graphics_fill_rect(&s_ctx, &cell_frame);
|
|
|
|
const GColor foreground_color = is_selected ? GColorWhite : GColorBlack;
|
|
graphics_context_set_text_color(&s_ctx, foreground_color);
|
|
graphics_context_set_tint_color(&s_ctx, foreground_color);
|
|
graphics_context_set_stroke_color(&s_ctx, foreground_color);
|
|
|
|
Layer cell_layer;
|
|
layer_init(&cell_layer, &cell_frame);
|
|
cell_layer.is_highlighted = is_selected;
|
|
s_cell_is_highlighted = is_selected;
|
|
|
|
const MenuCellLayerConfig config = {
|
|
.title_font = column_data->title_font ? fonts_get_system_font(column_data->title_font) : NULL,
|
|
.subtitle_font = column_data->subtitle_font ? fonts_get_system_font(column_data->subtitle_font) : NULL,
|
|
.value_font = column_data->value_font ? fonts_get_system_font(column_data->value_font) : NULL,
|
|
.title = row_data->title,
|
|
.subtitle = row_data->subtitle,
|
|
.value = row_data->value,
|
|
.icon = row_data->icon,
|
|
.icon_align = row_data->icon_align,
|
|
.icon_box_model = row_data->icon_box_model,
|
|
.icon_form_fit = row_data->icon_form_fit,
|
|
.horizontal_inset = row_data->horizontal_inset,
|
|
.overflow_mode = GTextOverflowModeFill,
|
|
};
|
|
prv_menu_cell_draw_dispatch(&s_ctx, &cell_layer, cell_type, &config, row_data->icon_subbitmap);
|
|
}
|
|
|
|
void prv_prepare_canvas_and_render_cells(MenuCellType cell_type, int16_t cell_width,
|
|
const MenuLayerSystemCellTestColumnData *columns,
|
|
unsigned int num_columns, bool is_legacy2) {
|
|
gbitmap_destroy(s_dest_bitmap);
|
|
|
|
const unsigned int num_row_heights = ARRAY_LENGTH(s_menu_system_cell_test_row_heights);
|
|
const unsigned int num_rows = ARRAY_LENGTH(s_menu_system_cell_test_row_data);
|
|
|
|
// Multiply num_columns * 2 to account for drawing both focused and unfocused side-by-side
|
|
const int num_columns_accounting_for_focused_unfocused = num_columns * 2;
|
|
const int16_t bitmap_width = (cell_width * num_columns_accounting_for_focused_unfocused) +
|
|
(GRID_CELL_PADDING * (num_columns_accounting_for_focused_unfocused + 1));
|
|
const int16_t bitmap_height = prv_calculate_overall_grid_height();
|
|
const GSize bitmap_size = GSize(bitmap_width, bitmap_height);
|
|
s_dest_bitmap = gbitmap_create_blank(bitmap_size,
|
|
PBL_IF_COLOR_ELSE(GBitmapFormat8Bit, GBitmapFormat1Bit));
|
|
|
|
s_ctx.dest_bitmap = *s_dest_bitmap;
|
|
s_ctx.draw_state.clip_box.size = bitmap_size;
|
|
s_ctx.draw_state.drawing_box.size = bitmap_size;
|
|
|
|
// Fill the bitmap with pink so it's easier to see errors
|
|
memset(s_dest_bitmap->addr, GColorShockingPinkARGB8,
|
|
s_dest_bitmap->row_size_bytes * s_dest_bitmap->bounds.size.h);
|
|
|
|
process_manager_set_compiled_with_legacy2_sdk(is_legacy2);
|
|
|
|
int16_t y_offset = 0;
|
|
for (unsigned int row_index = 0; row_index < num_rows; row_index++) {
|
|
const MenuLayerSystemCellTestRowData row_data =
|
|
s_menu_system_cell_test_row_data[row_index];
|
|
for (unsigned int row_height_index = 0; row_height_index < num_row_heights;
|
|
row_height_index++) {
|
|
y_offset += GRID_CELL_PADDING;
|
|
const int16_t row_height = prv_get_row_height_for_index(row_height_index);
|
|
for (unsigned int column_index = 0; column_index < num_columns; column_index++) {
|
|
const MenuLayerSystemCellTestColumnData column_data = columns[column_index];
|
|
|
|
int16_t x_offset = GRID_CELL_PADDING + column_index * ((GRID_CELL_PADDING + cell_width) * 2);
|
|
|
|
GRect cell_bounds = GRect(x_offset, y_offset, cell_width, row_height);
|
|
prv_draw_cell(cell_type, &cell_bounds, &row_data, &column_data, true);
|
|
cell_bounds.origin.x += cell_width + GRID_CELL_PADDING;
|
|
prv_draw_cell(cell_type, &cell_bounds, &row_data, &column_data, false);
|
|
}
|
|
y_offset += row_height;
|
|
}
|
|
}
|
|
|
|
process_manager_set_compiled_with_legacy2_sdk(false);
|
|
}
|
|
|
|
// Tests
|
|
//////////////////////
|
|
|
|
void test_menu_layer_system_cells__basic_cell_width_144_legacy2(void) {
|
|
#if PLATFORM_TINTIN || PLATFORM_SILK
|
|
// NOTE: The generated bitmap will look really funky because it's rendering 8bit gbitmaps as
|
|
// 1bit due to the legacy2 check in gbitmap_get_format. This is normal and expected.
|
|
|
|
const int16_t cell_width = 144;
|
|
prv_prepare_canvas_and_render_cells(
|
|
MenuCellType_Basic, cell_width,
|
|
s_menu_system_basic_cell_test_column_data,
|
|
ARRAY_LENGTH(s_menu_system_basic_cell_test_column_data),
|
|
/* is_legacy2 */ true);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
#endif
|
|
}
|
|
|
|
void test_menu_layer_system_cells__basic_cell_width_144(void) {
|
|
const int16_t cell_width = 144;
|
|
prv_prepare_canvas_and_render_cells(
|
|
MenuCellType_Basic, cell_width,
|
|
s_menu_system_basic_cell_test_column_data,
|
|
ARRAY_LENGTH(s_menu_system_basic_cell_test_column_data),
|
|
/* is_legacy2 */ false);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_menu_layer_system_cells__basic_custom_cell_width_144(void) {
|
|
const int16_t cell_width = 144;
|
|
prv_prepare_canvas_and_render_cells(
|
|
MenuCellType_BasicCustom, cell_width,
|
|
s_menu_system_cell_layer_test_column_data,
|
|
ARRAY_LENGTH(s_menu_system_cell_layer_test_column_data),
|
|
/* is_legacy2 */ false);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_menu_layer_system_cells__cell_width_32(void) {
|
|
#if PBL_ROUND
|
|
const int16_t cell_width = 32;
|
|
prv_prepare_canvas_and_render_cells(
|
|
MenuCellType_CellLayer, cell_width,
|
|
s_menu_system_cell_layer_test_column_data,
|
|
ARRAY_LENGTH(s_menu_system_cell_layer_test_column_data),
|
|
/* is_legacy2 */ false);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
#endif
|
|
}
|
|
|
|
void test_menu_layer_system_cells__cell_width_100(void) {
|
|
const int16_t cell_width = 100;
|
|
prv_prepare_canvas_and_render_cells(
|
|
MenuCellType_CellLayer, cell_width,
|
|
s_menu_system_cell_layer_test_column_data,
|
|
ARRAY_LENGTH(s_menu_system_cell_layer_test_column_data),
|
|
/* is_legacy2 */ false);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_menu_layer_system_cells__cell_width_144(void) {
|
|
const int16_t cell_width = 144;
|
|
prv_prepare_canvas_and_render_cells(
|
|
MenuCellType_CellLayer, cell_width,
|
|
s_menu_system_cell_layer_test_column_data,
|
|
ARRAY_LENGTH(s_menu_system_cell_layer_test_column_data),
|
|
/* is_legacy2 */ false);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_menu_layer_system_cells__cell_width_180(void) {
|
|
const int16_t cell_width = 180;
|
|
prv_prepare_canvas_and_render_cells(
|
|
MenuCellType_CellLayer, cell_width,
|
|
s_menu_system_cell_layer_test_column_data,
|
|
ARRAY_LENGTH(s_menu_system_cell_layer_test_column_data),
|
|
/* is_legacy2 */ false);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|