/* * 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); }