/* * 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 "clar.h" #include "applib/graphics/gdraw_command.h" #include "applib/graphics/gdraw_command_list.h" #include "applib/graphics/gdraw_command_frame.h" #include "applib/graphics/gdraw_command_private.h" #include "applib/graphics/gdraw_command_sequence.h" #include "applib/graphics/gtypes.h" #include "applib/graphics/graphics.h" #include "applib/graphics/gpath.h" #include "util/size.h" #include "stubs_applib_resource.h" #include "stubs_memory_layout.h" #include "stubs_passert.h" #include "stubs_pbl_malloc.h" #include "stubs_resources.h" #include "stubs_syscalls.h" // Stubs void graphics_context_set_stroke_color(GContext* ctx, GColor color) {} void graphics_context_set_fill_color(GContext* ctx, GColor color) {} void graphics_context_set_stroke_width(GContext* ctx, uint8_t stroke_width) {} void gpath_draw_stroke(GContext* ctx, GPath* path, bool open) {} void gpath_draw_filled(GContext* ctx, GPath *path) {} void graphics_draw_circle(GContext* ctx, GPoint p, uint16_t radius) {} void graphics_fill_circle(GContext* ctx, GPoint p, uint16_t radius) {} void graphics_context_move_draw_box(GContext* ctx, GPoint offset) {} void graphics_line_draw_precise_stroked(GContext* ctx, GPointPrecise p0, GPointPrecise p1) {} void gpath_fill_precise_internal(GContext *ctx, GPointPrecise *points, size_t num_points) {} void gpath_draw_outline_precise_internal(GContext *ctx, GPointPrecise *points, size_t num_points, bool open) {} typedef uint16_t ResourceId; const uint8_t *resource_get_builtin_bytes(ResAppNum app_num, uint32_t resource_id, uint32_t *num_bytes_out) { return NULL; } // setup and teardown void test_gdraw_command_sequence__initialize(void) { } void test_gdraw_command_sequence__cleanup(void) { } size_t prv_create_test_sequence(GDrawCommandSequence **sequence_ptr) { size_t size = sizeof(GDrawCommandSequence) + (sizeof(GDrawCommandFrame) * 2) + (sizeof(GDrawCommand) * 4) + (sizeof(GPoint) * 9); GDrawCommandSequence *sequence = calloc(1, size); *sequence_ptr = sequence; *sequence = (GDrawCommandSequence){ .version = GDRAW_COMMAND_VERSION, .num_frames = 2, .play_count = 1, }; GDrawCommandFrame *frame; frame = &sequence->frames[0]; *frame = (GDrawCommandFrame) { .duration = 15, }; frame->command_list = (GDrawCommandList) { .num_commands = 3 }; GDrawCommand *command; command = gdraw_command_list_get_command(&frame->command_list, 0); GPoint points1[] = { { 3, 97 }, {5, 5} }; *command = (GDrawCommand) { .type = GDrawCommandTypePath, .hidden = false, .stroke_color = GColorRed, .stroke_width = 1, .fill_color = GColorBlue, .path_open = false, .num_points = ARRAY_LENGTH(points1), }; memcpy(command->points, points1, sizeof(points1)); command = gdraw_command_list_get_command(&frame->command_list, 1); *command = (GDrawCommand) { .type = GDrawCommandTypeCircle, .hidden = false, .stroke_color = GColorGreen, .stroke_width = 1, .fill_color = GColorOrange, .radius = 300, .num_points = 1, }; command->points[0] = (GPoint) { 1, 2 }; command = gdraw_command_list_get_command(&frame->command_list, 2); GPoint points2[] = { { 6, 7 }, {5, 5}, { 0, 0 } }; *command = (GDrawCommand) { .type = GDrawCommandTypePath, .hidden = false, .stroke_color = GColorGreen, .stroke_width = 1, .fill_color = GColorPurple, .path_open = false, .num_points = ARRAY_LENGTH(points2), }; memcpy(command->points, points2, sizeof(points2)); frame = (GDrawCommandFrame *)(command->points + command->num_points); *frame = (GDrawCommandFrame) { .duration = 30, }; frame->command_list = (GDrawCommandList) { .num_commands = 1 }; command = gdraw_command_list_get_command(&frame->command_list, 0); points2[0].x++; // increment x value to distinguish draw command from command in previous frame *command = (GDrawCommand) { .type = GDrawCommandTypePath, .hidden = false, .stroke_color = GColorRed, .stroke_width = 5, .fill_color = GColorBlack, .path_open = false, .num_points = ARRAY_LENGTH(points2), }; memcpy(command->points, points2, sizeof(points2)); return size; } // tests void test_gdraw_command_sequence__validate(void) { GDrawCommandSequence *sequence; size_t size = prv_create_test_sequence(&sequence); cl_assert_equal_i(size, gdraw_command_sequence_get_data_size(sequence)); cl_assert(gdraw_command_sequence_validate(sequence, size)); cl_assert(!gdraw_command_sequence_validate(sequence, size - 1)); cl_assert(!gdraw_command_sequence_validate(sequence, size + 1)); cl_assert(!gdraw_command_sequence_validate(sequence, 0)); sequence->num_frames = 0; cl_assert(!gdraw_command_sequence_validate(sequence, size)); sequence->num_frames = 1; cl_assert(!gdraw_command_sequence_validate(sequence, size)); sequence->num_frames = 3; cl_assert(!gdraw_command_sequence_validate(sequence, size)); sequence->num_frames = 2; sequence->version = 0xFF; cl_assert(!gdraw_command_sequence_validate(sequence, size)); free(sequence); } void test_gdraw_command_sequence__get_frame_by_elapsed(void) { GDrawCommandSequence *sequence; prv_create_test_sequence(&sequence); GDrawCommandFrame *frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 0); cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 0)); cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_elapsed(sequence, 14)); frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 15); cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 1));; cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_elapsed(sequence, 44)); cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_elapsed(sequence, 45)); cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_elapsed(sequence, 46)); // test that frame is skipped when the duration is zero (first frame shown will be the first one // with non-zero duration frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 0); frame->duration = 0; frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 0); cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 1)); frame = gdraw_command_sequence_get_frame_by_index(sequence, 0); frame->duration = 15; // test that the sequence loops when the play count is greater than 1 sequence->play_count = 2; frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 45); cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 0)); frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 45 + 15); cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 1)); // test that the sequence loops infinitely when the play count is infinite sequence->play_count = (uint16_t)PLAY_COUNT_INFINITE; frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 45 * 5); cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 0)); frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, (45 + 15) * 5); cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 1)); // test that the sequence returns the last frame if the play count is zero sequence->play_count = 0; frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 1); cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 1)); free(sequence); } void test_gdraw_command_sequence__get_frame_by_index(void) { GDrawCommandSequence *sequence; prv_create_test_sequence(&sequence); GDrawCommandFrame *frame = gdraw_command_sequence_get_frame_by_index(sequence, 0); cl_assert_equal_i(frame->duration, 15); cl_assert_equal_i(frame->command_list.num_commands, 3); GDrawCommand *command = gdraw_command_list_get_command(&frame->command_list, 2); cl_assert_equal_i(command->type, GDrawCommandTypePath); cl_assert_equal_i(command->num_points, 3); cl_assert_equal_i(command->stroke_color.argb, GColorGreenARGB8); cl_assert_equal_i(command->fill_color.argb, GColorPurpleARGB8); cl_assert_equal_i(command->points[0].x, 6); frame = gdraw_command_sequence_get_frame_by_index(sequence, 1); cl_assert_equal_i(frame->duration, 30); cl_assert_equal_i(frame->command_list.num_commands, 1); command = gdraw_command_list_get_command(&frame->command_list, 0); cl_assert_equal_i(command->type, GDrawCommandTypePath); cl_assert_equal_i(command->num_points, 3); cl_assert_equal_i(command->stroke_color.argb, GColorRedARGB8); cl_assert_equal_i(command->fill_color.argb, GColorBlackARGB8); cl_assert_equal_i(command->points[0].x, 7); cl_assert_equal_p(gdraw_command_sequence_get_frame_by_index(sequence, 2), NULL); free(sequence); } void test_gdraw_command_sequence__clone(void) { cl_assert_equal_p(gdraw_command_sequence_clone(NULL), NULL); GDrawCommandSequence *sequence; prv_create_test_sequence(&sequence); GDrawCommandSequence *clone = gdraw_command_sequence_clone(sequence); cl_assert(clone != sequence); size_t expected_size = gdraw_command_sequence_get_data_size(sequence); cl_assert_equal_i(gdraw_command_sequence_get_data_size(clone), expected_size); cl_assert_equal_i(0, memcmp(clone, sequence, expected_size)); free(sequence); }