mirror of
https://github.com/google/pebble.git
synced 2025-07-23 15:54:54 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
518
tests/fw/ui/recognizer/test_recognizer.c
Normal file
518
tests/fw/ui/recognizer/test_recognizer.c
Normal file
|
@ -0,0 +1,518 @@
|
|||
/*
|
||||
* 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/ui/recognizer/recognizer.h"
|
||||
#include "applib/ui/recognizer/recognizer_impl.h"
|
||||
#include "applib/ui/recognizer/recognizer_manager.h"
|
||||
#include "applib/ui/recognizer/recognizer_private.h"
|
||||
#include "util/size.h"
|
||||
|
||||
// Stubs
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "test_recognizer_impl.h"
|
||||
|
||||
static bool s_manager_state_change = false;
|
||||
void recognizer_manager_handle_state_change(RecognizerManager *manager, Recognizer *changed) {
|
||||
s_manager_state_change = true;
|
||||
}
|
||||
|
||||
static TestImplData s_test_impl_data;
|
||||
|
||||
// setup and teardown
|
||||
void test_recognizer__initialize(void) {
|
||||
s_test_impl_data = (TestImplData) {};
|
||||
}
|
||||
|
||||
void test_recognizer__cleanup(void) {
|
||||
|
||||
}
|
||||
|
||||
// tests
|
||||
void test_recognizer__create_with_data(void) {
|
||||
int sub_data;
|
||||
void *dummy = &sub_data;
|
||||
RecognizerImpl s_test_impl = {
|
||||
.handle_touch_event = dummy,
|
||||
.cancel = dummy,
|
||||
.reset = dummy
|
||||
};
|
||||
Recognizer *r = recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
|
||||
sizeof(s_test_impl_data), dummy,
|
||||
&sub_data);
|
||||
cl_assert(r != NULL);
|
||||
cl_assert_equal_p(r->impl, &s_test_impl);
|
||||
cl_assert_equal_m(r->impl_data, &s_test_impl_data, sizeof(s_test_impl_data));
|
||||
cl_assert_equal_p(r->subscriber.event, dummy);
|
||||
cl_assert_equal_p(r->subscriber.data, &sub_data);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(r->flags, 0);
|
||||
cl_assert_equal_p(r->simultaneous_with_cb, NULL);
|
||||
cl_assert_equal_p(r->fail_after, NULL);
|
||||
|
||||
cl_assert_passert(recognizer_create_with_data(NULL, &s_test_impl_data,
|
||||
sizeof(s_test_impl_data), dummy,
|
||||
NULL));
|
||||
cl_assert_passert(recognizer_create_with_data(&s_test_impl, NULL,
|
||||
sizeof(s_test_impl_data), dummy,
|
||||
NULL));
|
||||
cl_assert_passert(recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
|
||||
0, dummy, NULL));
|
||||
cl_assert_equal_p(NULL, recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
|
||||
sizeof(s_test_impl_data), NULL, NULL));
|
||||
s_test_impl.handle_touch_event = NULL;
|
||||
cl_assert_passert(recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
|
||||
sizeof(s_test_impl_data), dummy,
|
||||
NULL));
|
||||
s_test_impl.handle_touch_event = dummy;
|
||||
s_test_impl.reset = NULL;
|
||||
cl_assert_passert(recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
|
||||
sizeof(s_test_impl_data), dummy,
|
||||
NULL));
|
||||
s_test_impl.reset = dummy;
|
||||
s_test_impl.cancel = NULL;
|
||||
cl_assert_passert(recognizer_create_with_data(&s_test_impl, &s_test_impl_data,
|
||||
sizeof(s_test_impl_data), dummy,
|
||||
NULL));
|
||||
|
||||
recognizer_destroy(r);
|
||||
}
|
||||
|
||||
void test_recognizer__transition_state(void) {
|
||||
RecognizerEvent event_type = -1;
|
||||
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, &event_type);
|
||||
// Test that manager state change handler is called when not called from a touch event handler
|
||||
recognizer_transition_state(r, RecognizerState_Failed);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Failed);
|
||||
cl_assert(s_manager_state_change);
|
||||
cl_assert_equal_i(event_type, -1);
|
||||
|
||||
r->state = RecognizerState_Possible;
|
||||
s_manager_state_change = false;
|
||||
recognizer_transition_state(r, RecognizerState_Completed);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Completed);
|
||||
cl_assert(s_manager_state_change);
|
||||
cl_assert_equal_i(event_type, RecognizerEvent_Completed);
|
||||
|
||||
s_manager_state_change = false;
|
||||
r->handling_touch_event = true;
|
||||
r->state = RecognizerState_Possible;
|
||||
event_type = -1;
|
||||
recognizer_transition_state(r, RecognizerState_Failed);
|
||||
cl_assert(!s_manager_state_change);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(event_type, -1);
|
||||
|
||||
// Test that invalid state transitions get caught by asserts
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Completed));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Started));
|
||||
|
||||
r->state = RecognizerState_Possible;
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Possible));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Updated));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Cancelled));
|
||||
|
||||
recognizer_transition_state(r, RecognizerState_Started);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(event_type, RecognizerEvent_Started);
|
||||
cl_assert(!s_manager_state_change);
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Failed));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Possible));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Started));
|
||||
|
||||
recognizer_transition_state(r, RecognizerState_Updated);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Updated);
|
||||
cl_assert_equal_i(event_type, RecognizerEvent_Updated);
|
||||
cl_assert(!s_manager_state_change);
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Failed));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Possible));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Started));
|
||||
|
||||
event_type = -1;
|
||||
recognizer_transition_state(r, RecognizerState_Updated);
|
||||
cl_assert_equal_i(event_type, RecognizerEvent_Updated);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Updated);
|
||||
cl_assert(!s_manager_state_change);
|
||||
|
||||
recognizer_transition_state(r, RecognizerState_Completed);
|
||||
cl_assert_equal_i(event_type, RecognizerEvent_Completed);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Completed);
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Failed));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Possible));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Started));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Updated));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Cancelled));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Completed));
|
||||
|
||||
r->state = RecognizerState_Updated;
|
||||
recognizer_transition_state(r, RecognizerState_Cancelled);
|
||||
cl_assert_equal_i(event_type, RecognizerEvent_Cancelled);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Cancelled);
|
||||
cl_assert(!s_manager_state_change);
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Failed));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Possible));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Started));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Updated));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Cancelled));
|
||||
cl_assert_passert(recognizer_transition_state(r, RecognizerState_Completed));
|
||||
|
||||
r->state = RecognizerState_Started;
|
||||
event_type = -1;
|
||||
recognizer_transition_state(r, RecognizerState_Cancelled);
|
||||
cl_assert_equal_i(event_type, RecognizerEvent_Cancelled);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Cancelled);
|
||||
cl_assert(!s_manager_state_change);
|
||||
|
||||
r->state = RecognizerState_Started;
|
||||
recognizer_transition_state(r, RecognizerState_Completed);
|
||||
cl_assert_equal_i(event_type, RecognizerEvent_Completed);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Completed);
|
||||
cl_assert(!s_manager_state_change);
|
||||
|
||||
r->state = RecognizerState_Possible;
|
||||
recognizer_transition_state(r, RecognizerState_Completed);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Completed);
|
||||
cl_assert(!s_manager_state_change);
|
||||
}
|
||||
|
||||
void test_recognizer__set_failed(void) {
|
||||
bool failed = false;
|
||||
s_test_impl_data.failed = &failed;
|
||||
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
|
||||
recognizer_set_failed(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Failed);
|
||||
cl_assert(failed);
|
||||
|
||||
// Failed -> Failed invalid transition
|
||||
cl_assert_passert(recognizer_set_failed(r));
|
||||
|
||||
// (!Possible) -> Failed invalid transition
|
||||
r->state = RecognizerState_Started;
|
||||
cl_assert_passert(recognizer_set_failed(r));
|
||||
|
||||
r->state = RecognizerState_Completed;
|
||||
cl_assert_passert(recognizer_set_failed(r));
|
||||
}
|
||||
|
||||
static void prv_sub_destroy(const Recognizer *r) {
|
||||
bool *destroyed = recognizer_get_user_data(r);
|
||||
*destroyed = true;
|
||||
}
|
||||
|
||||
void test_recognizer__destroy(void) {
|
||||
bool impl_destroyed = false;
|
||||
s_test_impl_data.destroyed = &impl_destroyed;
|
||||
|
||||
bool sub_destroyed = false;
|
||||
Recognizer *r = test_recognizer_create(&s_test_impl_data, &sub_destroyed);
|
||||
test_recognizer_enable_on_destroy();
|
||||
recognizer_set_on_destroy(r, prv_sub_destroy);
|
||||
|
||||
// can't destroy a recognizer if it is owned
|
||||
r->is_owned = true;
|
||||
recognizer_destroy(r);
|
||||
cl_assert_equal_b(impl_destroyed, false);
|
||||
cl_assert_equal_b(sub_destroyed, false);
|
||||
|
||||
r->is_owned = false;
|
||||
recognizer_destroy(r);
|
||||
cl_assert_equal_b(impl_destroyed, true);
|
||||
cl_assert_equal_b(sub_destroyed, true);
|
||||
}
|
||||
|
||||
void test_recognizer__reset(void) {
|
||||
bool reset = false;
|
||||
bool cancelled = false;
|
||||
s_test_impl_data.reset = &reset;
|
||||
s_test_impl_data.cancelled = &cancelled;
|
||||
|
||||
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
recognizer_reset(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Possible);
|
||||
cl_assert_equal_b(reset, true);
|
||||
cl_assert_equal_b(cancelled, false);
|
||||
|
||||
reset = false;
|
||||
r->state = RecognizerState_Failed;
|
||||
recognizer_reset(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Possible);
|
||||
cl_assert_equal_b(reset, true);
|
||||
cl_assert_equal_b(cancelled, false);
|
||||
|
||||
reset = false;
|
||||
r->state = RecognizerState_Cancelled;
|
||||
recognizer_reset(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Possible);
|
||||
cl_assert_equal_b(reset, true);
|
||||
cl_assert_equal_b(cancelled, false);
|
||||
|
||||
reset = false;
|
||||
r->state = RecognizerState_Completed;
|
||||
recognizer_reset(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Possible);
|
||||
cl_assert_equal_b(reset, true);
|
||||
cl_assert_equal_b(cancelled, false);
|
||||
|
||||
reset = false;
|
||||
r->state = RecognizerState_Started;
|
||||
recognizer_reset(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Possible);
|
||||
cl_assert_equal_b(reset, true);
|
||||
cl_assert_equal_b(cancelled, true);
|
||||
|
||||
reset = false;
|
||||
cancelled = false;
|
||||
r->state = RecognizerState_Updated;
|
||||
recognizer_reset(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Possible);
|
||||
cl_assert_equal_b(reset, true);
|
||||
cl_assert_equal_b(cancelled, true);
|
||||
}
|
||||
|
||||
void test_recognizer__cancel(void) {
|
||||
bool cancelled = false;
|
||||
s_test_impl_data.cancelled = &cancelled;
|
||||
RecognizerEvent rec_event = -1;
|
||||
|
||||
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, &rec_event);
|
||||
recognizer_cancel(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Possible);
|
||||
cl_assert_equal_b(cancelled, false);
|
||||
cl_assert_equal_i(rec_event, -1);
|
||||
|
||||
r->state = RecognizerState_Failed;
|
||||
recognizer_cancel(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Failed);
|
||||
cl_assert_equal_b(cancelled, false);
|
||||
cl_assert_equal_i(rec_event, -1);
|
||||
|
||||
r->state = RecognizerState_Cancelled;
|
||||
recognizer_cancel(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Cancelled);
|
||||
cl_assert_equal_b(cancelled, false);
|
||||
cl_assert_equal_i(rec_event, -1);
|
||||
|
||||
r->state = RecognizerState_Completed;
|
||||
recognizer_cancel(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Completed);
|
||||
cl_assert_equal_b(cancelled, false);
|
||||
cl_assert_equal_i(rec_event, -1);
|
||||
|
||||
r->state = RecognizerState_Started;
|
||||
recognizer_cancel(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Cancelled);
|
||||
cl_assert_equal_b(cancelled, true);
|
||||
cl_assert_equal_i(rec_event, RecognizerEvent_Cancelled);
|
||||
|
||||
cancelled = false;
|
||||
r->state = RecognizerState_Updated;
|
||||
rec_event = -1;
|
||||
recognizer_cancel(r);
|
||||
cl_assert_equal_i(r->state, RecognizerState_Cancelled);
|
||||
cl_assert_equal_b(cancelled, true);
|
||||
cl_assert_equal_i(rec_event, RecognizerEvent_Cancelled);
|
||||
}
|
||||
|
||||
void test_recognizer__handle_touch_events(void) {
|
||||
RecognizerEvent rec_event = -1;
|
||||
TouchEvent last_touch_event = { .type = TouchEvent_Liftoff };
|
||||
RecognizerState new_state;
|
||||
bool updated = false;
|
||||
s_test_impl_data.last_touch_event = &last_touch_event;
|
||||
s_test_impl_data.new_state = &new_state;
|
||||
s_test_impl_data.updated = &updated;
|
||||
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, &rec_event);
|
||||
|
||||
new_state = RecognizerState_Possible;
|
||||
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
|
||||
cl_assert_equal_i(last_touch_event.type, TouchEvent_Touchdown);
|
||||
cl_assert_equal_b(updated, false);
|
||||
|
||||
new_state = RecognizerState_Completed;
|
||||
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Liftoff });
|
||||
cl_assert_equal_i(last_touch_event.type, TouchEvent_Liftoff);
|
||||
cl_assert_equal_b(updated, true);
|
||||
cl_assert_equal_i(rec_event, RecognizerEvent_Completed);
|
||||
|
||||
r->state = RecognizerState_Possible;
|
||||
updated = false;
|
||||
new_state = RecognizerState_Started;
|
||||
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
|
||||
cl_assert_equal_i(last_touch_event.type, TouchEvent_Touchdown);
|
||||
cl_assert_equal_b(updated, true);
|
||||
cl_assert_equal_i(rec_event, RecognizerEvent_Started);
|
||||
|
||||
updated = false;
|
||||
new_state = RecognizerState_Updated;
|
||||
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_PositionUpdate });
|
||||
cl_assert_equal_i(last_touch_event.type, TouchEvent_PositionUpdate);
|
||||
cl_assert_equal_b(updated, true);
|
||||
cl_assert_equal_i(rec_event, RecognizerEvent_Updated);
|
||||
|
||||
updated = false;
|
||||
new_state = RecognizerState_Cancelled;
|
||||
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Liftoff });
|
||||
cl_assert_equal_i(last_touch_event.type, TouchEvent_Liftoff);
|
||||
cl_assert_equal_b(updated, true);
|
||||
cl_assert_equal_i(rec_event, RecognizerEvent_Cancelled);
|
||||
|
||||
// Should not pass touch events to recognizers that are not active
|
||||
cl_assert_passert(recognizer_handle_touch_event(r, &(TouchEvent) {}));
|
||||
|
||||
// Should not pass null touch events
|
||||
r->state = RecognizerState_Possible;
|
||||
cl_assert_passert(recognizer_handle_touch_event(r, NULL));
|
||||
}
|
||||
|
||||
void test_recognizer__handle_touch_events_fail_after(void) {
|
||||
RecognizerEvent rec_event = -1;
|
||||
RecognizerState new_state;
|
||||
bool updated = false;
|
||||
TouchEvent last_touch_event = { .type = TouchEvent_Liftoff };
|
||||
s_test_impl_data.new_state = &new_state;
|
||||
s_test_impl_data.updated = &updated;
|
||||
s_test_impl_data.last_touch_event = &last_touch_event;
|
||||
|
||||
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, &rec_event);
|
||||
NEW_RECOGNIZER(fail) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
recognizer_set_fail_after(r, fail);
|
||||
|
||||
new_state = RecognizerState_Completed;
|
||||
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
|
||||
cl_assert_equal_i(last_touch_event.type, TouchEvent_Liftoff);
|
||||
cl_assert_equal_b(updated, false);
|
||||
|
||||
fail->state = RecognizerState_Failed;
|
||||
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
|
||||
cl_assert_equal_i(last_touch_event.type, TouchEvent_Touchdown);
|
||||
cl_assert_equal_b(updated, true);
|
||||
cl_assert_equal_i(rec_event, RecognizerEvent_Completed);
|
||||
}
|
||||
|
||||
static bool prv_filter(const Recognizer *recognizer, const TouchEvent *touch_event) {
|
||||
bool *allow = recognizer_get_user_data(recognizer);
|
||||
return *allow;
|
||||
}
|
||||
|
||||
void test_recognizer__handle_touch_events_filter_cb(void) {
|
||||
RecognizerState new_state;
|
||||
bool updated = false;
|
||||
TouchEvent last_touch_event = { .type = TouchEvent_Liftoff };
|
||||
s_test_impl_data.new_state = &new_state;
|
||||
s_test_impl_data.updated = &updated;
|
||||
s_test_impl_data.last_touch_event = &last_touch_event;
|
||||
|
||||
bool allow = false;
|
||||
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, &allow);
|
||||
recognizer_set_touch_filter(r, prv_filter);
|
||||
|
||||
new_state = RecognizerState_Completed;
|
||||
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
|
||||
cl_assert_equal_i(last_touch_event.type, TouchEvent_Liftoff);
|
||||
cl_assert_equal_b(updated, false);
|
||||
|
||||
allow = true;
|
||||
recognizer_handle_touch_event(r, &(TouchEvent) { .type = TouchEvent_Touchdown });
|
||||
cl_assert_equal_i(last_touch_event.type, TouchEvent_Touchdown);
|
||||
cl_assert_equal_b(updated, true);
|
||||
cl_assert_equal_i(r->state, new_state);
|
||||
}
|
||||
|
||||
bool s_simultaneous = false;
|
||||
static bool prv_simultaneous_with_cb(const Recognizer *recognizer,
|
||||
const Recognizer *simultaneous_with) {
|
||||
return s_simultaneous;
|
||||
}
|
||||
|
||||
void test_recognizer__set_simultaneous_with(void) {
|
||||
NEW_RECOGNIZER(r1) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
NEW_RECOGNIZER(r2) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
|
||||
s_simultaneous = false;
|
||||
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, NULL));
|
||||
cl_assert(!recognizer_should_evaluate_simultaneously(r1, NULL));
|
||||
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, r2));
|
||||
cl_assert(!recognizer_should_evaluate_simultaneously(r1, r2));
|
||||
|
||||
recognizer_set_simultaneous_with(r1, prv_simultaneous_with_cb);
|
||||
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, NULL));
|
||||
cl_assert(!recognizer_should_evaluate_simultaneously(r1, NULL));
|
||||
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, r2));
|
||||
cl_assert(!recognizer_should_evaluate_simultaneously(r1, r2));
|
||||
|
||||
s_simultaneous = true;
|
||||
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, NULL));
|
||||
cl_assert(!recognizer_should_evaluate_simultaneously(r1, NULL));
|
||||
cl_assert(!recognizer_should_evaluate_simultaneously(NULL, r2));
|
||||
cl_assert(recognizer_should_evaluate_simultaneously(r1, r2));
|
||||
}
|
||||
|
||||
void test_recognizer__add_remove_list(void) {
|
||||
RecognizerList list = { NULL };
|
||||
NEW_RECOGNIZER(r1) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
NEW_RECOGNIZER(r2) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
|
||||
recognizer_add_to_list(r1, &list);
|
||||
recognizer_add_to_list(r2, &list);
|
||||
cl_assert_equal_i(list_count(list.node), 2);
|
||||
cl_assert(list_contains(list.node, &r1->node));
|
||||
cl_assert(list_contains(list.node, &r2->node));
|
||||
cl_assert(r1->is_owned);
|
||||
cl_assert(r2->is_owned);
|
||||
|
||||
recognizer_remove_from_list(r1, &list);
|
||||
cl_assert(!list_contains(list.node, &r1->node));
|
||||
cl_assert(!r1->is_owned);
|
||||
|
||||
recognizer_remove_from_list(r1, &list);
|
||||
cl_assert(!r1->is_owned);
|
||||
}
|
||||
|
||||
static int s_list_idx = 0;
|
||||
static bool prv_list_iterator(Recognizer *recognizer, void *context) {
|
||||
const char *names[] = { "R1", "R2", "R3" };
|
||||
cl_assert(s_list_idx < ARRAY_LENGTH(names));
|
||||
char s[20];
|
||||
snprintf(s, sizeof(s), "%s != %s", recognizer->subscriber.data, names[s_list_idx]);
|
||||
cl_assert_(strcmp(recognizer->subscriber.data, names[s_list_idx++]) == 0, s);
|
||||
|
||||
return (s_list_idx < *((int *)context));
|
||||
}
|
||||
|
||||
void test_recognizer__list_iterate(void) {
|
||||
NEW_RECOGNIZER(r1) = test_recognizer_create(&s_test_impl_data, "R1");
|
||||
NEW_RECOGNIZER(r2) = test_recognizer_create(&s_test_impl_data, "R2");
|
||||
NEW_RECOGNIZER(r3) = test_recognizer_create(&s_test_impl_data, "R3");
|
||||
|
||||
RecognizerList list = { NULL };
|
||||
recognizer_add_to_list(r1, &list);
|
||||
recognizer_add_to_list(r2, &list);
|
||||
recognizer_add_to_list(r3, &list);
|
||||
|
||||
s_list_idx = 0;
|
||||
int end = 4;
|
||||
recognizer_list_iterate(&list, prv_list_iterator, &end);
|
||||
cl_assert_equal_i(s_list_idx, 3);
|
||||
|
||||
end = 2;
|
||||
s_list_idx = 0;
|
||||
recognizer_list_iterate(&list, prv_list_iterator, &end);
|
||||
cl_assert_equal_i(s_list_idx, 2);
|
||||
}
|
112
tests/fw/ui/recognizer/test_recognizer_impl.c
Normal file
112
tests/fw/ui/recognizer/test_recognizer_impl.c
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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 "test_recognizer_impl.h"
|
||||
|
||||
#include "clar_asserts.h"
|
||||
|
||||
#include "applib/ui/recognizer/recognizer.h"
|
||||
#include "applib/ui/recognizer/recognizer_impl.h"
|
||||
|
||||
static const int TEST_PATTERN = 0xA5A5A5A5;
|
||||
|
||||
static RecognizerImpl s_test_impl;
|
||||
|
||||
static void prv_handle_touch_event(Recognizer *recognizer, const TouchEvent *touch_event) {
|
||||
TestImplData *data = recognizer_get_impl_data(recognizer, &s_test_impl);
|
||||
cl_assert_equal_i(data->test, TEST_PATTERN);
|
||||
if (data->last_touch_event) {
|
||||
*data->last_touch_event = *touch_event;
|
||||
}
|
||||
if (data->handled) {
|
||||
*data->handled = true;
|
||||
}
|
||||
if(data->new_state && (*data->new_state != RecognizerState_Possible)) {
|
||||
cl_assert(data->updated);
|
||||
cl_assert_equal_p(*data->updated, false);
|
||||
*data->updated = true;
|
||||
recognizer_transition_state(recognizer, *data->new_state);
|
||||
}
|
||||
}
|
||||
|
||||
static bool prv_cancel(Recognizer *recognizer) {
|
||||
TestImplData *data = recognizer_get_impl_data(recognizer, &s_test_impl);
|
||||
cl_assert_equal_i(data->test, TEST_PATTERN);
|
||||
if (data->cancelled) {
|
||||
cl_assert_equal_b(*data->cancelled, false);
|
||||
*data->cancelled = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_reset(Recognizer *recognizer) {
|
||||
TestImplData *data = recognizer_get_impl_data(recognizer, &s_test_impl);
|
||||
cl_assert_equal_i(data->test, TEST_PATTERN);
|
||||
if (data->reset) {
|
||||
cl_assert_equal_b(*data->reset, false);
|
||||
*data->reset = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_on_destroy(Recognizer *recognizer) {
|
||||
TestImplData *data = recognizer_get_impl_data(recognizer, &s_test_impl);
|
||||
cl_assert_equal_i(data->test, TEST_PATTERN);
|
||||
if (data->destroyed) {
|
||||
cl_assert_equal_b(*data->destroyed, false);
|
||||
*data->destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_on_fail(Recognizer *recognizer) {
|
||||
TestImplData *data = recognizer_get_impl_data(recognizer, &s_test_impl);
|
||||
cl_assert_equal_i(data->test, TEST_PATTERN);
|
||||
if (data->failed) {
|
||||
cl_assert_equal_b(*data->failed, false);
|
||||
*data->failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_sub_event_handler(const Recognizer *recognizer, RecognizerEvent event) {
|
||||
RecognizerEvent *event_type = recognizer_get_user_data(recognizer);
|
||||
if (event_type) {
|
||||
*event_type = event;
|
||||
}
|
||||
}
|
||||
|
||||
Recognizer *test_recognizer_create(TestImplData *test_impl_data, void *user_data) {
|
||||
s_test_impl = (RecognizerImpl) {
|
||||
.handle_touch_event = prv_handle_touch_event,
|
||||
.cancel = prv_cancel,
|
||||
.reset = prv_reset,
|
||||
.on_fail = prv_on_fail
|
||||
};
|
||||
test_impl_data->test = TEST_PATTERN;
|
||||
return recognizer_create_with_data(&s_test_impl, test_impl_data,
|
||||
sizeof(*test_impl_data), prv_sub_event_handler,
|
||||
user_data);
|
||||
}
|
||||
|
||||
void test_recognizer_enable_on_destroy(void) {
|
||||
s_test_impl.on_destroy = prv_on_destroy;
|
||||
}
|
||||
|
||||
void test_recognizer_destroy(Recognizer **recognizer) {
|
||||
recognizer_destroy(*recognizer);
|
||||
}
|
||||
|
||||
void *test_recognizer_get_data(Recognizer *recognizer) {
|
||||
return recognizer_get_impl_data(recognizer, &s_test_impl);
|
||||
}
|
43
tests/fw/ui/recognizer/test_recognizer_impl.h
Normal file
43
tests/fw/ui/recognizer/test_recognizer_impl.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "applib/ui/recognizer/recognizer.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define NEW_RECOGNIZER(r) \
|
||||
Recognizer *r __attribute__ ((__cleanup__(test_recognizer_destroy)))
|
||||
|
||||
typedef struct TestImplData {
|
||||
int test;
|
||||
bool *destroyed;
|
||||
bool *cancelled;
|
||||
bool *reset;
|
||||
bool *handled;
|
||||
bool *updated;
|
||||
bool *failed;
|
||||
TouchEvent *last_touch_event;
|
||||
RecognizerState *new_state;
|
||||
} TestImplData;
|
||||
|
||||
Recognizer *test_recognizer_create(TestImplData *test_impl_data, void *user_data);
|
||||
|
||||
void test_recognizer_destroy(Recognizer **recognizer);
|
||||
|
||||
void test_recognizer_enable_on_destroy(void);
|
943
tests/fw/ui/recognizer/test_recognizer_manager.c
Normal file
943
tests/fw/ui/recognizer/test_recognizer_manager.c
Normal file
|
@ -0,0 +1,943 @@
|
|||
/*
|
||||
* 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/ui/layer.h"
|
||||
#include "applib/ui/window.h"
|
||||
#include "applib/ui/recognizer/recognizer.h"
|
||||
#include "applib/ui/recognizer/recognizer_impl.h"
|
||||
#include "applib/ui/recognizer/recognizer_list.h"
|
||||
#include "applib/ui/recognizer/recognizer_manager.h"
|
||||
#include "applib/ui/recognizer/recognizer_private.h"
|
||||
#include "util/size.h"
|
||||
|
||||
|
||||
// Stubs
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_gbitmap.h"
|
||||
#include "stubs_graphics.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_process_manager.h"
|
||||
#include "stubs_ui_window.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
#include "test_recognizer_impl.h"
|
||||
|
||||
static RecognizerList *s_app_list;
|
||||
static Layer *s_active_layer;
|
||||
static RecognizerManager *s_manager;
|
||||
static TestImplData s_test_impl_data;
|
||||
|
||||
RecognizerList *app_state_get_recognizer_list(void) {
|
||||
return s_app_list;
|
||||
}
|
||||
|
||||
RecognizerList *window_get_recognizer_list(Window *window) {
|
||||
if (!window) {
|
||||
return NULL;
|
||||
}
|
||||
return layer_get_recognizer_list(&window->layer);
|
||||
}
|
||||
|
||||
RecognizerManager *window_get_recognizer_manager(Window *window) {
|
||||
return s_manager;
|
||||
}
|
||||
|
||||
struct Layer* window_get_root_layer(const Window *window) {
|
||||
if (!window) {
|
||||
return NULL;
|
||||
}
|
||||
return &((Window *)window)->layer;
|
||||
}
|
||||
|
||||
// Override find layer function so we don't have to muck around with points and layer bounds (also
|
||||
// this process can change and this test will keep on working)
|
||||
Layer *layer_find_layer_containing_point(const Layer *node, const GPoint *point) {
|
||||
return s_active_layer;
|
||||
}
|
||||
|
||||
typedef struct RecognizerHandled {
|
||||
ListNode node;
|
||||
int idx;
|
||||
} RecognizerHandled;
|
||||
|
||||
static ListNode *s_recognizers_handled;
|
||||
static ListNode *s_recognizers_reset;
|
||||
|
||||
static bool prv_simultaneous_with_cb(const Recognizer *recognizer,
|
||||
const Recognizer *simultaneous_with) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_handle_touch_event (Recognizer *recognizer, const TouchEvent *touch_event) {
|
||||
|
||||
}
|
||||
|
||||
static bool prv_cancel(Recognizer *recognizer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void prv_reset (Recognizer *recognizer) {
|
||||
|
||||
}
|
||||
|
||||
static RecognizerImpl s_dummy_impl;
|
||||
|
||||
static void prv_clear_recognizers_processed(ListNode **list) {
|
||||
ListNode *node = *list;
|
||||
while (node) {
|
||||
ListNode *next = list_pop_head(node);
|
||||
free(node);
|
||||
node = next;
|
||||
}
|
||||
*list = NULL;
|
||||
}
|
||||
|
||||
static void prv_compare_recognizers_processed(int indices[], uint32_t count, ListNode **list) {
|
||||
printf(list == &s_recognizers_handled ? "Handle touch: " : "");
|
||||
printf(list == &s_recognizers_reset ? "Reset: " : "");
|
||||
printf("{ ");
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
printf("%d, ", indices[i]);
|
||||
}
|
||||
printf("}");
|
||||
|
||||
ListNode *node = *list;
|
||||
int list_num = list_count(node);
|
||||
bool failed = list_num != count;
|
||||
if (failed) {
|
||||
count = list_num;
|
||||
}
|
||||
|
||||
if (!failed) {
|
||||
for (uint32_t i = 0; (i < count) && !failed; i++) {
|
||||
failed = (indices[i] != ((RecognizerHandled *)node)->idx);
|
||||
node = list_get_next(node);
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
node = *list;
|
||||
printf(" != { ");
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
printf("%d, ", ((RecognizerHandled *)node)->idx);
|
||||
node = list_get_next(node);
|
||||
}
|
||||
printf("}");
|
||||
}
|
||||
printf("\n");
|
||||
cl_assert(!failed);
|
||||
|
||||
prv_clear_recognizers_processed(list);
|
||||
}
|
||||
|
||||
static void prv_sub_event_handler(const Recognizer *recognizer, RecognizerEvent event) {
|
||||
|
||||
}
|
||||
|
||||
// setup and teardown
|
||||
void test_recognizer_manager__initialize(void) {
|
||||
s_test_impl_data = (TestImplData){};
|
||||
s_app_list = NULL;
|
||||
s_active_layer = NULL;
|
||||
s_manager = NULL;
|
||||
s_dummy_impl = (RecognizerImpl) {
|
||||
.handle_touch_event = prv_handle_touch_event,
|
||||
.cancel = prv_cancel,
|
||||
.reset = prv_reset,
|
||||
};
|
||||
}
|
||||
|
||||
void test_recognizer_manager__cleanup(void) {
|
||||
prv_clear_recognizers_processed(&s_recognizers_handled);
|
||||
prv_clear_recognizers_processed(&s_recognizers_reset);
|
||||
}
|
||||
|
||||
static void prv_store_recognizer_idx(Recognizer *recognizer, ListNode **list) {
|
||||
int *idx = recognizer_get_impl_data(recognizer, &s_dummy_impl);
|
||||
if (idx) {
|
||||
RecognizerHandled *rec = malloc(sizeof(RecognizerHandled));
|
||||
cl_assert(rec);
|
||||
*rec = (RecognizerHandled){ .idx = *idx };
|
||||
*list = list_get_head(list_append(*list, &rec->node));
|
||||
}
|
||||
}
|
||||
|
||||
static bool prv_handle_dummy_touch_event(Recognizer *recognizer, void *unused) {
|
||||
prv_store_recognizer_idx(recognizer, &s_recognizers_handled);
|
||||
return true;
|
||||
}
|
||||
|
||||
static Recognizer **prv_create_recognizers(int count) {
|
||||
Recognizer **recognizers = malloc(sizeof(Recognizer*) * count);
|
||||
cl_assert(recognizers);
|
||||
for (int i = 0; i < count; i++) {
|
||||
recognizers[i] = recognizer_create_with_data(&s_dummy_impl, &i,
|
||||
sizeof(i), prv_sub_event_handler,
|
||||
NULL);
|
||||
cl_assert(recognizers[i]);
|
||||
}
|
||||
return recognizers;
|
||||
}
|
||||
|
||||
static void prv_destroy_recognizers(Recognizer **recognizers, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
recognizer_destroy(recognizers[i]);
|
||||
}
|
||||
free(recognizers);
|
||||
}
|
||||
// tests
|
||||
|
||||
|
||||
|
||||
bool prv_process_all_recognizers(RecognizerManager *manager,
|
||||
RecognizerListIteratorCb iter_cb, void *context);
|
||||
|
||||
void test_recognizer_manager__process_all_recognizers(void) {
|
||||
const int k_rec_count = 7;
|
||||
Recognizer **recognizers = prv_create_recognizers(k_rec_count);
|
||||
RecognizerManager manager;
|
||||
recognizer_manager_init(&manager);
|
||||
|
||||
// ensure this runs without crashing even if there are no recognizer lists
|
||||
prv_process_all_recognizers(&manager, prv_handle_dummy_touch_event, NULL);
|
||||
|
||||
RecognizerList app_list = {};
|
||||
s_app_list = &app_list;
|
||||
Window window = {};
|
||||
layer_init(&window.layer, &GRectZero);
|
||||
manager.window = &window;
|
||||
|
||||
Layer layer_a, layer_b, layer_c;
|
||||
layer_init(&layer_a, &GRectZero);
|
||||
layer_init(&layer_b, &GRectZero);
|
||||
layer_init(&layer_c, &GRectZero);
|
||||
layer_add_child(&window.layer, &layer_a);
|
||||
layer_add_child(&layer_a, &layer_b);
|
||||
layer_add_child(&window.layer, &layer_c);
|
||||
manager.active_layer = &layer_c;
|
||||
|
||||
// ensure that this runs without crashing even if all the lists are empty
|
||||
prv_process_all_recognizers(&manager, prv_handle_dummy_touch_event, NULL);
|
||||
|
||||
// One recognizer attached to the active layer
|
||||
recognizer_add_to_list(recognizers[0], &layer_c.recognizer_list);
|
||||
cl_assert(prv_process_all_recognizers(&manager, prv_handle_dummy_touch_event, NULL));
|
||||
prv_compare_recognizers_processed((int[]) {0}, 1, &s_recognizers_handled);
|
||||
|
||||
// Two recognizers attached to the active layer - processed in order that they were added
|
||||
recognizer_add_to_list(recognizers[1], &layer_c.recognizer_list);
|
||||
cl_assert(prv_process_all_recognizers(&manager, prv_handle_dummy_touch_event, NULL));
|
||||
prv_compare_recognizers_processed((int[]) {0, 1}, 2, &s_recognizers_handled);
|
||||
|
||||
// Recognizers that attached to layers other than the active layer and its ancestors will not be
|
||||
// processed
|
||||
recognizer_add_to_list(recognizers[2], &layer_a.recognizer_list);
|
||||
recognizer_add_to_list(recognizers[3], &layer_a.recognizer_list);
|
||||
recognizer_add_to_list(recognizers[4], &layer_b.recognizer_list);
|
||||
cl_assert(prv_process_all_recognizers(&manager, prv_handle_dummy_touch_event, NULL));
|
||||
prv_compare_recognizers_processed((int[]) {0, 1}, 2, &s_recognizers_handled);
|
||||
|
||||
// Recognizers attached to children of active layer will not be evaluated
|
||||
manager.active_layer = &layer_a;
|
||||
cl_assert(prv_process_all_recognizers(&manager, prv_handle_dummy_touch_event, NULL));
|
||||
prv_compare_recognizers_processed((int[]) {2, 3}, 2, &s_recognizers_handled);
|
||||
|
||||
// Recognizers attached to active layer will be processed before those attached to their ancestors
|
||||
manager.active_layer = &layer_b;
|
||||
cl_assert(prv_process_all_recognizers(&manager, prv_handle_dummy_touch_event, NULL));
|
||||
prv_compare_recognizers_processed((int[]) {4, 2, 3}, 3, &s_recognizers_handled);
|
||||
|
||||
// Recognizers attached to window processed before layer recognizers
|
||||
recognizer_add_to_list(recognizers[5], window_get_recognizer_list(&window));
|
||||
cl_assert(prv_process_all_recognizers(&manager, prv_handle_dummy_touch_event, NULL));
|
||||
prv_compare_recognizers_processed((int[]) {5, 4, 2, 3}, 4, &s_recognizers_handled);
|
||||
|
||||
// Recognizers attached to app processed before window and layer recognizers
|
||||
recognizer_add_to_list(recognizers[6], &app_list);
|
||||
cl_assert(prv_process_all_recognizers(&manager, prv_handle_dummy_touch_event, NULL));
|
||||
prv_compare_recognizers_processed((int[]) {6, 5, 4, 2, 3}, 5, &s_recognizers_handled);
|
||||
|
||||
prv_destroy_recognizers(recognizers, k_rec_count);
|
||||
}
|
||||
|
||||
bool prv_dispatch_touch_event(Recognizer *recognizer, void *context);
|
||||
|
||||
void test_recognizer_manager__dispatch_touch_event(void) {
|
||||
bool handled = false;
|
||||
s_test_impl_data.handled = &handled;
|
||||
NEW_RECOGNIZER(r) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
|
||||
// Copied from recognizer_manager.c
|
||||
TouchEvent t;
|
||||
struct ProcessTouchCtx {
|
||||
Recognizer *triggered;
|
||||
const TouchEvent *touch_event;
|
||||
} ctx = { .triggered = NULL, .touch_event = &t };
|
||||
|
||||
cl_assert(prv_dispatch_touch_event(r, &ctx));
|
||||
cl_assert(handled);
|
||||
cl_assert(!ctx.triggered);
|
||||
|
||||
handled = false;
|
||||
// Recognizer should not get a touch event when it is in inactive states
|
||||
r->state = RecognizerState_Failed;
|
||||
cl_assert(prv_dispatch_touch_event(r, &ctx));
|
||||
cl_assert(!handled);
|
||||
cl_assert(!ctx.triggered);
|
||||
|
||||
r->state = RecognizerState_Cancelled;
|
||||
cl_assert(prv_dispatch_touch_event(r, &ctx));
|
||||
cl_assert(!handled);
|
||||
cl_assert(!ctx.triggered);
|
||||
|
||||
r->state = RecognizerState_Completed;
|
||||
cl_assert(prv_dispatch_touch_event(r, &ctx));
|
||||
cl_assert(!handled);
|
||||
cl_assert(!ctx.triggered);
|
||||
|
||||
r->state = RecognizerState_Started;
|
||||
cl_assert(prv_dispatch_touch_event(r, &ctx));
|
||||
cl_assert(handled);
|
||||
cl_assert_equal_p(ctx.triggered, r);
|
||||
ctx.triggered = NULL;
|
||||
handled = false;
|
||||
|
||||
r->state = RecognizerState_Updated;
|
||||
cl_assert(prv_dispatch_touch_event(r, &ctx));
|
||||
cl_assert(handled);
|
||||
cl_assert_equal_p(ctx.triggered, r);
|
||||
handled = false;
|
||||
ctx.triggered = NULL;
|
||||
|
||||
NEW_RECOGNIZER(s) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
s->state = RecognizerState_Started;
|
||||
r->state = RecognizerState_Possible;
|
||||
ctx.triggered = s;
|
||||
cl_assert(prv_dispatch_touch_event(r, &ctx));
|
||||
cl_assert(!handled);
|
||||
|
||||
recognizer_set_simultaneous_with(r, prv_simultaneous_with_cb);
|
||||
cl_assert(prv_dispatch_touch_event(r, &ctx));
|
||||
cl_assert(handled);
|
||||
cl_assert_equal_p(ctx.triggered, s);
|
||||
}
|
||||
|
||||
|
||||
bool prv_fail_recognizer(Recognizer *recognizer, void *context);
|
||||
|
||||
void test_recognizer_manager__fail_recognizer(void) {
|
||||
NEW_RECOGNIZER(r1) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
NEW_RECOGNIZER(r2) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
r2->state = RecognizerState_Started;
|
||||
|
||||
// copied from recognizer_manager.c
|
||||
struct FailRecognizerCtx {
|
||||
Recognizer *triggered;
|
||||
bool recognizers_active;
|
||||
} ctx = { .triggered = r2, .recognizers_active = false };
|
||||
|
||||
cl_assert(prv_fail_recognizer(r2, &ctx));
|
||||
cl_assert_equal_i(r2->state, RecognizerState_Started);
|
||||
cl_assert(!ctx.recognizers_active);
|
||||
|
||||
ctx.recognizers_active = false;
|
||||
r1->state = RecognizerState_Possible;
|
||||
cl_assert(prv_fail_recognizer(r1, &ctx));
|
||||
cl_assert_equal_i(r1->state, RecognizerState_Failed);
|
||||
cl_assert(!ctx.recognizers_active);
|
||||
|
||||
// Make sure that we don't try to fail a recognizer twice (causing an assert)
|
||||
cl_assert(prv_fail_recognizer(r1, &ctx));
|
||||
cl_assert_equal_i(r1->state, RecognizerState_Failed);
|
||||
|
||||
r1->state = RecognizerState_Possible;
|
||||
recognizer_set_simultaneous_with(r1, prv_simultaneous_with_cb);
|
||||
cl_assert(prv_fail_recognizer(r1, &ctx));
|
||||
cl_assert_equal_i(r1->state, RecognizerState_Possible);
|
||||
cl_assert(ctx.recognizers_active);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void prv_cancel_layer_tree_recognizers(RecognizerManager *manager, Layer *top_layer,
|
||||
Layer *bottom_layer);
|
||||
|
||||
static void prv_set_all_states(Recognizer **recognizers, int count, RecognizerState state) {
|
||||
for(int i = 0; i < count; i++) {
|
||||
recognizers[i]->state = state;
|
||||
}
|
||||
}
|
||||
|
||||
void test_recognizer_manager__cancel_layer_tree_recognizers(void) {
|
||||
const int k_rec_count = 4;
|
||||
Recognizer **recognizers = prv_create_recognizers(k_rec_count);
|
||||
|
||||
Window window = {};
|
||||
layer_init(&window.layer, &GRectZero);
|
||||
Layer *root = &window.layer;
|
||||
RecognizerManager manager;
|
||||
recognizer_manager_init(&manager);
|
||||
manager.window = &window;
|
||||
|
||||
Layer layer_a, layer_b, layer_c;
|
||||
layer_init(&layer_a, &GRectZero);
|
||||
layer_init(&layer_b, &GRectZero);
|
||||
layer_init(&layer_c, &GRectZero);
|
||||
layer_add_child(root, &layer_a);
|
||||
layer_add_child(root, &layer_b);
|
||||
layer_add_child(&layer_a, &layer_c);
|
||||
|
||||
recognizer_add_to_list(recognizers[0], window_get_recognizer_list(&window));
|
||||
recognizer_add_to_list(recognizers[1], &layer_a.recognizer_list);
|
||||
recognizer_add_to_list(recognizers[2], &layer_b.recognizer_list);
|
||||
recognizer_add_to_list(recognizers[3], &layer_c.recognizer_list);
|
||||
|
||||
prv_set_all_states(recognizers, k_rec_count, RecognizerState_Started);
|
||||
|
||||
// Layer C's recognizers reset when layer A becomes the new active layer
|
||||
manager.active_layer = &layer_c;
|
||||
prv_cancel_layer_tree_recognizers(&manager, &layer_a, &layer_c);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[2]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Cancelled);
|
||||
|
||||
// Layer C's and layer A's recognizers get reset when layer B becomes the new active layer
|
||||
prv_set_all_states(recognizers, k_rec_count, RecognizerState_Started);
|
||||
prv_cancel_layer_tree_recognizers(&manager, &layer_b, &layer_c);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Cancelled);
|
||||
cl_assert_equal_i(recognizers[2]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Cancelled);
|
||||
|
||||
// Layer C's and layer A's recognizers get cancelled when there is no new active layer
|
||||
prv_set_all_states(recognizers, k_rec_count, RecognizerState_Started);
|
||||
prv_cancel_layer_tree_recognizers(&manager, NULL, &layer_c);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Cancelled);
|
||||
cl_assert_equal_i(recognizers[2]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Cancelled);
|
||||
|
||||
// If recognizers are in the possible state, they will be failed, rather than cancelled
|
||||
prv_set_all_states(recognizers, k_rec_count, RecognizerState_Possible);
|
||||
prv_cancel_layer_tree_recognizers(&manager, NULL, &layer_c);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[2]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Failed);
|
||||
|
||||
}
|
||||
|
||||
static RecognizerState s_next_state = RecognizerStateCount;
|
||||
static int s_idx_to_change = -1;
|
||||
static void prv_handle_touch_event_test(Recognizer *recognizer, const TouchEvent *touch_event) {
|
||||
int *idx = recognizer_get_impl_data(recognizer, &s_dummy_impl);
|
||||
prv_store_recognizer_idx(recognizer, &s_recognizers_handled);
|
||||
if ((s_idx_to_change >= 0) && (*idx == s_idx_to_change)) {
|
||||
recognizer_transition_state(recognizer, s_next_state);
|
||||
s_idx_to_change = -1;
|
||||
s_next_state = RecognizerStateCount;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_reset_test(Recognizer *recognizer) {
|
||||
prv_store_recognizer_idx(recognizer, &s_recognizers_reset);
|
||||
}
|
||||
|
||||
void test_recognizer_manager__handle_touch_event(void) {
|
||||
const int k_rec_count = 5;
|
||||
s_dummy_impl.handle_touch_event = prv_handle_touch_event_test;
|
||||
s_dummy_impl.reset = prv_reset_test;
|
||||
Recognizer **recognizers = prv_create_recognizers(k_rec_count);
|
||||
|
||||
RecognizerList app_list = {};
|
||||
s_app_list = &app_list;
|
||||
|
||||
Window window = {};
|
||||
layer_init(&window.layer, &GRectZero);
|
||||
Layer *root = &window.layer;
|
||||
RecognizerManager manager;
|
||||
recognizer_manager_init(&manager);
|
||||
manager.window = &window;
|
||||
|
||||
Layer layer_a, layer_b, layer_c;
|
||||
layer_init(&layer_a, &GRectZero);
|
||||
layer_init(&layer_b, &GRectZero);
|
||||
layer_init(&layer_c, &GRectZero);
|
||||
layer_add_child(root, &layer_a);
|
||||
layer_add_child(root, &layer_b);
|
||||
layer_add_child(&layer_a, &layer_c);
|
||||
|
||||
recognizer_add_to_list(recognizers[0], window_get_recognizer_list(&window));
|
||||
recognizer_add_to_list(recognizers[1], &layer_a.recognizer_list);
|
||||
recognizer_add_to_list(recognizers[2], &layer_b.recognizer_list);
|
||||
recognizer_add_to_list(recognizers[3], &layer_c.recognizer_list);
|
||||
recognizer_add_to_list(recognizers[4], s_app_list);
|
||||
|
||||
s_active_layer = &layer_c;
|
||||
TouchEvent e = { .type = TouchEvent_PositionUpdate };
|
||||
|
||||
// No active recognizers because manager is waiting for a touchdown event
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_handled);
|
||||
|
||||
// Touchdown event occurs, active layer is found and all applicable recognizers receive events
|
||||
// while none have started recognizing
|
||||
e.type = TouchEvent_Touchdown;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 3, 1}, 4, &s_recognizers_handled);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_c);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersActive);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Possible);
|
||||
|
||||
// All recognizers receive events while none have started recognizing
|
||||
e.type = TouchEvent_PositionUpdate;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 3, 1}, 4, &s_recognizers_handled);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_c);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersActive);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Possible);
|
||||
|
||||
// Same as above. Different event type
|
||||
e.type = TouchEvent_Liftoff;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 3, 1}, 4, &s_recognizers_handled);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_c);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersActive);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Possible);
|
||||
|
||||
// Layer A recognizer's gesture starts to be recognized. All other recognizers failed
|
||||
e.type = TouchEvent_Touchdown;
|
||||
s_next_state = RecognizerState_Started;
|
||||
s_idx_to_change = 3;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 3}, 3, &s_recognizers_handled);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_c);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Failed);
|
||||
|
||||
// Only layer A recognizer's gesture receives touch events
|
||||
e.type = TouchEvent_PositionUpdate;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {3}, 1, &s_recognizers_handled);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_c);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Failed);
|
||||
|
||||
// Layer A recognizer's gesture updates. Only that recognizer receives touch events
|
||||
e.type = TouchEvent_Liftoff;
|
||||
s_next_state = RecognizerState_Updated;
|
||||
s_idx_to_change = 3;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {3}, 1, &s_recognizers_handled);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_c);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Updated);
|
||||
|
||||
// Layer A recognizer's gesture completes and all recognizers are reset
|
||||
e.type = TouchEvent_Liftoff;
|
||||
s_next_state = RecognizerState_Completed;
|
||||
s_idx_to_change = 3;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {3}, 1, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 3, 1}, 4, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_WaitForTouchdown);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[2]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Possible);
|
||||
|
||||
// Layer A recognizer's gesture does not complete because there is no active layer until a
|
||||
// touchdown occurs
|
||||
e.type = TouchEvent_PositionUpdate;
|
||||
s_next_state = RecognizerState_Completed;
|
||||
s_idx_to_change = 3;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_WaitForTouchdown);
|
||||
|
||||
// Layer A's recognizer's gesture completes immediately. All recognizers receive the touch event
|
||||
// because Layer A's recognizers receive the touch events last. All recognizers in the chain are
|
||||
// reset
|
||||
e.type = TouchEvent_Touchdown;
|
||||
s_next_state = RecognizerState_Completed;
|
||||
s_idx_to_change = 1;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 3, 1}, 4, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 3, 1}, 4, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_WaitForTouchdown);
|
||||
|
||||
// The app's recognizer's gesture completes immediately. Only the app's recognizer sees the touch
|
||||
// events. All recognizers in the chain are reset
|
||||
e.type = TouchEvent_Touchdown;
|
||||
s_next_state = RecognizerState_Completed;
|
||||
s_idx_to_change = 4;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4}, 1, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 3, 1}, 4, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_WaitForTouchdown);
|
||||
|
||||
// Layer C recognizer starts recognizing a gesture, failing other recognizers
|
||||
e.type = TouchEvent_Touchdown;
|
||||
s_next_state = RecognizerState_Started;
|
||||
s_idx_to_change = 1;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 3, 1}, 4, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Failed);
|
||||
|
||||
// A second touchdown event occurs while recognizers are active. A different layer is touched, so
|
||||
// the active recognizers on non-touched layers in the tree are cancelled
|
||||
s_active_layer = &layer_b;
|
||||
e.type = TouchEvent_Touchdown;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 2}, 3, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 2}, 3, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersActive);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Cancelled);
|
||||
cl_assert_equal_i(recognizers[2]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Possible);
|
||||
|
||||
// Window recognizer becomes triggered
|
||||
e.type = TouchEvent_PositionUpdate;
|
||||
s_next_state = RecognizerState_Started;
|
||||
s_idx_to_change = 0;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0 }, 2, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[2]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Failed);
|
||||
|
||||
// Another layer in a separate branch becomes active while a window recognizer is triggered
|
||||
e.type = TouchEvent_Touchdown;
|
||||
s_active_layer = &layer_a;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) { 0 }, 1, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Cancelled); // was already cancelled
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Failed);
|
||||
|
||||
// A child layer of the active layer becomes active when a window recognizer is triggered
|
||||
e.type = TouchEvent_Touchdown;
|
||||
s_active_layer = &layer_c;
|
||||
recognizers[3]->state = RecognizerState_Possible;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) { 0 }, 1, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Cancelled);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Failed);
|
||||
|
||||
// A touchdown occurs where no layers are touched while a window recognizer is active
|
||||
e.type = TouchEvent_Touchdown;
|
||||
s_active_layer = NULL;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) { 0 }, 1, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Cancelled);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Failed);
|
||||
|
||||
// Touchdown occurs, Window recognizer completes, active layer becomes non-null
|
||||
e.type = TouchEvent_Touchdown;
|
||||
s_next_state = RecognizerState_Completed;
|
||||
s_idx_to_change = 0;
|
||||
s_active_layer = &layer_a;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) { 0 }, 1, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed((int[]) { 4, 0, 1 }, 3, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_WaitForTouchdown);
|
||||
cl_assert_equal_p(manager.triggered, NULL);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Possible);
|
||||
|
||||
// A touchdown occurs where no layers are touched
|
||||
s_active_layer = NULL;
|
||||
e.type = TouchEvent_Touchdown;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0}, 2, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersActive);
|
||||
cl_assert_equal_p(manager.triggered, NULL);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Possible);
|
||||
|
||||
// A touchdown occurs and the active layer goes from non-null to null. All layer recognizers get
|
||||
// reset. All recognizers remain in the possible state.
|
||||
s_active_layer = &layer_a;
|
||||
e.type = TouchEvent_Touchdown;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 1}, 3, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed((int[]) {1}, 1, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersActive);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Possible);
|
||||
|
||||
// A touchdown occurs and a child of the previous active recognizer becomes the active layer. The
|
||||
// child is reset. All recognizers remain in the possible state.
|
||||
s_active_layer = &layer_c;
|
||||
e.type = TouchEvent_Touchdown;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 3, 1}, 4, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed((int[]) {3}, 1, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersActive);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Possible);
|
||||
|
||||
// A touchdown occurs and the parent of the previous active recognizer becomes the active layer.
|
||||
// No recognizers are reset and all recognizers remain in the possible state. The child is failed.
|
||||
s_active_layer = &layer_a;
|
||||
e.type = TouchEvent_Touchdown;
|
||||
recognizer_manager_handle_touch_event(&e, &manager);
|
||||
prv_compare_recognizers_processed((int[]) {4, 0, 1}, 3, &s_recognizers_handled);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersActive);
|
||||
cl_assert_equal_i(recognizers[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[1]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(recognizers[3]->state, RecognizerState_Failed);
|
||||
cl_assert_equal_i(recognizers[4]->state, RecognizerState_Possible);
|
||||
|
||||
prv_destroy_recognizers(recognizers, k_rec_count);
|
||||
}
|
||||
|
||||
void test_recognizer_manager__deregister_recognizer(void) {
|
||||
NEW_RECOGNIZER(r1) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
NEW_RECOGNIZER(r2) = test_recognizer_create(&s_test_impl_data, NULL);
|
||||
|
||||
Window window = {};
|
||||
layer_init(&window.layer, &GRectZero);
|
||||
Layer *root = &window.layer;
|
||||
RecognizerManager manager;
|
||||
recognizer_manager_init(&manager);
|
||||
|
||||
Layer layer_a;
|
||||
layer_init(&layer_a, &GRectZero);
|
||||
layer_add_child(root, &layer_a);
|
||||
|
||||
manager.window = &window;
|
||||
manager.active_layer = &layer_a;
|
||||
|
||||
recognizer_add_to_list(r1, &layer_a.recognizer_list);
|
||||
recognizer_add_to_list(r2, &layer_a.recognizer_list);
|
||||
|
||||
RecognizerManager manager2;
|
||||
recognizer_set_manager(r1, &manager2);
|
||||
|
||||
recognizer_manager_deregister_recognizer(&manager, r1);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_a);
|
||||
cl_assert_equal_p(recognizer_get_manager(r1), &manager2);
|
||||
|
||||
recognizer_set_manager(r1, &manager);
|
||||
|
||||
recognizer_manager_deregister_recognizer(&manager, r1);
|
||||
cl_assert(!recognizer_get_manager(r1));
|
||||
cl_assert_equal_p(manager.active_layer, &layer_a);
|
||||
|
||||
recognizer_set_manager(r1, &manager);
|
||||
r1->state = RecognizerState_Started;
|
||||
r2->state = RecognizerState_Failed;
|
||||
manager.triggered = r1;
|
||||
manager.state = RecognizerManagerState_RecognizersTriggered;
|
||||
recognizer_manager_deregister_recognizer(&manager, r1);
|
||||
cl_assert(!manager.triggered);
|
||||
cl_assert(!manager.active_layer);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_WaitForTouchdown);
|
||||
cl_assert_equal_i(r2->state, RecognizerState_Possible);
|
||||
cl_assert(!recognizer_get_manager(r1));
|
||||
|
||||
recognizer_set_manager(r1, &manager);
|
||||
r1->state = RecognizerState_Possible;
|
||||
r2->state = RecognizerState_Started;
|
||||
manager.active_layer = &layer_a;
|
||||
manager.triggered = r2;
|
||||
manager.state = RecognizerManagerState_RecognizersTriggered;
|
||||
recognizer_manager_deregister_recognizer(&manager, r1);
|
||||
cl_assert_equal_p(manager.triggered, r2);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_a);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_i(r2->state, RecognizerState_Started);
|
||||
cl_assert(!recognizer_get_manager(r1));
|
||||
|
||||
recognizer_set_manager(r1, &manager);
|
||||
recognizer_set_simultaneous_with(r2, prv_simultaneous_with_cb);
|
||||
r1->state = RecognizerState_Started;
|
||||
r2->state = RecognizerState_Started;
|
||||
manager.triggered = r1;
|
||||
manager.state = RecognizerManagerState_RecognizersTriggered;
|
||||
recognizer_manager_deregister_recognizer(&manager, r1);
|
||||
cl_assert_equal_p(manager.triggered, r2);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_a);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_i(r2->state, RecognizerState_Started);
|
||||
cl_assert(!recognizer_get_manager(r1));
|
||||
}
|
||||
|
||||
void test_recognizer_manager__handle_state_change(void) {
|
||||
const int k_rec_count = 2;
|
||||
s_dummy_impl.handle_touch_event = prv_handle_touch_event_test;
|
||||
s_dummy_impl.reset = prv_reset_test;
|
||||
Recognizer **r = prv_create_recognizers(k_rec_count);
|
||||
|
||||
Window window = {};
|
||||
layer_init(&window.layer, &GRectZero);
|
||||
Layer *root = &window.layer;
|
||||
RecognizerManager manager;
|
||||
recognizer_manager_init(&manager);
|
||||
|
||||
Layer layer_a;
|
||||
layer_init(&layer_a, &GRectZero);
|
||||
layer_add_child(root, &layer_a);
|
||||
|
||||
manager.window = &window;
|
||||
manager.active_layer = &layer_a;
|
||||
manager.state = RecognizerManagerState_RecognizersActive;
|
||||
|
||||
recognizer_add_to_list(r[0], &layer_a.recognizer_list);
|
||||
recognizer_add_to_list(r[1], &layer_a.recognizer_list);
|
||||
|
||||
recognizer_set_manager(r[0], &manager);
|
||||
recognizer_set_manager(r[1], &manager);
|
||||
|
||||
r[0]->state = RecognizerState_Failed;
|
||||
recognizer_manager_handle_state_change(&manager, r[0]);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersActive);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_a);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
|
||||
r[1]->state = RecognizerState_Failed;
|
||||
recognizer_manager_handle_state_change(&manager, r[1]);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_WaitForTouchdown);
|
||||
cl_assert_equal_p(manager.active_layer, NULL);
|
||||
prv_compare_recognizers_processed((int []) { 0, 1 }, 2, &s_recognizers_reset);
|
||||
|
||||
manager.active_layer = &layer_a;
|
||||
manager.state = RecognizerManagerState_RecognizersActive;
|
||||
manager.triggered = NULL;
|
||||
r[0]->state = RecognizerState_Started;
|
||||
r[1]->state = RecognizerState_Possible;
|
||||
recognizer_manager_handle_state_change(&manager, r[0]);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_p(manager.triggered, r[0]);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_a);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
cl_assert_equal_i(r[0]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(r[1]->state, RecognizerState_Failed);
|
||||
|
||||
r[0]->state = RecognizerState_Updated;
|
||||
recognizer_manager_handle_state_change(&manager, r[0]);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_p(manager.triggered, r[0]);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_a);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
|
||||
r[0]->state = RecognizerState_Completed;
|
||||
recognizer_manager_handle_state_change(&manager, r[0]);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_WaitForTouchdown);
|
||||
cl_assert_equal_p(manager.triggered, NULL);
|
||||
cl_assert_equal_p(manager.active_layer, NULL);
|
||||
prv_compare_recognizers_processed((int []) { 0, 1 }, 2, &s_recognizers_reset);
|
||||
cl_assert_equal_i(r[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(r[1]->state, RecognizerState_Possible);
|
||||
|
||||
r[0]->state = RecognizerState_Completed;
|
||||
manager.active_layer = &layer_a;
|
||||
manager.state = RecognizerManagerState_RecognizersActive;
|
||||
manager.triggered = NULL;
|
||||
recognizer_manager_handle_state_change(&manager, r[0]);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_WaitForTouchdown);
|
||||
cl_assert_equal_p(manager.triggered, NULL);
|
||||
cl_assert_equal_p(manager.active_layer, NULL);
|
||||
prv_compare_recognizers_processed((int []) { 0, 1 }, 2, &s_recognizers_reset);
|
||||
cl_assert_equal_i(r[0]->state, RecognizerState_Possible);
|
||||
cl_assert_equal_i(r[1]->state, RecognizerState_Possible);
|
||||
|
||||
r[0]->state = RecognizerState_Cancelled;
|
||||
manager.active_layer = &layer_a;
|
||||
manager.state = RecognizerManagerState_RecognizersActive;
|
||||
manager.triggered = r[0];
|
||||
recognizer_manager_handle_state_change(&manager, r[0]);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_WaitForTouchdown);
|
||||
cl_assert_equal_p(manager.triggered, NULL);
|
||||
cl_assert_equal_p(manager.active_layer, NULL);
|
||||
prv_compare_recognizers_processed((int []) { 0, 1 }, 2, &s_recognizers_reset);
|
||||
|
||||
recognizer_set_simultaneous_with(r[0], prv_simultaneous_with_cb);
|
||||
r[0]->state = RecognizerState_Started;
|
||||
r[1]->state = RecognizerState_Completed;
|
||||
manager.active_layer = &layer_a;
|
||||
manager.state = RecognizerManagerState_RecognizersTriggered;
|
||||
manager.triggered = r[0];
|
||||
recognizer_manager_handle_state_change(&manager, r[1]);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_p(manager.triggered, r[0]);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_a);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
cl_assert_equal_i(r[0]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(r[1]->state, RecognizerState_Completed);
|
||||
|
||||
recognizer_set_simultaneous_with(r[0], prv_simultaneous_with_cb);
|
||||
r[0]->state = RecognizerState_Started;
|
||||
r[1]->state = RecognizerState_Completed;
|
||||
manager.active_layer = &layer_a;
|
||||
manager.state = RecognizerManagerState_RecognizersTriggered;
|
||||
manager.triggered = r[1];
|
||||
recognizer_manager_handle_state_change(&manager, r[1]);
|
||||
cl_assert_equal_i(manager.state, RecognizerManagerState_RecognizersTriggered);
|
||||
cl_assert_equal_p(manager.triggered, r[0]);
|
||||
cl_assert_equal_p(manager.active_layer, &layer_a);
|
||||
prv_compare_recognizers_processed(NULL, 0, &s_recognizers_reset);
|
||||
cl_assert_equal_i(r[0]->state, RecognizerState_Started);
|
||||
cl_assert_equal_i(r[1]->state, RecognizerState_Completed);
|
||||
}
|
17
tests/fw/ui/recognizer/wscript
Normal file
17
tests/fw/ui/recognizer/wscript
Normal file
|
@ -0,0 +1,17 @@
|
|||
from waftools.pebble_test import clar
|
||||
|
||||
def build(ctx):
|
||||
clar(ctx,
|
||||
sources_ant_glob = " src/fw/applib/ui/recognizer/recognizer.c" \
|
||||
" tests/fw/ui/recognizer/test_recognizer_impl.c",
|
||||
test_sources_ant_glob = "test_recognizer.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = " src/fw/applib/graphics/gtypes.c" \
|
||||
" src/fw/applib/ui/layer.c" \
|
||||
" src/fw/applib/ui/recognizer/recognizer.c" \
|
||||
" src/fw/applib/ui/recognizer/recognizer_manager.c" \
|
||||
" tests/fw/ui/recognizer/test_recognizer_impl.c",
|
||||
defines = ['CAPABILITY_HAS_TOUCHSCREEN'],
|
||||
test_sources_ant_glob = "test_recognizer_manager.c")
|
||||
# vim:filetype=python
|
526
tests/fw/ui/test_action_menu_window.c
Normal file
526
tests/fw/ui/test_action_menu_window.c
Normal file
|
@ -0,0 +1,526 @@
|
|||
/*
|
||||
* 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/action_menu_hierarchy.h"
|
||||
#include "applib/ui/action_menu_layer.h"
|
||||
#include "applib/ui/action_menu_window.h"
|
||||
#include "applib/ui/action_menu_window_private.h"
|
||||
#include "applib/ui/app_window_stack.h"
|
||||
#include "applib/ui/content_indicator.h"
|
||||
#include "applib/ui/content_indicator_private.h"
|
||||
#include "apps/system_apps/settings/settings_notifications_private.h"
|
||||
#include "resource/resource.h"
|
||||
#include "shell/system_theme.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/graphics.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static GContext s_ctx;
|
||||
|
||||
// Fakes
|
||||
/////////////////////
|
||||
|
||||
#include "fake_content_indicator.h"
|
||||
#include "fake_spi_flash.h"
|
||||
#include "../../fixtures/load_test_resources.h"
|
||||
|
||||
GContext *graphics_context_get_current_context(void) {
|
||||
return &s_ctx;
|
||||
}
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_app_install_manager.h"
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_app_timer.h"
|
||||
#include "stubs_bootbits.h"
|
||||
#include "stubs_buffer.h"
|
||||
#include "stubs_click.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_layer.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_status_bar_layer.h"
|
||||
#include "stubs_syscall_internal.h"
|
||||
#include "stubs_syscalls.h"
|
||||
#include "stubs_task_watchdog.h"
|
||||
#include "stubs_window_manager.h"
|
||||
#include "stubs_window_stack.h"
|
||||
|
||||
int16_t interpolate_int16(int32_t normalized, int16_t from, int16_t to) {
|
||||
return to;
|
||||
}
|
||||
|
||||
AnimationProgress animation_timing_scaled(AnimationProgress time_normalized,
|
||||
AnimationProgress interval_start,
|
||||
AnimationProgress interval_end) {
|
||||
return interval_end;
|
||||
}
|
||||
|
||||
int64_t interpolate_moook(int32_t normalized, int64_t from, int64_t to) {
|
||||
return to;
|
||||
}
|
||||
|
||||
uint32_t interpolate_moook_duration() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Helper Functions
|
||||
/////////////////////
|
||||
|
||||
#include "../graphics/test_graphics.h"
|
||||
#include "../graphics/util.h"
|
||||
|
||||
// Setup and Teardown
|
||||
////////////////////////////////////
|
||||
|
||||
static FrameBuffer *fb = NULL;
|
||||
static GBitmap *s_dest_bitmap;
|
||||
|
||||
// To easily render multiple windows in a single canvas, we'll use an 8-bit bitmap for color
|
||||
// displays (including round), but we can use the native format for black and white displays (1-bit)
|
||||
#define CANVAS_GBITMAP_FORMAT PBL_IF_COLOR_ELSE(GBitmapFormat8Bit, GBITMAP_NATIVE_FORMAT)
|
||||
|
||||
// Overrides same function in graphics.c; we need to do this so we can pass in the GBitmapFormat
|
||||
// we need to use for the unit test output canvas instead of relying on GBITMAP_NATIVE_FORMAT, which
|
||||
// wouldn't work for Spalding since it uses GBitmapFormat8BitCircular
|
||||
GBitmap* graphics_capture_frame_buffer(GContext *ctx) {
|
||||
PBL_ASSERTN(ctx);
|
||||
return graphics_capture_frame_buffer_format(ctx, CANVAS_GBITMAP_FORMAT);
|
||||
}
|
||||
|
||||
// Overrides same function in graphics.c; we need to do this so we can release the framebuffer we're
|
||||
// using even though its format doesn't match GBITMAP_NATIVE_FORMAT (see comment for mocked
|
||||
// graphics_capture_frame_buffer() above)
|
||||
bool graphics_release_frame_buffer(GContext *ctx, GBitmap *buffer) {
|
||||
PBL_ASSERTN(ctx);
|
||||
ctx->lock = false;
|
||||
framebuffer_dirty_all(ctx->parent_framebuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void test_action_menu_window__initialize(void) {
|
||||
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();
|
||||
}
|
||||
|
||||
void test_action_menu_window__cleanup(void) {
|
||||
free(fb);
|
||||
|
||||
gbitmap_destroy(s_dest_bitmap);
|
||||
s_dest_bitmap = NULL;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
//////////////////////
|
||||
|
||||
static void prv_action_menu_did_close_cb(ActionMenu *action_menu,
|
||||
const ActionMenuItem *item,
|
||||
void *context) {
|
||||
ActionMenuLevel *root_level = action_menu_get_root_level(action_menu);
|
||||
action_menu_hierarchy_destroy(root_level, NULL, NULL);
|
||||
}
|
||||
|
||||
static void prv_noop_action_callback(ActionMenu *action_menu, const ActionMenuItem *action,
|
||||
void *context) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
// From action_menu_layer.c, needed to scroll the action menu layer to the point of interest
|
||||
void prv_set_selected_index(ActionMenuLayer *aml, int selected_index, bool animated);
|
||||
|
||||
typedef enum {
|
||||
ActionMenuLayerLongLabelScrollingAnimationState_Top,
|
||||
ActionMenuLayerLongLabelScrollingAnimationState_Middle,
|
||||
ActionMenuLayerLongLabelScrollingAnimationState_Bottom,
|
||||
ActionMenuLayerLongLabelScrollingAnimationStateCount
|
||||
} ActionMenuLayerLongLabelScrollingAnimationState;
|
||||
|
||||
void prv_set_cell_offset(void *subject, int16_t value);
|
||||
|
||||
static void prv_update_cell_for_long_label_scrolling_animation_state(
|
||||
ActionMenuLayer *aml, ActionMenuLayerLongLabelScrollingAnimationState state) {
|
||||
const bool item_animation_is_valid = (aml && aml->item_animation.animation);
|
||||
if (item_animation_is_valid) {
|
||||
ActionMenuItemAnimation *item_animation = &aml->item_animation;
|
||||
int16_t new_cell_origin_y = 0;
|
||||
switch (state) {
|
||||
case ActionMenuLayerLongLabelScrollingAnimationState_Top:
|
||||
new_cell_origin_y = item_animation->bottom_offset_y;
|
||||
break;
|
||||
case ActionMenuLayerLongLabelScrollingAnimationState_Middle:
|
||||
new_cell_origin_y = (item_animation->top_offset_y + item_animation->bottom_offset_y) / 2;
|
||||
break;
|
||||
case ActionMenuLayerLongLabelScrollingAnimationState_Bottom:
|
||||
new_cell_origin_y = item_animation->top_offset_y;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
prv_set_cell_offset(aml, new_cell_origin_y);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_render_action_menu_window(const ActionMenuLevel *root_level,
|
||||
unsigned int selected_index,
|
||||
ActionMenuLayerLongLabelScrollingAnimationState state,
|
||||
unsigned int additional_crumbs) {
|
||||
ActionMenuConfig config = {
|
||||
.root_level = root_level,
|
||||
.colors.background = GColorChromeYellow,
|
||||
.did_close = prv_action_menu_did_close_cb,
|
||||
};
|
||||
|
||||
ActionMenu *action_menu_window = app_action_menu_open(&config);
|
||||
|
||||
// Set the window on screen so its window handlers will be called
|
||||
window_set_on_screen(&action_menu_window->window, true, true);
|
||||
|
||||
// Scroll down to the selected index
|
||||
ActionMenuData *data = window_get_user_data(&action_menu_window->window);
|
||||
data->view_model.num_dots += additional_crumbs;
|
||||
data->crumbs_layer.level += additional_crumbs;
|
||||
prv_set_selected_index(&data->action_menu_layer, selected_index, false /* animated */);
|
||||
|
||||
// Render the window so that we set the state of the cells again now that we've scrolled
|
||||
window_render(&action_menu_window->window, &s_ctx);
|
||||
|
||||
// Update the animation state of the selected cell
|
||||
prv_update_cell_for_long_label_scrolling_animation_state(&data->action_menu_layer, state);
|
||||
|
||||
// Render the window (for real this time)!
|
||||
window_render(&action_menu_window->window, &s_ctx);
|
||||
}
|
||||
|
||||
#define GRID_CELL_PADDING 5
|
||||
|
||||
typedef void (*RenderCallback)(SettingsContentSize content_size, const ActionMenuLevel *root_level,
|
||||
unsigned int selected_index, unsigned int additional_crumbs);
|
||||
|
||||
static void prv_prepare_canvas_and_render_for_each_size(RenderCallback callback,
|
||||
const ActionMenuLevel *root_level,
|
||||
unsigned int selected_index,
|
||||
unsigned int num_rows,
|
||||
unsigned int additional_crumbs) {
|
||||
gbitmap_destroy(s_dest_bitmap);
|
||||
|
||||
const unsigned int num_columns = SettingsContentSizeCount;
|
||||
|
||||
const int16_t bitmap_width = (DISP_COLS * num_columns) + (GRID_CELL_PADDING * (num_columns + 1));
|
||||
const int16_t bitmap_height = (num_rows == 1) ? DISP_ROWS :
|
||||
((DISP_ROWS * num_rows) + (GRID_CELL_PADDING * (num_rows + 1)));
|
||||
const GSize bitmap_size = GSize(bitmap_width, bitmap_height);
|
||||
s_dest_bitmap = gbitmap_create_blank(bitmap_size, CANVAS_GBITMAP_FORMAT);
|
||||
|
||||
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 (on color) or white (on b&w) so it's easier to see errors
|
||||
memset(s_dest_bitmap->addr, PBL_IF_COLOR_ELSE(GColorShockingPinkARGB8, GColorWhiteARGB8),
|
||||
s_dest_bitmap->row_size_bytes * s_dest_bitmap->bounds.size.h);
|
||||
|
||||
for (SettingsContentSize content_size = 0; content_size < SettingsContentSizeCount;
|
||||
content_size++) {
|
||||
system_theme_set_content_size(settings_content_size_to_preferred_size(content_size));
|
||||
callback(content_size, root_level, selected_index, additional_crumbs);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_render_action_menus_static(
|
||||
SettingsContentSize content_size, const ActionMenuLevel *root_level,
|
||||
unsigned int selected_index, unsigned int additional_crumbs) {
|
||||
const int16_t x_offset = GRID_CELL_PADDING + (content_size * (GRID_CELL_PADDING + DISP_COLS));
|
||||
s_ctx.draw_state.drawing_box.origin = GPoint(x_offset, 0);
|
||||
|
||||
prv_render_action_menu_window(root_level, selected_index,
|
||||
ActionMenuLayerLongLabelScrollingAnimationState_Top,
|
||||
additional_crumbs);
|
||||
}
|
||||
|
||||
static void prv_render_action_menus_animated(
|
||||
SettingsContentSize content_size, const ActionMenuLevel *root_level,
|
||||
unsigned int selected_index, unsigned int additional_crumbs) {
|
||||
const int16_t x_offset = GRID_CELL_PADDING + (content_size * (GRID_CELL_PADDING + DISP_COLS));
|
||||
|
||||
for (ActionMenuLayerLongLabelScrollingAnimationState animation_state = 0;
|
||||
animation_state < ActionMenuLayerLongLabelScrollingAnimationStateCount;
|
||||
animation_state++) {
|
||||
const int16_t y_offset = GRID_CELL_PADDING +
|
||||
(animation_state * (GRID_CELL_PADDING + DISP_ROWS));
|
||||
s_ctx.draw_state.drawing_box.origin = GPoint(x_offset, y_offset);
|
||||
prv_render_action_menu_window(root_level, selected_index, animation_state,
|
||||
additional_crumbs);
|
||||
}
|
||||
}
|
||||
|
||||
void prv_prepare_canvas_and_render_action_menus_static(const ActionMenuLevel *root_level,
|
||||
unsigned int selected_index,
|
||||
unsigned int additional_crumbs) {
|
||||
prv_prepare_canvas_and_render_for_each_size(
|
||||
prv_render_action_menus_static, root_level, selected_index, 1 /* num_rows */,
|
||||
additional_crumbs);
|
||||
}
|
||||
|
||||
void prv_prepare_canvas_and_render_action_menus_animated(const ActionMenuLevel *root_level,
|
||||
unsigned int selected_index) {
|
||||
prv_prepare_canvas_and_render_for_each_size(
|
||||
prv_render_action_menus_animated, root_level, selected_index,
|
||||
ActionMenuLayerLongLabelScrollingAnimationStateCount,
|
||||
0 /* additional_crumbs */);
|
||||
}
|
||||
|
||||
// Tests
|
||||
//////////////////////
|
||||
|
||||
void test_action_menu_window__wide_display_mode_with_just_titles(void) {
|
||||
ActionMenuLevel *root_level = action_menu_level_create(3);
|
||||
action_menu_level_add_action(root_level,
|
||||
"I will text back",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_action(root_level,
|
||||
"Sorry, I can't talk right now, call me back at a later time",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_action(root_level,
|
||||
"I will call back",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
|
||||
const unsigned int selected_index = 1;
|
||||
prv_prepare_canvas_and_render_action_menus_animated(root_level, selected_index);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_action_menu_window__thin_display_mode_with_emoji(void) {
|
||||
// Copied from prv_create_emoji_level_from_action() in timeline_actions.c; it wouldn't work that
|
||||
// well to just make the array T_STATIC in that function because we need to know its length too
|
||||
static const char* thin_values[] = {
|
||||
"😃",
|
||||
"😉",
|
||||
"😂",
|
||||
"😍",
|
||||
"😘",
|
||||
"\xe2\x9d\xa4",
|
||||
"😇",
|
||||
"😎",
|
||||
"😛",
|
||||
"😟",
|
||||
"😩",
|
||||
"😭",
|
||||
"😴",
|
||||
"😐",
|
||||
"😯",
|
||||
"👍",
|
||||
"👎",
|
||||
"👌",
|
||||
"💩",
|
||||
"🎉",
|
||||
"🍺",
|
||||
};
|
||||
ActionMenuLevel *root_level = action_menu_level_create(ARRAY_LENGTH(thin_values));
|
||||
action_menu_level_set_display_mode(root_level, ActionMenuLevelDisplayModeThin);
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(thin_values); i++) {
|
||||
action_menu_level_add_action(root_level, thin_values[i], prv_noop_action_callback, NULL);
|
||||
}
|
||||
|
||||
const unsigned int selected_index = 0;
|
||||
prv_prepare_canvas_and_render_action_menus_static(root_level, selected_index, 0);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_action_menu_window__thin_display_mode_two_row(void) {
|
||||
static const char* thin_values[] = { "a", "b", "c", "d", "e" };
|
||||
ActionMenuLevel *root_level = action_menu_level_create(ARRAY_LENGTH(thin_values));
|
||||
action_menu_level_set_display_mode(root_level, ActionMenuLevelDisplayModeThin);
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(thin_values); i++) {
|
||||
action_menu_level_add_action(root_level, thin_values[i], prv_noop_action_callback, NULL);
|
||||
}
|
||||
|
||||
const unsigned int selected_index = 4;
|
||||
prv_prepare_canvas_and_render_action_menus_static(root_level, selected_index, 0);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_action_menu_window__thin_display_mode_one_row(void) {
|
||||
static const char* thin_values[] = { "Y", "N" };
|
||||
ActionMenuLevel *root_level = action_menu_level_create(ARRAY_LENGTH(thin_values));
|
||||
action_menu_level_set_display_mode(root_level, ActionMenuLevelDisplayModeThin);
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(thin_values); i++) {
|
||||
action_menu_level_add_action(root_level, thin_values[i], prv_noop_action_callback, NULL);
|
||||
}
|
||||
|
||||
const unsigned int selected_index = 1;
|
||||
prv_prepare_canvas_and_render_action_menus_static(root_level, selected_index, 0);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_action_menu_window__thin_display_mode_one_item(void) {
|
||||
static const char* thin_values[] = { "Y" };
|
||||
ActionMenuLevel *root_level = action_menu_level_create(ARRAY_LENGTH(thin_values));
|
||||
action_menu_level_set_display_mode(root_level, ActionMenuLevelDisplayModeThin);
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(thin_values); i++) {
|
||||
action_menu_level_add_action(root_level, thin_values[i], prv_noop_action_callback, NULL);
|
||||
}
|
||||
|
||||
const unsigned int selected_index = 0;
|
||||
prv_prepare_canvas_and_render_action_menus_static(root_level, selected_index, 0);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_action_menu_window__wide_display_mode_with_chevron(void) {
|
||||
ActionMenuLevel *root_level = action_menu_level_create(3);
|
||||
ActionMenuLevel *voice_level = action_menu_level_create(1);
|
||||
action_menu_level_add_action(voice_level,
|
||||
"This won't be seen",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_child(root_level, voice_level, "Voice");
|
||||
|
||||
ActionMenuLevel *template_level = action_menu_level_create(1);
|
||||
action_menu_level_add_action(template_level,
|
||||
"This won't be seen",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_child(root_level, template_level, "Template");
|
||||
|
||||
ActionMenuLevel *emoji_level = action_menu_level_create(1);
|
||||
action_menu_level_add_action(emoji_level,
|
||||
"This won't be seen",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_child(root_level, emoji_level, "Emoji");
|
||||
|
||||
const unsigned int selected_index = 1;
|
||||
prv_prepare_canvas_and_render_action_menus_static(root_level, selected_index, 0);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_action_menu_window__wide_display_mode_with_chevron_and_long_labels(void) {
|
||||
ActionMenuLevel *root_level = action_menu_level_create(3);
|
||||
ActionMenuLevel *voice_level = action_menu_level_create(1);
|
||||
action_menu_level_add_action(voice_level,
|
||||
"This won't be seen",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_child(root_level, voice_level, "I will text back");
|
||||
|
||||
ActionMenuLevel *template_level = action_menu_level_create(1);
|
||||
action_menu_level_add_action(template_level,
|
||||
"This won't be seen",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_child(root_level, template_level,
|
||||
"Sorry, I can't talk right now, call me back at a later time");
|
||||
|
||||
ActionMenuLevel *emoji_level = action_menu_level_create(1);
|
||||
action_menu_level_add_action(emoji_level,
|
||||
"This won't be seen",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_child(root_level, emoji_level, "I will call back");
|
||||
|
||||
const unsigned int selected_index = 1;
|
||||
prv_prepare_canvas_and_render_action_menus_animated(root_level, selected_index);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_action_menu_window__wide_display_mode_with_chevron_and_long_labels_hyphenated(void) {
|
||||
ActionMenuLevel *root_level = action_menu_level_create(3);
|
||||
ActionMenuLevel *voice_level = action_menu_level_create(1);
|
||||
action_menu_level_add_action(voice_level,
|
||||
"This won't be seen",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_child(root_level, voice_level, "Dismiss");
|
||||
|
||||
ActionMenuLevel *template_level = action_menu_level_create(1);
|
||||
action_menu_level_add_action(template_level,
|
||||
"This won't be seen",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_child(root_level, template_level,
|
||||
"Reply to HUBERT BLAINE WOLFESCHLEGELSTEINHAUSENBERGERDORFF");
|
||||
|
||||
ActionMenuLevel *emoji_level = action_menu_level_create(1);
|
||||
action_menu_level_add_action(emoji_level,
|
||||
"This won't be seen",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_child(root_level, emoji_level, "Open on phone");
|
||||
|
||||
const unsigned int selected_index = 1;
|
||||
prv_prepare_canvas_and_render_action_menus_animated(root_level, selected_index);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_action_menu_window__wide_display_mode_with_separator(void) {
|
||||
ActionMenuLevel *root_level = action_menu_level_create(3);
|
||||
action_menu_level_add_action(root_level,
|
||||
"Change Time",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
|
||||
action_menu_level_add_action(root_level,
|
||||
"Change Days",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
|
||||
ActionMenuLevel *snooze_level = action_menu_level_create(1);
|
||||
action_menu_level_add_action(snooze_level,
|
||||
"This won't be seen",
|
||||
prv_noop_action_callback,
|
||||
NULL);
|
||||
action_menu_level_add_child(root_level, snooze_level, "Snooze Delay");
|
||||
|
||||
root_level->separator_index = root_level->num_items - 1;
|
||||
|
||||
const unsigned int selected_index = 1;
|
||||
prv_prepare_canvas_and_render_action_menus_static(root_level, selected_index, 1);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
3753
tests/fw/ui/test_animation.c
Normal file
3753
tests/fw/ui/test_animation.c
Normal file
File diff suppressed because it is too large
Load diff
136
tests/fw/ui/test_animation_interpolate.c
Normal file
136
tests/fw/ui/test_animation_interpolate.c
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* 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/ui/animation_interpolate.h"
|
||||
#include "applib/ui/animation.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include "stubs/stubs_logging.h"
|
||||
#include "stubs/stubs_passert.h"
|
||||
|
||||
InterpolateInt64Function s_animation_private_current_interpolate_override;
|
||||
|
||||
InterpolateInt64Function animation_private_current_interpolate_override(void) {
|
||||
return s_animation_private_current_interpolate_override;
|
||||
}
|
||||
|
||||
void test_animation_interpolate__initialize(void) {
|
||||
s_animation_private_current_interpolate_override = NULL;
|
||||
}
|
||||
|
||||
void test_animation_interpolate__override_is_null(void) {
|
||||
cl_assert_equal_i(-10000, interpolate_int16(0, -10000, 10000));
|
||||
cl_assert_equal_i(10000, interpolate_int16(ANIMATION_NORMALIZED_MAX, -10000, 10000));
|
||||
}
|
||||
|
||||
static AnimationProgress s_override_progress;
|
||||
static int64_t s_override_from;
|
||||
static int64_t s_override_to;
|
||||
|
||||
int64_t prv_override_capture_args(AnimationProgress p, int64_t a, int64_t b) {
|
||||
s_override_progress = p;
|
||||
s_override_from = a;
|
||||
s_override_to = b;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void test_animation_interpolate__override_gets_called(void) {
|
||||
s_animation_private_current_interpolate_override = prv_override_capture_args;
|
||||
cl_assert_equal_i(1, interpolate_int16(2, 3, 4));
|
||||
cl_assert_equal_i(2, s_override_progress);
|
||||
cl_assert_equal_i(3, s_override_from);
|
||||
cl_assert_equal_i(4, s_override_to);
|
||||
}
|
||||
|
||||
int64_t prv_override_times_two(AnimationProgress p, int64_t a, int64_t b) {
|
||||
return interpolate_int64_linear(p, a, b) * 2;
|
||||
}
|
||||
|
||||
void test_animation_interpolate__override_gets_clipped(void) {
|
||||
s_animation_private_current_interpolate_override = prv_override_times_two;
|
||||
|
||||
cl_assert_equal_i(INT16_MIN, interpolate_int16(0, -20000, 20000));
|
||||
cl_assert_equal_i(INT16_MAX, interpolate_int16(ANIMATION_NORMALIZED_MAX, -20000, 20000));
|
||||
}
|
||||
|
||||
void test_animation_interpolate__moook(void) {
|
||||
const int expected[] = {-20000, -19999, -19980, 20004, 20002, 20001, 20000};
|
||||
const int num_frames = ARRAY_LENGTH(expected);
|
||||
for (int i = 0; i < num_frames; i++) {
|
||||
printf("frame: %d\n", i);
|
||||
cl_assert_equal_i(interpolate_moook((i * ANIMATION_NORMALIZED_MAX) / num_frames,
|
||||
-20000, 20000), expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void test_animation_interpolate__moook_in(void) {
|
||||
const int expected[] = {-20000, -19999, -19980};
|
||||
const int num_frames = ARRAY_LENGTH(expected);
|
||||
for (int i = 0; i < num_frames; i++) {
|
||||
printf("frame: %d\n", i);
|
||||
cl_assert_equal_i(interpolate_moook_in_only((i * ANIMATION_NORMALIZED_MAX) / num_frames,
|
||||
-20000, 20000), expected[i]);
|
||||
}
|
||||
cl_assert_equal_i(interpolate_moook_in_only(ANIMATION_NORMALIZED_MAX,
|
||||
-20000, 20000), 20000);
|
||||
}
|
||||
|
||||
void test_animation_interpolate__moook_out(void) {
|
||||
const int expected[] = {20004, 20002, 20001, 20000};
|
||||
const int num_frames = ARRAY_LENGTH(expected);
|
||||
for (int i = 0; i < num_frames; i++) {
|
||||
printf("frame: %d\n", i);
|
||||
cl_assert_equal_i(interpolate_moook_out((i * ANIMATION_NORMALIZED_MAX) / num_frames,
|
||||
-20000, 20000, 0, true), expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void test_animation_interpolate__moook_soft(void) {
|
||||
const int32_t moook_num_soft_frames = 3;
|
||||
cl_assert_equal_i(-20000, interpolate_moook_soft(0, -20000, 20000, moook_num_soft_frames));
|
||||
|
||||
// mid frame is closer to end due to more end frames
|
||||
cl_assert_equal_i(6676, interpolate_moook_soft(ANIMATION_NORMALIZED_MAX / 2, -20000,
|
||||
20000, moook_num_soft_frames));
|
||||
|
||||
cl_assert_equal_i(20000, interpolate_moook_soft(ANIMATION_NORMALIZED_MAX, -20000,
|
||||
20000, moook_num_soft_frames));
|
||||
}
|
||||
|
||||
static const int32_t s_custom_moook_in[] = {0, 2, 8};
|
||||
static const int32_t s_custom_moook_out[] = {21, 9, 3, 0};
|
||||
static const MoookConfig s_custom_moook = {
|
||||
.frames_in = s_custom_moook_in,
|
||||
.num_frames_in = ARRAY_LENGTH(s_custom_moook_in),
|
||||
.frames_out = s_custom_moook_out,
|
||||
.num_frames_out = ARRAY_LENGTH(s_custom_moook_out),
|
||||
.num_frames_mid = 3,
|
||||
};
|
||||
|
||||
void test_animation_interpolate__moook_custom(void) {
|
||||
cl_assert_equal_i(330, interpolate_moook_custom_duration(&s_custom_moook));
|
||||
|
||||
cl_assert_equal_i(-20000, interpolate_moook_custom(0, -20000, 20000, &s_custom_moook));
|
||||
|
||||
// mid frame is closer to end due to more end frames
|
||||
cl_assert_equal_i(6683, interpolate_moook_custom(ANIMATION_NORMALIZED_MAX / 2, -20000, 20000,
|
||||
&s_custom_moook));
|
||||
|
||||
cl_assert_equal_i(20000, interpolate_moook_custom(ANIMATION_NORMALIZED_MAX, -20000, 20000,
|
||||
&s_custom_moook));
|
||||
}
|
56
tests/fw/ui/test_animation_timing.c
Normal file
56
tests/fw/ui/test_animation_timing.c
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 "pebble_asserts.h"
|
||||
|
||||
#include "applib/ui/animation_timing.h"
|
||||
#include "applib/ui/animation.h"
|
||||
|
||||
// stubs
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
|
||||
// tests
|
||||
|
||||
void test_animation_timing__scaled(void) {
|
||||
AnimationProgress third = ANIMATION_NORMALIZED_MAX / 3;
|
||||
AnimationProgress half = ANIMATION_NORMALIZED_MAX / 2;
|
||||
AnimationProgress two_third = ANIMATION_NORMALIZED_MAX * 2 / 3;
|
||||
AnimationProgress four_third = ANIMATION_NORMALIZED_MAX * 4 / 3;
|
||||
|
||||
AnimationProgress (*f)(AnimationProgress time_normalized,
|
||||
AnimationProgress interval_start,
|
||||
AnimationProgress interval_end) = animation_timing_scaled;
|
||||
|
||||
cl_assert_equal_i(-half, f(-half, 0, ANIMATION_NORMALIZED_MAX));
|
||||
cl_assert_equal_i(0, f(0, 0, ANIMATION_NORMALIZED_MAX));
|
||||
cl_assert_equal_i(third, f(third, 0, ANIMATION_NORMALIZED_MAX));
|
||||
cl_assert_equal_i(two_third, f(two_third, 0, ANIMATION_NORMALIZED_MAX));
|
||||
cl_assert_equal_i(four_third, f(four_third, 0, ANIMATION_NORMALIZED_MAX));
|
||||
|
||||
cl_assert_equal_i(-65535, f(-half, 0, half));
|
||||
cl_assert_equal_i(0, f(0, 0, half));
|
||||
cl_assert_equal_i(43690, f(third, 0, half));
|
||||
cl_assert_equal_i(87381, f(two_third, 0, half));
|
||||
cl_assert_equal_i(174762, f(four_third, 0, half));
|
||||
|
||||
cl_assert_equal_i(-131066, f(-half, half, ANIMATION_NORMALIZED_MAX));
|
||||
cl_assert_equal_i(-65533, f(0, half, ANIMATION_NORMALIZED_MAX));
|
||||
cl_assert_equal_i(-21843, f(third, half, ANIMATION_NORMALIZED_MAX));
|
||||
cl_assert_equal_i(21845, f(two_third, half, ANIMATION_NORMALIZED_MAX));
|
||||
cl_assert_equal_i(109224, f(four_third, half, ANIMATION_NORMALIZED_MAX));
|
||||
}
|
298
tests/fw/ui/test_content_indicator.c
Normal file
298
tests/fw/ui/test_content_indicator.c
Normal file
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* 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/ui/content_indicator.h"
|
||||
#include "applib/ui/content_indicator_private.h"
|
||||
#include "util/buffer.h"
|
||||
|
||||
// Fakes
|
||||
////////////////////////////////////
|
||||
|
||||
#include "fake_app_timer.h"
|
||||
#include "fake_content_indicator.h"
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////
|
||||
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_compiled_with_legacy2_sdk.h"
|
||||
#include "stubs_gpath.h"
|
||||
#include "stubs_graphics.h"
|
||||
#include "stubs_graphics_context.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_ui_window.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
|
||||
extern void prv_content_indicator_update_proc(Layer *layer, GContext *ctx);
|
||||
|
||||
// Helpers
|
||||
////////////////////////////////////
|
||||
|
||||
static Layer s_content_indicator_dummy_layer;
|
||||
static LayerUpdateProc s_content_indicator_dummy_layer_update_proc;
|
||||
|
||||
ContentIndicatorConfig helper_get_dummy_config(void) {
|
||||
return (ContentIndicatorConfig) {
|
||||
.layer = &s_content_indicator_dummy_layer,
|
||||
.times_out = false,
|
||||
.alignment = GAlignLeft,
|
||||
.colors = {
|
||||
.foreground = GColorGreen,
|
||||
.background = GColorRed
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void helper_check_buffer_for_content_indicator(size_t index, ContentIndicator *content_indicator) {
|
||||
ContentIndicatorsBuffer *content_indicators_buffer = content_indicator_get_current_buffer();
|
||||
Buffer *buffer = &content_indicators_buffer->buffer;
|
||||
ContentIndicator **content_indicators = (ContentIndicator**)buffer->data;
|
||||
cl_assert_equal_p(content_indicators[index], content_indicator);
|
||||
}
|
||||
|
||||
void helper_check_configs_for_equality(ContentIndicatorConfig a, ContentIndicatorConfig b) {
|
||||
cl_assert_equal_p(a.layer, b.layer);
|
||||
cl_assert_equal_b(a.times_out, b.times_out);
|
||||
cl_assert(a.alignment == b.alignment);
|
||||
cl_assert(a.colors.foreground.argb == b.colors.foreground.argb);
|
||||
cl_assert(a.colors.background.argb == b.colors.background.argb);
|
||||
}
|
||||
|
||||
// Setup
|
||||
/////////////////////////////////
|
||||
|
||||
void test_content_indicator__initialize(void) {
|
||||
// Initialize the static buffer of content indicators
|
||||
content_indicator_init_buffer(content_indicator_get_current_buffer());
|
||||
// Reset the dummy layer's fields
|
||||
memset(&s_content_indicator_dummy_layer, 0, sizeof(Layer));
|
||||
}
|
||||
|
||||
void test_content_indicator__cleanup(void) {
|
||||
}
|
||||
|
||||
// Tests
|
||||
////////////////////////////////////
|
||||
|
||||
void test_content_indicator__create_should_add_to_buffer(void) {
|
||||
ContentIndicator *content_indicator;
|
||||
for (size_t i = 0; i < CONTENT_INDICATOR_BUFFER_SIZE; i++) {
|
||||
content_indicator = content_indicator_create();
|
||||
cl_assert(content_indicator);
|
||||
helper_check_buffer_for_content_indicator(i, content_indicator);
|
||||
}
|
||||
|
||||
// Creating more content indicators than the buffer can hold should return NULL
|
||||
cl_assert_equal_p(content_indicator_create(), NULL);
|
||||
}
|
||||
|
||||
void test_content_indicator__init_should_add_to_buffer(void) {
|
||||
ContentIndicator content_indicator;
|
||||
for (size_t i = 0; i < CONTENT_INDICATOR_BUFFER_SIZE; i++) {
|
||||
content_indicator_init(&content_indicator);
|
||||
helper_check_buffer_for_content_indicator(i, &content_indicator);
|
||||
}
|
||||
|
||||
// Initializing more content indicators than the buffer can hold should assert
|
||||
cl_assert_passert(content_indicator_init(&content_indicator));
|
||||
}
|
||||
|
||||
void test_content_indicator__deinit_should_remove_from_buffer(void) {
|
||||
ContentIndicatorsBuffer *content_indicators_buffer = content_indicator_get_current_buffer();
|
||||
Buffer *buffer = &content_indicators_buffer->buffer;
|
||||
|
||||
ContentIndicator content_indicator;
|
||||
size_t bytes_written = 0;
|
||||
for (size_t i = 0; i < CONTENT_INDICATOR_BUFFER_SIZE; i++) {
|
||||
content_indicator_init(&content_indicator);
|
||||
bytes_written += sizeof(ContentIndicator *);
|
||||
cl_assert_equal_i(buffer->bytes_written, bytes_written);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < CONTENT_INDICATOR_BUFFER_SIZE; i++) {
|
||||
content_indicator_deinit(&content_indicator);
|
||||
bytes_written -= sizeof(ContentIndicator *);
|
||||
cl_assert_equal_i(buffer->bytes_written, bytes_written);
|
||||
}
|
||||
}
|
||||
|
||||
void test_content_indicator__configuring_should_configure(void) {
|
||||
ContentIndicator content_indicator;
|
||||
content_indicator_init(&content_indicator);
|
||||
ContentIndicatorDirectionData *direction_data = content_indicator.direction_data;
|
||||
|
||||
// Test setting a dummy configuration for a direction
|
||||
const ContentIndicatorConfig dummy_config = helper_get_dummy_config();
|
||||
const ContentIndicatorDirection direction = ContentIndicatorDirectionUp;
|
||||
dummy_config.layer->update_proc = s_content_indicator_dummy_layer_update_proc;
|
||||
cl_assert(content_indicator_configure_direction(&content_indicator, direction, &dummy_config));
|
||||
helper_check_configs_for_equality(dummy_config, direction_data[direction].config);
|
||||
// Should save a reference to the config layer's update proc
|
||||
cl_assert_equal_p(dummy_config.layer->update_proc, direction_data->original_update_proc);
|
||||
cl_assert_equal_p(direction_data->original_update_proc,
|
||||
s_content_indicator_dummy_layer_update_proc);
|
||||
}
|
||||
|
||||
void test_content_indicator__configuring_different_directions_with_same_layer_should_fail(void) {
|
||||
ContentIndicator content_indicator;
|
||||
content_indicator_init(&content_indicator);
|
||||
|
||||
// Setting a dummy configuration for a direction should return true
|
||||
const ContentIndicatorConfig dummy_config = helper_get_dummy_config();
|
||||
dummy_config.layer->update_proc = s_content_indicator_dummy_layer_update_proc;
|
||||
cl_assert(content_indicator_configure_direction(&content_indicator,
|
||||
ContentIndicatorDirectionUp,
|
||||
&dummy_config));
|
||||
|
||||
// Using the same dummy configuration (which has the same layer) to configure a different
|
||||
// direction should fail
|
||||
cl_assert(!content_indicator_configure_direction(&content_indicator,
|
||||
ContentIndicatorDirectionDown,
|
||||
&dummy_config));
|
||||
}
|
||||
|
||||
void test_content_indicator__setting_content_available_should_update_layer_update_proc(void) {
|
||||
ContentIndicator content_indicator;
|
||||
content_indicator_init(&content_indicator);
|
||||
ContentIndicatorDirectionData *direction_data = content_indicator.direction_data;
|
||||
|
||||
const ContentIndicatorConfig dummy_config = helper_get_dummy_config();
|
||||
const ContentIndicatorDirection direction = ContentIndicatorDirectionUp;
|
||||
dummy_config.layer->update_proc = s_content_indicator_dummy_layer_update_proc;
|
||||
cl_assert(content_indicator_configure_direction(&content_indicator, direction, &dummy_config));
|
||||
cl_assert_equal_p(dummy_config.layer->update_proc, direction_data->original_update_proc);
|
||||
cl_assert_equal_p(direction_data->original_update_proc,
|
||||
s_content_indicator_dummy_layer_update_proc);
|
||||
|
||||
// Setting content available should switch the layer's update proc to draw an arrow
|
||||
content_indicator_set_content_available(&content_indicator, direction, true);
|
||||
cl_assert_equal_p(dummy_config.layer->update_proc, prv_content_indicator_update_proc);
|
||||
|
||||
// Setting content unavailable should revert the layer's update proc
|
||||
content_indicator_set_content_available(&content_indicator, direction, false);
|
||||
cl_assert_equal_p(dummy_config.layer->update_proc, direction_data->original_update_proc);
|
||||
cl_assert_equal_p(direction_data->original_update_proc,
|
||||
s_content_indicator_dummy_layer_update_proc);
|
||||
}
|
||||
|
||||
void test_content_indicator__creating_for_scroll_layer(void) {
|
||||
ScrollLayer scroll_layer;
|
||||
ContentIndicator *content_indicator = content_indicator_get_or_create_for_scroll_layer(
|
||||
&scroll_layer);
|
||||
cl_assert(content_indicator);
|
||||
// Should save a reference to the scroll layer
|
||||
cl_assert_equal_p(content_indicator->scroll_layer, &scroll_layer);
|
||||
|
||||
// Should retrieve the same content indicator with the same scroll layer
|
||||
ContentIndicator *content_indicator2 = content_indicator_get_or_create_for_scroll_layer(
|
||||
&scroll_layer);
|
||||
cl_assert(content_indicator2);
|
||||
// Should save a reference to the scroll layer
|
||||
cl_assert_equal_p(content_indicator2->scroll_layer, &scroll_layer);
|
||||
cl_assert_equal_p(content_indicator2, content_indicator);
|
||||
|
||||
// Should retrieve a different content indicator for a different scroll layer
|
||||
ScrollLayer scroll_layer2;
|
||||
ContentIndicator *content_indicator3 = content_indicator_get_or_create_for_scroll_layer(
|
||||
&scroll_layer2);
|
||||
cl_assert(content_indicator3);
|
||||
// Should save a reference to the scroll layer
|
||||
cl_assert_equal_p(content_indicator3->scroll_layer, &scroll_layer2);
|
||||
cl_assert(content_indicator3 != content_indicator);
|
||||
}
|
||||
|
||||
void test_content_indicator__should_only_be_created_for_scroll_layer_upon_client_access(void) {
|
||||
ContentIndicatorsBuffer *content_indicators_buffer = content_indicator_get_current_buffer();
|
||||
Buffer *buffer = &content_indicators_buffer->buffer;
|
||||
// At the start of the test, the buffer should be empty
|
||||
cl_assert(buffer_is_empty(buffer));
|
||||
|
||||
ScrollLayer scroll_layer;
|
||||
// Trying to access the ContentIndicator for this ScrollLayer should return NULL because we
|
||||
// haven't tried to access it as the client yet
|
||||
cl_assert_equal_p(content_indicator_get_for_scroll_layer(&scroll_layer), NULL);
|
||||
// And the buffer should still be empty
|
||||
cl_assert(buffer_is_empty(buffer));
|
||||
|
||||
// Now we try to access it as the client, which should actually create the ContentIndicator
|
||||
ContentIndicator *content_indicator = content_indicator_get_or_create_for_scroll_layer(
|
||||
&scroll_layer);
|
||||
cl_assert(content_indicator);
|
||||
// The ContentIndicator should have a reference to the ScrollLayer
|
||||
cl_assert_equal_p(content_indicator->scroll_layer, &scroll_layer);
|
||||
// The buffer should now hold the newly created ContentIndicator
|
||||
cl_assert(buffer->bytes_written == sizeof(ContentIndicator *));
|
||||
|
||||
// Finally, calling content_indicator_get_for_scroll_layer() again should return the same
|
||||
// ContentIndicator
|
||||
ContentIndicator *content_indicator2 = content_indicator_get_for_scroll_layer(&scroll_layer);
|
||||
cl_assert(content_indicator2);
|
||||
cl_assert_equal_p(content_indicator2, content_indicator);
|
||||
// The buffer should still only hold the single ContentIndicator
|
||||
cl_assert(buffer->bytes_written == sizeof(ContentIndicator *));
|
||||
}
|
||||
|
||||
void test_content_indicator__pass_null_config_to_reset_direction_data(void) {
|
||||
ContentIndicator content_indicator;
|
||||
content_indicator_init(&content_indicator);
|
||||
ContentIndicatorDirectionData *direction_data = content_indicator.direction_data;
|
||||
|
||||
const ContentIndicatorConfig dummy_config = helper_get_dummy_config();
|
||||
const ContentIndicatorDirection direction = ContentIndicatorDirectionUp;
|
||||
dummy_config.layer->update_proc = s_content_indicator_dummy_layer_update_proc;
|
||||
|
||||
cl_assert(content_indicator_configure_direction(&content_indicator, direction, &dummy_config));
|
||||
cl_assert_equal_p(dummy_config.layer->update_proc, direction_data->original_update_proc);
|
||||
cl_assert_equal_p(direction_data->original_update_proc,
|
||||
s_content_indicator_dummy_layer_update_proc);
|
||||
|
||||
// Setting content available should switch the layer's update proc
|
||||
content_indicator_set_content_available(&content_indicator, direction, true);
|
||||
cl_assert_equal_p(dummy_config.layer->update_proc, prv_content_indicator_update_proc);
|
||||
|
||||
// Direction data should be emptied and layer's update proc should return to original when NULL
|
||||
// config is passed
|
||||
cl_assert(content_indicator_configure_direction(&content_indicator, direction, NULL));
|
||||
cl_assert_equal_p(dummy_config.layer->update_proc, s_content_indicator_dummy_layer_update_proc);
|
||||
cl_assert(!direction_data[direction].config.layer);
|
||||
|
||||
// Setting content available should not change layer update proc without reconfiguring.
|
||||
content_indicator_set_content_available(&content_indicator, direction, true);
|
||||
cl_assert_equal_p(dummy_config.layer->update_proc, s_content_indicator_dummy_layer_update_proc);
|
||||
}
|
||||
|
||||
void test_content_indicator__re_configure_direction(void) {
|
||||
ContentIndicator content_indicator;
|
||||
content_indicator_init(&content_indicator);
|
||||
|
||||
const ContentIndicatorConfig dummy_config = helper_get_dummy_config();
|
||||
const ContentIndicatorDirection up = ContentIndicatorDirectionUp;
|
||||
const ContentIndicatorDirection down = ContentIndicatorDirectionDown;
|
||||
|
||||
cl_assert(content_indicator_configure_direction(&content_indicator, up, &dummy_config));
|
||||
|
||||
// re-configure with the same direction, should be a success
|
||||
cl_assert(content_indicator_configure_direction(&content_indicator, up, &dummy_config));
|
||||
|
||||
// re-configure with a different same direction, should fail
|
||||
cl_assert(!content_indicator_configure_direction(&content_indicator, down, &dummy_config));
|
||||
}
|
187
tests/fw/ui/test_emoji_fonts.c
Normal file
187
tests/fw/ui/test_emoji_fonts.c
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* 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 "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 "fixtures/load_test_resources.h"
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_bootbits.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_prompt.h"
|
||||
#include "stubs_serial.h"
|
||||
#include "stubs_sleep.h"
|
||||
#include "stubs_syscalls.h"
|
||||
#include "stubs_task_watchdog.h"
|
||||
#include "stubs_ui_window.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
|
||||
// Helper Functions
|
||||
/////////////////////
|
||||
#include "../graphics/test_graphics.h"
|
||||
#include "../graphics/util.h"
|
||||
|
||||
// Setup and Teardown
|
||||
////////////////////////////////////
|
||||
|
||||
static FrameBuffer *fb;
|
||||
static GContext s_ctx;
|
||||
static FontInfo s_font_info;
|
||||
|
||||
static GBitmap *s_dest_bitmap;
|
||||
|
||||
void test_emoji_fonts__initialize(void) {
|
||||
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 */);
|
||||
|
||||
memset(&s_font_info, 0, sizeof(s_font_info));
|
||||
|
||||
resource_init();
|
||||
|
||||
fb = malloc(sizeof(FrameBuffer));
|
||||
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
|
||||
|
||||
test_graphics_context_init(&s_ctx, fb);
|
||||
framebuffer_clear(fb);
|
||||
}
|
||||
|
||||
void test_emoji_fonts__cleanup(void) {
|
||||
free(fb);
|
||||
fb = NULL;
|
||||
|
||||
gbitmap_destroy(s_dest_bitmap);
|
||||
s_dest_bitmap = NULL;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
//////////////////////
|
||||
|
||||
static char s_emoji_string[] =
|
||||
"😄😃😀😊☺😉😍😘😚😗😙😜😝😛😳😁😔😌😒😞😣😢😂😭😥😪😰😅😓😩😫😨😱"
|
||||
"😠😡😤😖😆😋😷😎😴😵😲😟😧😈👿😮😬😐😕😯😶😇😏😑😺😸😻😽😼🙀😿😹😾💩"
|
||||
"👍👎👌👊✊✌👋✋👐👆👇👉👈🙌🙏☝👏💛💙💜💚❤💔💗💓💕💖💞💘💋🐥🎉💩🍻🍺"
|
||||
"💪🔥🐵🙈→►★🎤🎥📷🎵🎁";
|
||||
|
||||
static void prv_render_text(GContext *ctx, const char *text, GFont const font, const GRect *box,
|
||||
const GTextOverflowMode overflow_mode, const GTextAlignment alignment,
|
||||
GTextLayoutCacheRef layout, bool render) {
|
||||
if (render) {
|
||||
graphics_draw_text(ctx, text, font, *box, overflow_mode, alignment, layout);
|
||||
} else {
|
||||
graphics_text_layout_get_max_used_size(ctx, text, font, *box, overflow_mode, alignment,
|
||||
layout);
|
||||
}
|
||||
}
|
||||
|
||||
static GSize prv_draw_emoji(GContext *ctx, const GRect *bounds, GFont font, bool render) {
|
||||
TextLayoutExtended layout = {
|
||||
.line_spacing_delta = 2, // Give some space for the larger emojis
|
||||
};
|
||||
graphics_context_set_text_color(ctx, GColorBlack);
|
||||
prv_render_text(ctx, s_emoji_string, font, bounds, GTextOverflowModeWordWrap,
|
||||
GTextAlignmentLeft, (GTextLayoutCacheRef)&layout, true /* render */);
|
||||
return layout.max_used_size;
|
||||
}
|
||||
|
||||
void prv_prepare_canvas(GSize bitmap_size, GColor background_color) {
|
||||
gbitmap_destroy(s_dest_bitmap);
|
||||
|
||||
s_dest_bitmap = gbitmap_create_blank(bitmap_size, GBitmapFormat8Bit);
|
||||
|
||||
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;
|
||||
|
||||
memset(s_dest_bitmap->addr, background_color.argb,
|
||||
s_dest_bitmap->row_size_bytes * s_dest_bitmap->bounds.size.h);
|
||||
}
|
||||
|
||||
void prv_prepare_canvas_and_render_emoji(ResourceId font_handle) {
|
||||
// Calculate canvas size necessary
|
||||
cl_assert(text_resources_init_font(0, font_handle, 0, &s_font_info));
|
||||
|
||||
const GPoint margin = { 10, 10 }; // Canvas margins
|
||||
const int max_width = 300; // Canvas width, choose any visually pleasing width
|
||||
const int max_height = 2000; // Large size protected from overflow and automatically truncated
|
||||
const GSize max_size = { max_width, max_height };
|
||||
|
||||
// FIXME: PBL-34261 graphics_text_layout_get_max_used_size reports a max of 192px in a unit test
|
||||
// with the default aplite framebuffer size. Work around this issue by creating a larger canvas.
|
||||
prv_prepare_canvas(max_size, GColorWhite);
|
||||
|
||||
const GRect max_draw_frame = grect_inset_internal((GRect){ .size = max_size },
|
||||
margin.x, margin.y);
|
||||
const GSize used_size = prv_draw_emoji(&s_ctx, &max_draw_frame, &s_font_info,
|
||||
false /* render */);
|
||||
|
||||
// Resize the canvas to the used size.
|
||||
const GSize canvas_size = { max_width, used_size.h + 2 * margin.y };
|
||||
prv_prepare_canvas(canvas_size, GColorWhite);
|
||||
|
||||
// Create the new canvas
|
||||
const GRect draw_frame = grect_inset_internal((GRect){ .size = canvas_size },
|
||||
margin.x, margin.y);
|
||||
prv_draw_emoji(&s_ctx, &draw_frame, &s_font_info, true /* render */);
|
||||
}
|
||||
|
||||
// Tests
|
||||
//////////////////////
|
||||
|
||||
void test_emoji_fonts__gothic_14_emoji(void) {
|
||||
prv_prepare_canvas_and_render_emoji(RESOURCE_ID_GOTHIC_14_EMOJI);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_emoji_fonts__gothic_18_emoji(void) {
|
||||
prv_prepare_canvas_and_render_emoji(RESOURCE_ID_GOTHIC_18_EMOJI);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_emoji_fonts__gothic_24_emoji(void) {
|
||||
prv_prepare_canvas_and_render_emoji(RESOURCE_ID_GOTHIC_24_EMOJI);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_emoji_fonts__gothic_28_emoji(void) {
|
||||
prv_prepare_canvas_and_render_emoji(RESOURCE_ID_GOTHIC_28_EMOJI);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
176
tests/fw/ui/test_expandable_dialog.c
Normal file
176
tests/fw/ui/test_expandable_dialog.c
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* 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/app_window_stack.h"
|
||||
#include "applib/ui/content_indicator.h"
|
||||
#include "applib/ui/content_indicator_private.h"
|
||||
#include "applib/ui/dialogs/expandable_dialog.h"
|
||||
#include "applib/ui/text_layer.h"
|
||||
#include "applib/ui/window_private.h"
|
||||
#include "resource/resource.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/normal/timeline/timeline_resources.h"
|
||||
#include "shell/system_theme.h"
|
||||
#include "util/graphics.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static GContext s_ctx;
|
||||
|
||||
// Fakes
|
||||
/////////////////////
|
||||
|
||||
#include "fake_content_indicator.h"
|
||||
#include "fake_spi_flash.h"
|
||||
#include "fixtures/load_test_resources.h"
|
||||
|
||||
GContext *graphics_context_get_current_context(void) {
|
||||
return &s_ctx;
|
||||
}
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_app_install_manager.h"
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_app_timer.h"
|
||||
#include "stubs_bootbits.h"
|
||||
#include "stubs_buffer.h"
|
||||
#include "stubs_click.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_layer.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_status_bar_layer.h"
|
||||
#include "stubs_syscall_internal.h"
|
||||
#include "stubs_syscalls.h"
|
||||
#include "stubs_task_watchdog.h"
|
||||
#include "stubs_vibes.h"
|
||||
#include "stubs_window_manager.h"
|
||||
#include "stubs_window_stack.h"
|
||||
|
||||
AnimationProgress animation_timing_scaled(AnimationProgress time_normalized,
|
||||
AnimationProgress interval_start,
|
||||
AnimationProgress interval_end) {
|
||||
return interval_end;
|
||||
}
|
||||
|
||||
AnimationProgress animation_timing_curve(AnimationProgress time_normalized, AnimationCurve curve) {
|
||||
return time_normalized;
|
||||
}
|
||||
|
||||
KinoReel *kino_reel_scale_segmented_create(KinoReel *from_reel, bool take_ownership,
|
||||
GRect screen_frame) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void kino_reel_scale_segmented_set_deflate_effect(KinoReel *reel, int16_t expand) {}
|
||||
|
||||
bool kino_reel_scale_segmented_set_delay_by_distance(KinoReel *reel, GPoint target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t time_ms(time_t *tloc, uint16_t *out_ms) { return 0; }
|
||||
|
||||
// Helper Functions
|
||||
/////////////////////
|
||||
|
||||
#include "../graphics/test_graphics.h"
|
||||
#include "../graphics/util.h"
|
||||
|
||||
// Setup and Teardown
|
||||
////////////////////////////////////
|
||||
|
||||
static FrameBuffer *fb = NULL;
|
||||
|
||||
void test_expandable_dialog__initialize(void) {
|
||||
fb = malloc(sizeof(FrameBuffer));
|
||||
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
|
||||
// Must use System init mode to enable orphan avoidance algorithm
|
||||
const GContextInitializationMode context_init_mode = GContextInitializationMode_System;
|
||||
graphics_context_init(&s_ctx, fb, context_init_mode);
|
||||
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();
|
||||
}
|
||||
|
||||
void test_expandable_dialog__cleanup(void) {
|
||||
free(fb);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
//////////////////////
|
||||
|
||||
void prv_manual_scroll(ScrollLayer *scroll_layer, int8_t dir);
|
||||
|
||||
void prv_push_and_render_expandable_dialog(ExpandableDialog *expandable_dialog,
|
||||
uint32_t num_times_to_scroll_down) {
|
||||
Dialog *dialog = expandable_dialog_get_dialog(expandable_dialog);
|
||||
dialog_set_timeout(dialog, DIALOG_TIMEOUT_INFINITE /* no timeout */);
|
||||
|
||||
window_set_on_screen(&dialog->window, true, true);
|
||||
|
||||
for (uint32_t i = 0; i < num_times_to_scroll_down; i++) {
|
||||
scroll_layer_scroll(&expandable_dialog->scroll_layer, ScrollDirectionDown,
|
||||
false /* animated */);
|
||||
}
|
||||
|
||||
window_render(&dialog->window, &s_ctx);
|
||||
}
|
||||
|
||||
// Tests
|
||||
//////////////////////
|
||||
|
||||
void test_expandable_dialog__dismiss_tutorial_portuguese_orphan(void) {
|
||||
const char* tutorial_msg = "Remova rapidamente todas as notificações ao segurar o botão Select "
|
||||
"durante 2 segundos a partir de qualquer notificação.";
|
||||
|
||||
ExpandableDialog *expandable_dialog = expandable_dialog_create_with_params(
|
||||
"Dismiss First Use", RESOURCE_ID_QUICK_DISMISS, tutorial_msg,
|
||||
gcolor_legible_over(GColorLightGray), GColorLightGray, NULL,
|
||||
RESOURCE_ID_ACTION_BAR_ICON_CHECK, NULL);
|
||||
|
||||
// Scroll down to the last page where we will observe the orphan avoidance effect
|
||||
const uint32_t num_times_to_scroll_down = 2;
|
||||
prv_push_and_render_expandable_dialog(expandable_dialog, num_times_to_scroll_down);
|
||||
|
||||
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
104
tests/fw/ui/test_jumboji.c
Normal file
104
tests/fw/ui/test_jumboji.c
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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 "services/normal/timeline/notification_jumboji_table.h"
|
||||
#include "services/normal/timeline/notification_layout.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_attribute.h"
|
||||
#include "stubs_clock.h"
|
||||
#include "stubs_graphics.h"
|
||||
#include "stubs_graphics_context.h"
|
||||
#include "stubs_kino_layer.h"
|
||||
#include "stubs_layer.h"
|
||||
#include "stubs_layout_node.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pin_db.h"
|
||||
#include "stubs_resources.h"
|
||||
#include "stubs_shell_prefs.h"
|
||||
#include "stubs_text_node.h"
|
||||
#include "stubs_timeline_item.h"
|
||||
#include "stubs_timeline_resources.h"
|
||||
|
||||
// Statics
|
||||
////////////////////////////////////
|
||||
static const EmojiEntry s_emoji_table[] = JUMBOJI_TABLE(EMOJI_ENTRY);
|
||||
|
||||
// Tests
|
||||
//////////////////////
|
||||
|
||||
void test_jumboji__jumboji_table(void) {
|
||||
for (unsigned int i = 0; i < ARRAY_LENGTH(s_emoji_table); i++) {
|
||||
const EmojiEntry *emoji = &s_emoji_table[i];
|
||||
const Codepoint codepoint = utf8_peek_codepoint((utf8_t *)emoji->string, NULL);
|
||||
if (emoji->codepoint != codepoint) {
|
||||
printf(" ENTRY(\"%s\", 0x%05x, %s)\n", emoji->string, (unsigned int)codepoint,
|
||||
emoji->resource_name);
|
||||
}
|
||||
cl_check(emoji->codepoint == codepoint);
|
||||
}
|
||||
}
|
||||
|
||||
ResourceId prv_get_emoji_icon_by_string(const EmojiEntry *table, const char *str);
|
||||
|
||||
void test_jumboji__jumboji_detection(void) {
|
||||
// NULL is not an emoji
|
||||
cl_check(prv_get_emoji_icon_by_string(s_emoji_table, NULL) == INVALID_RESOURCE);
|
||||
|
||||
// Empty string is not an emoji
|
||||
cl_check(prv_get_emoji_icon_by_string(s_emoji_table, "") == INVALID_RESOURCE);
|
||||
|
||||
// Single emoji is detected
|
||||
cl_check(prv_get_emoji_icon_by_string(s_emoji_table, "😀") ==
|
||||
RESOURCE_ID_EMOJI_BIG_OPEN_SMILE_LARGE);
|
||||
|
||||
// Leading whitespace is ignored
|
||||
cl_check(prv_get_emoji_icon_by_string(s_emoji_table, " 😀") ==
|
||||
RESOURCE_ID_EMOJI_BIG_OPEN_SMILE_LARGE);
|
||||
|
||||
// Trailing whitespace is ignored
|
||||
cl_check(prv_get_emoji_icon_by_string(s_emoji_table, "😀 ") ==
|
||||
RESOURCE_ID_EMOJI_BIG_OPEN_SMILE_LARGE);
|
||||
|
||||
// Leading and trailing whitespace is ignored
|
||||
cl_check(prv_get_emoji_icon_by_string(s_emoji_table, " 😀 ") ==
|
||||
RESOURCE_ID_EMOJI_BIG_OPEN_SMILE_LARGE);
|
||||
|
||||
// Double emoji is ignored
|
||||
cl_check(prv_get_emoji_icon_by_string(s_emoji_table, "😀😂") == INVALID_RESOURCE);
|
||||
|
||||
// LTR indicator is ignored
|
||||
cl_check(prv_get_emoji_icon_by_string(s_emoji_table, "\u200E😂") ==
|
||||
RESOURCE_ID_EMOJI_LAUGHING_WITH_TEARS_LARGE);
|
||||
|
||||
// Zero-width-no-break at the end is ignored
|
||||
cl_check(prv_get_emoji_icon_by_string(s_emoji_table, "😂\uFEFF") ==
|
||||
RESOURCE_ID_EMOJI_LAUGHING_WITH_TEARS_LARGE);
|
||||
|
||||
// Skin tone modifier is ignored
|
||||
cl_check(prv_get_emoji_icon_by_string(s_emoji_table, "👍\xf0\x9f\x8f\xbe") ==
|
||||
RESOURCE_ID_EMOJI_THUMBS_UP_LARGE);
|
||||
}
|
412
tests/fw/ui/test_kino_player.c
Normal file
412
tests/fw/ui/test_kino_player.c
Normal file
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
* 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/ui/kino/kino_player.h"
|
||||
#include "applib/ui/kino/kino_reel.h"
|
||||
#include "applib/ui/kino/kino_reel_gbitmap.h"
|
||||
#include "applib/ui/kino/kino_reel_gbitmap_sequence.h"
|
||||
#include "applib/ui/kino/kino_reel_pdci.h"
|
||||
#include "applib/ui/kino/kino_reel_pdcs.h"
|
||||
#include "applib/ui/kino/kino_reel_custom.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
// Fakes
|
||||
////////////////////////////////////
|
||||
#include "fake_resource_syscalls.h"
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_applib_resource.h"
|
||||
#include "stubs_compiled_with_legacy2_sdk.h"
|
||||
#include "stubs_gpath.h"
|
||||
#include "stubs_graphics.h"
|
||||
#include "stubs_graphics_context.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_memory_layout.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_resources.h"
|
||||
#include "stubs_ui_window.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
|
||||
void graphics_context_move_draw_box(GContext* ctx, GPoint offset) {}
|
||||
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; }
|
||||
|
||||
typedef struct TestReelData {
|
||||
uint32_t elapsed_ms;
|
||||
uint32_t duration_ms;
|
||||
} TestReelData;
|
||||
|
||||
static int s_num_destructor_calls;
|
||||
|
||||
static void prv_destructor(KinoReel *reel) {
|
||||
s_num_destructor_calls++;
|
||||
free(kino_reel_custom_get_data(reel));
|
||||
}
|
||||
|
||||
static uint32_t prv_elapsed_getter(KinoReel *reel) {
|
||||
return ((TestReelData*)kino_reel_custom_get_data(reel))->elapsed_ms;
|
||||
}
|
||||
|
||||
static bool prv_elapsed_setter(KinoReel *reel, uint32_t elapsed_ms) {
|
||||
((TestReelData*)kino_reel_custom_get_data(reel))->elapsed_ms = elapsed_ms;
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t prv_duration_getter(KinoReel *reel) {
|
||||
return ((TestReelData*)kino_reel_custom_get_data(reel))->duration_ms;
|
||||
}
|
||||
|
||||
static struct TestReelData *test_reel_data;
|
||||
static KinoReelImpl *test_reel_impl = NULL;
|
||||
static KinoReel *test_reel = NULL;
|
||||
static KinoPlayer *test_player = NULL;
|
||||
|
||||
// Setup
|
||||
void test_kino_player__initialize(void) {
|
||||
test_reel_data = malloc(sizeof(TestReelData));
|
||||
memset(test_reel_data, 0, sizeof(TestReelData));
|
||||
|
||||
s_num_destructor_calls = 0;
|
||||
|
||||
test_reel_impl = malloc(sizeof(KinoReelImpl));
|
||||
*test_reel_impl = (KinoReelImpl) {
|
||||
.destructor = prv_destructor,
|
||||
.set_elapsed = prv_elapsed_setter,
|
||||
.get_elapsed = prv_elapsed_getter,
|
||||
.get_duration = prv_duration_getter
|
||||
};
|
||||
|
||||
test_reel = kino_reel_custom_create(test_reel_impl, test_reel_data);
|
||||
cl_assert(test_reel != NULL);
|
||||
|
||||
test_player = malloc(sizeof(KinoPlayer));
|
||||
memset(test_player, 0, sizeof(KinoPlayer));
|
||||
|
||||
kino_player_set_reel(test_player, test_reel, true);
|
||||
}
|
||||
|
||||
// Teardown
|
||||
void test_kino_player__cleanup(void) {
|
||||
kino_reel_destroy(test_reel);
|
||||
}
|
||||
|
||||
// Tests
|
||||
////////////////////////////////////
|
||||
|
||||
extern void prv_play_animation_update(Animation *animation, const AnimationProgress normalized);
|
||||
|
||||
void test_kino_player__finite_animation_finite_reel_foward(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = 300;
|
||||
kino_player_play(test_player);
|
||||
|
||||
animation_set_elapsed(test_player->animation, 1234); // intentionally bad value
|
||||
prv_play_animation_update(test_player->animation, ANIMATION_NORMALIZED_MAX * 20 / 300);
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20);
|
||||
}
|
||||
|
||||
void test_kino_player__create_finite_animation_finite_reel_foward(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = 300;
|
||||
Animation *animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
|
||||
animation_schedule(animation);
|
||||
animation_set_elapsed(animation, 1234); // intentionally bad value
|
||||
prv_play_animation_update(animation, ANIMATION_NORMALIZED_MAX * 20 / 300);
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20);
|
||||
}
|
||||
|
||||
void test_kino_player__finite_animation_finite_reel_reverse(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = 300;
|
||||
kino_player_play(test_player);
|
||||
|
||||
animation_set_reverse(test_player->animation, true);
|
||||
animation_set_elapsed(test_player->animation, 1234); // intentionally bad value
|
||||
prv_play_animation_update(test_player->animation,
|
||||
ANIMATION_NORMALIZED_MAX - ANIMATION_NORMALIZED_MAX * 20 / 300);
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 300 - 20);
|
||||
}
|
||||
|
||||
void test_kino_player__create_finite_animation_finite_reel_reverse(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = 300;
|
||||
Animation *animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
|
||||
animation_schedule(animation);
|
||||
animation_set_reverse(animation, true);
|
||||
animation_set_elapsed(animation, 1234); // intentionally bad value
|
||||
prv_play_animation_update(animation,
|
||||
ANIMATION_NORMALIZED_MAX - ANIMATION_NORMALIZED_MAX * 20 / 300);
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 300 - 20);
|
||||
}
|
||||
|
||||
void test_kino_player__finite_animation_infinite_reel_forward(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE;
|
||||
kino_player_play(test_player);
|
||||
|
||||
animation_set_elapsed(test_player->animation, 20);
|
||||
animation_set_duration(test_player->animation, 300);
|
||||
prv_play_animation_update(test_player->animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20);
|
||||
}
|
||||
|
||||
void test_kino_player__create_finite_animation_infinite_reel_forward(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE;
|
||||
Animation *animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
|
||||
animation_schedule(animation);
|
||||
animation_set_elapsed(animation, 20);
|
||||
animation_set_duration(animation, 300);
|
||||
prv_play_animation_update(animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20);
|
||||
}
|
||||
|
||||
void test_kino_player__infinite_animation_finite_reel_forward(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = 300;
|
||||
kino_player_play(test_player);
|
||||
|
||||
animation_set_elapsed(test_player->animation, 20);
|
||||
animation_set_duration(test_player->animation, ANIMATION_DURATION_INFINITE);
|
||||
prv_play_animation_update(test_player->animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20);
|
||||
}
|
||||
|
||||
void test_kino_player__create_infinite_animation_finite_reel_forward(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = 300;
|
||||
Animation *animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
|
||||
animation_schedule(animation);
|
||||
animation_set_elapsed(animation, 20);
|
||||
animation_set_duration(animation, ANIMATION_DURATION_INFINITE);
|
||||
prv_play_animation_update(animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20);
|
||||
}
|
||||
|
||||
void test_kino_player__infinite_animation_infinite_reel_forward(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE;
|
||||
kino_player_play(test_player);
|
||||
|
||||
animation_set_elapsed(test_player->animation, 20);
|
||||
animation_set_duration(test_player->animation, ANIMATION_DURATION_INFINITE);
|
||||
prv_play_animation_update(test_player->animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20);
|
||||
}
|
||||
|
||||
void test_kino_player__create_infinite_animation_infinite_reel_forward(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE;
|
||||
Animation *animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
|
||||
animation_schedule(animation);
|
||||
animation_set_elapsed(animation, 20);
|
||||
animation_set_duration(animation, ANIMATION_DURATION_INFINITE);
|
||||
prv_play_animation_update(animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20);
|
||||
}
|
||||
|
||||
void test_kino_player__infinite_animation_finite_reel_reverse(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = 300;
|
||||
kino_player_play(test_player);
|
||||
|
||||
animation_set_reverse(test_player->animation, true);
|
||||
animation_set_duration(test_player->animation, ANIMATION_DURATION_INFINITE);
|
||||
animation_set_elapsed(test_player->animation, 20);
|
||||
prv_play_animation_update(test_player->animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 300 - 20);
|
||||
}
|
||||
|
||||
void test_kino_player__create_infinite_animation_finite_reel_reverse(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = 300;
|
||||
Animation *animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
|
||||
animation_schedule(animation);
|
||||
animation_set_reverse(animation, true);
|
||||
animation_set_duration(animation, ANIMATION_DURATION_INFINITE);
|
||||
animation_set_elapsed(animation, 20);
|
||||
prv_play_animation_update(animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 300 - 20);
|
||||
}
|
||||
|
||||
void test_kino_player__finite_animation_infinite_reel_reverse(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE;
|
||||
kino_player_play(test_player);
|
||||
|
||||
animation_set_reverse(test_player->animation, true);
|
||||
animation_set_duration(test_player->animation, 300);
|
||||
animation_set_elapsed(test_player->animation, 20);
|
||||
prv_play_animation_update(test_player->animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), ANIMATION_DURATION_INFINITE);
|
||||
}
|
||||
|
||||
void test_kino_player__create_finite_animation_infinite_reel_reverse(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE;
|
||||
Animation *animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
|
||||
animation_schedule(animation);
|
||||
animation_set_reverse(animation, true);
|
||||
animation_set_duration(animation, 300);
|
||||
animation_set_elapsed(animation, 20);
|
||||
prv_play_animation_update(animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), ANIMATION_DURATION_INFINITE);
|
||||
}
|
||||
|
||||
void test_kino_player__infinite_animation_infinite_reel_reverse(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE;
|
||||
kino_player_play(test_player);
|
||||
|
||||
animation_set_reverse(test_player->animation, true);
|
||||
animation_set_duration(test_player->animation, ANIMATION_DURATION_INFINITE);
|
||||
animation_set_elapsed(test_player->animation, 20);
|
||||
prv_play_animation_update(test_player->animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), ANIMATION_DURATION_INFINITE);
|
||||
}
|
||||
|
||||
void test_kino_player__create_infinite_animation_infinite_reel_reverse(void) {
|
||||
// Choose duration and elapsed to have clean division for
|
||||
// ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number
|
||||
test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE;
|
||||
Animation *animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
|
||||
animation_schedule(animation);
|
||||
animation_set_reverse(animation, true);
|
||||
animation_set_duration(animation, ANIMATION_DURATION_INFINITE);
|
||||
animation_set_elapsed(animation, 20);
|
||||
prv_play_animation_update(animation, 0); // intentionally bad value
|
||||
|
||||
cl_assert_equal_i(kino_reel_get_elapsed(test_reel), ANIMATION_DURATION_INFINITE);
|
||||
}
|
||||
|
||||
void test_kino_player__create_animation_is_immutable(void) {
|
||||
test_reel_data->duration_ms = 300;
|
||||
Animation *animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
cl_assert_equal_i(animation_is_immutable(animation), true);
|
||||
}
|
||||
|
||||
void test_kino_player__create_animation_is_unscheduled_by_play_pause_rewind(void) {
|
||||
test_reel_data->duration_ms = 300;
|
||||
Animation *animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
animation_schedule(animation);
|
||||
|
||||
// Play plays a new animation
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), true);
|
||||
kino_player_play(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
|
||||
// Create play animation unschedules previous animation
|
||||
animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
animation_schedule(animation);
|
||||
|
||||
// Pause unschedules the current animation
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), true);
|
||||
kino_player_pause(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
|
||||
animation = (Animation *)kino_player_create_play_animation(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
animation_schedule(animation);
|
||||
|
||||
// Rewind unschedules the current animation
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), true);
|
||||
kino_player_rewind(test_player);
|
||||
cl_assert_equal_i(animation_is_scheduled(animation), false);
|
||||
}
|
||||
|
||||
void test_kino_player__set_reel_calls_destructor(void) {
|
||||
kino_player_set_reel(test_player, test_reel, true);
|
||||
cl_assert_equal_p(test_player->reel, test_reel);
|
||||
cl_assert_equal_i(s_num_destructor_calls, 0);
|
||||
|
||||
kino_player_set_reel(test_player, NULL, true);
|
||||
cl_assert_equal_p(test_player->reel, NULL);
|
||||
cl_assert_equal_i(s_num_destructor_calls, 1);
|
||||
|
||||
kino_player_set_reel(test_player, NULL, true);
|
||||
cl_assert_equal_p(test_player->reel, NULL);
|
||||
cl_assert_equal_i(s_num_destructor_calls, 1);
|
||||
|
||||
test_reel = NULL;
|
||||
}
|
||||
|
||||
void test_kino_player__set_reel_does_not_call_destructor(void) {
|
||||
test_player->owns_reel = false;
|
||||
|
||||
kino_player_set_reel(test_player, test_reel, false);
|
||||
cl_assert_equal_p(test_player->reel, test_reel);
|
||||
cl_assert_equal_i(s_num_destructor_calls, 0);
|
||||
|
||||
kino_player_set_reel(test_player, NULL, true);
|
||||
cl_assert_equal_p(test_player->reel, NULL);
|
||||
cl_assert_equal_i(s_num_destructor_calls, 0);
|
||||
}
|
196
tests/fw/ui/test_kino_reel.c
Normal file
196
tests/fw/ui/test_kino_reel.c
Normal file
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* 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/ui/kino/kino_reel.h"
|
||||
#include "applib/ui/kino/kino_reel_custom.h"
|
||||
#include "applib/ui/kino/kino_reel_gbitmap.h"
|
||||
#include "applib/ui/kino/kino_reel_gbitmap_sequence.h"
|
||||
#include "applib/ui/kino/kino_reel_pdci.h"
|
||||
#include "applib/ui/kino/kino_reel_pdcs.h"
|
||||
#include "util/graphics.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
// Fakes
|
||||
////////////////////////////////////
|
||||
#include "fake_resource_syscalls.h"
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_compiled_with_legacy2_sdk.h"
|
||||
#include "stubs_gpath.h"
|
||||
#include "stubs_graphics.h"
|
||||
#include "stubs_graphics_context.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_memory_layout.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_resources.h"
|
||||
#include "stubs_ui_window.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
|
||||
void framebuffer_clear(FrameBuffer* f) {}
|
||||
void graphics_context_move_draw_box(GContext* ctx, GPoint offset) {}
|
||||
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; }
|
||||
|
||||
// Helper Functions
|
||||
////////////////////////////////////
|
||||
#include "../graphics/test_graphics.h"
|
||||
#include "../graphics/8bit/test_framebuffer.h"
|
||||
|
||||
static FrameBuffer *fb = NULL;
|
||||
|
||||
// Setup
|
||||
void test_kino_reel__initialize(void) {
|
||||
fb = malloc(sizeof(FrameBuffer));
|
||||
fb->size = (GSize) {DISP_COLS, DISP_ROWS};
|
||||
}
|
||||
|
||||
// Teardown
|
||||
void test_kino_reel__cleanup(void) {
|
||||
free(fb);
|
||||
}
|
||||
|
||||
// Tests
|
||||
////////////////////////////////////
|
||||
|
||||
void test_kino_reel__resource_gbitmap(void) {
|
||||
GContext ctx;
|
||||
test_graphics_context_init(&ctx, fb);
|
||||
|
||||
// Test loading GBitmap Kino Reel
|
||||
uint32_t resource_id = sys_resource_load_file_as_resource(
|
||||
TEST_IMAGES_PATH, "test_kino_reel__resource_gbitmap.pbi");
|
||||
cl_assert(resource_id != UINT32_MAX);
|
||||
|
||||
KinoReel *kino_reel = kino_reel_create_with_resource(resource_id);
|
||||
cl_assert(kino_reel);
|
||||
cl_assert(kino_reel_get_type(kino_reel) == KinoReelTypeGBitmap);
|
||||
cl_assert_equal_i(kino_reel_get_data_size(kino_reel), 2308);
|
||||
|
||||
kino_reel_draw(kino_reel, &ctx, GPointZero);
|
||||
}
|
||||
|
||||
void test_kino_reel__resource_gbitmap_sequence(void) {
|
||||
GContext ctx;
|
||||
test_graphics_context_init(&ctx, fb);
|
||||
|
||||
// Test loading GBitmap Sequence Kino Reel
|
||||
uint32_t resource_id = sys_resource_load_file_as_resource(
|
||||
TEST_IMAGES_PATH, "test_kino_reel__resource_gbitmap_sequence.apng");
|
||||
cl_assert(resource_id != UINT32_MAX);
|
||||
|
||||
KinoReel *kino_reel = kino_reel_create_with_resource(resource_id);
|
||||
cl_assert(kino_reel);
|
||||
cl_assert(kino_reel_get_type(kino_reel) == KinoReelTypeGBitmapSequence);
|
||||
// We expect the default 0 because get_data_size hasn't been implemented for GBitmapSequence
|
||||
cl_assert_equal_i(kino_reel_get_data_size(kino_reel), 0);
|
||||
|
||||
kino_reel_draw(kino_reel, &ctx, GPointZero);
|
||||
}
|
||||
|
||||
void test_kino_reel__resource_pdci(void) {
|
||||
// Test loading PDCI Kino Reel
|
||||
uint32_t resource_id = sys_resource_load_file_as_resource(
|
||||
TEST_IMAGES_PATH, "test_kino_reel__resource_pdci.pdc");
|
||||
cl_assert(resource_id != UINT32_MAX);
|
||||
|
||||
KinoReel *kino_reel = kino_reel_create_with_resource(resource_id);
|
||||
cl_assert(kino_reel);
|
||||
cl_assert(kino_reel_get_type(kino_reel) == KinoReelTypePDCI);
|
||||
cl_assert_equal_i(kino_reel_get_data_size(kino_reel), 192);
|
||||
}
|
||||
|
||||
void test_kino_reel__resource_pdcs(void) {
|
||||
// Test loading PDCS Kino Reel
|
||||
uint32_t resource_id = sys_resource_load_file_as_resource(
|
||||
TEST_IMAGES_PATH, "test_kino_reel__resource_pdcs.pdc");
|
||||
cl_assert(resource_id != UINT32_MAX);
|
||||
|
||||
KinoReel *kino_reel = kino_reel_create_with_resource(resource_id);
|
||||
cl_assert(kino_reel);
|
||||
cl_assert(kino_reel_get_type(kino_reel) == KinoReelTypePDCS);
|
||||
cl_assert_equal_i(kino_reel_get_data_size(kino_reel), 356);
|
||||
}
|
||||
|
||||
void test_kino_reel__verify_pdci_get_list(void) {
|
||||
// Test loading PDCI Kino Reel
|
||||
uint32_t resource_id = sys_resource_load_file_as_resource(
|
||||
TEST_IMAGES_PATH, "test_kino_reel__resource_pdci.pdc");
|
||||
cl_assert(resource_id != UINT32_MAX);
|
||||
|
||||
KinoReel *kino_reel = kino_reel_create_with_resource(resource_id);
|
||||
cl_assert(kino_reel);
|
||||
cl_assert(kino_reel_get_type(kino_reel) == KinoReelTypePDCI);
|
||||
|
||||
// Verify that list retrieved from kino_reel same as from command_image
|
||||
GDrawCommandList *list = kino_reel_get_gdraw_command_list(kino_reel);
|
||||
GDrawCommandList *list_direct = gdraw_command_image_get_command_list(
|
||||
kino_reel_get_gdraw_command_image(kino_reel));
|
||||
cl_assert(list == list_direct && list != NULL);
|
||||
}
|
||||
|
||||
void test_kino_reel__verify_pdcs_get_list(void) {
|
||||
// Test loading PDCI Kino Reel
|
||||
uint32_t resource_id = sys_resource_load_file_as_resource(
|
||||
TEST_IMAGES_PATH, "test_kino_reel__resource_pdcs.pdc");
|
||||
cl_assert(resource_id != UINT32_MAX);
|
||||
|
||||
KinoReel *kino_reel = kino_reel_create_with_resource(resource_id);
|
||||
cl_assert(kino_reel);
|
||||
cl_assert(kino_reel_get_type(kino_reel) == KinoReelTypePDCS);
|
||||
|
||||
kino_reel_set_elapsed(kino_reel, 0);
|
||||
GDrawCommandList *list1 = kino_reel_get_gdraw_command_list(kino_reel);
|
||||
GDrawCommandList *list1_direct = gdraw_command_frame_get_command_list(
|
||||
gdraw_command_sequence_get_frame_by_elapsed(
|
||||
kino_reel_get_gdraw_command_sequence(kino_reel), 0));
|
||||
cl_assert(list1 != NULL);
|
||||
cl_assert(list1 == list1_direct);
|
||||
|
||||
// Test that after elapsed, frame has changed and new list is correct
|
||||
kino_reel_set_elapsed(kino_reel, 100);
|
||||
GDrawCommandList *list2 = kino_reel_get_gdraw_command_list(kino_reel);
|
||||
GDrawCommandList *list2_direct = gdraw_command_frame_get_command_list(
|
||||
gdraw_command_sequence_get_frame_by_elapsed(
|
||||
kino_reel_get_gdraw_command_sequence(kino_reel), 100));
|
||||
cl_assert(list2 != NULL);
|
||||
cl_assert(list2 != list1);
|
||||
cl_assert(list2 == list2_direct);
|
||||
}
|
||||
|
||||
static KinoReelProcessor s_dummy_processor;
|
||||
|
||||
static void prv_dummy_impl_draw_processed(KinoReel *reel, GContext *ctx, GPoint offset,
|
||||
KinoReelProcessor *processor) {
|
||||
cl_assert_equal_p(processor, &s_dummy_processor);
|
||||
}
|
||||
|
||||
void test_kino_reel__draw_processed(void) {
|
||||
// Calling kino_reel_draw_processed() should pass the processor to the .draw_processed function
|
||||
const KinoReelImpl dummy_impl = (KinoReelImpl) {
|
||||
.draw_processed = prv_dummy_impl_draw_processed,
|
||||
};
|
||||
KinoReel *kino_reel = kino_reel_custom_create(&dummy_impl, NULL);
|
||||
GContext ctx;
|
||||
kino_reel_draw_processed(kino_reel, &ctx, GPointZero, &s_dummy_processor);
|
||||
}
|
||||
|
433
tests/fw/ui/test_layer.c
Normal file
433
tests/fw/ui/test_layer.c
Normal file
|
@ -0,0 +1,433 @@
|
|||
/*
|
||||
* 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/ui/layer.h"
|
||||
#include "applib/ui/layer_private.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_bitblt.h"
|
||||
#include "stubs_compiled_with_legacy2_sdk.h"
|
||||
#include "stubs_gbitmap.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_resources.h"
|
||||
#include "stubs_syscalls.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
|
||||
// Setup
|
||||
////////////////////////////////////
|
||||
|
||||
void test_layer__initialize(void) {
|
||||
}
|
||||
|
||||
void test_layer__cleanup(void) {
|
||||
}
|
||||
|
||||
GDrawState graphics_context_get_drawing_state(GContext *ctx) {
|
||||
return (GDrawState) { 0 };
|
||||
}
|
||||
|
||||
bool graphics_release_frame_buffer(GContext *ctx, GBitmap *buffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void graphics_context_set_drawing_state(GContext *ctx, GDrawState draw_state) {
|
||||
}
|
||||
|
||||
void window_schedule_render(struct Window *window) {
|
||||
}
|
||||
|
||||
void recognizer_destroy(Recognizer *recognizer) {}
|
||||
|
||||
void recognizer_add_to_list(Recognizer *recognizer, RecognizerList *list) {}
|
||||
|
||||
void recognizer_remove_from_list(Recognizer *recognizer, RecognizerList *list) {}
|
||||
|
||||
RecognizerManager *window_get_recognizer_manager(Window *window) { return NULL; }
|
||||
|
||||
bool recognizer_list_iterate(RecognizerList *list, RecognizerListIteratorCb iter_cb,
|
||||
void *context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void recognizer_manager_register_recognizer(RecognizerManager *manager, Recognizer *recognizer) {}
|
||||
|
||||
void recognizer_manager_deregister_recognizer(RecognizerManager *manager, Recognizer *recognizer) {}
|
||||
// Tests
|
||||
////////////////////////////////////
|
||||
|
||||
void test_layer__add_child_and_remove_from_parent(void) {
|
||||
Layer parent, child_a, child_b, child_c, grand_child_a;
|
||||
Layer *layers[] = {&parent, &child_a, &child_b, &child_c, &grand_child_a};
|
||||
for (int i = 0; i < ARRAY_LENGTH(layers); ++i) {
|
||||
layer_init(layers[i], &GRectZero);
|
||||
}
|
||||
// Create this hierarchy:
|
||||
// This hits first_child and next_sibling add code paths.
|
||||
//
|
||||
// +-parent
|
||||
// |
|
||||
// '->child_a->child_b->child_c
|
||||
// |
|
||||
// '->grand_child_a
|
||||
//
|
||||
cl_assert(parent.first_child == NULL);
|
||||
layer_add_child(&parent, &child_a);
|
||||
cl_assert(parent.first_child == &child_a);
|
||||
layer_add_child(&parent, &child_b);
|
||||
cl_assert(parent.first_child == &child_a);
|
||||
cl_assert(child_a.next_sibling == &child_b);
|
||||
cl_assert(child_a.parent == &parent);
|
||||
cl_assert(child_b.parent == &parent);
|
||||
layer_add_child(&parent, &child_c);
|
||||
cl_assert(child_c.parent == &parent);
|
||||
cl_assert(child_b.next_sibling == &child_c);
|
||||
layer_add_child(&child_a, &grand_child_a);
|
||||
cl_assert(grand_child_a.parent == &child_a);
|
||||
|
||||
// Remove non-first-child (child_b):
|
||||
//
|
||||
// +-parent
|
||||
// |
|
||||
// '->child_a->child_c
|
||||
// |
|
||||
// '->grand_child_a
|
||||
//
|
||||
// +-child_b
|
||||
//
|
||||
layer_remove_from_parent(&child_b);
|
||||
cl_assert(child_b.parent == NULL);
|
||||
cl_assert(child_b.next_sibling == NULL);
|
||||
cl_assert(parent.first_child == &child_a);
|
||||
cl_assert(child_a.next_sibling == &child_c);
|
||||
cl_assert(grand_child_a.parent == &child_a);
|
||||
cl_assert(child_c.parent == &parent);
|
||||
|
||||
// Remove first-child (child_a):
|
||||
//
|
||||
// +-parent
|
||||
// |
|
||||
// '->child_c
|
||||
//
|
||||
// +-child_a
|
||||
// |
|
||||
// '->grand_child_a
|
||||
//
|
||||
layer_remove_from_parent(&child_a);
|
||||
cl_assert(parent.first_child == &child_c);
|
||||
cl_assert(child_c.parent == &parent);
|
||||
cl_assert(child_a.parent == NULL);
|
||||
cl_assert(child_a.next_sibling == NULL);
|
||||
cl_assert(grand_child_a.parent == &child_a);
|
||||
|
||||
// Return early when (parent->paren == NULL):
|
||||
layer_remove_from_parent(&parent);
|
||||
}
|
||||
|
||||
void test_layer__remove_child_layers(void) {
|
||||
Layer parent, child_a, child_b;
|
||||
Layer *layers[] = {&parent, &child_a, &child_b};
|
||||
for (int i = 0; i < ARRAY_LENGTH(layers); ++i) {
|
||||
layer_init(layers[i], &GRectZero);
|
||||
}
|
||||
// Create this hierarchy:
|
||||
//
|
||||
// +-parent
|
||||
// |
|
||||
// '->child_a->child_b
|
||||
//
|
||||
layer_add_child(&parent, &child_a);
|
||||
layer_add_child(&parent, &child_b);
|
||||
layer_remove_child_layers(&parent);
|
||||
cl_assert(child_a.parent == NULL);
|
||||
cl_assert(child_a.next_sibling == NULL);
|
||||
cl_assert(child_b.parent == NULL);
|
||||
cl_assert(parent.first_child == NULL);
|
||||
}
|
||||
|
||||
void test_layer__insert_below(void) {
|
||||
Layer parent, child_a, child_b, child_c;
|
||||
Layer *layers[] = {&parent, &child_a, &child_b, &child_c};
|
||||
for (int i = 0; i < ARRAY_LENGTH(layers); ++i) {
|
||||
layer_init(layers[i], &GRectZero);
|
||||
}
|
||||
// Create this hierarchy:
|
||||
//
|
||||
// +-parent
|
||||
// |
|
||||
// '->child_a
|
||||
//
|
||||
layer_add_child(&parent, &child_a);
|
||||
|
||||
// Insert child_b below child_b (first_child code path):
|
||||
//
|
||||
// +-parent
|
||||
// |
|
||||
// '->child_b->child_a
|
||||
//
|
||||
layer_insert_below_sibling(&child_b, &child_a);
|
||||
cl_assert(child_b.parent == &parent);
|
||||
cl_assert(child_b.next_sibling == &child_a);
|
||||
cl_assert(child_a.next_sibling == NULL);
|
||||
|
||||
// Insert child_c below child_a (next_sibling code path):
|
||||
//
|
||||
// +-parent
|
||||
// |
|
||||
// '->child_b->child_c->child_a
|
||||
//
|
||||
layer_insert_below_sibling(&child_c, &child_a);
|
||||
cl_assert(parent.first_child == &child_b);
|
||||
cl_assert(child_b.next_sibling == &child_c);
|
||||
cl_assert(child_c.parent == &parent);
|
||||
cl_assert(child_c.next_sibling == &child_a);
|
||||
cl_assert(child_a.next_sibling == NULL);
|
||||
}
|
||||
|
||||
void test_layer__insert_above(void) {
|
||||
Layer parent, child_a, child_b, child_c;
|
||||
Layer *layers[] = {&parent, &child_a, &child_b, &child_c};
|
||||
for (int i = 0; i < ARRAY_LENGTH(layers); ++i) {
|
||||
layer_init(layers[i], &GRectZero);
|
||||
}
|
||||
// Create this hierarchy:
|
||||
//
|
||||
// +-parent
|
||||
// |
|
||||
// '->child_b
|
||||
//
|
||||
layer_add_child(&parent, &child_b);
|
||||
|
||||
// Insert child_a above child_b (first_child code path):
|
||||
//
|
||||
// +-parent
|
||||
// |
|
||||
// '->child_b->child_a
|
||||
//
|
||||
layer_insert_above_sibling(&child_a, &child_b);
|
||||
cl_assert(child_b.parent == &parent);
|
||||
cl_assert(child_b.next_sibling == &child_a);
|
||||
cl_assert(child_a.next_sibling == NULL);
|
||||
|
||||
// Insert child_c above child_b (next_sibling code path):
|
||||
//
|
||||
// +-parent
|
||||
// |
|
||||
// '->child_b->child_c->child_a
|
||||
//
|
||||
layer_insert_above_sibling(&child_c, &child_b);
|
||||
cl_assert(parent.first_child == &child_b);
|
||||
cl_assert(child_b.next_sibling == &child_c);
|
||||
cl_assert(child_c.parent == &parent);
|
||||
cl_assert(child_c.next_sibling == &child_a);
|
||||
cl_assert(child_a.next_sibling == NULL);
|
||||
}
|
||||
|
||||
void test_layer__traverse(void) {
|
||||
Layer *stack[5];
|
||||
uint8_t current_stack = 0;
|
||||
|
||||
Layer *a = layer_create(GRectZero);
|
||||
Layer *aa = layer_create(GRectZero);
|
||||
Layer *aaa = layer_create(GRectZero);
|
||||
Layer *aaaa = layer_create(GRectZero);
|
||||
Layer *ab = layer_create(GRectZero);
|
||||
Layer *b = layer_create(GRectZero);
|
||||
|
||||
layer_add_child(a, aa);
|
||||
layer_add_child(aa, aaa);
|
||||
layer_add_child(aaa, aaaa);
|
||||
layer_add_child(a, ab);
|
||||
a->next_sibling = b;
|
||||
|
||||
stack[0] = a;
|
||||
|
||||
// go to child if possible
|
||||
Layer *actual = __layer_tree_traverse_next__test_accessor(stack, ARRAY_LENGTH(stack), ¤t_stack,
|
||||
true);
|
||||
cl_assert_equal_p(aa, actual);
|
||||
cl_assert_equal_i(1, current_stack);
|
||||
cl_assert_equal_p(a, stack[0]);
|
||||
cl_assert_equal_p(aa, stack[1]);
|
||||
|
||||
// go to child if possible
|
||||
actual = __layer_tree_traverse_next__test_accessor(stack, ARRAY_LENGTH(stack), ¤t_stack, true);
|
||||
cl_assert_equal_p(aaa, actual);
|
||||
cl_assert_equal_i(2, current_stack);
|
||||
cl_assert_equal_p(a, stack[0]);
|
||||
cl_assert_equal_p(aa, stack[1]);
|
||||
cl_assert_equal_p(aaa, stack[2]);
|
||||
|
||||
// go to child if possible
|
||||
actual = __layer_tree_traverse_next__test_accessor(stack, ARRAY_LENGTH(stack), ¤t_stack, true);
|
||||
cl_assert_equal_p(aaaa, actual);
|
||||
cl_assert_equal_i(3, current_stack);
|
||||
cl_assert_equal_p(a, stack[0]);
|
||||
cl_assert_equal_p(aa, stack[1]);
|
||||
cl_assert_equal_p(aaa, stack[2]);
|
||||
cl_assert_equal_p(aaaa, stack[3]);
|
||||
|
||||
// go back two levels and then to sibling
|
||||
actual = __layer_tree_traverse_next__test_accessor(stack, ARRAY_LENGTH(stack), ¤t_stack, true);
|
||||
cl_assert_equal_p(ab, actual);
|
||||
cl_assert_equal_i(1, current_stack);
|
||||
cl_assert_equal_p(a, stack[0]);
|
||||
cl_assert_equal_p(ab, stack[1]);
|
||||
|
||||
// go back one level and then to sibling
|
||||
actual = __layer_tree_traverse_next__test_accessor(stack, ARRAY_LENGTH(stack), ¤t_stack, true);
|
||||
cl_assert_equal_p(b, actual);
|
||||
cl_assert_equal_i(0, current_stack);
|
||||
cl_assert_equal_p(b, stack[0]);
|
||||
|
||||
// no more siblings on root level
|
||||
actual = __layer_tree_traverse_next__test_accessor(stack, ARRAY_LENGTH(stack), ¤t_stack, true);
|
||||
cl_assert_equal_p(NULL, actual);
|
||||
cl_assert_equal_i(0, current_stack);
|
||||
|
||||
// do not descend
|
||||
stack[0] = a;
|
||||
current_stack = 0;
|
||||
actual = __layer_tree_traverse_next__test_accessor(stack, ARRAY_LENGTH(stack), ¤t_stack, false);
|
||||
cl_assert_equal_p(b, actual);
|
||||
cl_assert_equal_i(0, current_stack);
|
||||
cl_assert_equal_p(b, stack[0]);
|
||||
|
||||
|
||||
// test limited stack size (go to sibling instead of child)
|
||||
stack[0] = a;
|
||||
current_stack = 0;
|
||||
actual = __layer_tree_traverse_next__test_accessor(stack, 1, ¤t_stack, true);
|
||||
cl_assert_equal_p(b, actual);
|
||||
cl_assert_equal_i(0, current_stack);
|
||||
cl_assert_equal_p(b, stack[0]);
|
||||
|
||||
// test limited stack size (go to sibling of parent instead of child)
|
||||
stack[0] = a;
|
||||
stack[1] = aa;
|
||||
stack[2] = aaa;
|
||||
current_stack = 2;
|
||||
actual = __layer_tree_traverse_next__test_accessor(stack, 3, ¤t_stack, true);
|
||||
cl_assert_equal_p(ab, actual);
|
||||
cl_assert_equal_i(1, current_stack);
|
||||
cl_assert_equal_p(a, stack[0]);
|
||||
cl_assert_equal_p(ab, stack[1]);
|
||||
|
||||
// not necessary to free memory on unit tests
|
||||
}
|
||||
|
||||
void test_layer__is_ancestor(void) {
|
||||
Layer parent, child_a, child_b, child_c;
|
||||
Layer *layers[] = {&parent, &child_a, &child_b, &child_c};
|
||||
for (int i = 0; i < ARRAY_LENGTH(layers); ++i) {
|
||||
layer_init(layers[i], &GRectZero);
|
||||
}
|
||||
|
||||
layer_add_child(&parent, &child_a);
|
||||
cl_assert(layer_is_descendant(&child_a, &parent));
|
||||
cl_assert(!layer_is_descendant(&parent, &child_a));
|
||||
cl_assert(!layer_is_descendant(&child_b, &parent));
|
||||
|
||||
layer_add_child(&parent, &child_b);
|
||||
cl_assert(layer_is_descendant(&child_b, &parent));
|
||||
cl_assert(!layer_is_descendant(&child_b, &child_a));
|
||||
cl_assert(!layer_is_descendant(&child_c, &child_a));
|
||||
|
||||
layer_add_child(&child_a, &child_c);
|
||||
cl_assert(layer_is_descendant(&child_c, &child_a));
|
||||
cl_assert(layer_is_descendant(&child_c, &parent));
|
||||
cl_assert(!layer_is_descendant(&child_c, &child_b));
|
||||
}
|
||||
|
||||
void test_layer__find_layer_contains_point(void) {
|
||||
Layer parent, child_a, child_b, child_c, child_d, child_e, child_f;
|
||||
Layer *layers[] = {&parent, &child_a, &child_b, &child_c, &child_d, &child_e, &child_f};
|
||||
for (int i = 0; i < ARRAY_LENGTH(layers); ++i) {
|
||||
layer_init(layers[i], &GRectZero);
|
||||
}
|
||||
layer_set_frame(&parent, &GRect(0, 0, 20, 20));
|
||||
layer_set_frame(&child_a, &GRect(0, 0, 10, 10));
|
||||
layer_set_frame(&child_b, &GRect(2, 2, 6, 6));
|
||||
layer_set_frame(&child_c, &GRect(10, 10, 10, 10));
|
||||
layer_set_frame(&child_d, &GRect(2, 2, 6, 6));
|
||||
layer_set_frame(&child_e, &GRect(10, 10, 10, 10));
|
||||
layer_set_frame(&child_f, &GRect(-10, -10, 40, 40));
|
||||
layer_add_child(&parent, &child_a);
|
||||
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&child_a, &GPoint(11, 11)), NULL);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&child_a, &GPoint(10, 10)), NULL);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&child_a, &GPoint(9, 9)), &child_a);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&child_a, &GPoint(0, 0)), &child_a);
|
||||
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(9, 9)), &child_a);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(10, 10)), &parent);
|
||||
|
||||
layer_add_child(&child_a, &child_f);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(9, 9)), &child_f);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(1, 1)), &child_f);
|
||||
|
||||
// child layers are subject to their parents' bounds as well as their own (parent layers clip the
|
||||
// bounds of child layers)
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(15, 15)), &parent);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(-5, -5)), NULL);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(21, 21)), NULL);
|
||||
layer_remove_from_parent(&child_f);
|
||||
|
||||
layer_add_child(&parent, &child_b);
|
||||
layer_add_child(&parent, &child_c);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(9, 9)), &child_a);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(6, 6)), &child_b);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(15, 15)), &child_c);
|
||||
|
||||
layer_add_child(&child_a, &child_d);
|
||||
layer_add_child(&child_c, &child_e);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(9, 9)), &child_a);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(6, 6)), &child_d);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(15, 15)), &child_e);
|
||||
}
|
||||
|
||||
static bool prv_override_layer_contains_point(const Layer *layer, const GPoint *point) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void test_layer__find_layer_contains_point_override_layer_contains_point(void) {
|
||||
Layer parent, child_a, child_b;
|
||||
Layer *layers[] = {&parent, &child_a, &child_b};
|
||||
for (int i = 0; i < ARRAY_LENGTH(layers); ++i) {
|
||||
layer_init(layers[i], &GRectZero);
|
||||
}
|
||||
layer_set_frame(&parent, &GRect(0, 0, 20, 20));
|
||||
layer_set_frame(&child_a, &GRect(0, 0, 10, 10));
|
||||
layer_set_frame(&child_b, &GRect(2, 2, 6, 6));
|
||||
layer_add_child(&parent, &child_a);
|
||||
layer_add_child(&child_a, &child_b);
|
||||
layer_set_contains_point_override(&child_b, prv_override_layer_contains_point);
|
||||
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&child_b, &GPoint(9, 9)), &child_b);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(9, 9)), &child_b);
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(0, 0)), &child_b);
|
||||
|
||||
// outside the bounds of child a, so child b is not found
|
||||
cl_assert_equal_p(layer_find_layer_containing_point(&parent, &GPoint(15, 15)), &parent);
|
||||
}
|
110
tests/fw/ui/test_layer_rect.c
Normal file
110
tests/fw/ui/test_layer_rect.c
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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/ui/layer.h"
|
||||
#include "applib/ui/layer_private.h"
|
||||
|
||||
#include "clar.h"
|
||||
#include "pebble_asserts.h"
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_bitblt.h"
|
||||
#include "stubs_gbitmap.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_resources.h"
|
||||
#include "stubs_syscalls.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
|
||||
GDrawState graphics_context_get_drawing_state(GContext *ctx) {
|
||||
return (GDrawState) { 0 };
|
||||
}
|
||||
|
||||
bool graphics_release_frame_buffer(GContext *ctx, GBitmap *buffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void graphics_context_set_drawing_state(GContext *ctx, GDrawState draw_state) {
|
||||
}
|
||||
|
||||
void window_schedule_render(struct Window *window) {
|
||||
}
|
||||
|
||||
static bool s_process_manager_compiled_with_legacy2_sdk;
|
||||
|
||||
bool process_manager_compiled_with_legacy2_sdk(void) {
|
||||
return s_process_manager_compiled_with_legacy2_sdk;
|
||||
}
|
||||
|
||||
// Setup
|
||||
////////////////////////////////////
|
||||
|
||||
void test_layer_rect__initialize(void) {
|
||||
s_process_manager_compiled_with_legacy2_sdk = false;
|
||||
}
|
||||
|
||||
void test_layer_rect__cleanup(void) {
|
||||
}
|
||||
|
||||
// Tests
|
||||
////////////////////////////////////
|
||||
|
||||
void test_layer_rect__2_x_extend_shrink(void) {
|
||||
Layer l;
|
||||
s_process_manager_compiled_with_legacy2_sdk = true;
|
||||
|
||||
layer_init(&l, &GRect(10, 20, 30, 40));
|
||||
cl_assert_equal_grect(l.bounds, GRect(0, 0, 30, 40));
|
||||
|
||||
// expands
|
||||
layer_set_frame(&l, &GRect(10, 20, 300, 400));
|
||||
cl_assert_equal_grect(l.bounds, GRect(0, 0, 300, 400));
|
||||
|
||||
// only expands .h, keeps .w
|
||||
layer_set_frame(&l, &GRect(10, 20, 200, 500));
|
||||
cl_assert_equal_grect(l.bounds, GRect(0, 0, 300, 500));
|
||||
}
|
||||
|
||||
void test_layer_rect__3_x_sync_if_applicable(void) {
|
||||
Layer l;
|
||||
s_process_manager_compiled_with_legacy2_sdk = false;
|
||||
|
||||
layer_init(&l, &GRect(10, 20, 30, 40));
|
||||
cl_assert_equal_grect(l.bounds, GRect(0, 0, 30, 40));
|
||||
|
||||
// expands
|
||||
layer_set_frame(&l, &GRect(10, 20, 300, 400));
|
||||
cl_assert_equal_grect(l.bounds, GRect(0, 0, 300, 400));
|
||||
|
||||
// keeps size in sync
|
||||
layer_set_frame(&l, &GRect(10, 20, 200, 500));
|
||||
cl_assert_equal_grect(l.bounds, GRect(0, 0, 200, 500));
|
||||
|
||||
// act as 2.x once bounds.origin is different from (0, 0)
|
||||
layer_set_bounds(&l, &GRect(1, 1, 200, 500));
|
||||
layer_set_frame(&l, &GRect(10, 20, 100, 600));
|
||||
cl_assert_equal_grect(l.bounds, GRect(1, 1, 200, 599));
|
||||
|
||||
// act as 2.x once bounds.size isn't same as frame.size
|
||||
layer_set_bounds(&l, &GRect(0, 0, 150, 600));
|
||||
layer_set_frame(&l, &GRect(10, 20, 100, 700));
|
||||
cl_assert_equal_grect(l.bounds, GRect(0, 0, 150, 700));
|
||||
}
|
460
tests/fw/ui/test_menu_layer.c
Normal file
460
tests/fw/ui/test_menu_layer.c
Normal file
|
@ -0,0 +1,460 @@
|
|||
/*
|
||||
* 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 "pebble_asserts.h"
|
||||
|
||||
#include "applib/ui/menu_layer.h"
|
||||
#include "applib/ui/content_indicator_private.h"
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_graphics.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_ui_window.h"
|
||||
#include "stubs_process_manager.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
|
||||
|
||||
// Fakes
|
||||
////////////////////////
|
||||
|
||||
//#include "fake_gbitmap_png.c"
|
||||
|
||||
GDrawState graphics_context_get_drawing_state(GContext* ctx) {
|
||||
return (GDrawState){};
|
||||
}
|
||||
|
||||
void graphics_context_set_drawing_state(GContext* ctx, GDrawState draw_state) {}
|
||||
void graphics_context_set_fill_color(GContext* ctx, GColor color){}
|
||||
|
||||
Layer* inverter_layer_get_layer(InverterLayer *inverter_layer) {
|
||||
return &inverter_layer->layer;
|
||||
}
|
||||
|
||||
void inverter_layer_init(InverterLayer *inverter, const GRect *frame) {}
|
||||
|
||||
void window_long_click_subscribe(ButtonId button_id, uint16_t delay_ms,
|
||||
ClickHandler down_handler, ClickHandler up_handler) {}
|
||||
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) {}
|
||||
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 content_indicator_destroy_for_scroll_layer(ScrollLayer *scroll_layer) {}
|
||||
|
||||
ContentIndicator s_content_indicator;
|
||||
ContentIndicator *content_indicator_get_for_scroll_layer(ScrollLayer *scroll_layer) {
|
||||
return &s_content_indicator;
|
||||
}
|
||||
ContentIndicator *content_indicator_get_or_create_for_scroll_layer(ScrollLayer *scroll_layer) {
|
||||
return &s_content_indicator;
|
||||
}
|
||||
|
||||
static bool s_content_available[NumContentIndicatorDirections];
|
||||
void content_indicator_set_content_available(ContentIndicator *content_indicator,
|
||||
ContentIndicatorDirection direction,
|
||||
bool available) {
|
||||
s_content_available[direction] = available;
|
||||
}
|
||||
|
||||
void graphics_context_set_compositing_mode(GContext* ctx, GCompOp mode) {}
|
||||
void graphics_draw_bitmap_in_rect(GContext *ctx, const GBitmap *bitmap, const GRect *rect){}
|
||||
|
||||
int16_t menu_cell_basic_cell_height(void) {
|
||||
return 44;
|
||||
}
|
||||
|
||||
// Tests
|
||||
//////////////////////
|
||||
|
||||
|
||||
static uint16_t s_num_rows;
|
||||
|
||||
void test_menu_layer__initialize(void) {
|
||||
s_num_rows = 10;
|
||||
}
|
||||
|
||||
void test_menu_layer__cleanup(void) {
|
||||
}
|
||||
|
||||
static void prv_draw_row(GContext* ctx,
|
||||
const Layer *cell_layer,
|
||||
MenuIndex *cell_index,
|
||||
void *callback_context) {}
|
||||
|
||||
static uint16_t prv_get_num_rows(struct MenuLayer *menu_layer,
|
||||
uint16_t section_index,
|
||||
void *callback_context) {
|
||||
return s_num_rows;
|
||||
}
|
||||
|
||||
void test_menu_layer__test_set_selected_classic(void) {
|
||||
MenuLayer l;
|
||||
menu_layer_init(&l, &GRect(10, 10, 180, 180));
|
||||
menu_layer_set_callbacks(&l, NULL, &(MenuLayerCallbacks){
|
||||
.draw_row = prv_draw_row,
|
||||
.get_num_rows = prv_get_num_rows,
|
||||
});
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(0, l.selection.y);
|
||||
cl_assert_equal_i(0, scroll_layer_get_content_offset(&l.scroll_layer).y);
|
||||
|
||||
menu_layer_set_selected_index(&l, MenuIndex(0, 1), MenuRowAlignTop, false);
|
||||
cl_assert_equal_i(1, menu_layer_get_selected_index(&l).row);
|
||||
const int16_t basic_cell_height = menu_cell_basic_cell_height();
|
||||
cl_assert_equal_i(basic_cell_height, l.selection.y);
|
||||
cl_assert_equal_i(-basic_cell_height,
|
||||
scroll_layer_get_content_offset(&l.scroll_layer).y);
|
||||
}
|
||||
|
||||
void test_menu_layer__test_set_selected_center_focused(void) {
|
||||
MenuLayer l;
|
||||
const int height = 180;
|
||||
menu_layer_init(&l, &GRect(10, 10, height, 180));
|
||||
menu_layer_set_center_focused(&l, true);
|
||||
menu_layer_set_callbacks(&l, NULL, &(MenuLayerCallbacks){
|
||||
.draw_row = prv_draw_row,
|
||||
.get_num_rows = prv_get_num_rows,
|
||||
});
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(0, l.selection.y);
|
||||
const int16_t basic_cell_height = menu_cell_basic_cell_height();
|
||||
const int row0_vertically_centered = (height - basic_cell_height)/2;
|
||||
cl_assert_equal_i(row0_vertically_centered, scroll_layer_get_content_offset(&l.scroll_layer).y);
|
||||
|
||||
menu_layer_set_selected_index(&l, MenuIndex(0, 1), MenuRowAlignTop, false);
|
||||
cl_assert_equal_i(1, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(basic_cell_height, l.selection.y);
|
||||
|
||||
const int y_center_of_row_1 = basic_cell_height + basic_cell_height / 2;
|
||||
const int row1_vertically_centered = height / 2 - y_center_of_row_1;
|
||||
cl_assert_equal_i(row1_vertically_centered, scroll_layer_get_content_offset(&l.scroll_layer).y);
|
||||
}
|
||||
|
||||
void test_menu_layer__test_set_selection_animation(void) {
|
||||
MenuLayer l;
|
||||
const int height = 180;
|
||||
menu_layer_init(&l, &GRect(10, 10, height, 180));
|
||||
menu_layer_set_callbacks(&l, NULL, &(MenuLayerCallbacks){
|
||||
.draw_row = prv_draw_row,
|
||||
.get_num_rows = prv_get_num_rows,
|
||||
});
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(0, l.selection.y);
|
||||
|
||||
// Test disabled first
|
||||
l.selection_animation_disabled = true;
|
||||
menu_layer_set_selected_index(&l, MenuIndex(0, 1), MenuRowAlignTop, true);
|
||||
cl_assert_equal_i(1, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert(!l.animation.animation);
|
||||
|
||||
// Test enabled
|
||||
l.selection_animation_disabled = false;
|
||||
menu_layer_set_selected_index(&l, MenuIndex(0, 0), MenuRowAlignTop, true);
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert(l.animation.animation);
|
||||
}
|
||||
|
||||
int16_t prv_get_row_height_depending_on_selection_state(struct MenuLayer *menu_layer,
|
||||
MenuIndex *cell_index,
|
||||
void *callback_context) {
|
||||
MenuIndex selected_index = menu_layer_get_selected_index(menu_layer);
|
||||
bool is_selected = menu_index_compare(&selected_index, cell_index) == 0;
|
||||
return is_selected ? MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT : menu_cell_basic_cell_height();
|
||||
}
|
||||
|
||||
void test_menu_layer__default_ignores_row_height_for_selection(void) {
|
||||
MenuLayer l;
|
||||
const int height = 180;
|
||||
menu_layer_init(&l, &GRect(10, 10, height, 180));
|
||||
menu_layer_set_callbacks(&l, NULL, &(MenuLayerCallbacks){
|
||||
.draw_row = prv_draw_row,
|
||||
.get_num_rows = prv_get_num_rows,
|
||||
.get_cell_height = prv_get_row_height_depending_on_selection_state,
|
||||
});
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(0, l.selection.y);
|
||||
cl_assert_equal_i(0, scroll_layer_get_content_offset(&l.scroll_layer).y);
|
||||
cl_assert_equal_b(false, s_content_available[ContentIndicatorDirectionUp]);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionDown]);
|
||||
|
||||
const int FOCUSED = MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT;
|
||||
const int NORMAL = menu_cell_basic_cell_height();
|
||||
|
||||
cl_assert_equal_i(FOCUSED, l.selection.h);
|
||||
|
||||
menu_layer_set_selected_index(&l, MenuIndex(0, 2), MenuRowAlignNone, false);
|
||||
|
||||
cl_assert(menu_layer_get_center_focused(&l) == false);
|
||||
// non-center-focus behavior: don't ask adjust for changed height of row(0,0)
|
||||
cl_assert_equal_i(FOCUSED + 1 * NORMAL, l.selection.y);
|
||||
// also non-center-focus behavior: don't update selected_index before asking row (0,1) for height
|
||||
cl_assert_equal_i(NORMAL, l.selection.h);
|
||||
cl_assert_equal_b(false, s_content_available[ContentIndicatorDirectionUp]);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionDown]);
|
||||
|
||||
// in general, the default behavior does not handle changes in row height correctly
|
||||
menu_layer_set_selected_next(&l, false, MenuRowAlignNone, false);
|
||||
cl_assert_equal_i(2 * FOCUSED + NORMAL, l.selection.y);
|
||||
cl_assert_equal_i(NORMAL, l.selection.h);
|
||||
cl_assert_equal_b(false, s_content_available[ContentIndicatorDirectionUp]);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionDown]);
|
||||
|
||||
// totally wrong
|
||||
menu_layer_set_selected_next(&l, true, MenuRowAlignNone, false);
|
||||
cl_assert_equal_i(2 * FOCUSED, l.selection.y);
|
||||
cl_assert_equal_i(NORMAL, l.selection.h);
|
||||
cl_assert_equal_b(false, s_content_available[ContentIndicatorDirectionUp]);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionDown]);
|
||||
|
||||
// WTF?!
|
||||
menu_layer_set_selected_index(&l, MenuIndex(0, 1), MenuRowAlignNone, false);
|
||||
cl_assert_equal_i(2 * FOCUSED - NORMAL, l.selection.y);
|
||||
cl_assert_equal_i(NORMAL, l.selection.h);
|
||||
cl_assert_equal_b(false, s_content_available[ContentIndicatorDirectionUp]);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionDown]);
|
||||
}
|
||||
|
||||
void test_menu_layer__center_focused_respects_row_height_for_selection(void) {
|
||||
MenuLayer l;
|
||||
const int height = 180;
|
||||
menu_layer_init(&l, &GRect(10, 10, height, 180));
|
||||
menu_layer_set_center_focused(&l, true);
|
||||
menu_layer_set_callbacks(&l, NULL, &(MenuLayerCallbacks){
|
||||
.draw_row = prv_draw_row,
|
||||
.get_num_rows = prv_get_num_rows,
|
||||
.get_cell_height = prv_get_row_height_depending_on_selection_state,
|
||||
});
|
||||
|
||||
const int FOCUSED = MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT;
|
||||
const int NORMAL = menu_cell_basic_cell_height();
|
||||
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(0, l.selection.y);
|
||||
const int row0_vertically_centered = (height - FOCUSED)/2;
|
||||
cl_assert_equal_i(row0_vertically_centered, scroll_layer_get_content_offset(&l.scroll_layer).y);
|
||||
cl_assert_equal_i(FOCUSED, l.selection.h);
|
||||
cl_assert_equal_b(false, s_content_available[ContentIndicatorDirectionUp]);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionDown]);
|
||||
|
||||
menu_layer_set_selected_index(&l, MenuIndex(0, 2), MenuRowAlignNone, false);
|
||||
// new center-focus behavior: adjust for changed row sizes depending on focused row
|
||||
cl_assert(menu_layer_get_center_focused(&l) == true);
|
||||
cl_assert_equal_i(2 * NORMAL, l.selection.y);
|
||||
cl_assert_equal_i(NORMAL - FOCUSED, scroll_layer_get_content_offset(&l.scroll_layer).y);
|
||||
cl_assert_equal_i(FOCUSED, l.selection.h);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionUp]);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionDown]);
|
||||
|
||||
menu_layer_set_selected_next(&l, false, MenuRowAlignNone, false);
|
||||
cl_assert_equal_i(3 * NORMAL, l.selection.y);
|
||||
cl_assert_equal_i(-FOCUSED, scroll_layer_get_content_offset(&l.scroll_layer).y);
|
||||
cl_assert_equal_i(FOCUSED, l.selection.h);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionUp]);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionDown]);
|
||||
|
||||
menu_layer_set_selected_next(&l, true, MenuRowAlignNone, false);
|
||||
cl_assert_equal_i(2 * NORMAL, l.selection.y);
|
||||
cl_assert_equal_i(NORMAL - FOCUSED, scroll_layer_get_content_offset(&l.scroll_layer).y);
|
||||
cl_assert_equal_i(FOCUSED, l.selection.h);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionUp]);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionDown]);
|
||||
|
||||
menu_layer_set_selected_index(&l, MenuIndex(0, 1), MenuRowAlignNone, false);
|
||||
cl_assert_equal_i(1 * NORMAL, l.selection.y);
|
||||
cl_assert_equal_i(2 * NORMAL - FOCUSED, scroll_layer_get_content_offset(&l.scroll_layer).y);
|
||||
cl_assert_equal_i(FOCUSED, l.selection.h);
|
||||
cl_assert_equal_b(false, s_content_available[ContentIndicatorDirectionUp]);
|
||||
cl_assert_equal_b(true, s_content_available[ContentIndicatorDirectionDown]);
|
||||
}
|
||||
|
||||
static void prv_skip_odd_rows(struct MenuLayer *menu_layer,
|
||||
MenuIndex *new_index,
|
||||
MenuIndex old_index,
|
||||
void *callback_context) {
|
||||
if (new_index->row == 1) {
|
||||
new_index->row = 2;
|
||||
}
|
||||
if (new_index->row == 3) {
|
||||
new_index->row = 4;
|
||||
}
|
||||
}
|
||||
|
||||
void test_menu_layer__center_focused_handles_skipped_rows(void) {
|
||||
MenuLayer l;
|
||||
menu_layer_init(&l, &GRect(10, 10, DISP_COLS, DISP_ROWS));
|
||||
menu_layer_set_center_focused(&l, true);
|
||||
menu_layer_set_callbacks(&l, NULL, &(MenuLayerCallbacks) {
|
||||
.draw_row = prv_draw_row,
|
||||
.get_num_rows = prv_get_num_rows,
|
||||
.selection_will_change = prv_skip_odd_rows,
|
||||
});
|
||||
menu_layer_reload_data(&l);
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).section);
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(0, l.selection.y);
|
||||
|
||||
menu_layer_set_selected_next(&l, false, MenuRowAlignNone, false);
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).section);
|
||||
cl_assert_equal_i(2, menu_layer_get_selected_index(&l).row);
|
||||
const int16_t basic_cell_height = menu_cell_basic_cell_height();
|
||||
cl_assert_equal_i(2 * basic_cell_height, l.selection.y);
|
||||
|
||||
menu_layer_set_selected_next(&l, false, MenuRowAlignNone, false);
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).section);
|
||||
cl_assert_equal_i(4, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(4 * basic_cell_height, l.selection.y);
|
||||
|
||||
menu_layer_set_selected_next(&l, false, MenuRowAlignNone, false);
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).section);
|
||||
cl_assert_equal_i(5, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(5 * basic_cell_height, l.selection.y);
|
||||
|
||||
menu_layer_set_selected_next(&l, true, MenuRowAlignNone, false);
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).section);
|
||||
cl_assert_equal_i(4, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(4 * basic_cell_height, l.selection.y);
|
||||
}
|
||||
|
||||
void test_menu_layer__center_focused_handles_skipped_rows_animated(void) {
|
||||
MenuLayer l;
|
||||
menu_layer_init(&l, &GRect(10, 10, DISP_COLS, DISP_ROWS));
|
||||
menu_layer_set_center_focused(&l, true);
|
||||
menu_layer_set_callbacks(&l, NULL, &(MenuLayerCallbacks) {
|
||||
.draw_row = prv_draw_row,
|
||||
.get_num_rows = prv_get_num_rows,
|
||||
.selection_will_change = prv_skip_odd_rows,
|
||||
});
|
||||
menu_layer_reload_data(&l);
|
||||
const int16_t basic_cell_height = menu_cell_basic_cell_height();
|
||||
const int initial_scroll_offset = (DISP_ROWS - basic_cell_height) / 2;
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).section);
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(0, l.selection.y);
|
||||
cl_assert_equal_i(initial_scroll_offset, l.scroll_layer.content_sublayer.bounds.origin.y);
|
||||
|
||||
menu_layer_set_selected_next(&l, false, MenuRowAlignNone, true);
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).section);
|
||||
// these values are unchanged until the animation updates them
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(0 * basic_cell_height, l.selection.y);
|
||||
cl_assert_equal_i(initial_scroll_offset, l.scroll_layer.content_sublayer.bounds.origin.y);
|
||||
|
||||
// in this test setup, we can directly cast an animation to AnimationPrivate
|
||||
AnimationPrivate *ap = (AnimationPrivate *) l.animation.animation;
|
||||
const AnimationImplementation *const impl = ap->implementation;
|
||||
impl->update(l.animation.animation, ANIMATION_NORMALIZED_MAX / 10);
|
||||
// still unchanged
|
||||
cl_assert_equal_i(0, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(0 * basic_cell_height, l.selection.y);
|
||||
cl_assert_equal_i(initial_scroll_offset, l.scroll_layer.content_sublayer.bounds.origin.y);
|
||||
|
||||
// and updated
|
||||
impl->update(l.animation.animation, ANIMATION_NORMALIZED_MAX * 9 / 10);
|
||||
cl_assert_equal_i(2, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(2 * basic_cell_height, l.selection.y);
|
||||
cl_assert_equal_i(initial_scroll_offset - 2 * basic_cell_height,
|
||||
l.scroll_layer.content_sublayer.bounds.origin.y);
|
||||
|
||||
animation_unschedule(l.animation.animation);
|
||||
menu_layer_set_selected_next(&l, false, MenuRowAlignNone, true);
|
||||
// these values are unchanged until the animation updates them
|
||||
cl_assert_equal_i(2, menu_layer_get_selected_index(&l).row);
|
||||
cl_assert_equal_i(2 * basic_cell_height, l.selection.y);
|
||||
cl_assert_equal_i(initial_scroll_offset - 2 * basic_cell_height,
|
||||
l.scroll_layer.content_sublayer.bounds.origin.y);
|
||||
}
|
||||
|
||||
static MenuLayer s_menu_layer_hierarchy;
|
||||
|
||||
static void prv_menu_cell_is_part_of_hierarchy_draw_row(GContext* ctx,
|
||||
const Layer *cell_layer,
|
||||
MenuIndex *cell_index,
|
||||
void *callback_context) {
|
||||
cl_assert_equal_p(cell_layer->window, s_menu_layer_hierarchy.scroll_layer.layer.window);
|
||||
cl_assert_equal_p(cell_layer->parent, &s_menu_layer_hierarchy.scroll_layer.content_sublayer);
|
||||
const GPoint actual = layer_convert_point_to_screen(cell_layer, GPointZero);
|
||||
const GPoint expected = layer_convert_point_to_screen(&s_menu_layer_hierarchy.scroll_layer.layer,
|
||||
GPoint(0, cell_index->row * 44));
|
||||
cl_assert_equal_gpoint(actual, expected);
|
||||
}
|
||||
|
||||
int prv_num_sublayers(const Layer *l) {
|
||||
int result = 0;
|
||||
Layer *child = l->first_child;
|
||||
while (l) {
|
||||
l = l->next_sibling;
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void test_menu_layer__menu_cell_is_part_of_hierarchy(void) {
|
||||
menu_layer_init(&s_menu_layer_hierarchy, &GRect(10, 10, 100, 180));
|
||||
Layer *layer = &s_menu_layer_hierarchy.scroll_layer.content_sublayer;
|
||||
// two layers (inverter + shadow)
|
||||
cl_assert_equal_i(2, prv_num_sublayers(layer));
|
||||
menu_layer_set_callbacks(&s_menu_layer_hierarchy, NULL, &(MenuLayerCallbacks){
|
||||
.draw_row = prv_menu_cell_is_part_of_hierarchy_draw_row,
|
||||
.get_num_rows = prv_get_num_rows,
|
||||
});
|
||||
menu_layer_reload_data(&s_menu_layer_hierarchy);
|
||||
GContext ctx = {};
|
||||
cl_assert_equal_i(2, prv_num_sublayers(layer));
|
||||
layer->update_proc(layer, &ctx);
|
||||
cl_assert_equal_i(2, prv_num_sublayers(layer));
|
||||
}
|
||||
|
||||
void test_menu_layer__center_focused_updates_height_on_reload(void) {
|
||||
MenuLayer l;
|
||||
const int height = DISP_ROWS;
|
||||
menu_layer_init(&l, &GRect(10, 10, height, DISP_COLS));
|
||||
menu_layer_set_center_focused(&l, true);
|
||||
s_num_rows = 3;
|
||||
menu_layer_set_callbacks(&l, NULL, &(MenuLayerCallbacks) {
|
||||
.draw_row = prv_draw_row,
|
||||
.get_num_rows = prv_get_num_rows,
|
||||
.get_cell_height = prv_get_row_height_depending_on_selection_state,
|
||||
});
|
||||
menu_layer_set_center_focused(&l, true);
|
||||
menu_layer_reload_data(&l);
|
||||
const int focused_height = MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT;
|
||||
|
||||
// focus last row
|
||||
menu_layer_set_selected_index(&l, MenuIndex(0, s_num_rows - 1), MenuRowAlignNone, false);
|
||||
cl_assert_equal_i(focused_height, l.selection.h);
|
||||
|
||||
s_num_rows--;
|
||||
cl_assert_equal_i(2, s_num_rows);
|
||||
menu_layer_reload_data(&l);
|
||||
cl_assert_equal_i(s_num_rows - 1, l.selection.index.row);
|
||||
cl_assert_equal_i(focused_height, l.selection.h);
|
||||
|
||||
s_num_rows--;
|
||||
cl_assert_equal_i(1, s_num_rows);
|
||||
menu_layer_reload_data(&l);
|
||||
cl_assert_equal_i(s_num_rows - 1, l.selection.index.row);
|
||||
cl_assert_equal_i(focused_height, l.selection.h);
|
||||
}
|
417
tests/fw/ui/test_menu_layer_system_cells.c
Normal file
417
tests/fw/ui/test_menu_layer_system_cells.c
Normal file
|
@ -0,0 +1,417 @@
|
|||
/*
|
||||
* 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));
|
||||
}
|
461
tests/fw/ui/test_notification_window.c
Normal file
461
tests/fw/ui/test_notification_window.c
Normal file
|
@ -0,0 +1,461 @@
|
|||
/*
|
||||
* 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/preferred_content_size.h"
|
||||
#include "applib/ui/window_private.h"
|
||||
#include "apps/system_apps/settings/settings_notifications_private.h"
|
||||
#include "popups/notifications/notification_window.h"
|
||||
#include "popups/notifications/notification_window_private.h"
|
||||
#include "resource/timeline_resource_ids.auto.h"
|
||||
#include "services/normal/timeline/notification_layout.h"
|
||||
#include "util/trig.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
|
||||
#include "stubs_action_menu.h"
|
||||
#include "stubs_alarm_layout.h"
|
||||
#include "stubs_alerts.h"
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_ancs_filtering.h"
|
||||
#include "stubs_app_install_manager.h"
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_app_timer.h"
|
||||
#include "stubs_app_window_stack.h"
|
||||
#include "stubs_bluetooth_persistent_storage.h"
|
||||
#include "stubs_bootbits.h"
|
||||
#include "stubs_buffer.h"
|
||||
#include "stubs_calendar_layout.h"
|
||||
#include "stubs_click.h"
|
||||
#include "stubs_content_indicator.h"
|
||||
#include "stubs_dialog.h"
|
||||
#include "stubs_do_not_disturb.h"
|
||||
#include "stubs_event_loop.h"
|
||||
#include "stubs_event_service_client.h"
|
||||
#include "stubs_evented_timer.h"
|
||||
#include "stubs_generic_layout.h"
|
||||
#include "stubs_health_layout.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_i18n.h"
|
||||
#include "stubs_ios_notif_pref_db.h"
|
||||
#include "stubs_layer.h"
|
||||
#include "stubs_light.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_memory_layout.h"
|
||||
#include "stubs_menu_cell_layer.h"
|
||||
#include "stubs_modal_manager.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_notification_storage.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_process_info.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_peek_layer.h"
|
||||
#include "stubs_pin_db.h"
|
||||
#include "stubs_print.h"
|
||||
#include "stubs_process_manager.h"
|
||||
#include "stubs_prompt.h"
|
||||
#include "stubs_regular_timer.h"
|
||||
#include "stubs_reminder_db.h"
|
||||
#include "stubs_reminders.h"
|
||||
#include "stubs_serial.h"
|
||||
#include "stubs_session.h"
|
||||
#include "stubs_shell_prefs.h"
|
||||
#include "stubs_simple_dialog.h"
|
||||
#include "stubs_sleep.h"
|
||||
#include "stubs_sports_layout.h"
|
||||
#include "stubs_stringlist.h"
|
||||
#include "stubs_syscall_internal.h"
|
||||
#include "stubs_syscalls.h"
|
||||
#include "stubs_task_watchdog.h"
|
||||
#include "stubs_time.h"
|
||||
#include "stubs_timeline.h"
|
||||
#include "stubs_timeline_actions.h"
|
||||
#include "stubs_timeline_item.h"
|
||||
#include "stubs_timeline_layer.h"
|
||||
#include "stubs_timeline_peek.h"
|
||||
#include "stubs_vibes.h"
|
||||
#include "stubs_weather_layout.h"
|
||||
#include "stubs_window_manager.h"
|
||||
#include "stubs_window_stack.h"
|
||||
|
||||
int16_t interpolate_int16(int32_t normalized, int16_t from, int16_t to) {
|
||||
return to;
|
||||
}
|
||||
|
||||
uint32_t interpolate_uint32(int32_t normalized, uint32_t from, uint32_t to) {
|
||||
return to;
|
||||
}
|
||||
|
||||
int64_t interpolate_moook(int32_t normalized, int64_t from, int64_t to) {
|
||||
return to;
|
||||
}
|
||||
|
||||
uint32_t interpolate_moook_duration() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t interpolate_moook_soft(int32_t normalized, int64_t from, int64_t to,
|
||||
int32_t num_frames_mid) {
|
||||
return to;
|
||||
}
|
||||
|
||||
uint32_t interpolate_moook_soft_duration(int32_t num_frames_mid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fakes
|
||||
/////////////////////
|
||||
|
||||
#include "fake_animation.h"
|
||||
#include "fake_app_state.h"
|
||||
#include "fake_content_indicator.h"
|
||||
#include "fake_graphics_context.h"
|
||||
#include "fake_spi_flash.h"
|
||||
#include "../../fixtures/load_test_resources.h"
|
||||
|
||||
typedef struct NotificationWindowTestData {
|
||||
uint32_t icon_id;
|
||||
const char *app_name;
|
||||
const char *title;
|
||||
const char *subtitle;
|
||||
const char *location_name;
|
||||
const char *body;
|
||||
const char *timestamp;
|
||||
const char *reminder_timestamp;
|
||||
GColor primary_color;
|
||||
GColor background_color;
|
||||
bool show_notification_timestamp;
|
||||
bool is_reminder;
|
||||
struct {
|
||||
AttributeList attr_list;
|
||||
TimelineItem timeline_item;
|
||||
} statics;
|
||||
} NotificationWindowTestData;
|
||||
|
||||
static NotificationWindowTestData s_test_data;
|
||||
|
||||
void clock_get_since_time(char *buffer, int buf_size, time_t timestamp) {
|
||||
if (buffer && s_test_data.timestamp) {
|
||||
strncpy(buffer, s_test_data.timestamp, (size_t)buf_size);
|
||||
buffer[buf_size - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void clock_get_until_time(char *buffer, int buf_size, time_t timestamp, int max_relative_hrs) {
|
||||
if (buffer && s_test_data.reminder_timestamp) {
|
||||
strncpy(buffer, s_test_data.reminder_timestamp, (size_t)buf_size);
|
||||
buffer[buf_size - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void clock_copy_time_string(char *buffer, uint8_t buf_size) {
|
||||
if (buffer) {
|
||||
strncpy(buffer, "12:00 PM", buf_size);
|
||||
buffer[buf_size - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
//! This function overrides the implementation in swap_layer.c as a way of providing the data
|
||||
//! we want to display in each notification
|
||||
LayoutLayer *prv_get_layout_handler(SwapLayer *swap_layer, int8_t rel_position,
|
||||
void *context) {
|
||||
// Only support one layout at a time for now
|
||||
if (rel_position != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NotificationWindowData *data = context;
|
||||
|
||||
AttributeList *attr_list = &s_test_data.statics.attr_list;
|
||||
attribute_list_add_resource_id(attr_list, AttributeIdIconTiny, s_test_data.icon_id);
|
||||
if (s_test_data.app_name) {
|
||||
attribute_list_add_cstring(attr_list, AttributeIdAppName, s_test_data.app_name);
|
||||
}
|
||||
if (s_test_data.title) {
|
||||
attribute_list_add_cstring(attr_list, AttributeIdTitle, s_test_data.title);
|
||||
}
|
||||
if (s_test_data.subtitle) {
|
||||
attribute_list_add_cstring(attr_list, AttributeIdSubtitle, s_test_data.subtitle);
|
||||
}
|
||||
if (s_test_data.location_name) {
|
||||
attribute_list_add_cstring(attr_list, AttributeIdLocationName, s_test_data.location_name);
|
||||
}
|
||||
if (s_test_data.body) {
|
||||
attribute_list_add_cstring(attr_list, AttributeIdBody, s_test_data.body);
|
||||
}
|
||||
if (!gcolor_is_invisible(s_test_data.primary_color)) {
|
||||
attribute_list_add_uint8(attr_list, AttributeIdPrimaryColor, s_test_data.primary_color.argb);
|
||||
}
|
||||
if (!gcolor_is_invisible(s_test_data.background_color)) {
|
||||
attribute_list_add_uint8(attr_list, AttributeIdBgColor, s_test_data.background_color.argb);
|
||||
}
|
||||
|
||||
s_test_data.statics.timeline_item = (TimelineItem) {
|
||||
.header = (CommonTimelineItemHeader) {
|
||||
.layout = LayoutIdNotification,
|
||||
.type = s_test_data.is_reminder ? TimelineItemTypeReminder : TimelineItemTypeNotification,
|
||||
},
|
||||
.attr_list = *attr_list,
|
||||
};
|
||||
|
||||
TimelineItem *item = &s_test_data.statics.timeline_item;
|
||||
|
||||
NotificationLayoutInfo layout_info = (NotificationLayoutInfo) {
|
||||
.item = item,
|
||||
.show_notification_timestamp = s_test_data.show_notification_timestamp,
|
||||
};
|
||||
const LayoutLayerConfig config = {
|
||||
.frame = &data->window.layer.bounds,
|
||||
.attributes = &item->attr_list,
|
||||
.mode = LayoutLayerModeCard,
|
||||
.app_id = &data->notification_app_id,
|
||||
.context = &layout_info,
|
||||
};
|
||||
return notification_layout_create(&config);
|
||||
}
|
||||
|
||||
static void prv_property_animation_grect_update(Animation *animation,
|
||||
const AnimationProgress progress) {
|
||||
PropertyAnimationPrivate *property_animation = (PropertyAnimationPrivate *)animation;
|
||||
if (property_animation) {
|
||||
layer_set_frame(property_animation->subject, &property_animation->values.to.grect);
|
||||
}
|
||||
}
|
||||
|
||||
static const PropertyAnimationImplementation s_frame_layer_implementation = {
|
||||
.base.update = prv_property_animation_grect_update,
|
||||
.accessors = {
|
||||
.setter.grect = (const GRectSetter)layer_set_frame_by_value,
|
||||
.getter.grect = (const GRectGetter)layer_get_frame_by_value,
|
||||
},
|
||||
};
|
||||
|
||||
//! Overrides the stub in stubs_animation.c to provide the proper plumbing for scrolling
|
||||
PropertyAnimation *property_animation_create_layer_frame(
|
||||
struct Layer *layer, GRect *from_frame, GRect *to_frame) {
|
||||
PropertyAnimationPrivate *animation = (PropertyAnimationPrivate *)
|
||||
property_animation_create(&s_frame_layer_implementation, layer, from_frame, to_frame);
|
||||
if (from_frame) {
|
||||
animation->values.from.grect = *from_frame;
|
||||
PropertyAnimationImplementation *impl =
|
||||
(PropertyAnimationImplementation *)animation->animation.implementation;
|
||||
impl->accessors.setter.grect(animation->subject, animation->values.from.grect);
|
||||
}
|
||||
if (to_frame) {
|
||||
animation->values.to.grect = *to_frame;
|
||||
}
|
||||
return (PropertyAnimation *)animation;
|
||||
}
|
||||
|
||||
// Helper Functions
|
||||
/////////////////////
|
||||
|
||||
#include "../graphics/test_graphics.h"
|
||||
#include "../graphics/util.h"
|
||||
|
||||
// Setup and Teardown
|
||||
////////////////////////////////////
|
||||
|
||||
// To easily render multiple windows in a single canvas, we'll use an 8-bit bitmap for color
|
||||
// displays (including round), but we can use the native format for black and white displays (1-bit)
|
||||
#define CANVAS_GBITMAP_FORMAT PBL_IF_COLOR_ELSE(GBitmapFormat8Bit, GBITMAP_NATIVE_FORMAT)
|
||||
|
||||
// Overrides same function in graphics.c; we need to do this so we can pass in the GBitmapFormat
|
||||
// we need to use for the unit test output canvas instead of relying on GBITMAP_NATIVE_FORMAT, which
|
||||
// wouldn't work for Spalding since it uses GBitmapFormat8BitCircular
|
||||
GBitmap* graphics_capture_frame_buffer(GContext *ctx) {
|
||||
PBL_ASSERTN(ctx);
|
||||
return graphics_capture_frame_buffer_format(ctx, CANVAS_GBITMAP_FORMAT);
|
||||
}
|
||||
|
||||
// Overrides same function in graphics.c; we need to do this so we can release the framebuffer we're
|
||||
// using even though its format doesn't match GBITMAP_NATIVE_FORMAT (see comment for mocked
|
||||
// graphics_capture_frame_buffer() above)
|
||||
bool graphics_release_frame_buffer(GContext *ctx, GBitmap *buffer) {
|
||||
PBL_ASSERTN(ctx);
|
||||
ctx->lock = false;
|
||||
framebuffer_dirty_all(ctx->parent_framebuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void test_notification_window__initialize(void) {
|
||||
fake_app_state_init();
|
||||
load_system_resources_fixture();
|
||||
|
||||
attribute_list_destroy_list(&s_test_data.statics.attr_list);
|
||||
s_test_data = (NotificationWindowTestData) {};
|
||||
}
|
||||
|
||||
void test_notification_window__cleanup(void) {
|
||||
}
|
||||
|
||||
// Helpers
|
||||
//////////////////////
|
||||
|
||||
extern NotificationWindowData s_notification_window_data;
|
||||
|
||||
//! Static function in swap_layer.c used to scroll
|
||||
void prv_attempt_scroll(SwapLayer *swap_layer, ScrollDirection direction, bool is_repeating);
|
||||
|
||||
static void prv_render_notification_window(unsigned int num_down_scrolls) {
|
||||
Window *window = &s_notification_window_data.window;
|
||||
|
||||
// Set the window on screen so its load/appear handlers will be called
|
||||
window_set_on_screen(window, true, true);
|
||||
|
||||
// Trigger a reload of the NotificationWindow's SwapLayer so it will be updated with the content
|
||||
// in s_test_data
|
||||
swap_layer_reload_data(&s_notification_window_data.swap_layer);
|
||||
|
||||
// Scroll down the specified number of times
|
||||
SwapLayer *swap_layer = &s_notification_window_data.swap_layer;
|
||||
for (int i = 0; i < num_down_scrolls; i++) {
|
||||
prv_attempt_scroll(swap_layer, ScrollDirectionDown, false /* is_repeating */);
|
||||
fake_animation_complete(swap_layer->animation);
|
||||
swap_layer->animation = NULL;
|
||||
}
|
||||
|
||||
// Force the display of the action button
|
||||
layer_set_hidden(&s_notification_window_data.action_button_layer, false);
|
||||
|
||||
// Render the window
|
||||
window_render(window, fake_graphics_context_get_context());
|
||||
}
|
||||
|
||||
//! @note This must be a multiple of 8 so that we are word-aligned when using a 1-bit bitmap.
|
||||
#define GRID_CELL_PADDING 8
|
||||
|
||||
static void prv_prepare_canvas_and_render_notification_windows(unsigned int num_down_scrolls) {
|
||||
// Initialize the notification window module before rendering anything
|
||||
notification_window_init(false /* is_modal */);
|
||||
|
||||
const unsigned int num_columns = SettingsContentSizeCount;
|
||||
const unsigned int num_rows = num_down_scrolls + 1;
|
||||
|
||||
const int16_t bitmap_width = (DISP_COLS * num_columns) + (GRID_CELL_PADDING * (num_columns + 1));
|
||||
const int16_t bitmap_height =
|
||||
(int16_t)((num_rows == 1) ? DISP_ROWS :
|
||||
((DISP_ROWS * num_rows) + (GRID_CELL_PADDING * (num_rows + 1))));
|
||||
const GSize bitmap_size = GSize(bitmap_width, bitmap_height);
|
||||
GBitmap *canvas_bitmap = gbitmap_create_blank(bitmap_size, CANVAS_GBITMAP_FORMAT);
|
||||
PBL_ASSERTN(canvas_bitmap);
|
||||
|
||||
GContext *ctx = fake_graphics_context_get_context();
|
||||
ctx->dest_bitmap = *canvas_bitmap;
|
||||
// We modify the bitmap's data pointer below so save a reference to the original here
|
||||
uint8_t *saved_bitmap_addr = ctx->dest_bitmap.addr;
|
||||
const uint8_t bitdepth = gbitmap_get_bits_per_pixel(ctx->dest_bitmap.info.format);
|
||||
|
||||
// Fill the bitmap with pink (on color) or white (on b&w) so it's easier to see errors
|
||||
const GColor out_of_bounds_color = PBL_IF_COLOR_ELSE(GColorShockingPink, GColorWhite);
|
||||
memset(canvas_bitmap->addr, out_of_bounds_color.argb,
|
||||
canvas_bitmap->row_size_bytes * canvas_bitmap->bounds.size.h);
|
||||
|
||||
for (int settings_content_size = 0; settings_content_size < SettingsContentSizeCount;
|
||||
settings_content_size++) {
|
||||
const PreferredContentSize content_size =
|
||||
settings_content_size_to_preferred_size((SettingsContentSize)settings_content_size);
|
||||
system_theme_set_content_size(content_size);
|
||||
|
||||
const int16_t x_offset =
|
||||
(int16_t)(GRID_CELL_PADDING + (settings_content_size * (GRID_CELL_PADDING + DISP_COLS)));
|
||||
|
||||
for (int down_scrolls = 0; down_scrolls <= num_down_scrolls; down_scrolls++) {
|
||||
const int16_t y_offset =
|
||||
(int16_t)((num_rows == 1) ? 0 :
|
||||
GRID_CELL_PADDING + (down_scrolls * (GRID_CELL_PADDING + DISP_ROWS)));
|
||||
// Set the GContext bitmap's data pointer to the position in the larger bitmap where we
|
||||
// want to draw this particular notification window
|
||||
ctx->dest_bitmap.addr =
|
||||
saved_bitmap_addr + (y_offset * ctx->dest_bitmap.row_size_bytes) +
|
||||
(x_offset * bitdepth / 8);
|
||||
|
||||
prv_render_notification_window((unsigned int)down_scrolls);
|
||||
|
||||
// On Round we end up drawing outside the visible screen bounds, so let's draw a circle where
|
||||
// those bounds are to help us visualize each copy of the screen
|
||||
#if PBL_ROUND
|
||||
graphics_context_set_fill_color(ctx, GColorBlack);
|
||||
graphics_fill_radial(ctx, DISP_FRAME, GOvalScaleModeFitCircle, 1, 0, TRIG_MAX_ANGLE);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the bitmap's original data pointer
|
||||
ctx->dest_bitmap.addr = saved_bitmap_addr;
|
||||
}
|
||||
|
||||
// Tests
|
||||
//////////////////////
|
||||
|
||||
void test_notification_window__title_body(void) {
|
||||
s_test_data = (NotificationWindowTestData) {
|
||||
.icon_id = TIMELINE_RESOURCE_NOTIFICATION_FACEBOOK_MESSENGER,
|
||||
.title = "Henry Levak",
|
||||
.body = "Nu, Shara. Where are my designs, blat?",
|
||||
.show_notification_timestamp = true,
|
||||
.timestamp = "Just now",
|
||||
.background_color = GColorPictonBlue,
|
||||
};
|
||||
const unsigned int num_down_scrolls =
|
||||
PBL_IF_RECT_ELSE((PreferredContentSizeDefault < PreferredContentSizeLarge) ? 1 : 0, 0);
|
||||
prv_prepare_canvas_and_render_notification_windows(num_down_scrolls);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
||||
|
||||
void test_notification_window__title_subtitle_body(void) {
|
||||
s_test_data = (NotificationWindowTestData) {
|
||||
.icon_id = TIMELINE_RESOURCE_NOTIFICATION_GOOGLE_INBOX,
|
||||
.title = "Henry Levak",
|
||||
.subtitle = "Henry Levak sent you a 1-1 message",
|
||||
.body = "Good morning to you my friend!",
|
||||
.background_color = GColorRed,
|
||||
};
|
||||
prv_prepare_canvas_and_render_notification_windows(PBL_IF_RECT_ELSE(2, 1) /* num_down_scrolls */);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
||||
|
||||
void test_notification_window__reminder(void) {
|
||||
s_test_data = (NotificationWindowTestData) {
|
||||
.icon_id = TIMELINE_RESOURCE_NOTIFICATION_REMINDER,
|
||||
.title = "Feed Humphrey",
|
||||
.location_name = "RWC Office",
|
||||
.body = "Only the best!",
|
||||
.reminder_timestamp = "In 15 minutes",
|
||||
.is_reminder = true,
|
||||
};
|
||||
const unsigned int num_down_scrolls =
|
||||
(PreferredContentSizeDefault >= PreferredContentSizeLarge) ? 0 : 1;
|
||||
prv_prepare_canvas_and_render_notification_windows(num_down_scrolls);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
||||
|
||||
void test_notification_window__body_icon(void) {
|
||||
s_test_data = (NotificationWindowTestData) {
|
||||
.icon_id = TIMELINE_RESOURCE_NOTIFICATION_GOOGLE_HANGOUTS,
|
||||
.title = "Kevin Conley",
|
||||
.subtitle = (PreferredContentSizeDefault >= PreferredContentSizeLarge) ? "New mail!" : NULL,
|
||||
.body = "❤",
|
||||
.background_color = GColorIslamicGreen,
|
||||
};
|
||||
prv_prepare_canvas_and_render_notification_windows(0 /* num_down_scrolls */);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
228
tests/fw/ui/test_option_menu_window.c
Normal file
228
tests/fw/ui/test_option_menu_window.c
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* 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/ui/option_menu_window.h"
|
||||
#include "resource/resource.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/normal/timeline/timeline_resources.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
// Fakes
|
||||
/////////////////////
|
||||
|
||||
#include "fake_app_state.h"
|
||||
#include "fake_content_indicator.h"
|
||||
#include "fake_graphics_context.h"
|
||||
#include "fixtures/load_test_resources.h"
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_animation_timing.h"
|
||||
#include "stubs_app_install_manager.h"
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_app_timer.h"
|
||||
#include "stubs_bootbits.h"
|
||||
#include "stubs_buffer.h"
|
||||
#include "stubs_click.h"
|
||||
#include "stubs_compiled_with_legacy2_sdk.h"
|
||||
#include "stubs_event_service_client.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_unobstructed_area.h"
|
||||
#include "stubs_window_manager.h"
|
||||
#include "stubs_window_stack.h"
|
||||
|
||||
// Setup and Teardown
|
||||
////////////////////////////////////
|
||||
|
||||
typedef struct OptionMenuTestData {
|
||||
OptionMenu option_menu;
|
||||
} OptionMenuTestData;
|
||||
|
||||
OptionMenuTestData s_data;
|
||||
|
||||
void test_option_menu_window__initialize(void) {
|
||||
fake_app_state_init();
|
||||
load_system_resources_fixture();
|
||||
|
||||
s_data = (OptionMenuTestData) {};
|
||||
rtc_set_time(3 * SECONDS_PER_DAY);
|
||||
}
|
||||
|
||||
void test_option_menu_window__cleanup(void) {
|
||||
}
|
||||
|
||||
// Helpers
|
||||
//////////////////////
|
||||
|
||||
typedef struct MenuItemConfig {
|
||||
const char *title;
|
||||
const char *subtitle;
|
||||
} MenuItemConfig;
|
||||
|
||||
typedef struct MenuConfig {
|
||||
OptionMenuCallbacks callbacks;
|
||||
const char *title;
|
||||
size_t num_items;
|
||||
MenuItemConfig *items;
|
||||
OptionMenuContentType content_type;
|
||||
bool icons_enabled;
|
||||
} MenuConfig;
|
||||
|
||||
static uint16_t prv_menu_get_num_rows(OptionMenu *option_menu, void *context) {
|
||||
return ((MenuConfig *)context)->num_items;
|
||||
}
|
||||
|
||||
static void prv_menu_draw_row(OptionMenu *option_menu, GContext *ctx, const Layer *cell_layer,
|
||||
const GRect *cell_frame, uint32_t row, bool selected, void *context) {
|
||||
MenuConfig *data = context;
|
||||
option_menu_system_draw_row(option_menu, ctx, cell_layer, cell_frame, data->items[row].title,
|
||||
selected, context);
|
||||
}
|
||||
|
||||
static void prv_create_menu_and_render(MenuConfig *config) {
|
||||
option_menu_init(&s_data.option_menu);
|
||||
|
||||
const OptionMenuConfig option_menu_config = {
|
||||
.title = config->title ?: "Option Menu",
|
||||
.content_type = config->content_type,
|
||||
.status_colors = { GColorWhite, GColorBlack },
|
||||
.highlight_colors = { PBL_IF_COLOR_ELSE(GColorCobaltBlue, GColorBlack), GColorWhite },
|
||||
.icons_enabled = config->icons_enabled,
|
||||
};
|
||||
option_menu_configure(&s_data.option_menu, &option_menu_config);
|
||||
|
||||
const OptionMenuCallbacks callbacks = {
|
||||
.draw_row = config->callbacks.draw_row ?: prv_menu_draw_row,
|
||||
.get_num_rows = config->callbacks.get_num_rows ?: prv_menu_get_num_rows,
|
||||
.get_cell_height = config->callbacks.get_cell_height ?: NULL,
|
||||
};
|
||||
option_menu_set_callbacks(&s_data.option_menu, &callbacks, config);
|
||||
|
||||
window_set_on_screen(&s_data.option_menu.window, true, true);
|
||||
window_render(&s_data.option_menu.window, fake_graphics_context_get_context());
|
||||
}
|
||||
|
||||
// Tests
|
||||
//////////////////////
|
||||
|
||||
// These tests test all permutations on all platforms even if the combination on the particular
|
||||
// platform was not designed and thus does not appear pleasant. Make sure you are looking at the
|
||||
// combination relevant to your use case when examining the unit test output of Option Menu.
|
||||
|
||||
void prv_create_menu_and_render_long_title(bool icons_enabled, const char *title,
|
||||
bool special_height) {
|
||||
prv_create_menu_and_render(&(MenuConfig) {
|
||||
.title = title,
|
||||
.content_type = special_height ? OptionMenuContentType_DoubleLine :
|
||||
OptionMenuContentType_Default,
|
||||
.num_items = 3,
|
||||
.items = (MenuItemConfig[]) {
|
||||
{
|
||||
.title = "Allow All Notifications",
|
||||
}, {
|
||||
.title = "Allow Phone Calls Only",
|
||||
}, {
|
||||
.title = "Mute All Notifications",
|
||||
}
|
||||
},
|
||||
.icons_enabled = icons_enabled,
|
||||
});
|
||||
}
|
||||
|
||||
void test_option_menu_window__long_title_default_height(void) {
|
||||
prv_create_menu_and_render_long_title(false /* icons_enabled */, "Default Height",
|
||||
false /* special_height */);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
||||
|
||||
void test_option_menu_window__long_title_default_height_icons(void) {
|
||||
prv_create_menu_and_render_long_title(true /* icons_enabled */, "Default Height",
|
||||
false /* special_height */);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
||||
|
||||
void test_option_menu_window__long_title_special_height(void) {
|
||||
prv_create_menu_and_render_long_title(false /* icons_enabled */, "Special Height",
|
||||
true /* special_height */);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
||||
|
||||
void test_option_menu_window__long_title_special_height_icons(void) {
|
||||
prv_create_menu_and_render_long_title(true /* icons_enabled */, "Special Height",
|
||||
true /* special_height */);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
||||
|
||||
void prv_create_menu_and_render_short_title(bool icons_enabled, const char *title,
|
||||
bool special_height) {
|
||||
prv_create_menu_and_render(&(MenuConfig) {
|
||||
.title = title,
|
||||
.content_type = special_height ? OptionMenuContentType_SingleLine :
|
||||
OptionMenuContentType_Default,
|
||||
.num_items = 3,
|
||||
.items = (MenuItemConfig[]) {
|
||||
{
|
||||
.title = "Smaller",
|
||||
}, {
|
||||
.title = "Default",
|
||||
}, {
|
||||
.title = "Larger",
|
||||
}
|
||||
},
|
||||
.icons_enabled = icons_enabled,
|
||||
});
|
||||
}
|
||||
|
||||
void test_option_menu_window__short_title_default_height(void) {
|
||||
prv_create_menu_and_render_short_title(false /* icons_enabled */, "Default Height",
|
||||
false /* special_height */);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
||||
|
||||
void test_option_menu_window__short_title_default_height_icons(void) {
|
||||
prv_create_menu_and_render_short_title(true /* icons_enabled */, "Default Height",
|
||||
false /* special_height */);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
||||
|
||||
void test_option_menu_window__short_title_special_height(void) {
|
||||
prv_create_menu_and_render_short_title(false /* icons_enabled */, "Special Height",
|
||||
true /* special_height */);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
||||
|
||||
void test_option_menu_window__short_title_special_height_icons(void) {
|
||||
prv_create_menu_and_render_short_title(true /* icons_enabled */, "Special Height",
|
||||
true /* special_height */);
|
||||
FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE();
|
||||
}
|
214
tests/fw/ui/test_scroll_layer.c
Normal file
214
tests/fw/ui/test_scroll_layer.c
Normal file
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* 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/ui/scroll_layer.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_compiled_with_legacy2_sdk.h"
|
||||
#include "stubs_content_indicator.h"
|
||||
#include "stubs_graphics_context.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_resources.h"
|
||||
#include "stubs_syscalls.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
|
||||
#define DEFAULT_SCROLL_HEIGHT 32
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////
|
||||
static GRect s_graphics_draw_bitmap_in_rect__rect = GRectZero;
|
||||
|
||||
void graphics_draw_bitmap_in_rect(GContext* ctx, const GBitmap *src_bitmap, const GRect *rect) {
|
||||
s_graphics_draw_bitmap_in_rect__rect = *rect;
|
||||
}
|
||||
bool graphics_release_frame_buffer(GContext *ctx, GBitmap *buffer) {return false;}
|
||||
void window_schedule_render(struct Window *window) {}
|
||||
void window_set_click_config_provider_with_context(
|
||||
struct Window *window, ClickConfigProvider click_config_provider, void *context) {}
|
||||
void window_set_click_context(ButtonId button_id, void *context) {}
|
||||
void window_single_repeating_click_subscribe(
|
||||
ButtonId button_id, uint16_t repeat_interval_ms, ClickHandler handler) {}
|
||||
|
||||
// Internal definitions
|
||||
////////////////////////////////////
|
||||
extern bool prv_scroll_layer_is_paging_enabled(ScrollLayer *scroll_layer);
|
||||
extern void prv_scroll_layer_set_content_offset_internal(ScrollLayer *scroll_layer, GPoint offset);
|
||||
extern uint16_t prv_scroll_layer_get_paging_height(ScrollLayer *scroll_layer);
|
||||
|
||||
// Setup
|
||||
////////////////////////////////////
|
||||
|
||||
void test_scroll_layer__initialize(void) {}
|
||||
void test_scroll_layer__cleanup(void) {}
|
||||
|
||||
// Tests
|
||||
////////////////////////////////////
|
||||
|
||||
void test_scroll_layer__enable_paging(void) {
|
||||
GRect scroll_bounds = GRect(0,0,180,180);
|
||||
ScrollLayer *scroll_layer = scroll_layer_create(scroll_bounds);
|
||||
|
||||
// Verify paging is disabled by default
|
||||
cl_assert_equal_b(false, prv_scroll_layer_is_paging_enabled(scroll_layer));
|
||||
// Verify shadow_layer is not hidden when paging disabled
|
||||
cl_assert_equal_b(false, scroll_layer_get_shadow_hidden(scroll_layer));
|
||||
|
||||
scroll_layer_set_paging(scroll_layer, true);
|
||||
|
||||
// Verify paging is enabled
|
||||
cl_assert_equal_b(true, prv_scroll_layer_is_paging_enabled(scroll_layer));
|
||||
// Verify shadow_layer is hidden now that paging enabled
|
||||
cl_assert_equal_b(true, scroll_layer_get_shadow_hidden(scroll_layer));
|
||||
|
||||
// verify disable paging works
|
||||
scroll_layer_set_paging(scroll_layer, false);
|
||||
cl_assert_equal_b(false, prv_scroll_layer_is_paging_enabled(scroll_layer));
|
||||
// verify shadow layer is hidden on paging disabled
|
||||
cl_assert_equal_b(true, scroll_layer_get_shadow_hidden(scroll_layer));
|
||||
}
|
||||
|
||||
void test_scroll_layer__paging_vs_shadow_bits(void) {
|
||||
ScrollLayer *scroll_layer = scroll_layer_create(GRect(0,0,180,180));
|
||||
|
||||
// Validate that paging_disabled is same position as shadow clips
|
||||
scroll_layer->shadow_sublayer.clips = true;
|
||||
cl_assert_equal_b(true, scroll_layer->paging.paging_disabled);
|
||||
cl_assert_equal_b(false, scroll_layer->paging.shadow_hidden);
|
||||
|
||||
scroll_layer->shadow_sublayer.clips = false;
|
||||
cl_assert_equal_b(false, scroll_layer->paging.paging_disabled);
|
||||
cl_assert_equal_b(false, scroll_layer->paging.shadow_hidden);
|
||||
|
||||
// Validate that shadow_hidden is same position as layer hidden in shadow sublayer
|
||||
scroll_layer->shadow_sublayer.hidden = true;
|
||||
cl_assert_equal_b(false, scroll_layer->paging.paging_disabled);
|
||||
cl_assert_equal_b(true, scroll_layer->paging.shadow_hidden);
|
||||
|
||||
scroll_layer->shadow_sublayer.hidden = false;
|
||||
cl_assert_equal_b(false, scroll_layer->paging.paging_disabled);
|
||||
cl_assert_equal_b(false, scroll_layer->paging.shadow_hidden);
|
||||
|
||||
}
|
||||
|
||||
void test_scroll_layer__scrolling(void) {
|
||||
GRect scroll_bounds = GRect(0,0,180,180);
|
||||
ScrollLayer *scroll_layer = scroll_layer_create(scroll_bounds);
|
||||
|
||||
GSize content_size = GSize(180, 2000);
|
||||
scroll_layer_set_content_size(scroll_layer, content_size);
|
||||
|
||||
int32_t scroll_height = DEFAULT_SCROLL_HEIGHT;
|
||||
|
||||
int32_t offset = 0;
|
||||
|
||||
for (offset = 0; offset < content_size.h - scroll_bounds.size.h; offset += scroll_height) {
|
||||
// scroll offset for scroll down is negative, so invert offset.y
|
||||
cl_assert_equal_i(offset, -((int32_t)scroll_layer_get_content_offset(scroll_layer).y));
|
||||
scroll_layer_scroll(scroll_layer, ScrollDirectionDown, false);
|
||||
}
|
||||
|
||||
// can only scroll to content offset == content_size.h - bounds.size.h
|
||||
// so the last scroll from the above loop is expected to have stopped short
|
||||
cl_assert(offset > -((int32_t)scroll_layer_get_content_offset(scroll_layer).y));
|
||||
}
|
||||
|
||||
void test_scroll_layer__paging_with_scroll(void) {
|
||||
ScrollLayer *scroll_layer = scroll_layer_create(GRect(0,0,180,180));
|
||||
int16_t page_height = 0;
|
||||
|
||||
page_height = scroll_layer->layer.frame.size.h;
|
||||
scroll_layer_set_paging(scroll_layer, true);
|
||||
cl_assert_equal_i(page_height, prv_scroll_layer_get_paging_height(scroll_layer));
|
||||
|
||||
// paging should force < page_height offsets to ceil of modulo page height
|
||||
scroll_layer_set_content_size(scroll_layer, GSize(180, 2000));
|
||||
scroll_layer_scroll(scroll_layer, ScrollDirectionDown, false);
|
||||
// scroll offset for scroll down is negative, so invert offset.y
|
||||
cl_assert_equal_i(page_height, -((int32_t)scroll_layer_get_content_offset(scroll_layer).y));
|
||||
}
|
||||
|
||||
void test_scroll_layer__paging_last_pages_content(void) {
|
||||
uint16_t page_height = 86;
|
||||
ScrollLayer *scroll_layer = scroll_layer_create(GRect(0,0,180,page_height));
|
||||
|
||||
// validate enable paging works for paging height
|
||||
scroll_layer_set_paging(scroll_layer, true);
|
||||
cl_assert_equal_i(page_height, prv_scroll_layer_get_paging_height(scroll_layer));
|
||||
|
||||
int pages = 2;
|
||||
int offset = 0;
|
||||
// setup content size to be slightly more than 2 pages
|
||||
GSize content_size = GSize(180, page_height * pages + 10);
|
||||
scroll_layer_set_content_size(scroll_layer, content_size);
|
||||
|
||||
// paging should force full contents of last page to show
|
||||
// so content size rounded up to the next modulo of page_height
|
||||
cl_assert_equal_i(offset, -scroll_layer_get_content_offset(scroll_layer).y);
|
||||
|
||||
scroll_layer_scroll(scroll_layer, ScrollDirectionDown, false);
|
||||
offset += page_height;
|
||||
cl_assert_equal_i(offset, -scroll_layer_get_content_offset(scroll_layer).y);
|
||||
|
||||
// we expect to scroll to the end of content padded to the last full page
|
||||
scroll_layer_scroll(scroll_layer, ScrollDirectionDown, false);
|
||||
offset += page_height;
|
||||
cl_assert_equal_i(offset, -scroll_layer_get_content_offset(scroll_layer).y);
|
||||
cl_assert_equal_i(page_height * pages, -scroll_layer_get_content_offset(scroll_layer).y);
|
||||
|
||||
// once the last full page of content has been displayed
|
||||
// another scroll down shouldn't advance the offset
|
||||
scroll_layer_scroll(scroll_layer, ScrollDirectionDown, false);
|
||||
cl_assert_equal_i(page_height * pages, -scroll_layer_get_content_offset(scroll_layer).y);
|
||||
}
|
||||
|
||||
void test_scroll_layer__fullscreen_paging(void) {
|
||||
GRect scroll_bounds = GRect(0,0,180,180);
|
||||
ScrollLayer *scroll_layer = scroll_layer_create(scroll_bounds);
|
||||
|
||||
int16_t page_height = scroll_bounds.size.h;
|
||||
scroll_layer_set_paging(scroll_layer, true);
|
||||
cl_assert_equal_i(page_height, prv_scroll_layer_get_paging_height(scroll_layer));
|
||||
|
||||
int pages = 22;
|
||||
int offset = 0;
|
||||
// setup content size to be slightly more than the pages
|
||||
GSize content_size = GSize(scroll_bounds.size.w, page_height * pages + 24);
|
||||
scroll_layer_set_content_size(scroll_layer, content_size);
|
||||
|
||||
// paging should force full contents of last page to show
|
||||
// so content size rounded up to the next modulo of page_height
|
||||
cl_assert_equal_i(offset, -scroll_layer_get_content_offset(scroll_layer).y);
|
||||
|
||||
// we expect to scroll to the end of content padded to the last full page
|
||||
for (int i = 0; i < pages; i++) {
|
||||
scroll_layer_scroll(scroll_layer, ScrollDirectionDown, false);
|
||||
offset += page_height;
|
||||
cl_assert_equal_i(offset, -scroll_layer_get_content_offset(scroll_layer).y);
|
||||
}
|
||||
|
||||
// once the last full page of content has been displayed
|
||||
// another scroll down shouldn't advance the offset
|
||||
scroll_layer_scroll(scroll_layer, ScrollDirectionDown, false);
|
||||
cl_assert_equal_i(page_height * pages, -scroll_layer_get_content_offset(scroll_layer).y);
|
||||
}
|
238
tests/fw/ui/test_selection_windows.c
Normal file
238
tests/fw/ui/test_selection_windows.c
Normal file
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* 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/time_range_selection_window.h"
|
||||
#include "applib/ui/time_selection_window.h"
|
||||
#include "applib/ui/app_window_stack.h"
|
||||
#include "apps/system_apps/settings/settings_notifications_private.h"
|
||||
#include "resource/resource.h"
|
||||
#include "shell/system_theme.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/graphics.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// Helper Functions
|
||||
/////////////////////
|
||||
|
||||
#include "../graphics/test_graphics.h"
|
||||
#include "../graphics/util.h"
|
||||
|
||||
// Fakes
|
||||
/////////////////////
|
||||
|
||||
#include "fake_graphics_context.h"
|
||||
#include "fake_spi_flash.h"
|
||||
#include "../../fixtures/load_test_resources.h"
|
||||
|
||||
static GContext s_ctx;
|
||||
|
||||
void clock_get_time_tm(struct tm* time_tm) {
|
||||
rtc_get_time_tm(time_tm);
|
||||
}
|
||||
|
||||
static FrameBuffer *fb = NULL;
|
||||
static GBitmap *s_dest_bitmap;
|
||||
|
||||
// To easily render multiple windows in a single canvas, we'll use an 8-bit bitmap for color
|
||||
// displays (including round), but we can use the native format for black and white displays (1-bit)
|
||||
#define CANVAS_GBITMAP_FORMAT PBL_IF_COLOR_ELSE(GBitmapFormat8Bit, GBITMAP_NATIVE_FORMAT)
|
||||
|
||||
// Overrides same function in graphics.c; we need to do this so we can pass in the GBitmapFormat
|
||||
// we need to use for the unit test output canvas instead of relying on GBITMAP_NATIVE_FORMAT, which
|
||||
// wouldn't work for Spalding since it uses GBitmapFormat8BitCircular
|
||||
GBitmap* graphics_capture_frame_buffer(GContext *ctx) {
|
||||
PBL_ASSERTN(ctx);
|
||||
return graphics_capture_frame_buffer_format(ctx, CANVAS_GBITMAP_FORMAT);
|
||||
}
|
||||
|
||||
// Overrides same function in graphics.c; we need to do this so we can release the framebuffer we're
|
||||
// using even though its format doesn't match GBITMAP_NATIVE_FORMAT (see comment for mocked
|
||||
// graphics_capture_frame_buffer() above)
|
||||
bool graphics_release_frame_buffer(GContext *ctx, GBitmap *buffer) {
|
||||
PBL_ASSERTN(ctx);
|
||||
ctx->lock = false;
|
||||
framebuffer_dirty_all(ctx->parent_framebuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
GContext *graphics_context_get_current_context(void) {
|
||||
return &s_ctx;
|
||||
}
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_animation_timing.h"
|
||||
#include "stubs_app_install_manager.h"
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_app_timer.h"
|
||||
#include "stubs_bootbits.h"
|
||||
#include "stubs_buffer.h"
|
||||
#include "stubs_click.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_layer.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_memory_layout.h"
|
||||
#include "stubs_menu_cell_layer.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_status_bar_layer.h"
|
||||
#include "stubs_syscall_internal.h"
|
||||
#include "stubs_syscalls.h"
|
||||
#include "stubs_task_watchdog.h"
|
||||
#include "stubs_text_layer_flow.h"
|
||||
#include "stubs_window_manager.h"
|
||||
#include "stubs_window_stack.h"
|
||||
|
||||
// Setup and Teardown
|
||||
////////////////////////////////////
|
||||
|
||||
void test_selection_windows__initialize(void) {
|
||||
fb = malloc(sizeof(FrameBuffer));
|
||||
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
|
||||
test_graphics_context_init(&s_ctx, fb);
|
||||
framebuffer_clear(fb);
|
||||
|
||||
load_system_resources_fixture();
|
||||
}
|
||||
|
||||
void test_selection_windows__cleanup(void) {
|
||||
free(fb);
|
||||
|
||||
if (s_dest_bitmap) {
|
||||
gbitmap_destroy(s_dest_bitmap);
|
||||
}
|
||||
s_dest_bitmap = NULL;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
//////////////////////
|
||||
|
||||
#define GRID_CELL_PADDING 5
|
||||
|
||||
typedef void (*RenderCallback)(GContext *ctx, SettingsContentSize content_size);
|
||||
|
||||
static void prv_prepare_canvas_and_render_for_each_size(RenderCallback callback) {
|
||||
GContext *ctx = &s_ctx;
|
||||
|
||||
const unsigned int num_columns = SettingsContentSizeCount;
|
||||
|
||||
const int16_t bitmap_width = (DISP_COLS * num_columns) + (GRID_CELL_PADDING * (num_columns + 1));
|
||||
const int16_t bitmap_height = DISP_ROWS;
|
||||
const GSize bitmap_size = GSize(bitmap_width, bitmap_height);
|
||||
s_dest_bitmap = gbitmap_create_blank(bitmap_size, CANVAS_GBITMAP_FORMAT);
|
||||
|
||||
ctx->dest_bitmap = *s_dest_bitmap;
|
||||
ctx->draw_state.clip_box.size = bitmap_size;
|
||||
ctx->draw_state.drawing_box.size = bitmap_size;
|
||||
|
||||
// Fill the bitmap with pink (on color) or white (on b&w) so it's easier to see errors
|
||||
memset(s_dest_bitmap->addr, PBL_IF_COLOR_ELSE(GColorShockingPinkARGB8, GColorWhiteARGB8),
|
||||
s_dest_bitmap->row_size_bytes * s_dest_bitmap->bounds.size.h);
|
||||
|
||||
for (SettingsContentSize content_size = 0; content_size < SettingsContentSizeCount;
|
||||
content_size++) {
|
||||
system_theme_set_content_size(settings_content_size_to_preferred_size(content_size));
|
||||
callback(ctx, content_size);
|
||||
}
|
||||
}
|
||||
|
||||
void time_selection_window_set_to_current_time(TimeSelectionWindowData *date_time_window);
|
||||
|
||||
void time_selection_window_configure(TimeSelectionWindowData *time_selection_window,
|
||||
const TimeSelectionWindowConfig *config);
|
||||
|
||||
void time_selection_window_init(TimeSelectionWindowData *time_selection_window,
|
||||
const TimeSelectionWindowConfig *config);
|
||||
|
||||
#define SELECTION_COLOR PBL_IF_COLOR_ELSE(GColorJaegerGreen, GColorBlack)
|
||||
|
||||
static const TimeSelectionWindowConfig s_time_config = {
|
||||
.label = "Time Config",
|
||||
.color = SELECTION_COLOR,
|
||||
.range = {
|
||||
.update = true,
|
||||
.text = "Range text yo!",
|
||||
.duration_m = 30,
|
||||
.enabled = true,
|
||||
},
|
||||
};
|
||||
|
||||
static void prv_render_time_selection_window(GContext *ctx, SettingsContentSize content_size) {
|
||||
const int16_t x_offset = GRID_CELL_PADDING + (content_size * (GRID_CELL_PADDING + DISP_COLS));
|
||||
ctx->draw_state.drawing_box.origin = GPoint(x_offset, 0);
|
||||
|
||||
TimeSelectionWindowData selection_window;
|
||||
time_selection_window_init(&selection_window, &s_time_config);
|
||||
|
||||
selection_window.time_data.hour = 16;
|
||||
selection_window.time_data.minute = 20;
|
||||
time_selection_window_configure(&selection_window, &s_time_config);
|
||||
|
||||
// Set the window on screen so its window handlers will be called
|
||||
window_set_on_screen(&selection_window.window, true, true);
|
||||
|
||||
window_render(&selection_window.window, ctx);
|
||||
}
|
||||
|
||||
static void prv_render_time_range_selection_window(GContext *ctx,
|
||||
SettingsContentSize content_size) {
|
||||
const int16_t x_offset = GRID_CELL_PADDING + (content_size * (GRID_CELL_PADDING + DISP_COLS));
|
||||
ctx->draw_state.drawing_box.origin = GPoint(x_offset, 0);
|
||||
|
||||
TimeRangeSelectionWindowData selection_window;
|
||||
time_range_selection_window_init(&selection_window, SELECTION_COLOR, NULL, NULL);
|
||||
|
||||
selection_window.from.hour = 16;
|
||||
selection_window.from.minute = 20;
|
||||
selection_window.to.hour = 18;
|
||||
selection_window.to.minute = 9;
|
||||
|
||||
// Set the window on screen so its window handlers will be called
|
||||
window_set_on_screen(&selection_window.window, true, true);
|
||||
|
||||
window_render(&selection_window.window, ctx);
|
||||
}
|
||||
|
||||
// Tests
|
||||
//////////////////////
|
||||
|
||||
void test_selection_windows__time_selection_window(void) {
|
||||
prv_prepare_canvas_and_render_for_each_size(prv_render_time_selection_window);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_selection_windows__time_range_selection_window(void) {
|
||||
prv_prepare_canvas_and_render_for_each_size(prv_render_time_range_selection_window);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
287
tests/fw/ui/test_simple_dialog.c
Normal file
287
tests/fw/ui/test_simple_dialog.c
Normal file
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* 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/app_window_stack.h"
|
||||
#include "applib/ui/content_indicator.h"
|
||||
#include "applib/ui/content_indicator_private.h"
|
||||
#include "applib/ui/dialogs/simple_dialog.h"
|
||||
#include "applib/ui/text_layer.h"
|
||||
#include "applib/ui/window_private.h"
|
||||
#include "resource/resource.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/normal/timeline/timeline_resources.h"
|
||||
#include "shell/system_theme.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/graphics.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static GContext s_ctx;
|
||||
|
||||
// Fakes
|
||||
/////////////////////
|
||||
|
||||
#include "fake_content_indicator.h"
|
||||
#include "fake_spi_flash.h"
|
||||
#include "fixtures/load_test_resources.h"
|
||||
|
||||
GContext *graphics_context_get_current_context(void) {
|
||||
return &s_ctx;
|
||||
}
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_app_install_manager.h"
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_app_timer.h"
|
||||
#include "stubs_bootbits.h"
|
||||
#include "stubs_buffer.h"
|
||||
#include "stubs_click.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_layer.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_prompt.h"
|
||||
#include "stubs_serial.h"
|
||||
#include "stubs_shell_prefs.h"
|
||||
#include "stubs_sleep.h"
|
||||
#include "stubs_status_bar_layer.h"
|
||||
#include "stubs_syscall_internal.h"
|
||||
#include "stubs_syscalls.h"
|
||||
#include "stubs_task_watchdog.h"
|
||||
#include "stubs_vibes.h"
|
||||
#include "stubs_window_manager.h"
|
||||
#include "stubs_window_stack.h"
|
||||
|
||||
AnimationProgress animation_timing_scaled(AnimationProgress time_normalized,
|
||||
AnimationProgress interval_start,
|
||||
AnimationProgress interval_end) {
|
||||
return interval_end;
|
||||
}
|
||||
|
||||
KinoReel *kino_reel_scale_segmented_create(KinoReel *from_reel, bool take_ownership,
|
||||
GRect screen_frame) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void kino_reel_scale_segmented_set_deflate_effect(KinoReel *reel, int16_t expand) {}
|
||||
|
||||
bool kino_reel_scale_segmented_set_delay_by_distance(KinoReel *reel, GPoint target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Helper Functions
|
||||
/////////////////////
|
||||
|
||||
#include "../graphics/test_graphics.h"
|
||||
#include "../graphics/util.h"
|
||||
|
||||
// Setup and Teardown
|
||||
////////////////////////////////////
|
||||
|
||||
static FrameBuffer *fb = NULL;
|
||||
static GBitmap *s_dest_bitmap;
|
||||
|
||||
// To easily render multiple windows in a single canvas, we'll use an 8-bit bitmap for color
|
||||
// displays (including round), but we can use the native format for black and white displays (1-bit)
|
||||
#define CANVAS_GBITMAP_FORMAT PBL_IF_COLOR_ELSE(GBitmapFormat8Bit, GBITMAP_NATIVE_FORMAT)
|
||||
|
||||
// Overrides same function in graphics.c; we need to do this so we can pass in the GBitmapFormat
|
||||
// we need to use for the unit test output canvas instead of relying on GBITMAP_NATIVE_FORMAT, which
|
||||
// wouldn't work for Spalding since it uses GBitmapFormat8BitCircular
|
||||
GBitmap* graphics_capture_frame_buffer(GContext *ctx) {
|
||||
PBL_ASSERTN(ctx);
|
||||
return graphics_capture_frame_buffer_format(ctx, CANVAS_GBITMAP_FORMAT);
|
||||
}
|
||||
|
||||
// Overrides same function in graphics.c; we need to do this so we can release the framebuffer we're
|
||||
// using even though its format doesn't match GBITMAP_NATIVE_FORMAT (see comment for mocked
|
||||
// graphics_capture_frame_buffer() above)
|
||||
bool graphics_release_frame_buffer(GContext *ctx, GBitmap *buffer) {
|
||||
PBL_ASSERTN(ctx);
|
||||
ctx->lock = false;
|
||||
framebuffer_dirty_all(ctx->parent_framebuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void test_simple_dialog__initialize(void) {
|
||||
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();
|
||||
}
|
||||
|
||||
void test_simple_dialog__cleanup(void) {
|
||||
free(fb);
|
||||
|
||||
gbitmap_destroy(s_dest_bitmap);
|
||||
s_dest_bitmap = NULL;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
//////////////////////
|
||||
|
||||
void prv_push_and_render_simple_dialog(Dialog *dialog) {
|
||||
gbitmap_destroy(s_dest_bitmap);
|
||||
|
||||
const int16_t bitmap_width = DISP_COLS;
|
||||
const int16_t bitmap_height = DISP_ROWS;
|
||||
const GSize bitmap_size = GSize(bitmap_width, bitmap_height);
|
||||
s_dest_bitmap = gbitmap_create_blank(bitmap_size, CANVAS_GBITMAP_FORMAT);
|
||||
|
||||
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 (on color) or white (on b&w) so it's easier to see errors
|
||||
memset(s_dest_bitmap->addr, PBL_IF_COLOR_ELSE(GColorShockingPinkARGB8, GColorWhiteARGB8),
|
||||
s_dest_bitmap->row_size_bytes * s_dest_bitmap->bounds.size.h);
|
||||
|
||||
window_set_on_screen(&dialog->window, true, true);
|
||||
window_render(&dialog->window, &s_ctx);
|
||||
}
|
||||
|
||||
// Tests
|
||||
//////////////////////
|
||||
|
||||
void test_simple_dialog__watchface_crashed(void) {
|
||||
SimpleDialog *simple_dialog = simple_dialog_create("Watchface crashed");
|
||||
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
|
||||
dialog_set_text(dialog, "Watchface is not responding");
|
||||
dialog_set_icon(dialog, RESOURCE_ID_GENERIC_WARNING_LARGE);
|
||||
dialog_set_timeout(dialog, DIALOG_TIMEOUT_INFINITE /* no timeout */);
|
||||
|
||||
prv_push_and_render_simple_dialog(dialog);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_simple_dialog__battery_charged(void) {
|
||||
SimpleDialog *simple_dialog = simple_dialog_create("Battery Status");
|
||||
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
|
||||
dialog_set_text(dialog, "Fully Charged");
|
||||
dialog_set_background_color(dialog, GColorKellyGreen);
|
||||
dialog_set_icon(dialog, RESOURCE_ID_BATTERY_ICON_FULL_LARGE);
|
||||
|
||||
prv_push_and_render_simple_dialog(dialog);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_simple_dialog__battery_warning(void) {
|
||||
SimpleDialog *simple_dialog = simple_dialog_create("Battery Status");
|
||||
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
|
||||
uint32_t percent = 20;
|
||||
dialog_set_background_color(dialog, GColorRed);
|
||||
char buffer[25];
|
||||
snprintf(buffer, sizeof(buffer), "%d%% Battery", percent);
|
||||
dialog_set_text(dialog, buffer);
|
||||
dialog_set_icon(dialog, RESOURCE_ID_BATTERY_ICON_LOW_LARGE);
|
||||
|
||||
prv_push_and_render_simple_dialog(dialog);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_simple_dialog__ping(void) {
|
||||
SimpleDialog *simple_dialog = simple_dialog_create("Ping");
|
||||
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
|
||||
dialog_set_background_color(dialog, GColorCobaltBlue);
|
||||
dialog_set_text_color(dialog, GColorWhite);
|
||||
dialog_set_text(dialog, "Ping");
|
||||
|
||||
prv_push_and_render_simple_dialog(dialog);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_simple_dialog__alarm_snooze(void) {
|
||||
SimpleDialog *simple_dialog = simple_dialog_create("Alarm Snooze");
|
||||
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
|
||||
const char *snooze_text = "Snooze delay set to %d minutes";
|
||||
char snooze_buf[64];
|
||||
snprintf(snooze_buf, sizeof(snooze_buf), snooze_text, 10);
|
||||
dialog_set_text(dialog, snooze_buf);
|
||||
dialog_set_icon(dialog, RESOURCE_ID_GENERIC_CONFIRMATION_LARGE);
|
||||
dialog_set_background_color(dialog, GColorJaegerGreen);
|
||||
dialog_set_timeout(dialog, DIALOG_TIMEOUT_DEFAULT);
|
||||
|
||||
prv_push_and_render_simple_dialog(dialog);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_simple_dialog__alarm_deleted(void) {
|
||||
SimpleDialog *simple_dialog = simple_dialog_create("Alarm Deleted");
|
||||
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
|
||||
dialog_set_text(dialog, "Alarm Deleted");
|
||||
dialog_set_icon(dialog, RESOURCE_ID_RESULT_SHREDDED_LARGE);
|
||||
dialog_set_background_color(dialog, GColorJaegerGreen);
|
||||
dialog_set_timeout(dialog, DIALOG_TIMEOUT_DEFAULT);
|
||||
|
||||
prv_push_and_render_simple_dialog(dialog);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_simple_dialog__calendar_unmute(void) {
|
||||
SimpleDialog *simple_dialog = simple_dialog_create("Calendar Unmute");
|
||||
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
|
||||
dialog_set_text(dialog, "Calendar Unmuted");
|
||||
dialog_set_icon(dialog, RESOURCE_ID_RESULT_MUTE_LARGE);
|
||||
dialog_set_timeout(dialog, DIALOG_TIMEOUT_DEFAULT);
|
||||
|
||||
prv_push_and_render_simple_dialog(dialog);
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_simple_dialog__does_text_fit(void) {
|
||||
const bool use_status_bar = true;
|
||||
const GSize icon_size = timeline_resources_get_gsize(TimelineResourceSizeLarge);
|
||||
|
||||
char *msg = "1 line error";
|
||||
bool text_fits = simple_dialog_does_text_fit(msg, DISP_FRAME.size, icon_size, use_status_bar);
|
||||
cl_assert(text_fits);
|
||||
|
||||
msg = "This error fits on all of our displays";
|
||||
text_fits = simple_dialog_does_text_fit(msg, DISP_FRAME.size, icon_size, use_status_bar);
|
||||
cl_assert(text_fits);
|
||||
|
||||
msg = "This error is too long for rect displays";
|
||||
text_fits = simple_dialog_does_text_fit(msg, DISP_FRAME.size, icon_size, use_status_bar);
|
||||
PBL_IF_RECT_ELSE(cl_assert(!text_fits), cl_assert(text_fits));
|
||||
|
||||
msg = "This error is too long to fit on any display shape :(";
|
||||
text_fits = simple_dialog_does_text_fit(msg, DISP_FRAME.size, icon_size, use_status_bar);
|
||||
cl_assert(!text_fits);
|
||||
}
|
93
tests/fw/ui/test_status_bar_layer.c
Normal file
93
tests/fw/ui/test_status_bar_layer.c
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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/ui/status_bar_layer.h"
|
||||
#include "util/list.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "resource/resource.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
// Fakes
|
||||
////////////////////////////////////
|
||||
#include "fake_fonts.h"
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_app_timer.h"
|
||||
#include "stubs_applib_resource.h"
|
||||
#include "stubs_compiled_with_legacy2_sdk.h"
|
||||
#include "stubs_event_service_client.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_logging.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_resources.h"
|
||||
#include "stubs_syscalls.h"
|
||||
#include "stubs_ui_window.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
#include "stubs_window_stack.h"
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////
|
||||
GContext *graphics_context_get_current_context(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Setup
|
||||
////////////////////////////////////
|
||||
|
||||
ResourceCallbackHandle resource_watch(ResAppNum app_num, uint32_t resource_id,
|
||||
ResourceChangedCallback callback, void* data) {
|
||||
return (ResourceCallbackHandle) { 0 };
|
||||
}
|
||||
|
||||
// Helpers
|
||||
////////////////////////////////////
|
||||
|
||||
#define cl_assert_status_bar_height(status_bar) \
|
||||
do { \
|
||||
cl_assert(status_bar.layer.frame.size.h == STATUS_BAR_LAYER_HEIGHT); \
|
||||
cl_assert(status_bar.layer.bounds.size.h == STATUS_BAR_LAYER_HEIGHT); \
|
||||
} while (0);
|
||||
|
||||
// Tests
|
||||
////////////////////////////////////
|
||||
|
||||
//! The height of the status bar should always be locked to STATUS_BAR_LAYER_HEIGHT.
|
||||
//! Make sure that after marking dirty, it is always reset to STATUS_BAR_LAYER_HEIGHT.
|
||||
void test_status_bar_layer__modify_height(void) {
|
||||
StatusBarLayer status_bar;
|
||||
status_bar_layer_init(&status_bar);
|
||||
|
||||
cl_assert_status_bar_height(status_bar);
|
||||
|
||||
GRect frame = status_bar.layer.frame;
|
||||
GRect bounds = status_bar.layer.bounds;
|
||||
|
||||
frame.size.h = STATUS_BAR_LAYER_HEIGHT - 5;
|
||||
layer_set_frame(&status_bar.layer, &frame);
|
||||
cl_assert_status_bar_height(status_bar);
|
||||
|
||||
bounds.size.h = STATUS_BAR_LAYER_HEIGHT + 5;
|
||||
layer_set_bounds(&status_bar.layer, &bounds);
|
||||
cl_assert_status_bar_height(status_bar);
|
||||
}
|
||||
|
187
tests/fw/ui/test_text_layer.c
Normal file
187
tests/fw/ui/test_text_layer.c
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* 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/ui/text_layer.h"
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_graphics.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_process_manager.h"
|
||||
#include "stubs_system_theme.h"
|
||||
#include "stubs_ui_window.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
|
||||
GFont fonts_get_system_font(const char *font_key) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void graphics_context_set_fill_color(GContext* ctx, GColor color){}
|
||||
void graphics_context_set_text_color(GContext* ctx, GColor color){}
|
||||
|
||||
// Fakes
|
||||
////////////////////////
|
||||
|
||||
|
||||
#define MOCKED_CREATED_LAYOUT (GTextLayoutCacheRef)123456
|
||||
void graphics_text_layout_cache_init(GTextLayoutCacheRef *layout_cache) {
|
||||
*layout_cache = MOCKED_CREATED_LAYOUT;
|
||||
}
|
||||
|
||||
void graphics_text_layout_cache_deinit(GTextLayoutCacheRef *layout_cache) {}
|
||||
void graphics_text_layout_set_line_spacing_delta(GTextLayoutCacheRef layout, int16_t delta) {}
|
||||
|
||||
int16_t graphics_text_layout_get_line_spacing_delta(const GTextLayoutCacheRef layout) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
GSize graphics_text_layout_get_max_used_size(GContext *ctx, const char *text,
|
||||
GFont const font, const GRect box,
|
||||
const GTextOverflowMode overflow_mode,
|
||||
const GTextAlignment alignment,
|
||||
GTextLayoutCacheRef layout) {
|
||||
return GSizeZero;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
struct {
|
||||
GTextLayoutCacheRef layout;
|
||||
} disable_text_flow;
|
||||
struct {
|
||||
GTextLayoutCacheRef layout;
|
||||
uint8_t inset;
|
||||
} enable_text_flow;
|
||||
struct {
|
||||
GTextLayoutCacheRef layout;
|
||||
} disable_paging;
|
||||
struct {
|
||||
GTextLayoutCacheRef layout;
|
||||
GPoint origin;
|
||||
GRect paging;
|
||||
} enable_paging;
|
||||
} MockValues;
|
||||
|
||||
static MockValues s_actual;
|
||||
|
||||
void graphics_text_attributes_restore_default_text_flow(GTextLayoutCacheRef layout) {
|
||||
s_actual.disable_text_flow.layout = layout;
|
||||
}
|
||||
|
||||
void graphics_text_attributes_enable_screen_text_flow(GTextLayoutCacheRef layout, uint8_t inset) {
|
||||
s_actual.enable_text_flow.layout = layout;
|
||||
s_actual.enable_text_flow.inset = inset;
|
||||
}
|
||||
|
||||
void graphics_text_attributes_restore_default_paging(GTextLayoutCacheRef layout) {
|
||||
s_actual.disable_paging.layout = layout;
|
||||
}
|
||||
|
||||
void graphics_text_attributes_enable_paging(GTextLayoutCacheRef layout,
|
||||
GPoint content_origin_on_screen, GRect paging_on_screen) {
|
||||
s_actual.enable_paging.layout = layout;
|
||||
s_actual.enable_paging.origin = content_origin_on_screen;
|
||||
s_actual.enable_paging.paging = paging_on_screen;
|
||||
}
|
||||
|
||||
#define MOCKED_PAGING_ORIGIN GPoint(1 ,2)
|
||||
#define MOCKED_PAGING_PAGE GRect(3, 4, 5, 6)
|
||||
bool s_text_layer_calc_text_flow_paging_values_result;
|
||||
bool text_layer_calc_text_flow_paging_values(const TextLayer *text_layer,
|
||||
GPoint *content_origin_on_screen,
|
||||
GRect *page_rect_on_screen) {
|
||||
*content_origin_on_screen = MOCKED_PAGING_ORIGIN;
|
||||
*page_rect_on_screen = MOCKED_PAGING_PAGE;
|
||||
return s_text_layer_calc_text_flow_paging_values_result;
|
||||
}
|
||||
|
||||
// Tests
|
||||
//////////////////////
|
||||
|
||||
#define cl_assert_mocks_called(expected) \
|
||||
do { \
|
||||
cl_assert_equal_p((expected).disable_text_flow.layout, s_actual.disable_text_flow.layout); \
|
||||
cl_assert_equal_p((expected).enable_text_flow.layout, s_actual.enable_text_flow.layout); \
|
||||
cl_assert_equal_i((expected).enable_text_flow.inset, s_actual.enable_text_flow.inset); \
|
||||
cl_assert_equal_p((expected).disable_paging.layout, s_actual.disable_paging.layout); \
|
||||
cl_assert_equal_p((expected).enable_paging.layout, s_actual.enable_paging.layout); \
|
||||
cl_assert_equal_i((expected).enable_paging.origin.x, s_actual.enable_paging.origin.x); \
|
||||
cl_assert_equal_i((expected).enable_paging.origin.y, s_actual.enable_paging.origin.y); \
|
||||
cl_assert_equal_i((expected).enable_paging.paging.origin.x, s_actual.enable_paging.paging.origin.x); \
|
||||
cl_assert_equal_i((expected).enable_paging.paging.origin.y, s_actual.enable_paging.paging.origin.y); \
|
||||
cl_assert_equal_i((expected).enable_paging.paging.size.w, s_actual.enable_paging.paging.size.w); \
|
||||
cl_assert_equal_i((expected).enable_paging.paging.size.h, s_actual.enable_paging.paging.size.h); \
|
||||
} while(0)
|
||||
|
||||
Window window;
|
||||
TextLayer text_layer;
|
||||
|
||||
void test_text_layer__initialize(void) {
|
||||
s_actual = (MockValues){};
|
||||
window = (Window){};
|
||||
text_layer_init(&text_layer, &GRect(10, 20, 30, 40));
|
||||
s_text_layer_calc_text_flow_paging_values_result = true;
|
||||
}
|
||||
|
||||
void test_text_layer__cleanup(void) {
|
||||
}
|
||||
|
||||
void test_text_layer__enable_text_flow_does_nothing_outside_view_hierarchy(void) {
|
||||
text_layer_enable_screen_text_flow_and_paging(&text_layer, 8);
|
||||
// nothing called
|
||||
cl_assert_mocks_called((MockValues){});
|
||||
cl_assert(text_layer.layout_cache == NULL);
|
||||
}
|
||||
|
||||
void test_text_layer__enable_text_flow(void) {
|
||||
text_layer.layer.window = &window;
|
||||
text_layer_enable_screen_text_flow_and_paging(&text_layer, 8);
|
||||
|
||||
cl_assert_mocks_called(((MockValues){
|
||||
.enable_text_flow.layout = text_layer.layout_cache,
|
||||
.enable_text_flow.inset = 8,
|
||||
.enable_paging.layout = text_layer.layout_cache,
|
||||
.enable_paging.origin = MOCKED_PAGING_ORIGIN,
|
||||
.enable_paging.paging = MOCKED_PAGING_PAGE,
|
||||
}));
|
||||
cl_assert(text_layer.layout_cache == MOCKED_CREATED_LAYOUT);
|
||||
}
|
||||
|
||||
void test_text_layer__enable_text_flow_requires_successful_calc_for_paging(void) {
|
||||
text_layer.layer.window = &window;
|
||||
s_text_layer_calc_text_flow_paging_values_result = false;
|
||||
text_layer_enable_screen_text_flow_and_paging(&text_layer, 8);
|
||||
cl_assert_mocks_called(((MockValues){
|
||||
.enable_text_flow.layout = text_layer.layout_cache,
|
||||
.enable_text_flow.inset = 8,
|
||||
}));
|
||||
cl_assert(text_layer.layout_cache == MOCKED_CREATED_LAYOUT);
|
||||
}
|
||||
|
||||
void test_text_layer__disable_text_flow(void) {
|
||||
text_layer.layout_cache = MOCKED_CREATED_LAYOUT;
|
||||
text_layer_restore_default_text_flow_and_paging(&text_layer);
|
||||
cl_assert_mocks_called(((MockValues){
|
||||
.disable_paging.layout = text_layer.layout_cache,
|
||||
.disable_text_flow.layout = text_layer.layout_cache,
|
||||
}));
|
||||
}
|
120
tests/fw/ui/test_text_layer_flow.c
Normal file
120
tests/fw/ui/test_text_layer_flow.c
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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 "pebble_asserts.h"
|
||||
|
||||
#include "applib/ui/text_layer_flow.h"
|
||||
#include "applib/ui/scroll_layer.h"
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_fonts.h"
|
||||
#include "stubs_graphics.h"
|
||||
#include "stubs_heap.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_ui_window.h"
|
||||
#include "stubs_process_manager.h"
|
||||
#include "stubs_system_theme.h"
|
||||
#include "stubs_text_layout.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
|
||||
void graphics_context_set_fill_color(GContext* ctx, GColor color){}
|
||||
void graphics_context_set_text_color(GContext* ctx, GColor color){}
|
||||
|
||||
// Fakes
|
||||
////////////////////////
|
||||
|
||||
Layer *scroll_layer_is_instance_value;
|
||||
bool scroll_layer_is_instance(const Layer *layer) {
|
||||
return scroll_layer_is_instance_value == layer;
|
||||
}
|
||||
|
||||
TextLayer text_layer;
|
||||
Window window;
|
||||
|
||||
void test_text_layer_flow__initialize(void) {
|
||||
window = (Window){.layer.window = &window};
|
||||
text_layer_init(&text_layer, &GRect(10, 20, 30, 40));
|
||||
text_layer.layer.window = &window;
|
||||
scroll_layer_is_instance_value = NULL;
|
||||
}
|
||||
|
||||
void test_text_layer_flow__cleanup(void) {
|
||||
}
|
||||
|
||||
void test_text_layer_flow__return_value_handling(void) {
|
||||
GPoint origin;
|
||||
GRect page;
|
||||
cl_assert(false == text_layer_calc_text_flow_paging_values(&text_layer, &origin, &page));
|
||||
layer_add_child(&window.layer, &text_layer.layer);
|
||||
cl_assert(true == text_layer_calc_text_flow_paging_values(&text_layer, &origin, &page));
|
||||
cl_assert(true == text_layer_calc_text_flow_paging_values(&text_layer, &origin, NULL));
|
||||
cl_assert(true == text_layer_calc_text_flow_paging_values(&text_layer, NULL, &page));
|
||||
cl_assert(true == text_layer_calc_text_flow_paging_values(&text_layer, NULL, NULL));
|
||||
cl_assert(false == text_layer_calc_text_flow_paging_values(NULL, NULL, NULL));
|
||||
|
||||
cl_assert_equal_gpoint(origin, text_layer.layer.frame.origin);
|
||||
cl_assert_equal_gpoint(page.origin, origin);
|
||||
cl_assert_equal_gsize(page.size, GSize(text_layer.layer.frame.size.w,
|
||||
TEXT_LAYER_FLOW_DEFAULT_PAGING_HEIGHT));
|
||||
}
|
||||
|
||||
void test_text_layer_flow__paging_container(void) {
|
||||
Layer container;
|
||||
layer_init(&container, &GRect(30, 40, 100, 10));
|
||||
layer_add_child(&window.layer, &container);
|
||||
layer_add_child(&container, &text_layer.layer);
|
||||
|
||||
GPoint origin;
|
||||
GRect page;
|
||||
scroll_layer_is_instance_value = NULL;
|
||||
cl_assert(true == text_layer_calc_text_flow_paging_values(&text_layer, &origin, &page));
|
||||
|
||||
// text_layer's absolute coordinate as
|
||||
cl_assert_equal_gpoint(origin, GPoint(40, 60));
|
||||
// text_layer's absolute coordinate as there's no container
|
||||
cl_assert_equal_gpoint(page.origin, GPoint(40, 60));
|
||||
cl_assert_equal_gsize(page.size, GSize(30, TEXT_LAYER_FLOW_DEFAULT_PAGING_HEIGHT));
|
||||
|
||||
scroll_layer_is_instance_value = &container;
|
||||
cl_assert(true == text_layer_calc_text_flow_paging_values(&text_layer, &origin, &page));
|
||||
|
||||
cl_assert_equal_gpoint(origin, GPoint(40, 60));
|
||||
// containers's absolute coordinate
|
||||
cl_assert_equal_gpoint(page.origin, GPoint(30, 40));
|
||||
cl_assert_equal_gsize(page.size, GSize(100, 10));
|
||||
}
|
||||
|
||||
void test_text_layer_flow__no_overflow_on_default_page_height(void) {
|
||||
// first, make sure that grect_max_y itself overflows
|
||||
cl_assert(grect_get_max_y(&(GRect){.origin.y = 1, .size.h = INT16_MAX}) < 0);
|
||||
|
||||
text_layer.layer.frame.origin.y = 1;
|
||||
layer_add_child(&window.layer, &text_layer.layer);
|
||||
|
||||
GPoint origin;
|
||||
GRect page;
|
||||
cl_assert(text_layer_calc_text_flow_paging_values(&text_layer, &origin, &page));
|
||||
cl_assert_equal_i(page.origin.y, text_layer.layer.frame.origin.y);
|
||||
|
||||
// must not overflow
|
||||
cl_assert(grect_get_max_y(&page) > 0);
|
||||
}
|
462
tests/fw/ui/test_timeline_peek.c
Normal file
462
tests/fw/ui/test_timeline_peek.c
Normal file
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
* 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/bitblt.h"
|
||||
#include "applib/graphics/framebuffer.h"
|
||||
#include "applib/graphics/graphics.h"
|
||||
#include "applib/ui/app_window_stack.h"
|
||||
#include "applib/ui/text_layer.h"
|
||||
#include "applib/ui/window_private.h"
|
||||
#include "popups/timeline/peek_private.h"
|
||||
#include "resource/resource.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/normal/timeline/timeline_resources.h"
|
||||
#include "util/buffer.h"
|
||||
#include "util/graphics.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/size.h"
|
||||
#include "util/trig.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// Fakes
|
||||
/////////////////////
|
||||
|
||||
#include "fake_rtc.h"
|
||||
#include "fake_spi_flash.h"
|
||||
#include "fixtures/load_test_resources.h"
|
||||
|
||||
void clock_get_until_time(char *buffer, int buf_size, time_t timestamp, int max_relative_hrs) {
|
||||
snprintf(buffer, buf_size, "In 5 minutes");
|
||||
}
|
||||
|
||||
// Stubs
|
||||
/////////////////////
|
||||
|
||||
#include "stubs_activity.h"
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_animation_timing.h"
|
||||
#include "stubs_app_install_manager.h"
|
||||
#include "stubs_app_state.h"
|
||||
#include "stubs_app_timer.h"
|
||||
#include "stubs_bootbits.h"
|
||||
#include "stubs_click.h"
|
||||
#include "stubs_cron.h"
|
||||
#include "stubs_event_loop.h"
|
||||
#include "stubs_i18n.h"
|
||||
#include "stubs_layer.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_memory_layout.h"
|
||||
#include "stubs_menu_cell_layer.h"
|
||||
#include "stubs_modal_manager.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_pebble_process_info.h"
|
||||
#include "stubs_pebble_tasks.h"
|
||||
#include "stubs_pin_db.h"
|
||||
#include "stubs_process_manager.h"
|
||||
#include "stubs_prompt.h"
|
||||
#include "stubs_property_animation.h"
|
||||
#include "stubs_scroll_layer.h"
|
||||
#include "stubs_serial.h"
|
||||
#include "stubs_shell_prefs.h"
|
||||
#include "stubs_sleep.h"
|
||||
#include "stubs_status_bar_layer.h"
|
||||
#include "stubs_syscalls.h"
|
||||
#include "stubs_task_watchdog.h"
|
||||
#include "stubs_timeline_event.h"
|
||||
#include "stubs_timeline_layer.h"
|
||||
#include "stubs_unobstructed_area.h"
|
||||
#include "stubs_window_manager.h"
|
||||
#include "stubs_window_stack.h"
|
||||
|
||||
// Helper Functions
|
||||
/////////////////////
|
||||
|
||||
#include "fw/graphics/test_graphics.h"
|
||||
#include "fw/graphics/util.h"
|
||||
|
||||
static GContext s_ctx;
|
||||
|
||||
GContext *graphics_context_get_current_context(void) {
|
||||
return &s_ctx;
|
||||
}
|
||||
|
||||
static bool s_is_watchface_running;
|
||||
|
||||
bool app_manager_is_watchface_running(void) {
|
||||
return s_is_watchface_running;
|
||||
}
|
||||
|
||||
// Setup and Teardown
|
||||
////////////////////////////////////
|
||||
|
||||
static FrameBuffer s_fb;
|
||||
|
||||
static GBitmap *s_dest_bitmap;
|
||||
|
||||
void test_timeline_peek__initialize(void) {
|
||||
// Setup time
|
||||
TimezoneInfo tz_info = {
|
||||
.tm_zone = "UTC",
|
||||
};
|
||||
time_util_update_timezone(&tz_info);
|
||||
rtc_set_timezone(&tz_info);
|
||||
rtc_set_time(SECONDS_PER_DAY);
|
||||
|
||||
// We start time out at 5pm on Jan 1, 2015 for all of these tests
|
||||
struct tm time_tm = {
|
||||
// Thursday, Jan 1, 2015, 5pm
|
||||
.tm_hour = 17,
|
||||
.tm_mday = 1,
|
||||
.tm_year = 115
|
||||
};
|
||||
|
||||
const time_t utc_sec = mktime(&time_tm);
|
||||
fake_rtc_init(0 /* initial_ticks */, utc_sec);
|
||||
|
||||
// Setup graphics context
|
||||
framebuffer_init(&s_fb, &DISP_FRAME.size);
|
||||
framebuffer_clear(&s_fb);
|
||||
graphics_context_init(&s_ctx, &s_fb, GContextInitializationMode_App);
|
||||
s_app_state_get_graphics_context = &s_ctx;
|
||||
|
||||
// Setup resources
|
||||
fake_spi_flash_init(0 /* offset */, 0x1000000 /* length */);
|
||||
pfs_init(false /* run filesystem check */);
|
||||
pfs_format(true /* write erase headers */);
|
||||
load_resource_fixture_in_flash(RESOURCES_FIXTURE_PATH, SYSTEM_RESOURCES_FIXTURE_NAME,
|
||||
false /* is_next */);
|
||||
resource_init();
|
||||
|
||||
// Initialize peek
|
||||
s_is_watchface_running = true;
|
||||
timeline_peek_init();
|
||||
}
|
||||
|
||||
void test_timeline_peek__cleanup(void) {
|
||||
}
|
||||
|
||||
// Helpers
|
||||
//////////////////////
|
||||
|
||||
static void prv_render_layer(Layer *layer, const GRect *box, bool use_screen) {
|
||||
gbitmap_destroy(s_dest_bitmap);
|
||||
|
||||
const GRect *drawing_box = use_screen ? &DISP_FRAME : box;
|
||||
const GSize bitmap_size = drawing_box->size;
|
||||
s_dest_bitmap = gbitmap_create_blank(bitmap_size, GBITMAP_NATIVE_FORMAT);
|
||||
|
||||
s_ctx.dest_bitmap = *s_dest_bitmap;
|
||||
s_ctx.draw_state.clip_box.size = bitmap_size;
|
||||
s_ctx.draw_state.drawing_box = *drawing_box;
|
||||
|
||||
layer_render_tree(layer, &s_ctx);
|
||||
|
||||
if (use_screen) {
|
||||
GBitmap *screen_bitmap = s_dest_bitmap;
|
||||
screen_bitmap->bounds = (GRect) { gpoint_neg(box->origin), box->size };
|
||||
s_dest_bitmap = gbitmap_create_blank(box->size, PBL_IF_COLOR_ELSE(GBitmapFormat8Bit, GBitmapFormat1Bit));
|
||||
bitblt_bitmap_into_bitmap(s_dest_bitmap, screen_bitmap, GPointZero, GCompOpAssign,
|
||||
GColorClear);
|
||||
gbitmap_destroy(screen_bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct TimelinePeekItemConfig {
|
||||
time_t timestamp;
|
||||
const char *title;
|
||||
const char *subtitle;
|
||||
TimelineResourceId icon;
|
||||
int num_concurrent;
|
||||
} TimelinePeekItemConfig;
|
||||
|
||||
static TimelineItem *prv_set_timeline_item(const TimelinePeekItemConfig *config, bool animated) {
|
||||
TimelineItem *item = NULL;
|
||||
const time_t now = rtc_get_time();
|
||||
const time_t timestamp = config ? (config->timestamp ?: now) : now;
|
||||
if (config) {
|
||||
AttributeList list;
|
||||
attribute_list_init_list(3 /* num_attributes */, &list);
|
||||
attribute_list_add_cstring(&list, AttributeIdTitle, config->title);
|
||||
if (config->subtitle) {
|
||||
attribute_list_add_cstring(&list, AttributeIdSubtitle, config->subtitle);
|
||||
}
|
||||
attribute_list_add_uint32(&list, AttributeIdIconPin, config->icon);
|
||||
item = timeline_item_create_with_attributes(timestamp, MINUTES_PER_HOUR,
|
||||
TimelineItemTypePin, LayoutIdGeneric,
|
||||
&list, NULL);
|
||||
attribute_list_destroy_list(&list);
|
||||
}
|
||||
timeline_peek_set_item(item, timestamp >= now, config ? config->num_concurrent : 0,
|
||||
false /* first */, animated);
|
||||
return item;
|
||||
}
|
||||
|
||||
static void prv_render_timeline_peek(const TimelinePeekItemConfig *config) {
|
||||
TimelineItem *item = prv_set_timeline_item(config, false /* animated */);
|
||||
// Force timeline peek to be visible
|
||||
timeline_peek_set_visible(true, false /* animated */);
|
||||
|
||||
TimelinePeek *peek = timeline_peek_get_peek();
|
||||
const Layer *layer = &peek->layout_layer;
|
||||
// For text flow, the whole screen is needed. Render the screen, then reduce to the layer.
|
||||
const bool use_screen = PBL_IF_ROUND_ELSE(true, false);
|
||||
prv_render_layer(&peek->window.layer,
|
||||
&(GRect) { gpoint_neg(layer->frame.origin), layer->frame.size },
|
||||
use_screen);
|
||||
|
||||
timeline_item_destroy(item);
|
||||
}
|
||||
|
||||
// Visual Layout Tests
|
||||
//////////////////////
|
||||
|
||||
void test_timeline_peek__peek(void) {
|
||||
prv_render_timeline_peek(&(TimelinePeekItemConfig) {
|
||||
.title = "CoreUX Design x Eng",
|
||||
.subtitle = "ConfRM-Missile Command",
|
||||
.icon = TIMELINE_RESOURCE_TIMELINE_CALENDAR,
|
||||
.num_concurrent = 0,
|
||||
});
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_newline(void) {
|
||||
prv_render_timeline_peek(&(TimelinePeekItemConfig) {
|
||||
.title = "NY 3\nSF 12",
|
||||
.subtitle = "Bottom of\nthe 9th",
|
||||
.icon = TIMELINE_RESOURCE_TIMELINE_BASEBALL,
|
||||
.num_concurrent = 1,
|
||||
});
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_title_only_newline(void) {
|
||||
prv_render_timeline_peek(&(TimelinePeekItemConfig) {
|
||||
.title = "NY 3\nSF 12",
|
||||
.icon = TIMELINE_RESOURCE_TIMELINE_BASEBALL,
|
||||
.num_concurrent = 1,
|
||||
});
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_concurrent_1(void) {
|
||||
prv_render_timeline_peek(&(TimelinePeekItemConfig) {
|
||||
.title = "NY 3 - SF 12",
|
||||
.subtitle = "Bottom of the 9th",
|
||||
.icon = TIMELINE_RESOURCE_TIMELINE_BASEBALL,
|
||||
.num_concurrent = 1,
|
||||
});
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_concurrent_2(void) {
|
||||
prv_render_timeline_peek(&(TimelinePeekItemConfig) {
|
||||
.title = "Stock for party 🍺",
|
||||
.subtitle = "Pebble Pad on Park",
|
||||
.icon = TIMELINE_RESOURCE_NOTIFICATION_REMINDER,
|
||||
.num_concurrent = 2,
|
||||
});
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_concurrent_2_max(void) {
|
||||
prv_render_timeline_peek(&(TimelinePeekItemConfig) {
|
||||
.title = ":parrot: :parrot:",
|
||||
.subtitle = ":parrot: :parrot: :parrot:",
|
||||
.icon = TIMELINE_RESOURCE_GENERIC_CONFIRMATION,
|
||||
.num_concurrent = 3,
|
||||
});
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_title_only(void) {
|
||||
prv_render_timeline_peek(&(TimelinePeekItemConfig) {
|
||||
.title = "Trash up the Place 🔥",
|
||||
.icon = TIMELINE_RESOURCE_TIDE_IS_HIGH,
|
||||
.num_concurrent = 0,
|
||||
});
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_title_only_concurrent_1(void) {
|
||||
prv_render_timeline_peek(&(TimelinePeekItemConfig) {
|
||||
.title = "No Watch No Life",
|
||||
.icon = TIMELINE_RESOURCE_DAY_SEPARATOR,
|
||||
.num_concurrent = 1,
|
||||
});
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_title_only_concurrent_2(void) {
|
||||
prv_render_timeline_peek(&(TimelinePeekItemConfig) {
|
||||
.title = "OMG I think the text fits!",
|
||||
.icon = TIMELINE_RESOURCE_GENERIC_WARNING,
|
||||
.num_concurrent = 2,
|
||||
});
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_in_5_minutes(void) {
|
||||
prv_render_timeline_peek(&(TimelinePeekItemConfig) {
|
||||
.timestamp = rtc_get_time() + (5 * SECONDS_PER_MINUTE),
|
||||
.title = "Stock for party 🍺",
|
||||
.subtitle = "Pebble Pad on Park",
|
||||
.icon = TIMELINE_RESOURCE_NOTIFICATION_REMINDER,
|
||||
.num_concurrent = 2,
|
||||
});
|
||||
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
||||
}
|
||||
|
||||
// Visibility Tests
|
||||
//////////////////////
|
||||
|
||||
void test_timeline_peek__peek_visibility(void) {
|
||||
prv_set_timeline_item(NULL, false /* animated */);
|
||||
TimelinePeek *peek = timeline_peek_get_peek();
|
||||
const Layer *layer = &peek->layout_layer;
|
||||
// Normally it is animated, but for this unit test, we don't request `animated`
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
|
||||
// Peek service shows the peek UI. Not animated for this unit test.
|
||||
TimelineItem *item = prv_set_timeline_item(&(TimelinePeekItemConfig) {
|
||||
.title = "CoreUX Design x Eng",
|
||||
.subtitle = "ConfRM-Missile Command",
|
||||
.icon = TIMELINE_RESOURCE_TIMELINE_CALENDAR,
|
||||
.num_concurrent = 0,
|
||||
}, false /* animated */);
|
||||
// Peek should now be on-screen.
|
||||
cl_assert(layer->frame.origin.y < DISP_ROWS);
|
||||
|
||||
// Peek service hides the peek UI. Not animated for this unit test.
|
||||
prv_set_timeline_item(NULL, false /* animated */);
|
||||
// Peek should now be off-screen.
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_visible_to_hidden_outside_of_watchface(void) {
|
||||
TimelineItem *item = prv_set_timeline_item(&(TimelinePeekItemConfig) {
|
||||
.title = "CoreUX Design x Eng",
|
||||
.subtitle = "ConfRM-Missile Command",
|
||||
.icon = TIMELINE_RESOURCE_TIMELINE_CALENDAR,
|
||||
.num_concurrent = 0,
|
||||
}, false /* animated */);
|
||||
TimelinePeek *peek = timeline_peek_get_peek();
|
||||
const Layer *layer = &peek->layout_layer;
|
||||
// Normally it is animated, but for this unit test, we don't request `animated`
|
||||
cl_assert(layer->frame.origin.y < DISP_ROWS);
|
||||
|
||||
// Transition away from the watchface
|
||||
s_is_watchface_running = false;
|
||||
timeline_peek_set_visible(false, false /* animated */);
|
||||
// For simplicity, the implementation also moves the layer even though it is not necessary.
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
|
||||
// Peek service hides the peek UI using the animated code path.
|
||||
prv_set_timeline_item(NULL, true /* animated */);
|
||||
// This time we set the item to NULL, not just request invisibility. Since we're not in the
|
||||
// watchface, even though `animated` was requested, it should immediately move the position.
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
|
||||
// Transition back to the watchface
|
||||
s_is_watchface_running = true;
|
||||
timeline_peek_set_visible(true, false /* animated */);
|
||||
// Peek should be visible again, but it should still be off-screen.
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
timeline_item_destroy(item);
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_hidden_to_visible_outside_of_watchface(void) {
|
||||
prv_set_timeline_item(NULL, false /* animated */);
|
||||
TimelinePeek *peek = timeline_peek_get_peek();
|
||||
const Layer *layer = &peek->layout_layer;
|
||||
// Normally it is animated, but for this unit test, we don't request `animated`
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
|
||||
// Transition away from the watchface
|
||||
s_is_watchface_running = false;
|
||||
timeline_peek_set_visible(false, false /* animated */);
|
||||
// For simplicity, the implementation also moves the layer even though it is not necessary.
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
|
||||
// Peek service shows the peek UI using the animated code path.
|
||||
TimelineItem *item = prv_set_timeline_item(&(TimelinePeekItemConfig) {
|
||||
.title = "CoreUX Design x Eng",
|
||||
.subtitle = "ConfRM-Missile Command",
|
||||
.icon = TIMELINE_RESOURCE_TIMELINE_CALENDAR,
|
||||
.num_concurrent = 0,
|
||||
}, true /* animated */);
|
||||
// Since we're not in the watchface, the peek remains off-screen.
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
|
||||
// Transition back to the watchface
|
||||
s_is_watchface_running = true;
|
||||
timeline_peek_set_visible(true, false /* animated */);
|
||||
// Peek should be visible again and now on-screen.
|
||||
cl_assert(layer->frame.origin.y < DISP_ROWS);
|
||||
timeline_item_destroy(item);
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_visible_leaving_and_entering_watchface(void) {
|
||||
TimelineItem *item = prv_set_timeline_item(&(TimelinePeekItemConfig) {
|
||||
.title = "CoreUX Design x Eng",
|
||||
.subtitle = "ConfRM-Missile Command",
|
||||
.icon = TIMELINE_RESOURCE_TIMELINE_CALENDAR,
|
||||
.num_concurrent = 0,
|
||||
}, false /* animated */);
|
||||
TimelinePeek *peek = timeline_peek_get_peek();
|
||||
const Layer *layer = &peek->layout_layer;
|
||||
// Normally it is animated, but for this unit test, we don't request `animated`
|
||||
cl_assert(layer->frame.origin.y < DISP_ROWS);
|
||||
|
||||
// Transition away from the watchface
|
||||
s_is_watchface_running = false;
|
||||
timeline_peek_set_visible(false, false /* animated */);
|
||||
// For simplicity, the implementation also moves the layer even though it is not necessary.
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
|
||||
// Transition back to the watchface
|
||||
s_is_watchface_running = true;
|
||||
timeline_peek_set_visible(true, false /* animated */);
|
||||
// Peek should be visible again and on-screen.
|
||||
cl_assert(layer->frame.origin.y < DISP_ROWS);
|
||||
timeline_item_destroy(item);
|
||||
}
|
||||
|
||||
void test_timeline_peek__peek_hidden_leaving_and_entering_watchface(void) {
|
||||
prv_set_timeline_item(NULL, true /* animated */);
|
||||
TimelinePeek *peek = timeline_peek_get_peek();
|
||||
const Layer *layer = &peek->layout_layer;
|
||||
// Peek should be off-screen.
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
|
||||
// Transition away from the watchface
|
||||
s_is_watchface_running = false;
|
||||
timeline_peek_set_visible(false, false /* animated */);
|
||||
// Peek should be hidden and off-screen.
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
|
||||
// Transition back to the watchface
|
||||
s_is_watchface_running = true;
|
||||
timeline_peek_set_visible(true, false /* animated */);
|
||||
// Peek should be visible again, but it should still be off-screen.
|
||||
cl_assert(layer->frame.origin.y >= DISP_ROWS);
|
||||
}
|
1439
tests/fw/ui/test_window_stack.c
Normal file
1439
tests/fw/ui/test_window_stack.c
Normal file
File diff suppressed because it is too large
Load diff
462
tests/fw/ui/wscript
Normal file
462
tests/fw/ui/wscript
Normal file
|
@ -0,0 +1,462 @@
|
|||
from waftools.pebble_test import clar
|
||||
|
||||
def build(ctx):
|
||||
clar(ctx,
|
||||
sources_ant_glob = "src/fw/applib/graphics/gtypes.c "
|
||||
"src/fw/applib/graphics/graphics_private_raw.c "
|
||||
"tests/fakes/fake_gbitmap_png.c "
|
||||
"src/fw/applib/ui/layer.c ",
|
||||
test_sources_ant_glob="test_layer.c",
|
||||
defines=['CAPABILITY_HAS_TOUCHSCREEN'])
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = "src/fw/applib/graphics/graphics_private_raw.c "
|
||||
"src/fw/applib/graphics/gtypes.c "
|
||||
"tests/fakes/fake_gbitmap_png.c "
|
||||
"src/fw/applib/ui/layer.c ",
|
||||
test_sources_ant_glob="test_layer_rect.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = "src/fw/applib/graphics/gtypes.c "
|
||||
"tests/stubs/stubs_animation.c "
|
||||
"tests/fakes/fake_gbitmap_png.c "
|
||||
"src/fw/applib/ui/animation_interpolate.c "
|
||||
"src/fw/applib/ui/layer.c "
|
||||
"src/fw/applib/ui/shadows.c "
|
||||
"src/fw/applib/ui/scroll_layer.c ",
|
||||
test_sources_ant_glob="test_scroll_layer.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = \
|
||||
" src/fw/applib/graphics/8_bit/framebuffer.c"
|
||||
" src/fw/applib/graphics/framebuffer.c"
|
||||
" src/fw/applib/graphics/bitblt.c"
|
||||
" src/fw/applib/graphics/8_bit/bitblt_private.c"
|
||||
" src/fw/applib/graphics/gbitmap.c"
|
||||
" src/fw/applib/graphics/gcolor_definitions.c"
|
||||
" src/fw/applib/graphics/gtypes.c"
|
||||
" src/fw/applib/graphics/graphics.c"
|
||||
" src/fw/applib/graphics/graphics_private.c"
|
||||
" src/fw/applib/graphics/graphics_private_raw.c"
|
||||
" src/fw/applib/graphics/graphics_circle.c"
|
||||
" src/fw/applib/graphics/graphics_line.c"
|
||||
" src/fw/applib/graphics/text_layout.c"
|
||||
" src/fw/applib/graphics/utf8.c"
|
||||
" src/fw/applib/graphics/text_render.c"
|
||||
" src/fw/applib/graphics/text_resources.c"
|
||||
" src/fw/applib/fonts/codepoint.c"
|
||||
" tests/fakes/fake_clock.c"
|
||||
" tests/fakes/fake_fonts.c"
|
||||
" tests/fakes/fake_gbitmap_png.c"
|
||||
" src/fw/applib/ui/layer.c"
|
||||
" src/fw/applib/ui/status_bar_layer.c",
|
||||
test_sources_ant_glob="test_status_bar_layer.c",
|
||||
defines=['FONT_KEY_GOTHIC_14="RESOURCE_ID_GOTHIC_14"'])
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = "src/fw/applib/ui/animation.c "
|
||||
"src/fw/applib/ui/animation_interpolate.c "
|
||||
"src/fw/applib/ui/animation_timing.c "
|
||||
"src/fw/applib/ui/property_animation.c "
|
||||
"src/fw/applib/legacy2/ui/animation_legacy2.c "
|
||||
"src/fw/applib/legacy2/ui/property_animation_legacy2.c "
|
||||
"src/fw/applib/graphics/gtypes.c "
|
||||
"src/fw/applib/graphics/gcolor_definitions.c "
|
||||
"src/fw/applib/ui/layer.c "
|
||||
"tests/fakes/fake_events.c "
|
||||
"tests/fakes/fake_rtc.c "
|
||||
"tests/fakes/fake_gbitmap_png.c "
|
||||
"src/fw/services/common/animation_service.c",
|
||||
defines=['DUMA_DISABLED'], # PBL-18358 Invalid memory read access
|
||||
test_sources_ant_glob = "test_animation.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = "src/fw/applib/ui/animation_interpolate.c ",
|
||||
test_sources_ant_glob = "test_animation_interpolate.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = "src/fw/applib/ui/animation_timing.c",
|
||||
test_sources_ant_glob = "test_animation_timing.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = "src/fw/applib/graphics/gtypes.c"
|
||||
" src/fw/applib/ui/layer.c"
|
||||
" src/fw/applib/ui/kino/kino_reel.c"
|
||||
" src/fw/applib/ui/kino/kino_reel_pdci.c"
|
||||
" src/fw/applib/ui/kino/kino_reel_pdcs.c"
|
||||
" src/fw/applib/ui/kino/kino_reel_gbitmap.c"
|
||||
" src/fw/applib/ui/kino/kino_reel_gbitmap_sequence.c"
|
||||
" src/fw/applib/ui/kino/kino_reel_custom.c"
|
||||
" src/fw/applib/graphics/gbitmap.c"
|
||||
" src/fw/applib/graphics/gbitmap_sequence.c"
|
||||
" src/fw/applib/graphics/gbitmap_png.c"
|
||||
" src/fw/applib/vendor/uPNG/upng.c"
|
||||
" src/fw/applib/vendor/tinflate/tinflate.c"
|
||||
" src/fw/applib/graphics/gcolor_definitions.c"
|
||||
" src/fw/applib/graphics/gdraw_command.c"
|
||||
" src/fw/applib/graphics/gdraw_command_list.c"
|
||||
" src/fw/applib/graphics/gdraw_command_image.c"
|
||||
" src/fw/applib/graphics/gdraw_command_frame.c"
|
||||
" src/fw/applib/graphics/gdraw_command_sequence.c"
|
||||
" tests/fakes/fake_resource_syscalls.c"
|
||||
" tests/fakes/fake_applib_resource.c"
|
||||
" tests/fakes/fake_rtc.c",
|
||||
defines = ctx.env.test_image_defines,
|
||||
test_sources_ant_glob = "test_kino_reel.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = "src/fw/applib/graphics/gtypes.c"
|
||||
" src/fw/applib/graphics/gbitmap.c"
|
||||
" src/fw/applib/ui/layer.c"
|
||||
" src/fw/applib/ui/animation_interpolate.c"
|
||||
" src/fw/applib/ui/kino/kino_player.c"
|
||||
" src/fw/applib/ui/kino/kino_reel.c"
|
||||
" src/fw/applib/ui/kino/kino_reel_pdci.c"
|
||||
" src/fw/applib/ui/kino/kino_reel_pdcs.c"
|
||||
" src/fw/applib/ui/kino/kino_reel_gbitmap.c"
|
||||
" src/fw/applib/ui/kino/kino_reel_gbitmap_sequence.c"
|
||||
" src/fw/applib/ui/kino/kino_reel_custom.c"
|
||||
" src/fw/applib/graphics/gbitmap_sequence.c"
|
||||
" src/fw/applib/graphics/gbitmap_png.c"
|
||||
" src/fw/applib/vendor/uPNG/upng.c"
|
||||
" src/fw/applib/vendor/tinflate/tinflate.c"
|
||||
" src/fw/applib/graphics/gcolor_definitions.c"
|
||||
" src/fw/applib/graphics/gdraw_command.c"
|
||||
" src/fw/applib/graphics/gdraw_command_list.c"
|
||||
" src/fw/applib/graphics/gdraw_command_image.c"
|
||||
" src/fw/applib/graphics/gdraw_command_frame.c"
|
||||
" src/fw/applib/graphics/gdraw_command_sequence.c"
|
||||
" tests/stubs/stubs_animation.c"
|
||||
" tests/fakes/fake_resource_syscalls.c"
|
||||
" tests/fakes/fake_rtc.c",
|
||||
defines = ctx.env.test_image_defines,
|
||||
test_sources_ant_glob = "test_kino_player.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob =
|
||||
" src/fw/applib/graphics/gcolor_definitions.c"
|
||||
" src/fw/applib/graphics/gtypes.c"
|
||||
" src/fw/applib/ui/app_window_stack.c"
|
||||
" src/fw/applib/ui/layer.c"
|
||||
" src/fw/applib/ui/window.c"
|
||||
" src/fw/applib/ui/window_manager.c"
|
||||
" src/fw/applib/ui/window_stack.c"
|
||||
" src/fw/kernel/ui/modals/modal_manager.c"
|
||||
" tests/fakes/fake_animation.c"
|
||||
" tests/fakes/fake_events.c"
|
||||
" tests/stubs/stubs_click.c",
|
||||
test_sources_ant_glob = "test_window_stack.c",
|
||||
defines=['SCREEN_COLOR_DEPTH_BITS=8'],
|
||||
override_includes=['dummy_board'])
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = "src/fw/applib/graphics/gtypes.c"
|
||||
" src/fw/util/buffer.c"
|
||||
" src/fw/applib/ui/layer.c"
|
||||
" src/fw/applib/ui/content_indicator.c",
|
||||
test_sources_ant_glob = "test_content_indicator.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = " " \
|
||||
" src/fw/applib/ui/layer.c" \
|
||||
" src/fw/applib/ui/scroll_layer.c" \
|
||||
" src/fw/applib/ui/menu_layer.c" \
|
||||
" src/fw/applib/ui/shadows.c" \
|
||||
" src/fw/applib/ui/animation_interpolate.c "
|
||||
" src/fw/applib/ui/animation_timing.c" \
|
||||
" src/fw/applib/graphics/gcolor_definitions.c" \
|
||||
" src/fw/applib/graphics/gtypes.c" \
|
||||
" tests/stubs/stubs_animation.c",
|
||||
test_sources_ant_glob = "test_menu_layer.c")
|
||||
|
||||
menu_layer_system_cell_rendering_sources = \
|
||||
" src/fw/applib/fonts/codepoint.c" \
|
||||
" src/fw/applib/graphics/${BITDEPTH}_bit/framebuffer.c" \
|
||||
" src/fw/applib/graphics/${BITDEPTH}_bit/bitblt_private.c" \
|
||||
" src/fw/applib/graphics/bitblt.c" \
|
||||
" src/fw/applib/graphics/framebuffer.c" \
|
||||
" src/fw/applib/graphics/gbitmap.c" \
|
||||
" src/fw/applib/graphics/gbitmap_png.c" \
|
||||
" src/fw/applib/graphics/gbitmap_sequence.c" \
|
||||
" src/fw/applib/graphics/gcolor_definitions.c" \
|
||||
" src/fw/applib/graphics/gdraw_command.c" \
|
||||
" src/fw/applib/graphics/gdraw_command_frame.c" \
|
||||
" src/fw/applib/graphics/gdraw_command_image.c" \
|
||||
" src/fw/applib/graphics/gdraw_command_list.c" \
|
||||
" src/fw/applib/graphics/gdraw_command_sequence.c" \
|
||||
" src/fw/applib/graphics/gpath.c" \
|
||||
" src/fw/applib/graphics/graphics.c" \
|
||||
" src/fw/applib/graphics/graphics_bitmap.c" \
|
||||
" src/fw/applib/graphics/graphics_circle.c" \
|
||||
" src/fw/applib/graphics/graphics_line.c" \
|
||||
" src/fw/applib/graphics/graphics_private.c" \
|
||||
" src/fw/applib/graphics/graphics_private_raw.c" \
|
||||
" src/fw/applib/graphics/gtransform.c" \
|
||||
" src/fw/applib/graphics/gtypes.c" \
|
||||
" src/fw/applib/graphics/perimeter.c" \
|
||||
" src/fw/applib/graphics/text_layout.c" \
|
||||
" src/fw/applib/graphics/text_render.c" \
|
||||
" src/fw/applib/graphics/text_resources.c" \
|
||||
" src/fw/applib/graphics/utf8.c" \
|
||||
" src/fw/applib/ui/kino/kino_reel.c" \
|
||||
" src/fw/applib/ui/kino/kino_reel_gbitmap.c" \
|
||||
" src/fw/applib/ui/kino/kino_reel_gbitmap_sequence.c" \
|
||||
" src/fw/applib/ui/kino/kino_reel_pdci.c" \
|
||||
" src/fw/applib/ui/kino/kino_reel_pdcs.c" \
|
||||
" src/fw/applib/ui/layer.c" \
|
||||
" src/fw/applib/ui/menu_layer_system_cells.c" \
|
||||
" src/fw/board/displays/display_spalding.c" \
|
||||
" src/fw/drivers/flash/flash_crc.c" \
|
||||
" src/fw/flash_region/filesystem_regions.c" \
|
||||
" src/fw/flash_region/flash_region.c" \
|
||||
" src/fw/resource/resource.c" \
|
||||
" src/fw/resource/resource_storage.c" \
|
||||
" src/fw/resource/resource_storage_builtin.c" \
|
||||
" src/fw/resource/resource_storage_file.c" \
|
||||
" src/fw/resource/resource_storage_flash.c" \
|
||||
" src/fw/services/normal/filesystem/app_file.c" \
|
||||
" src/fw/services/normal/filesystem/flash_translation.c" \
|
||||
" src/fw/services/normal/filesystem/pfs.c" \
|
||||
" src/fw/system/hexdump.c" \
|
||||
" src/fw/util/crc8.c" \
|
||||
" src/fw/util/legacy_checksum.c" \
|
||||
" src/fw/applib/vendor/tinflate/tinflate.c" \
|
||||
" src/fw/applib/vendor/uPNG/upng.c" \
|
||||
" tests/fakes/fake_applib_resource.c" \
|
||||
" tests/fakes/fake_display.c" \
|
||||
" tests/fakes/fake_gbitmap_get_data_row.c" \
|
||||
" tests/fakes/fake_rtc.c" \
|
||||
" tests/fakes/fake_spi_flash.c" \
|
||||
" tests/fixtures/resources/builtin_resources.auto.c" \
|
||||
" tests/fixtures/resources/pfs_resource_table.c" \
|
||||
" tests/stubs/stubs_animation.c" \
|
||||
" tests/stubs/stubs_system_theme.c"
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = menu_layer_system_cell_rendering_sources + \
|
||||
" src/fw/shell/system_theme.c" \
|
||||
" tests/fakes/fake_fonts.c" \
|
||||
" tests/fixtures/resources/timeline_resource_table.auto.c",
|
||||
test_sources_ant_glob = "test_menu_layer_system_cells.c",
|
||||
defines=ctx.env.test_image_defines,
|
||||
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis + ctx.env.test_pfos,
|
||||
override_includes=['dummy_board'],
|
||||
platforms=['snowy', 'spalding', 'silk', 'robert'])
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = menu_layer_system_cell_rendering_sources + \
|
||||
" src/fw/applib/ui/action_menu_hierarchy.c" \
|
||||
" src/fw/applib/ui/action_menu_layer.c" \
|
||||
" src/fw/applib/ui/action_menu_window.c" \
|
||||
" src/fw/applib/ui/crumbs_layer.c" \
|
||||
" src/fw/applib/ui/window.c" \
|
||||
" src/fw/applib/ui/menu_layer.c" \
|
||||
" src/fw/applib/ui/scroll_layer.c" \
|
||||
" src/fw/applib/ui/inverter_layer.c" \
|
||||
" src/fw/applib/ui/content_indicator.c" \
|
||||
" tests/fakes/fake_fonts.c" \
|
||||
" src/fw/shell/system_theme.c" \
|
||||
" src/fw/applib/ui/shadows.c",
|
||||
test_sources_ant_glob = "test_action_menu_window.c",
|
||||
defines=ctx.env.test_image_defines,
|
||||
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis + ctx.env.test_pfos,
|
||||
override_includes=['dummy_board'],
|
||||
platforms=['tintin', 'snowy', 'spalding', 'robert'])
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob=(
|
||||
menu_layer_system_cell_rendering_sources + " " +
|
||||
" src/fw/applib/ui/action_button.c"
|
||||
" src/fw/applib/ui/kino/kino_layer.c"
|
||||
" src/fw/applib/ui/kino/kino_player.c"
|
||||
" src/fw/applib/ui/scroll_layer.c"
|
||||
" src/fw/applib/ui/shadows.c"
|
||||
" src/fw/applib/ui/status_bar_layer.c"
|
||||
" src/fw/applib/ui/window.c"
|
||||
" src/fw/apps/system_apps/timeline/text_node.c"
|
||||
" src/fw/popups/notifications/notification_window.c"
|
||||
" src/fw/popups/notifications/notifications_presented_list.c"
|
||||
" src/fw/services/normal/timeline/attribute.c"
|
||||
" src/fw/services/normal/timeline/layout_layer.c"
|
||||
" src/fw/services/normal/timeline/layout_node.c"
|
||||
" src/fw/services/normal/timeline/notification_layout.c"
|
||||
" src/fw/services/normal/timeline/swap_layer.c"
|
||||
" src/fw/services/normal/timeline/timeline_layout.c"
|
||||
" src/fw/services/normal/timeline/timeline_resources.c"
|
||||
" src/fw/shell/system_theme.c"
|
||||
" tests/fakes/fake_animation.c"
|
||||
" tests/fakes/fake_fonts.c"
|
||||
" tests/fakes/fake_graphics_context.c"
|
||||
" tests/fixtures/resources/timeline_resource_table.auto.c"
|
||||
" tests/stubs/stubs_clock.c"
|
||||
),
|
||||
test_sources_ant_glob = "test_notification_window.c",
|
||||
defines=ctx.env.test_image_defines + ["USE_DISPLAY_PERIMETER_ON_FONT_LAYOUT=1"],
|
||||
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis + ctx.env.test_pfos,
|
||||
override_includes=['dummy_board'],
|
||||
platforms=['silk', 'snowy', 'spalding', 'robert'])
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = menu_layer_system_cell_rendering_sources + \
|
||||
" src/fw/applib/ui/dialogs/simple_dialog.c" \
|
||||
" src/fw/applib/ui/dialogs/dialog.c" \
|
||||
" src/fw/applib/ui/dialogs/dialog_private.c" \
|
||||
" src/fw/applib/ui/animation_interpolate.c" \
|
||||
" src/fw/applib/ui/kino/kino_layer.c" \
|
||||
" src/fw/applib/ui/kino/kino_player.c" \
|
||||
" src/fw/applib/ui/kino/kino_reel_custom.c" \
|
||||
" src/fw/applib/ui/kino/kino_reel/transform.c" \
|
||||
" src/fw/applib/ui/text_layer.c" \
|
||||
" src/fw/applib/ui/text_layer_flow.c" \
|
||||
" src/fw/applib/ui/window.c" \
|
||||
" src/fw/applib/ui/menu_layer.c" \
|
||||
" src/fw/applib/ui/scroll_layer.c" \
|
||||
" src/fw/applib/ui/shadows.c"
|
||||
" src/fw/applib/ui/inverter_layer.c" \
|
||||
" src/fw/applib/ui/content_indicator.c" \
|
||||
" tests/fakes/fake_fonts.c",
|
||||
test_sources_ant_glob = "test_simple_dialog.c",
|
||||
defines=ctx.env.test_image_defines,
|
||||
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis + ctx.env.test_pfos,
|
||||
override_includes=['dummy_board'],
|
||||
platforms=['tintin', 'snowy', 'spalding'])
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = menu_layer_system_cell_rendering_sources + \
|
||||
" src/fw/applib/fonts/fonts.c",
|
||||
test_sources_ant_glob = "test_emoji_fonts.c",
|
||||
defines=ctx.env.test_image_defines,
|
||||
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis + ctx.env.test_pfos,
|
||||
override_includes=['dummy_board'])
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = menu_layer_system_cell_rendering_sources + \
|
||||
" src/fw/applib/ui/action_bar_layer.c" \
|
||||
" src/fw/applib/ui/animation_interpolate.c" \
|
||||
" src/fw/applib/ui/content_indicator.c" \
|
||||
" src/fw/applib/ui/dialogs/dialog.c" \
|
||||
" src/fw/applib/ui/dialogs/dialog_private.c" \
|
||||
" src/fw/applib/ui/dialogs/expandable_dialog.c" \
|
||||
" src/fw/applib/ui/inverter_layer.c" \
|
||||
" src/fw/applib/ui/kino/kino_layer.c" \
|
||||
" src/fw/applib/ui/kino/kino_player.c" \
|
||||
" src/fw/applib/ui/kino/kino_reel/transform.c" \
|
||||
" src/fw/applib/ui/kino/kino_reel_custom.c" \
|
||||
" src/fw/applib/ui/menu_layer.c" \
|
||||
" src/fw/applib/ui/scroll_layer.c" \
|
||||
" src/fw/applib/ui/shadows.c" \
|
||||
" src/fw/applib/ui/text_layer.c" \
|
||||
" src/fw/applib/ui/text_layer_flow.c" \
|
||||
" src/fw/applib/ui/window.c" \
|
||||
" tests/fakes/fake_fonts.c",
|
||||
test_sources_ant_glob = "test_expandable_dialog.c",
|
||||
defines=ctx.env.test_image_defines + ["USE_DISPLAY_PERIMETER_ON_FONT_LAYOUT=1"],
|
||||
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis + ctx.env.test_pfos,
|
||||
override_includes=["dummy_board"],
|
||||
platforms=["spalding"])
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = " " \
|
||||
" src/fw/applib/ui/layer.c" \
|
||||
" src/fw/applib/ui/text_layer.c" \
|
||||
" src/fw/applib/graphics/gcolor_definitions.c" \
|
||||
" src/fw/applib/graphics/gtypes.c", \
|
||||
test_sources_ant_glob = "test_text_layer.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob=(
|
||||
menu_layer_system_cell_rendering_sources +
|
||||
" src/fw/applib/ui/date_time_selection_window_private.c"
|
||||
" src/fw/applib/ui/selection_layer.c"
|
||||
" src/fw/applib/ui/text_layer.c"
|
||||
" src/fw/applib/ui/time_range_selection_window.c"
|
||||
" src/fw/applib/ui/time_selection_window.c"
|
||||
" src/fw/applib/ui/window.c"
|
||||
" src/fw/shell/system_theme.c"
|
||||
" src/fw/util/date.c"
|
||||
" tests/fakes/fake_clock.c"
|
||||
" tests/fakes/fake_fonts.c"
|
||||
),
|
||||
test_sources_ant_glob="test_selection_windows.c",
|
||||
defines=ctx.env.test_image_defines,
|
||||
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis + ctx.env.test_pfos,
|
||||
override_includes=['dummy_board'],
|
||||
platforms=['tintin', 'snowy', 'spalding', 'robert'])
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = " " \
|
||||
" src/fw/applib/ui/layer.c" \
|
||||
" src/fw/applib/ui/text_layer.c" \
|
||||
" src/fw/applib/ui/text_layer_flow.c" \
|
||||
" src/fw/applib/graphics/gcolor_definitions.c" \
|
||||
" src/fw/applib/graphics/gtypes.c", \
|
||||
test_sources_ant_glob = "test_text_layer_flow.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob = " src/fw/applib/fonts/codepoint.c" \
|
||||
" src/fw/applib/graphics/utf8.c" \
|
||||
" src/fw/services/normal/timeline/notification_layout.c",
|
||||
test_sources_ant_glob = "test_jumboji.c")
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob=(
|
||||
menu_layer_system_cell_rendering_sources + " " +
|
||||
"src/fw/applib/ui/animation_interpolate.c "
|
||||
"src/fw/applib/ui/kino/kino_layer.c "
|
||||
"src/fw/applib/ui/kino/kino_player.c "
|
||||
"src/fw/applib/ui/text_layer.c "
|
||||
"src/fw/applib/ui/text_layer_flow.c "
|
||||
"src/fw/applib/ui/window.c "
|
||||
"src/fw/apps/system_apps/timeline/text_node.c "
|
||||
"src/fw/popups/timeline/peek.c "
|
||||
"src/fw/services/normal/timeline/attribute.c "
|
||||
"src/fw/services/normal/timeline/attribute_group.c "
|
||||
"src/fw/services/normal/timeline/attributes_actions.c "
|
||||
"src/fw/services/normal/timeline/generic_layout.c "
|
||||
"src/fw/services/normal/timeline/item.c "
|
||||
"src/fw/services/normal/timeline/layout_layer.c "
|
||||
"src/fw/services/normal/timeline/layout_node.c "
|
||||
"src/fw/services/normal/timeline/timeline_layout.c "
|
||||
"src/fw/services/normal/timeline/timeline_resources.c "
|
||||
"src/fw/shell/system_theme.c "
|
||||
"src/fw/util/stringlist.c "
|
||||
"src/fw/util/time/time.c "
|
||||
"tests/fakes/fake_clock.c "
|
||||
"tests/fakes/fake_fonts.c "
|
||||
"tests/fixtures/resources/timeline_resource_table.auto.c "
|
||||
"tests/stubs/stubs_app_manager.c "
|
||||
"tests/stubs/stubs_clock.c "
|
||||
"tests/stubs/stubs_timeline_layout.c "
|
||||
"tests/stubs/stubs_timeline_peek.c "
|
||||
),
|
||||
test_sources_ant_glob="test_timeline_peek.c",
|
||||
defines=ctx.env.test_image_defines + ["CAPABILITY_HAS_TIMELINE_PEEK=1",
|
||||
"USE_DISPLAY_PERIMETER_ON_FONT_LAYOUT=1"],
|
||||
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis + ctx.env.test_pfos,
|
||||
override_includes=['dummy_board'],
|
||||
platforms=['snowy', 'spalding', 'silk', 'robert'])
|
||||
|
||||
clar(ctx,
|
||||
sources_ant_glob=(
|
||||
menu_layer_system_cell_rendering_sources + " " +
|
||||
"src/fw/applib/ui/animation_interpolate.c "
|
||||
"src/fw/applib/ui/content_indicator.c "
|
||||
"src/fw/applib/ui/inverter_layer.c "
|
||||
"src/fw/applib/ui/menu_layer.c "
|
||||
"src/fw/applib/ui/option_menu_window.c "
|
||||
"src/fw/applib/ui/scroll_layer.c "
|
||||
"src/fw/applib/ui/shadows.c "
|
||||
"src/fw/applib/ui/status_bar_layer.c "
|
||||
"src/fw/applib/ui/window.c "
|
||||
"src/fw/shell/system_theme.c "
|
||||
"tests/fakes/fake_clock.c "
|
||||
"tests/fakes/fake_fonts.c "
|
||||
"tests/fakes/fake_graphics_context.c "
|
||||
),
|
||||
test_sources_ant_glob="test_option_menu_window.c",
|
||||
defines=ctx.env.test_image_defines + ["USE_DISPLAY_PERIMETER_ON_FONT_LAYOUT=1"],
|
||||
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis + ctx.env.test_pfos,
|
||||
override_includes=['dummy_board'],
|
||||
platforms=['snowy', 'spalding', 'silk', 'robert'])
|
||||
|
||||
# vim:filetype=vim
|
Loading…
Add table
Add a link
Reference in a new issue