diff --git a/src/Makefile_Elementary.am b/src/Makefile_Elementary.am index f7f0016713..3b7413b79b 100644 --- a/src/Makefile_Elementary.am +++ b/src/Makefile_Elementary.am @@ -118,6 +118,10 @@ elm_public_eolian_files = \ lib/elementary/efl_config_global.eo \ lib/elementary/efl_ui_clock.eo \ lib/elementary/efl_ui_image_factory.eo \ + lib/elementary/efl_ui_focus_manager.eo \ + lib/elementary/efl_ui_focus_manager_sub.eo \ + lib/elementary/efl_ui_focus_object.eo \ + lib/elementary/efl_ui_focus_user.eo \ $(NULL) # Private classes (not exposed or shipped) @@ -669,6 +673,9 @@ lib_elementary_libelementary_la_SOURCES = \ lib/elementary/efl_ui_clock.c \ lib/elementary/efl_ui_clock_private.h \ lib/elementary/efl_ui_image_factory.c \ + lib/elementary/efl_ui_focus_manager.c \ + lib/elementary/efl_ui_focus_manager_sub.c \ + lib/elementary/efl_ui_focus_object.c \ $(NULL) @@ -1332,7 +1339,14 @@ tests_elementary_elm_suite_SOURCES = \ tests/elementary/elm_code_test_widget.c \ tests/elementary/elm_code_test_widget_text.c \ tests/elementary/elm_code_test_widget_selection.c \ - tests/elementary/elm_code_test_widget_undo.c + tests/elementary/elm_code_test_widget_undo.c \ + tests/elementary/elm_test_focus_common.c \ + tests/elementary/elm_test_focus.c \ + tests/elementary/elm_test_focus_sub.c + +tests/elementary/tests_elementary_elm_suite-elm_test_focus.$(OBJEXT): tests/elementary/focus_test.eo.c tests/elementary/focus_test.eo.h + +tests/elementary/tests_elementary_elm_suite-elm_test_focus_sub.$(OBJEXT): tests/elementary/focus_test_sub.eo.c tests/elementary/focus_test_sub.eo.h tests_elementary_elm_suite_CPPFLAGS = \ -DTESTS_BUILD_DIR=\"${top_builddir}/src/tests/elementary\" \ diff --git a/src/lib/elementary/Elementary.h b/src/lib/elementary/Elementary.h index d917a572c0..db56d7e2ce 100644 --- a/src/lib/elementary/Elementary.h +++ b/src/lib/elementary/Elementary.h @@ -136,11 +136,25 @@ typedef struct _Elm_Version EAPI extern Elm_Version *elm_version; + /* include these first for general used definitions */ #include #include #include #include + +#ifdef EFL_EO_API_SUPPORT +# include "efl_ui_focus_object.eo.h" +# include "efl_ui_focus_manager.eo.h" +# include "efl_ui_focus_manager_sub.eo.h" +# include "efl_ui_focus_user.eo.h" +#else +# include "efl_ui_focus_object.eo.legacy.h" +# include "efl_ui_focus_manager.eo.legacy.h" +# include "efl_ui_focus_manager_sub.eo.legacy.h" +# include "efl_ui_focus_user.eo.legacy.h" +#endif + #include #include #include diff --git a/src/lib/elementary/efl_ui_focus_manager.c b/src/lib/elementary/efl_ui_focus_manager.c new file mode 100644 index 0000000000..6a4f090d82 --- /dev/null +++ b/src/lib/elementary/efl_ui_focus_manager.c @@ -0,0 +1,1051 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include +#include "elm_priv.h" + +#define MY_CLASS EFL_UI_FOCUS_MANAGER_CLASS +#define FOCUS_DATA(obj) Efl_Ui_Focus_Manager_Data *pd = efl_data_scope_get(obj, MY_CLASS); + +#define DIM_EFL_UI_FOCUS_DIRECTION(dim,neg) dim*2+neg +#define NODE_DIRECTIONS_COUNT 4 + +#define DIRECTION_CHECK(dir) (dir >= 0 && dir < EFL_UI_FOCUS_DIRECTION_LAST) + +//#define DEBUG +#define DEBUG_TUPLE(obj) efl_name_get(obj), efl_class_name_get(obj) + +typedef struct { + Eina_Bool positive; + Efl_Ui_Focus_Object *anchor; +} Anchor; + +typedef enum { + DIMENSION_X = 0, + DIMENSION_Y = 1, +} Dimension; + +typedef struct _Border Border; +typedef struct _Node Node; + +struct _Border { + Eina_List *partners; +}; + +typedef enum { + NODE_TYPE_NORMAL = 0, + NODE_TYPE_LISTENER = 1, +} Node_Type; + +struct _Node{ + Node_Type type; //type of the node + + Efl_Ui_Focus_Object *focusable; + Efl_Ui_Focus_Manager *manager; + + union { + struct { + Efl_Ui_Focus_Manager *manager; + } listener; + struct { + + } normal; + } data; + + struct _Tree_Node{ + Node *parent; //the parent in the tree + Eina_List *children; //this saves the original set of elements + }tree; + + struct _Graph_Node { + Border directions[NODE_DIRECTIONS_COUNT]; + } graph; +}; + +#define T(n) (n->tree) +#define G(n) (n->graph) + +typedef struct { + Eina_List *focus_stack; + Eina_Hash *node_hash; + Efl_Ui_Focus_Manager *redirect; + Eina_List *dirty; + + Node *root; +} Efl_Ui_Focus_Manager_Data; + +static Efl_Ui_Focus_Direction +_complement(Efl_Ui_Focus_Direction dir) +{ + #define COMP(a,b) \ + if (dir == a) return b; \ + if (dir == b) return a; + + COMP(EFL_UI_FOCUS_DIRECTION_RIGHT, EFL_UI_FOCUS_DIRECTION_LEFT) + COMP(EFL_UI_FOCUS_DIRECTION_UP, EFL_UI_FOCUS_DIRECTION_DOWN) + COMP(EFL_UI_FOCUS_DIRECTION_PREV, EFL_UI_FOCUS_DIRECTION_NEXT) + + #undef COMP + + return EFL_UI_FOCUS_DIRECTION_LAST; +} + +/* + * Set this new list of partners to the border. + * All old partners will be deleted + */ +static void +border_partners_set(Node *node, Efl_Ui_Focus_Direction direction, Eina_List *list) +{ + Node *partner; + Eina_List *lnode; + Border *border = &G(node).directions[direction]; + + EINA_LIST_FREE(border->partners, partner) + { + Border *comp_border = &G(partner).directions[_complement(direction)]; + + comp_border->partners = eina_list_remove(comp_border->partners, node); + } + + border->partners = list; + + EINA_LIST_FOREACH(border->partners, lnode, partner) + { + Border *comp_border = &G(partner).directions[_complement(direction)]; + + comp_border->partners = eina_list_append(comp_border->partners, node); + } +} + +/** + * Create a new node + */ +static Node* +node_new(Efl_Ui_Focus_Object *focusable, Efl_Ui_Focus_Manager *manager) +{ + Node *node; + + node = calloc(1, sizeof(Node)); + + node->focusable = focusable; + node->manager = manager; + + return node; +} + +static Node* +node_get(Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *focusable) +{ + Node *ret; + + ret = eina_hash_find(pd->node_hash, &focusable); + + if (ret) return ret; + + ERR("Focusable %p not registered in manager", focusable); + + return NULL; +} + +/** + * Free a node item and unlink this item from all direction + */ +static void +node_item_free(Node *item) +{ + Node *n; + Eina_List *l; + //free the graph items + for(int i = 0;i < NODE_DIRECTIONS_COUNT; i++) + { + border_partners_set(item, i, NULL); + } + + if (!item->tree.parent && item->tree.children) + { + ERR("Freeing the root with children is going to break the logical tree!"); + } + + if (item->tree.parent && item->tree.children) + { + Node *parent; + + parent = item->tree.parent; + //reparent everything into the next layer + EINA_LIST_FOREACH(item->tree.children, l, n) + { + n->tree.parent = item->tree.parent; + } + parent->tree.children = eina_list_merge(parent->tree.children , item->tree.children); + } + + if (item->tree.parent) + { + Node *parent; + + parent = item->tree.parent; + T(parent).children = eina_list_remove(T(parent).children, item); + } + + free(item); +} + + +//CALCULATING STUFF + +static inline int +_distance(Eina_Rectangle node, Eina_Rectangle op, Dimension dim) +{ + int min, max, point; + int v1, v2; + + if (dim == DIMENSION_X) + { + min = op.x; + max = eina_rectangle_max_x(&op); + point = node.x + node.w/2; + } + else + { + min = op.y; + max = eina_rectangle_max_y(&op); + point = node.y + node.h/2; + } + + v1 = min - point; + v2 = max - point; + + if (abs(v1) < abs(v2)) + return v1; + else + return v2; +} + +static inline void +_calculate_node(Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *node, Dimension dim, Eina_List **pos, Eina_List **neg) +{ + Eina_Rectangle rect = EINA_RECTANGLE_INIT; + Efl_Ui_Focus_Object *op; + Efl_Ui_Focus_Object **focus_key; + int dim_min, dim_max; + Eina_Iterator *nodes; + int cur_pos_min = 0, cur_neg_min = 0; + + nodes = eina_hash_iterator_key_new(pd->node_hash); + efl_ui_focus_object_geometry_get(node, &rect); + + *pos = NULL; + *neg = NULL; + + if (dim == DIMENSION_X) + { + dim_min = rect.y; + dim_max = rect.y + rect.h; + } + else + { + dim_min = rect.x; + dim_max = rect.x + rect.w; + } + + EINA_ITERATOR_FOREACH(nodes, focus_key) + { + Eina_Rectangle op_rect = EINA_RECTANGLE_INIT; + int min, max; + + op = *focus_key; + if (op == node) continue; + + efl_ui_focus_object_geometry_get(op, &op_rect); + + if (dim == DIMENSION_X) + { + min = op_rect.y; + max = eina_rectangle_max_y(&op_rect); + } + else + { + min = op_rect.x; + max = eina_rectangle_max_x(&op_rect); + } + + + /* two only way the calculation does make sense is if the two number + * lines are not disconnected. + * If they are connected one point of the 4 lies between the min and max of the other line + */ + if (!((min <= max && max <= dim_min && dim_min <= dim_max) || + (dim_min <= dim_max && dim_max <= min && min <= max)) && + !eina_rectangle_intersection(&op_rect, &rect)) + { + //this thing hits horizontal + int tmp_dis; + + tmp_dis = _distance(rect, op_rect, dim); + + if (tmp_dis < 0) + { + if (tmp_dis == cur_neg_min) + { + //add it + *neg = eina_list_append(*neg, op); + } + else if (tmp_dis > cur_neg_min + || cur_neg_min == 0) //init case + { + //nuke the old and add +#ifdef DEBUG + printf("CORRECTION FOR %s-%s\n found anchor %s-%s in distance %d\n (%d,%d,%d,%d)\n (%d,%d,%d,%d)\n\n", DEBUG_TUPLE(node), DEBUG_TUPLE(op), + tmp_dis, + op_rect.x, op_rect.y, op_rect.w, op_rect.h, + rect.x, rect.y, rect.w, rect.h); +#endif + *neg = eina_list_free(*neg); + *neg = eina_list_append(NULL, op); + cur_neg_min = tmp_dis; + } + } + else + { + if (tmp_dis == cur_pos_min) + { + //add it + *pos = eina_list_append(*pos, op); + } + else if (tmp_dis < cur_pos_min + || cur_pos_min == 0) //init case + { + //nuke the old and add +#ifdef DEBUG + printf("CORRECTION FOR %s-%s\n found anchor %s-%s in distance %d\n (%d,%d,%d,%d)\n (%d,%d,%d,%d)\n\n", DEBUG_TUPLE(node), DEBUG_TUPLE(op), + tmp_dis, + op_rect.x, op_rect.y, op_rect.w, op_rect.h, + rect.x, rect.y, rect.w, rect.h); +#endif + *pos = eina_list_free(*pos); + *pos = eina_list_append(NULL, op); + cur_pos_min = tmp_dis; + } + } + + +#if 0 + printf("(%d,%d,%d,%d)%s vs(%d,%d,%d,%d)%s\n", rect.x, rect.y, rect.w, rect.h, elm_widget_part_text_get(node, NULL), op_rect.x, op_rect.y, op_rect.w, op_rect.h, elm_widget_part_text_get(op, NULL)); + printf("(%d,%d,%d,%d)\n", min, max, dim_min, dim_max); + printf("Candidate %d\n", tmp_dis); + if (anchor->anchor == NULL || abs(tmp_dis) < abs(distance)) //init case + { + distance = tmp_dis; + anchor->positive = tmp_dis > 0 ? EINA_FALSE : EINA_TRUE; + anchor->anchor = op; + //Helper for debugging wrong calculations + + } +#endif + } + + } +} + +#ifdef DEBUG +static void +_debug_node(Node *node) +{ + Eina_List *tmp = NULL; + + if (!node) return; + + printf("NODE %s-%s\n", DEBUG_TUPLE(node->focusable)); + +#define DIR_LIST(dir) G(node).directions[dir].partners + +#define DIR_OUT(dir)\ + tmp = DIR_LIST(dir); \ + { \ + Eina_List *list_node; \ + Node *partner; \ + printf("-"#dir"-> ("); \ + EINA_LIST_FOREACH(tmp, list_node, partner) \ + printf("%s-%s,", DEBUG_TUPLE(partner->focusable)); \ + printf(")\n"); \ + } + + DIR_OUT(EFL_UI_FOCUS_DIRECTION_RIGHT) + DIR_OUT(EFL_UI_FOCUS_DIRECTION_LEFT) + DIR_OUT(EFL_UI_FOCUS_DIRECTION_UP) + DIR_OUT(EFL_UI_FOCUS_DIRECTION_DOWN) + +} +#endif + +static void +convert_border_set(Efl_Ui_Focus_Manager_Data *pd, Node *node, Eina_List *focusable_list, Efl_Ui_Focus_Direction dir) +{ + Eina_List *partners = NULL; + Efl_Ui_Focus_Object *obj; + + EINA_LIST_FREE(focusable_list, obj) + { + Node *entry; + + entry = node_get(pd, obj); + if (!entry) + { + CRI("Found a obj in graph without node-entry!"); + return; + } + partners = eina_list_append(partners, entry); + } + + border_partners_set(node, dir, partners); +} + +static void +dirty_flush(Efl_Ui_Focus_Manager *obj, Efl_Ui_Focus_Manager_Data *pd) +{ + Node *node; + + efl_event_callback_call(obj, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL); + + EINA_LIST_FREE(pd->dirty, node) + { + Eina_List *x_partners_pos, *x_partners_neg; + Eina_List *y_partners_pos, *y_partners_neg; + + _calculate_node(pd, node->focusable, DIMENSION_X, &x_partners_pos, &x_partners_neg); + _calculate_node(pd, node->focusable, DIMENSION_Y, &y_partners_pos, &y_partners_neg); + + convert_border_set(pd, node, x_partners_pos, EFL_UI_FOCUS_DIRECTION_RIGHT); + convert_border_set(pd, node, x_partners_neg, EFL_UI_FOCUS_DIRECTION_LEFT); + convert_border_set(pd, node, y_partners_neg, EFL_UI_FOCUS_DIRECTION_UP); + convert_border_set(pd, node, y_partners_pos, EFL_UI_FOCUS_DIRECTION_DOWN); + +#ifdef DEBUG + _debug_node(node); +#endif + } +} +static void +dirty_add(Eo *obj, Efl_Ui_Focus_Manager_Data *pd, Node *dirty) +{ + //if (eina_list_data_find(pd->dirty, dirty)) return; + pd->dirty = eina_list_remove(pd->dirty, dirty); + pd->dirty = eina_list_append(pd->dirty, dirty); + + efl_event_callback_call(obj, EFL_UI_FOCUS_MANAGER_EVENT_COORDS_DIRTY, NULL); +} + + +static void +_node_new_geometery_cb(void *data, const Efl_Event *event) +{ + Node *node; + FOCUS_DATA(data) + + node = node_get(pd, event->object); + + dirty_add(data, pd, node); + + return; +} + +static void +_focus_in_cb(void *data, const Efl_Event *event) +{ + efl_ui_focus_manager_focus(data, event->object); +} + +static void +_child_del(void *data, const Efl_Event *event) +{ + WRN("The manager itself catched a deletion of a child. BAD"); + efl_ui_focus_manager_unregister(data, event->object); +} + +EFL_CALLBACKS_ARRAY_DEFINE(focusable_node, + {EFL_EVENT_DEL, _child_del}, + {EFL_GFX_EVENT_RESIZE, _node_new_geometery_cb}, + {EFL_GFX_EVENT_MOVE, _node_new_geometery_cb}, + //FIXME this is not correctly NOOOO ELM WIDGETS EVENTS HERE + {ELM_WIDGET_EVENT_FOCUSED, _focus_in_cb} +); + +//============================= + +static Node* +_register(Eo *obj, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *child, Node *parent) +{ + Node *node; + if (!!eina_hash_find(pd->node_hash, &child)) + { + ERR("Child %p is already registered in the graph", child); + return NULL; + } + + node = node_new(child, obj); + eina_hash_add(pd->node_hash, &child, node); + + //add the parent + if (parent) + { + T(node).parent = parent; + T(parent).children = eina_list_append(T(parent).children, node); + } + + //listen to changes + efl_event_callback_array_add(child, focusable_node(), obj); + + return node; +} + +EOLIAN static Eina_Bool +_efl_ui_focus_manager_register(Eo *obj, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *child, Efl_Ui_Focus_Object *parent, Efl_Ui_Focus_Manager *redirect) +{ + Node *node = NULL; + Node *pnode = NULL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(child, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(parent, EINA_FALSE); + + pnode = node_get(pd, parent); + if (!pnode) return EINA_FALSE; + + node = _register(obj, pd, child, pnode); + if (!node) return EINA_FALSE; + + if (!redirect) + { + node->type = NODE_TYPE_NORMAL; + } + else + { + node->type = NODE_TYPE_LISTENER; + node->data.listener.manager = redirect; + } + + //mark dirty + dirty_add(obj, pd, node); + + return EINA_TRUE; +} + +EOLIAN static Eina_Bool +_efl_ui_focus_manager_update_redirect(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *child, Efl_Ui_Focus_Manager *redirect) +{ + Node *node = node_get(pd, child); + + if (!node) return EINA_FALSE; + if (node->type != NODE_TYPE_LISTENER) return EINA_FALSE; + + node->data.listener.manager = redirect; + + return EINA_TRUE; +} + +EOLIAN static Eina_Bool +_efl_ui_focus_manager_update_parent(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *child, Efl_Ui_Focus_Object *parent_obj) +{ + Node *node; + Node *parent; + + EINA_SAFETY_ON_NULL_RETURN_VAL(parent_obj, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(child, EINA_FALSE); + + node = node_get(pd, child); + parent = node_get(pd, parent_obj); + + if (!node || !parent) return EINA_FALSE; + + if (T(node).parent) + { + Node *old_parent; + + old_parent = T(node).parent; + + T(old_parent).children = eina_list_remove(T(old_parent).children, node); + } + + T(node).parent = parent; + + if (T(node).parent) + { + T(parent).children = eina_list_append(T(parent).children, node); + } + + return EINA_TRUE; +} + +static Eina_Bool +_equal_set(Eina_List *none_nodes, Eina_List *nodes) +{ + Eina_List *n; + Node *node; + + if (eina_list_count(nodes) != eina_list_count(none_nodes)) return EINA_FALSE; + + EINA_LIST_FOREACH(nodes, n, node) + { + if (!eina_list_data_find(none_nodes, node->focusable)) + return EINA_FALSE; + } + + return EINA_TRUE; +} + +EOLIAN static Eina_Bool +_efl_ui_focus_manager_update_children(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *parent, Eina_List *order) +{ + Node *pnode; + + pnode = node_get(pd, parent); + + if (!pnode) + return EINA_FALSE; + + if (!_equal_set(order, T(pnode).children)) + { + ERR("Set of children is not equal"); + return EINA_FALSE; + } + + T(pnode).children = order; + + return EINA_TRUE; +} + +EOLIAN static void +_efl_ui_focus_manager_unregister(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *child) +{ + Node *node; + + node = node_get(pd, child); + + if (!node) return; + + //remove the object from the stack if it hasnt dont that until now + //after this its not at the top anymore + //elm_widget_focus_set(node->focusable, EINA_FALSE); + //delete again from the list, for the case it was not at the top + pd->focus_stack = eina_list_remove(pd->focus_stack, node); + + //add all neighboors of the node to the dirty list + for(int i = 0; i < 4; i++) + { + Node *partner; + Eina_List *n; + + EINA_LIST_FOREACH(node->graph.directions[i].partners, n, partner) + { + dirty_add(obj, pd, partner); + } + } + + //remove from the dirty parts + pd->dirty = eina_list_remove(pd->dirty, node); + + eina_hash_del_by_key(pd->node_hash, &child); +} + +EOLIAN static void +_efl_ui_focus_manager_redirect_set(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Manager *redirect) +{ + if (pd->redirect == redirect) return; + + if (pd->redirect) + efl_unref(pd->redirect); + + pd->redirect = redirect; + + if (pd->redirect) + efl_ref(pd->redirect); +} + +EOLIAN static Efl_Ui_Focus_Manager * +_efl_ui_focus_manager_redirect_get(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd) +{ + return pd->redirect; +} + +static void +_free_node(void *data) +{ + Node *node = data; + FOCUS_DATA(node->manager); + + efl_event_callback_array_del(node->focusable, focusable_node(), node->manager); + + if (pd->root != data) + { + node_item_free(node); + } +} + +EOLIAN static Efl_Object * +_efl_ui_focus_manager_efl_object_constructor(Eo *obj, Efl_Ui_Focus_Manager_Data *pd) +{ + pd->node_hash = eina_hash_pointer_new(_free_node); + return efl_constructor(efl_super(obj, MY_CLASS)); +} + +EOLIAN static Efl_Object * +_efl_ui_focus_manager_efl_object_provider_find(Eo *obj, Efl_Ui_Focus_Manager_Data *pd EINA_UNUSED, const Efl_Object *klass) +{ + if (klass == MY_CLASS) + return obj; + + return efl_provider_find(efl_super(obj, MY_CLASS), klass); +} + +EOLIAN static void +_efl_ui_focus_manager_efl_object_destructor(Eo *obj, Efl_Ui_Focus_Manager_Data *pd) +{ + eina_list_free(pd->focus_stack); + eina_list_free(pd->dirty); + + eina_hash_free(pd->node_hash); + + if (pd->root) + node_item_free(pd->root); + pd->root = NULL; + + efl_destructor(efl_super(obj, MY_CLASS)); +} + +typedef struct { + Eina_Iterator iterator; + Eina_Iterator *real_iterator; + Efl_Ui_Focus_Manager *object; +} Border_Elements_Iterator; + +static Eina_Bool +_iterator_next(Border_Elements_Iterator *it, void **data) +{ + Node *node; + + while(eina_iterator_next(it->real_iterator, (void**)&node)) + { + for(int i = 0 ;i < NODE_DIRECTIONS_COUNT; i++) + { + if (!node->graph.directions[i].partners) + { + *data = node->focusable; + return EINA_TRUE; + } + } + } + return EINA_FALSE; +} + +static Elm_Layout * +_iterator_get_container(Border_Elements_Iterator *it) +{ + return it->object; +} + +static void +_iterator_free(Border_Elements_Iterator *it) +{ + eina_iterator_free(it->real_iterator); + free(it); +} + +EOLIAN static Eina_Iterator* +_efl_ui_focus_manager_border_elements_get(Eo *obj, Efl_Ui_Focus_Manager_Data *pd) +{ + Border_Elements_Iterator *it; + + dirty_flush(obj, pd); + + it = calloc(1, sizeof(Border_Elements_Iterator)); + + EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR); + + it->real_iterator = eina_hash_iterator_data_new(pd->node_hash); + it->iterator.version = EINA_ITERATOR_VERSION; + it->iterator.next = FUNC_ITERATOR_NEXT(_iterator_next); + it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER(_iterator_get_container); + it->iterator.free = FUNC_ITERATOR_FREE(_iterator_free); + it->object = obj; + + return (Eina_Iterator*) it; +} + +static Node* +_no_history_element(Eina_Hash *node_hash) +{ + //nothing is selected yet, just try to use the first element in the iterator + Eina_Iterator *iter; + Node *upper; + + iter = eina_hash_iterator_data_new(node_hash); + + if (!eina_iterator_next(iter, (void**)&upper)) + return NULL; + else + return upper; + eina_iterator_free(iter); +} + +static Node* +_coords_movement(Efl_Ui_Focus_Manager_Data *pd, Node *upper, Efl_Ui_Focus_Direction direction) +{ + Node *candidate; + Eina_List *node; + + //we are searcing which of the partners is lower to the history + EINA_LIST_REVERSE_FOREACH(pd->focus_stack, node, candidate) + { + if (eina_list_data_find(G(upper).directions[direction].partners, candidate)) + { + //this is the next accessable part + return candidate; + } + } + + //if we havent found anything in the history, just use the first partner ... we have to start somewhere + //FIXME maybe decide coordinate wise? + return eina_list_data_get(G(upper).directions[direction].partners); +} + + +static Node* +_parent_item(Node *node, Eina_Bool next) +{ + Node *parent; + Eina_List *lnode; + + parent = T(node).parent; + lnode = eina_list_data_find_list(T(parent).children, node); + + if (next) + lnode = eina_list_next(lnode); + else + lnode = eina_list_prev(lnode); + + if (lnode) + return eina_list_data_get(lnode); + return NULL; +} + +static Node* +_next(Node *node) +{ + Node *n; + + //Case 1 we are having children + if (T(node).children) + return eina_list_data_get(T(node).children); + + //case 2 we are the root and we dont have children, return ourself + if (!T(node).parent) + return node; + + //case 3 we are not at the end of the parents list + n = _parent_item(node, EINA_TRUE); + if (n) + return n; + + //case 4 we are at the end of the parents list + n = node; + while(T(n).parent) + { + Node *parent_next; + + parent_next = _parent_item(n, EINA_TRUE); + + if (parent_next) + return parent_next; + + n = T(n).parent; + } + //this is then the root again + return n; +} + +static Node* +_prev(Node *node) +{ + Node *n = NULL; + + //this is the root there is no parent + if (!T(node).parent) + { + Node *subtree; + + subtree = node; + //search the most down right item + while(T(subtree).children) + { + subtree = eina_list_last_data_get(T(subtree).children); + } + return subtree; + } + + n =_parent_item(node, EINA_FALSE); + //case 1 there is a item in the parent previous to node, which has children + if (n && T(n).children) + return eina_list_last_data_get(T(n).children); + + //case 2 there is a item in the parent preivous to node, which has no children + if (n) + return n; + + //case 3 there is a no item in the parent provious to this one + //if (!n) + return T(node).parent; +} + + +static Node* +_logical_movement(Efl_Ui_Focus_Manager_Data *pd EINA_UNUSED, Node *upper, Efl_Ui_Focus_Direction direction) +{ + if (direction == EFL_UI_FOCUS_DIRECTION_NEXT) + return _next(upper); + else + return _prev(upper); +} + +EOLIAN static Efl_Ui_Focus_Object* +_efl_ui_focus_manager_request_move(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Direction direction) +{ + dirty_flush(obj, pd); + + EINA_SAFETY_ON_FALSE_RETURN_VAL(DIRECTION_CHECK(direction), NULL); + + if (pd->redirect) + return efl_ui_focus_manager_request_move(pd->redirect, direction); + else + { + Node *upper = NULL, *dir = NULL; + + upper = eina_list_last_data_get(pd->focus_stack); + + if (!upper) + { + upper = _no_history_element(pd->node_hash); + if (upper) + return upper->focusable; + return NULL; + + } +#ifdef DEBUG + _debug_node(upper); +#endif + if (direction == EFL_UI_FOCUS_DIRECTION_PREV + || direction == EFL_UI_FOCUS_DIRECTION_NEXT) + dir = _logical_movement(pd, upper, direction); + else + dir = _coords_movement(pd, upper, direction); + + //return the widget + if (dir) + return dir->focusable; + else + return NULL; + } +} + +EOLIAN static void +_efl_ui_focus_manager_focus(Eo *obj, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *focus) +{ + Node *node; + Node *old_focus; + + EINA_SAFETY_ON_NULL_RETURN(focus); + + //check if node is part of this manager object + node = node_get(pd, focus); + if (!node) return; + + //check if this is already the focused object + old_focus = eina_list_last_data_get(pd->focus_stack); + + //check if this is already at the top + if (old_focus && old_focus->focusable == focus) return; + + //remove the object from the list and add it again + pd->focus_stack = eina_list_remove(pd->focus_stack, node); + pd->focus_stack = eina_list_append(pd->focus_stack, node); + + //populate the new change + if (old_focus) efl_ui_focus_object_focus_set(old_focus->focusable, EINA_FALSE); + efl_ui_focus_object_focus_set(node->focusable, EINA_TRUE); + + //now check if this is also a listener object + if (node->type == NODE_TYPE_LISTENER) + { + Efl_Ui_Focus_Manager *redirect; + + redirect = node->data.listener.manager; + + efl_ui_focus_manager_redirect_set(obj, redirect); + } +} + +EOLIAN static Efl_Ui_Focus_Object* +_efl_ui_focus_manager_move(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Direction direction) +{ + Efl_Ui_Focus_Object *candidate; + + EINA_SAFETY_ON_FALSE_RETURN_VAL(DIRECTION_CHECK(direction), NULL); + + if (pd->redirect) + { + return efl_ui_focus_manager_move(pd->redirect, direction); + } + else + { + candidate = efl_ui_focus_manager_request_move(obj, direction); + if (candidate) + efl_ui_focus_manager_focus(obj, candidate); + } + +#ifdef DEBUG + printf("Focus, MOVE %s %s\n", DEBUG_TUPLE(candidate)); +#endif + return candidate; +} + +EOLIAN static void +_efl_ui_focus_manager_root_set(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *root) +{ + Node *node; + + if (pd->root) + { + ERR("Root element can only be set once!"); + return; + } + + node = _register(obj, pd, root, NULL); + + pd->root = node; +} + +EOLIAN static Efl_Ui_Focus_Object* +_efl_ui_focus_manager_root_get(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd) +{ + if (!pd->root) return NULL; + + return pd->root->focusable; +} + +EOLIAN static Efl_Object* +_efl_ui_focus_manager_efl_object_finalize(Eo *obj, Efl_Ui_Focus_Manager_Data *pd) +{ + Efl_Object *result; + + if (!pd->root) + { + ERR("Constructing failed. No root element set."); + return NULL; + } + + result = efl_finalize(efl_super(obj, MY_CLASS)); + + return result; +} + +#include "efl_ui_focus_manager.eo.c" \ No newline at end of file diff --git a/src/lib/elementary/efl_ui_focus_manager.eo b/src/lib/elementary/efl_ui_focus_manager.eo new file mode 100644 index 0000000000..ab70c27c21 --- /dev/null +++ b/src/lib/elementary/efl_ui_focus_manager.eo @@ -0,0 +1,129 @@ +enum Efl.Ui.Focus.Direction { + right = 0, + left = 1, + down = 2, + up = 3, + next = 4, + prev = 5, + last = 6 +} + + +class Efl.Ui.Focus.Manager (Efl.Object) { + methods { + move { + [[Move the focus into the given direction + + This call flushes all changes. + This means all changes between the last flush and now are computed + ]] + params { + direction : Efl.Ui.Focus.Direction; [[The direction to move to]] + } + return : Efl.Ui.Focus.Object; [[The element which is now focused]] + } + request_move { + [[Returns the object which would be the next object to focus in the given direction]] + params { + direction : Efl.Ui.Focus.Direction; + } + return : Efl.Ui.Focus.Object; + } + register { + [[Register a new item in the graph. + + The parent has to be none null, it will be used as the parent in the logical tree. + The redirect argument will be set as redirect property on that manager, once child gets focused. + ]] + params { + child : Efl.Ui.Focus.Object @nonull; [[The object to register]] + parent : Efl.Ui.Focus.Object @nonull; [[The parent to use in the logical tree]] + redirect : Efl.Ui.Focus.Manager; [[The redirect manager to set once this child is focused can be NULL for no redirect]] + } + return : bool; [[$true if it was successfull $false if not]] + } + update_redirect { + [[Set a new redirect object for the given child + + Once the child is focused the redirect manager will be set in the redirect property. + Set to $null if nothing should happen + ]] + params { + child : Efl.Ui.Focus.Object @nonull; + redirect : Efl.Ui.Focus.Manager; [[Once $child got focused this element will be set as redirect]] + } + return : bool; + } + update_parent { + [[Set a new logical parent for the given child]] + params { + child : Efl.Ui.Focus.Object @nonull; [[The child to update]] + parent : Efl.Ui.Focus.Object @nonull; [[The parent which now will be the logical parent of child]] + } + return : bool; + } + update_children { + [[Give the list of children a different order]] + params { + parent : Efl.Ui.Focus.Object @nonull; [[the parent to update]] + children : list; [[the list with the new order]] + } + return : bool; + } + unregister { + [[unregister the given item from the graph]] + params { + child : Efl.Ui.Focus.Object; + } + } + focus { + [[Make the given object the currently focused object in this manager. + + The object has to be part of this manager object. + If you want to focus something in the redirect manager, just call the function on the redirect manager]] + params { + focus : Efl.Ui.Focus.Object @nonull; + } + } + @property redirect { + [[Add a another manager to serve the move requests. + + If this value is set all move requests are redirected to this manager object. + Set it to $null once nothing should be redirected anymore.]] + values { + redirect : Efl.Ui.Focus.Manager; + } + } + @property border_elements { + [[The list of elements which are at the border of the graph + This means one of the relations right,left or down,up are not set. + + This call flushes all changes. see @Efl.Ui.Focus.Manager.move + ]] + get { + } + values { + border_elements : iterator; + } + } + @property root { + [[Root node for all logical subtrees. + + This property can only be set once. + ]] + values { + root : Efl.Ui.Focus.Object @nonull; [[Will be registered into this manager object]] + } + } + } + implements { + Efl.Object.constructor; + Efl.Object.finalize; + Efl.Object.provider_find; + Efl.Object.destructor; + } + events { + pre,flush; [[Emitted once the graph calculationg will be performed]] + coords,dirty; [[Emitted once the graph is dirty, this means there are potential changes in border_elements you want to know about]] + } +} \ No newline at end of file diff --git a/src/lib/elementary/efl_ui_focus_manager_sub.c b/src/lib/elementary/efl_ui_focus_manager_sub.c new file mode 100644 index 0000000000..98fa07503f --- /dev/null +++ b/src/lib/elementary/efl_ui_focus_manager_sub.c @@ -0,0 +1,188 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include +#include "elm_priv.h" + +#define MY_CLASS EFL_UI_FOCUS_MANAGER_SUB_CLASS +#define MY_DATA(o, p) Efl_Ui_Focus_Manager_Sub_Data *pd = efl_data_scope_get(o, MY_CLASS); +typedef struct { + Efl_Ui_Focus_Manager *manager; + Efl_Ui_Focus_Manager *parent; + Eina_Bool self_dirty; + Eina_List *current_border; +} Efl_Ui_Focus_Manager_Sub_Data; + +static Eina_List* +_set_a_without_b(Eina_List *a, Eina_List *b) +{ + Eina_List *a_out = NULL, *node; + void *data; + + a_out = eina_list_clone(a); + + EINA_LIST_FOREACH(b, node, data) + { + a_out = eina_list_remove(a_out, data); + } + + return a_out; +} + +static void +_border_flush(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd) +{ + Eina_Iterator *borders; + Eina_List *selection, *tmp; + Efl_Ui_Focus_Object *node; + + borders = efl_ui_focus_manager_border_elements_get(obj); + selection = efl_ui_focus_manager_sub_select_set(obj, borders); + + //elements which are not in the current border elements + tmp = eina_list_clone(pd->current_border); + tmp = _set_a_without_b(tmp , selection); + + EINA_LIST_FREE(tmp, node) + { + efl_ui_focus_manager_unregister(pd->manager, node); + } + + //set of the elements which are new without those which are currently registered + tmp = eina_list_clone(selection); + tmp = _set_a_without_b(tmp, pd->current_border); + + EINA_LIST_FREE(tmp, node) + { + efl_ui_focus_manager_register(pd->manager, node, obj, obj); + } + + eina_list_free(pd->current_border); + pd->current_border = selection; +} + +static void +_border_unregister(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Sub_Data *pd) +{ + Efl_Ui_Focus_Object *node; + + EINA_LIST_FREE(pd->current_border, node) + { + efl_ui_focus_manager_unregister(pd->manager, node); + } + + pd->current_border = NULL; +} + +static void +_parent_manager_pre_flush(void *data, const Efl_Event *ev EINA_UNUSED) +{ + MY_DATA(data, pd); + + if (!pd->self_dirty) return; //we are not interested + + _border_flush(data, pd); +} + +EFL_CALLBACKS_ARRAY_DEFINE(parent_manager, + {EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, _parent_manager_pre_flush} +); + +static void +_parent_set(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd, Efl_Ui_Focus_Manager *manager) +{ + if (pd->manager) + { + //remove ourself from the manager + efl_ui_focus_manager_unregister(pd->manager, obj); + + + efl_event_callback_array_del(pd->manager, parent_manager(), obj); + _border_unregister(obj, pd); + } + + pd->manager = manager; + + if (pd->manager) + { + //register our own root in the upper manager + efl_ui_focus_manager_register(pd->manager, obj, pd->parent, obj); + + //listen to the manager + efl_event_callback_array_add(pd->manager, parent_manager(), obj); + _border_flush(obj, pd); + } +} + +static void +_self_parent_change(void *data EINA_UNUSED, const Efl_Event *ev) +{ + MY_DATA(ev->object , pd); + + if (pd->manager == ev->info) return; + + _parent_set(ev->object, pd, ev->info); +} + +static void +_self_manager_dirty(void *data EINA_UNUSED, const Efl_Event *ev) +{ + MY_DATA(ev->object , pd); + + pd->self_dirty = EINA_TRUE; +} + +EFL_CALLBACKS_ARRAY_DEFINE(self_manager, + {EFL_UI_FOCUS_USER_EVENT_MANAGER_CHANGED, _self_parent_change}, + {EFL_UI_FOCUS_MANAGER_EVENT_COORDS_DIRTY, _self_manager_dirty}, +); +EOLIAN static void +_efl_ui_focus_manager_sub_parent_set(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd, Efl_Ui_Focus_Object *node) +{ + if (node == pd->parent) return; + + pd->parent = node; + + efl_ui_focus_manager_update_parent(pd->manager, obj, node); +} + +EOLIAN static Efl_Ui_Focus_Object* +_efl_ui_focus_manager_sub_parent_get(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Sub_Data *pd EINA_UNUSED) +{ + return pd->parent; +} + +EOLIAN static Efl_Object* +_efl_ui_focus_manager_sub_efl_object_constructor(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd EINA_UNUSED) +{ + efl_event_callback_array_add(obj, self_manager(), NULL); + + return efl_constructor(efl_super(obj, MY_CLASS)); +} + +EOLIAN static void +_efl_ui_focus_manager_sub_efl_object_destructor(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd) +{ + efl_event_callback_array_del(obj, self_manager(), NULL); + + _parent_set(obj, pd, NULL); + + return efl_destructor(efl_super(obj, MY_CLASS)); +} + +EOLIAN static Efl_Object* +_efl_ui_focus_manager_sub_efl_object_finalize(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd) +{ + Efl_Ui_Focus_Manager *manager; + + manager = efl_ui_focus_user_manager_get(obj); + + _parent_set(obj, pd, manager); + + return efl_finalize(efl_super(obj, MY_CLASS)); +} + + + +#include "efl_ui_focus_manager_sub.eo.c" \ No newline at end of file diff --git a/src/lib/elementary/efl_ui_focus_manager_sub.eo b/src/lib/elementary/efl_ui_focus_manager_sub.eo new file mode 100644 index 0000000000..984b7e67bb --- /dev/null +++ b/src/lib/elementary/efl_ui_focus_manager_sub.eo @@ -0,0 +1,25 @@ +abstract Efl.Ui.Focus.Manager.Sub (Efl.Ui.Focus.Manager, Efl.Ui.Focus.Object, Efl.Ui.Focus.User) +{ + methods { + select_set { + params { + objects : iterator; + } + return : list; + } + @property parent { + values { + node : Efl.Ui.Focus.Object; + } + } + } + implements { + @empty .select_set; + @empty Efl.Ui.Focus.Object.geometry_get; + @empty Efl.Ui.Focus.Object.focus.get; + @empty Efl.Ui.Focus.User.manager.get; + Efl.Object.constructor; + Efl.Object.destructor; + Efl.Object.finalize; + } +} \ No newline at end of file diff --git a/src/lib/elementary/efl_ui_focus_object.c b/src/lib/elementary/efl_ui_focus_object.c new file mode 100644 index 0000000000..16d6c3b33e --- /dev/null +++ b/src/lib/elementary/efl_ui_focus_object.c @@ -0,0 +1,30 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include +#include "elm_priv.h" + +typedef struct { + +} Efl_Ui_Focus_Object_Data; + +EOLIAN static void +_efl_ui_focus_object_focus_set(Eo *obj, Efl_Ui_Focus_Object_Data *pd EINA_UNUSED, Eina_Bool focus) +{ + const Efl_Event_Description *desc; + + if (focus) + desc = EFL_UI_FOCUS_OBJECT_EVENT_FOCUS; + else + desc = EFL_UI_FOCUS_OBJECT_EVENT_UNFOCUS; + + efl_event_callback_call(obj, desc, NULL); +} + + +#include "efl_ui_focus_object.eo.c" +typedef struct { + +} Efl_Ui_Focus_User_Data; +#include "efl_ui_focus_user.eo.c" \ No newline at end of file diff --git a/src/lib/elementary/efl_ui_focus_object.eo b/src/lib/elementary/efl_ui_focus_object.eo new file mode 100644 index 0000000000..9db36b2022 --- /dev/null +++ b/src/lib/elementary/efl_ui_focus_object.eo @@ -0,0 +1,28 @@ +mixin Efl.Ui.Focus.Object +{ + [[Functions of focusable objects]] + methods { + geometry_get { + params { + @out rect : Eina.Rectangle; + } + } + @property focus { + [[This gets called by the manager and should never be called by someone else + + It can be used by a implementation of a focus object to adapt to changes which are needed + ]] + values { + focus : bool; + } + } + } + implements { + @empty .geometry_get; + @empty .focus.get; + } + events { + focus; + unfocus; + } +} \ No newline at end of file diff --git a/src/lib/elementary/efl_ui_focus_user.eo b/src/lib/elementary/efl_ui_focus_user.eo new file mode 100644 index 0000000000..173988f08d --- /dev/null +++ b/src/lib/elementary/efl_ui_focus_user.eo @@ -0,0 +1,18 @@ +mixin Efl.Ui.Focus.User { + methods { + @property manager { + get { + + } + values { + manager : Efl.Ui.Focus.Manager; + } + } + } + implements { + @empty .manager.get; + } + events { + manager,changed : Efl.Ui.Focus.Manager; [[emitted if a new manager is the parent for this one]] + } +} \ No newline at end of file diff --git a/src/tests/elementary/elm_suite.c b/src/tests/elementary/elm_suite.c index dee4d09351..197a9d08db 100644 --- a/src/tests/elementary/elm_suite.c +++ b/src/tests/elementary/elm_suite.c @@ -83,6 +83,8 @@ static const Efl_Test_Case etc[] = { { "elm_code_widget_text", elm_code_test_widget_text }, { "elm_code_widget_selection", elm_code_test_widget_selection }, { "elm_code_widget_undo", elm_code_test_widget_undo }, + { "elm_focus", elm_test_focus}, + { "elm_focus_sub", elm_test_focus_sub}, { NULL, NULL } }; diff --git a/src/tests/elementary/elm_suite.h b/src/tests/elementary/elm_suite.h index cfa46d95eb..2cae845284 100644 --- a/src/tests/elementary/elm_suite.h +++ b/src/tests/elementary/elm_suite.h @@ -69,6 +69,8 @@ void elm_test_panes(TCase *tc); void elm_test_slideshow(TCase *tc); void elm_test_spinner(TCase *tc); void elm_test_plug(TCase *tc); +void elm_test_focus(TCase *tc); +void elm_test_focus_sub(TCase *tc); void elm_code_file_test_load(TCase *tc); void elm_code_file_test_memory(TCase *tc); diff --git a/src/tests/elementary/elm_test_focus.c b/src/tests/elementary/elm_test_focus.c new file mode 100644 index 0000000000..044d391897 --- /dev/null +++ b/src/tests/elementary/elm_test_focus.c @@ -0,0 +1,347 @@ +#include "elm_test_focus_common.h" + +START_TEST(focus_unregister_twice) +{ + elm_init(1, NULL); + Efl_Ui_Focus_Object *r1 = efl_add(FOCUS_TEST_CLASS, NULL); + Efl_Ui_Focus_Object *r2 = efl_add(FOCUS_TEST_CLASS, NULL); + + Efl_Ui_Focus_Manager *m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, r1) + ); + + fail_if(!efl_ui_focus_manager_register(m, r2, r1, NULL)); + + efl_ui_focus_manager_unregister(m, r1); + efl_ui_focus_manager_unregister(m, r1); + efl_ui_focus_manager_unregister(m, r1); + + efl_del(r2); + efl_del(r1); + efl_del(m); + + elm_shutdown(); +} +END_TEST + +START_TEST(focus_register_twice) +{ + elm_init(1, NULL); + + Efl_Ui_Focus_Object *r1 = elm_focus_test_object_new("r1", 0, 0, 10, 10); + Efl_Ui_Focus_Object *r2 = elm_focus_test_object_new("r2", 0, 10, 10, 10); + + Efl_Ui_Focus_Manager *m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, r1) + ); + + fail_if(!efl_ui_focus_manager_register(m, r2, r1, NULL)); + fail_if(efl_ui_focus_manager_register(m, r2, r1, NULL)); + + efl_del(r1); + efl_del(m); + + elm_shutdown(); +} +END_TEST + +START_TEST(pos_check) +{ + Efl_Ui_Focus_Manager *m; + Efl_Ui_Focus_Object *middle, *east, *west, *north, *south; + + elm_init(1, NULL); + + elm_focus_test_setup_cross(&middle, &south, &north, &east, &west); + + m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, middle) + ); + + efl_ui_focus_manager_register(m, north, middle, NULL); + efl_ui_focus_manager_register(m, south, middle, NULL); + efl_ui_focus_manager_register(m, west, middle, NULL); + efl_ui_focus_manager_register(m, east, middle, NULL); + +#define CHECK(obj, r,l,u,d) \ + efl_ui_focus_manager_focus(m, obj); \ + ck_assert_ptr_eq(efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_RIGHT), r); \ + efl_ui_focus_manager_focus(m, obj); \ + ck_assert_ptr_eq(efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_LEFT), l); \ + efl_ui_focus_manager_focus(m, obj); \ + ck_assert_ptr_eq(efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_UP), u); \ + efl_ui_focus_manager_focus(m, obj); \ + ck_assert_ptr_eq(efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_DOWN), d); \ + efl_ui_focus_manager_focus(m, obj); + + CHECK(middle, east, west, north, south) + CHECK(east, NULL, middle, NULL, NULL) + CHECK(west, middle, NULL, NULL, NULL) + CHECK(north, NULL, NULL, NULL, middle) + CHECK(south, NULL, NULL, middle, NULL) + + efl_del(middle); + efl_del(south); + efl_del(north); + efl_del(east); + efl_del(west); + + elm_shutdown(); +} +END_TEST + +START_TEST(redirect) +{ + elm_init(1, NULL); + + TEST_OBJ_NEW(root, 0, 0, 20, 20); + TEST_OBJ_NEW(one, 0, 0, 20, 20); + TEST_OBJ_NEW(two, 20, 0, 20, 20); + + Efl_Ui_Focus_Manager *m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, root) + ); + + Efl_Ui_Focus_Manager *m2 = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, one) + ); + + efl_ui_focus_manager_register(m2, two, one, NULL); + + efl_ui_focus_manager_redirect_set(m, m2); + efl_ui_focus_manager_focus(m2, one); + + ck_assert_ptr_eq(efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_RIGHT), two); + + elm_shutdown(); +} +END_TEST + +START_TEST(border_check) +{ + Efl_Ui_Focus_Manager *m; + Efl_Ui_Focus_Object *middle, *east, *west, *north, *south; + Eina_List *list = NULL; + Eina_Iterator *iter; + Efl_Ui_Focus_Object *obj; + + elm_init(1, NULL); + + elm_focus_test_setup_cross(&middle, &south, &north, &east, &west); + + m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, middle) + ); + efl_ui_focus_manager_register(m, south, middle, NULL); + efl_ui_focus_manager_register(m, north, middle, NULL); + efl_ui_focus_manager_register(m, east, middle, NULL); + efl_ui_focus_manager_register(m, west, middle, NULL); + + iter = efl_ui_focus_manager_border_elements_get(m); + + EINA_ITERATOR_FOREACH(iter, obj) + { + list = eina_list_append(list, obj); + } + + eina_iterator_free(iter); + + ck_assert(eina_list_data_find(list, east) == east); + ck_assert(eina_list_data_find(list, north) == north); + ck_assert(eina_list_data_find(list, west) == west); + ck_assert(eina_list_data_find(list, east) == east); + ck_assert(eina_list_data_find(list, middle) == NULL); + ck_assert(eina_list_count(list) == 4); + + elm_shutdown(); +} +END_TEST + +START_TEST(logical_chain) +{ + Efl_Ui_Focus_Manager *m; + int i = 0; + + elm_init(1, NULL); + + TEST_OBJ_NEW(root, 0, 0, 20, 20); + + m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, root) + ); + fail_if(!m); + + efl_ui_focus_manager_focus(m, root); + + i++; + TEST_OBJ_NEW(child1, 0, i*20, 20, 20); + i++; + TEST_OBJ_NEW(child2, 0, i*20, 20, 20); + i++; + TEST_OBJ_NEW(child3, 0, i*20, 20, 20); + + i++; + TEST_OBJ_NEW(subchild11, 0, i*20, 20, 20); + i++; + TEST_OBJ_NEW(subchild12, 0, i*20, 20, 20); + i++; + TEST_OBJ_NEW(subchild13, 0, i*20, 20, 20); + + i++; + TEST_OBJ_NEW(subchild21, 0, i*20, 20, 20); + i++; + TEST_OBJ_NEW(subchild22, 0, i*20, 20, 20); + i++; + TEST_OBJ_NEW(subchild23, 0, i*20, 20, 20); + + //register everything + efl_ui_focus_manager_register(m, child1, root, NULL); + efl_ui_focus_manager_register(m, child2, root, NULL); + efl_ui_focus_manager_register(m, child3, root, NULL); + efl_ui_focus_manager_register(m, subchild11, child1, NULL); + efl_ui_focus_manager_register(m, subchild12, child1, NULL); + efl_ui_focus_manager_register(m, subchild13, child1, NULL); + efl_ui_focus_manager_register(m, subchild21, child3, NULL); + efl_ui_focus_manager_register(m, subchild22, child3, NULL); + efl_ui_focus_manager_register(m, subchild23, child3, NULL); + + Efl_Object *logical_chain[] = { + child1, subchild11, subchild12, subchild13, + child2, child3, subchild21, subchild22, subchild23, root, NULL + }; + for (i = 0; logical_chain[i]; ++i) + { + ck_assert_ptr_eq(logical_chain[i], efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_NEXT)); + } + i-= 2; + for (; i > 0; --i) + { + ck_assert_ptr_eq(logical_chain[i], efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_PREV)); + } + elm_shutdown(); +} +END_TEST + +START_TEST(finalize_check) +{ + Efl_Ui_Focus_Manager *m; + + elm_init(1, NULL); + + m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL); + fail_if(m); + + elm_shutdown(); +} +END_TEST + +START_TEST(redirect_param) +{ + Efl_Ui_Focus_Manager *m, *m2; + + elm_init(1, NULL); + + TEST_OBJ_NEW(root, 0, 20, 20, 20); + TEST_OBJ_NEW(root2, 0, 20, 20, 20); + TEST_OBJ_NEW(child, 0, 20, 20, 20); + + m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, root) + ); + + m2 = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, root2) + ); + + efl_ui_focus_manager_register(m, child, root, m2); + efl_ui_focus_manager_focus(m, child); + + ck_assert_ptr_eq(efl_ui_focus_manager_redirect_get(m), m2); + + elm_shutdown(); +} +END_TEST + +START_TEST(invalid_args_check) +{ + Efl_Ui_Focus_Manager *m; + + elm_init(1, NULL); + + TEST_OBJ_NEW(root, 0, 20, 20, 20); + TEST_OBJ_NEW(child, 0, 20, 20, 20); + TEST_OBJ_NEW(child2, 0, 20, 20, 20); + + m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, root) + ); + + //no child and no parent + ck_assert_int_eq(efl_ui_focus_manager_register(m, NULL, NULL, NULL), 0); + ck_assert_int_eq(efl_ui_focus_manager_register(m, child, NULL, NULL), 0); + ck_assert_int_eq(efl_ui_focus_manager_register(m, NULL, root, NULL), 0); + + ck_assert_int_eq(efl_ui_focus_manager_register(m, child, root, NULL), 1); + + ck_assert_int_eq(efl_ui_focus_manager_update_parent(m, child, NULL), 0); + ck_assert_int_eq(efl_ui_focus_manager_update_parent(m, NULL, NULL), 0); + ck_assert_int_eq(efl_ui_focus_manager_update_parent(m, child, child2), 0); + + ck_assert_int_eq(efl_ui_focus_manager_register(m, child2, root, NULL), 1); + ck_assert_int_eq(efl_ui_focus_manager_update_parent(m, child, child2), 1); + + elm_shutdown(); +} +END_TEST + +START_TEST(order_check) +{ + Efl_Ui_Focus_Manager *m; + Eina_List *order = NULL; + + elm_init(1, NULL); + + TEST_OBJ_NEW(root, 0, 20, 20, 20); + TEST_OBJ_NEW(child1, 0, 20, 20, 20); + TEST_OBJ_NEW(child2, 0, 20, 20, 20); + TEST_OBJ_NEW(child3, 0, 20, 20, 20); + + m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, root) + ); + + //no child and no parent + efl_ui_focus_manager_register(m, child1, root, NULL); + efl_ui_focus_manager_register(m, child2, root, NULL); + efl_ui_focus_manager_register(m, child3, root, NULL); + + //positiv check + order = eina_list_append(order, child2); + order = eina_list_append(order, child3); + order = eina_list_append(order, child1); + ck_assert_int_eq(efl_ui_focus_manager_update_children(m, root, order), 1); + + eina_list_free(order); + order = NULL; + + //negativ check + order = eina_list_append(order, child1); + order = eina_list_append(order, child2); + ck_assert_int_eq(efl_ui_focus_manager_update_children(m, root, order), 0); + + elm_shutdown(); +} +END_TEST +void elm_test_focus(TCase *tc) +{ + tcase_add_test(tc, focus_register_twice); + tcase_add_test(tc, focus_unregister_twice); + tcase_add_test(tc, pos_check); + tcase_add_test(tc, redirect); + tcase_add_test(tc, border_check); + tcase_add_test(tc, finalize_check); + tcase_add_test(tc, logical_chain); + tcase_add_test(tc, redirect_param); + tcase_add_test(tc, invalid_args_check); + tcase_add_test(tc, order_check); +} diff --git a/src/tests/elementary/elm_test_focus_common.c b/src/tests/elementary/elm_test_focus_common.c new file mode 100644 index 0000000000..402fac809f --- /dev/null +++ b/src/tests/elementary/elm_test_focus_common.c @@ -0,0 +1,79 @@ +#include "elm_test_focus_common.h" + +#define Q(o,_x,_y,_w,_h) \ + do {\ + Eina_Rectangle rect = EINA_RECTANGLE_INIT; \ + rect.x = _x; \ + rect.y = _y; \ + rect.w = _w; \ + rect.h = _h; \ + focus_test_size(o, rect); \ + } while (0) + +Efl_Ui_Focus_Object* +elm_focus_test_object_new(const char *name, int x, int y, int w, int h) +{ + Efl_Ui_Focus_Object *ret; + + ret = efl_add(FOCUS_TEST_CLASS, NULL, + efl_name_set(efl_added, name) + ); + Q(ret, x, y, w, h); + + return ret; +} + +void +elm_focus_test_setup_cross(Efl_Ui_Focus_Object **middle, + Efl_Ui_Focus_Object **south, + Efl_Ui_Focus_Object **north, + Efl_Ui_Focus_Object **east, + Efl_Ui_Focus_Object **west) + { + + *middle = elm_focus_test_object_new("middle", 40, 40, 20, 20); + *south = elm_focus_test_object_new("south", 40, 80, 20, 20); + *north = elm_focus_test_object_new("north", 40, 0, 20, 20); + *east = elm_focus_test_object_new("east", 80, 40, 20, 20); + *west = elm_focus_test_object_new("west", 0, 40, 20, 20); +} + +//Test class implementation + +typedef struct { + Eina_Rectangle rect; + Eina_Bool focus; +} Focus_Test_Data; + +EOLIAN static Efl_Object* +_focus_test_efl_object_constructor(Eo *obj, Focus_Test_Data *pd) +{ + Eo *eo; + + eo = efl_constructor(efl_super(obj, FOCUS_TEST_CLASS)); + eina_rectangle_coords_from(&pd->rect, 0, 0, 0, 0); + return eo; +} + +EOLIAN static void +_focus_test_efl_ui_focus_object_focus_set(Eo *obj, Focus_Test_Data *pd, Eina_Bool focus) +{ + pd->focus = focus; + printf("Object %p now focused\n", obj); +} + +EOLIAN static void +_focus_test_efl_ui_focus_object_geometry_get(Eo *obj EINA_UNUSED, Focus_Test_Data *pd, Eina_Rectangle *rect) +{ + if (!rect) return; + + *rect = pd->rect; +} + +EOLIAN static void +_focus_test_size(Eo *obj EINA_UNUSED, Focus_Test_Data *pd, Eina_Rectangle rect) +{ + pd->rect = rect; +} + +#include "focus_test.eo.c" diff --git a/src/tests/elementary/elm_test_focus_common.h b/src/tests/elementary/elm_test_focus_common.h new file mode 100644 index 0000000000..97fbdd17bc --- /dev/null +++ b/src/tests/elementary/elm_test_focus_common.h @@ -0,0 +1,28 @@ +#ifndef ELM_TEST_FOCUS_COMMON_H +#define ELM_TEST_FOCUS_COMMON_H + +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_INTERFACE_ATSPI_ACCESSIBLE_PROTECTED +#define ELM_INTERNAL_API_ARGESFSDFEFC +#include +#include "elm_suite.h" +#include "elm_widget.h" +#include "focus_test.eo.h" + +#define TEST_OBJ_NEW(name, x, y, w, h) \ + Efl_Ui_Focus_Object* name; \ + name = elm_focus_test_object_new("" #name "",x, y, w, h); \ + + +Efl_Ui_Focus_Object* elm_focus_test_object_new(const char *name, int x, int y, int w, int h); + +void elm_focus_test_setup_cross(Efl_Ui_Focus_Object **middle, + Efl_Ui_Focus_Object **south, + Efl_Ui_Focus_Object **north, + Efl_Ui_Focus_Object **east, + Efl_Ui_Focus_Object **west); + +#endif \ No newline at end of file diff --git a/src/tests/elementary/elm_test_focus_sub.c b/src/tests/elementary/elm_test_focus_sub.c new file mode 100644 index 0000000000..3b253f5e1f --- /dev/null +++ b/src/tests/elementary/elm_test_focus_sub.c @@ -0,0 +1,242 @@ +#include "elm_test_focus_common.h" +#include "focus_test_sub.eo.h" + +typedef struct { + +} Focus_Test_Sub_Data; + +EOLIAN static Eina_List* +_focus_test_sub_efl_ui_focus_manager_sub_select_set(Eo *obj EINA_UNUSED, Focus_Test_Sub_Data *pd EINA_UNUSED, Eina_Iterator *objects) +{ + Eina_List *list = NULL; + Efl_Ui_Focus_Object *o; + + EINA_ITERATOR_FOREACH(objects, o) + { + list = eina_list_append(list, o); + } + + eina_iterator_free(objects); + + return list; +} + +EOLIAN static void +_focus_test_sub_efl_ui_focus_object_geometry_get(Eo *obj EINA_UNUSED, Focus_Test_Sub_Data *pd EINA_UNUSED, Eina_Rectangle *rect EINA_UNUSED) +{ + rect->y = rect->x = 0; + rect->w = rect->h = 20; +} + +EOLIAN static Eina_Bool +_focus_test_sub_efl_ui_focus_object_focus_get(Eo *obj EINA_UNUSED, Focus_Test_Sub_Data *pd EINA_UNUSED) +{ + return EINA_FALSE; +} + +EOLIAN static Efl_Ui_Focus_Manager* +_focus_test_sub_efl_ui_focus_user_manager_get(Eo *obj, Focus_Test_Sub_Data *pd EINA_UNUSED) +{ + return efl_parent_get(obj); +} + +static Eina_List *registered; +static Eina_List *unregistered; + +static Eina_Bool +_register(Eo *eo, void* data EINA_UNUSED, Efl_Ui_Focus_Object *child, Efl_Ui_Focus_Object *parent, Efl_Ui_Focus_Manager *manager) +{ + registered = eina_list_append(registered, child); + printf("REGISTERED %p %s\n", child, efl_name_get(child)); + + return efl_ui_focus_manager_register(efl_super(eo, EFL_OBJECT_OVERRIDE_CLASS) , child, parent, manager); +} + +static void +_unregister(Eo *eo, void* data EINA_UNUSED, Efl_Ui_Focus_Object *child) +{ + unregistered = eina_list_append(unregistered, child); + printf("UNREGISTERED %p %s\n", child, efl_name_get(child)); + + efl_ui_focus_manager_unregister(efl_super(eo, EFL_OBJECT_OVERRIDE_CLASS) , child); +} + +static Eina_Bool +_set_equal(Eina_List *a, Eina_List *b) +{ + Eina_List *n; + void *d; + + if (eina_list_count(a) != eina_list_count(b)) return EINA_FALSE; + + EINA_LIST_FOREACH(a, n, d) + { + if (!eina_list_data_find(b, d)) return EINA_FALSE; + } + return EINA_TRUE; +} + +#include "focus_test_sub.eo.c" + +static void +_setup(Efl_Ui_Focus_Manager **m, Efl_Ui_Focus_Manager_Sub **sub, Efl_Ui_Focus_Object **r) +{ + + TEST_OBJ_NEW(root, 10, 10, 10, 10); + TEST_OBJ_NEW(root_manager, 0, 20, 20, 20); + + EFL_OPS_DEFINE(manager_tracker, + EFL_OBJECT_OP_FUNC(efl_ui_focus_manager_register, _register), + EFL_OBJECT_OP_FUNC(efl_ui_focus_manager_unregister, _unregister), + ); + + Efl_Ui_Focus_Manager *manager = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL, + efl_ui_focus_manager_root_set(efl_added, root_manager) + ); + //flush now all changes + efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL); + registered = NULL; + unregistered = NULL; + + efl_object_override(manager, &manager_tracker); + + Efl_Ui_Focus_Manager_Sub *subm = efl_add(FOCUS_TEST_SUB_CLASS, manager, + efl_ui_focus_manager_sub_parent_set(efl_added, root_manager), + efl_ui_focus_manager_root_set(efl_added, root) + ); + + efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL); + + *sub = subm; + *m = manager; + *r = root; +} + +START_TEST(correct_register) +{ + Eina_List *set1 = NULL; + Efl_Ui_Focus_Object *root; + Efl_Ui_Focus_Manager *manager, *sub; + elm_init(0, NULL); + + _setup(&manager, &sub, &root); + + TEST_OBJ_NEW(child1, 0, 0, 10, 10); + TEST_OBJ_NEW(child2, 10, 0, 10, 10); + TEST_OBJ_NEW(child3, 0, 10, 10, 10); + + set1 = eina_list_append(set1, sub); + set1 = eina_list_append(set1, root); + set1 = eina_list_append(set1, child1); + set1 = eina_list_append(set1, child2); + set1 = eina_list_append(set1, child3); + + //test register stuff + efl_ui_focus_manager_register(sub, child1, root, NULL); + efl_ui_focus_manager_register(sub, child2, root, NULL); + efl_ui_focus_manager_register(sub, child3, root, NULL); + //now force submanager to flush things + efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL); + ck_assert_ptr_eq(unregistered, NULL); + fail_if(!_set_equal(registered, set1)); + + efl_del(sub); + efl_del(manager); + efl_del(root); + efl_del(child1); + efl_del(child2); + efl_del(child3); + elm_shutdown(); +} +END_TEST + +START_TEST(correct_unregister) +{ + Eina_List *set = NULL; + Efl_Ui_Focus_Object *root; + Efl_Ui_Focus_Manager *manager, *sub; + elm_init(0, NULL); + + _setup(&manager, &sub, &root); + + TEST_OBJ_NEW(child1, 0, 0, 10, 10); + TEST_OBJ_NEW(child2, 10, 0, 10, 10); + TEST_OBJ_NEW(child3, 0, 10, 10, 10); + + set = eina_list_append(set, child3); + + //test register stuff + efl_ui_focus_manager_register(sub, child1, root, NULL); + efl_ui_focus_manager_register(sub, child2, root, NULL); + efl_ui_focus_manager_register(sub, child3, root, NULL); + efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL); + eina_list_free(unregistered); + unregistered = NULL; + eina_list_free(registered); + registered = NULL; + + //test unregister stuff + efl_ui_focus_manager_unregister(sub, child3); + efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL); + ck_assert_ptr_eq(registered, NULL); + fail_if(!_set_equal(unregistered, set)); + eina_list_free(unregistered); + unregistered = NULL; + + efl_del(sub); + efl_del(manager); + efl_del(root); + efl_del(child1); + efl_del(child2); + efl_del(child3); + elm_shutdown(); +} +END_TEST + +START_TEST(correct_un_register) +{ + Eina_List *set_add = NULL, *set_del = NULL; + Efl_Ui_Focus_Object *root; + Efl_Ui_Focus_Manager *manager, *sub; + elm_init(0, NULL); + + _setup(&manager, &sub, &root); + + TEST_OBJ_NEW(child1, 0, 0, 10, 10); + TEST_OBJ_NEW(child2, 10, 0, 10, 10); + TEST_OBJ_NEW(child3, 0, 10, 10, 10); + + set_add = eina_list_append(set_add, child2); + set_del = eina_list_append(set_del, child3); + //test register stuff + efl_ui_focus_manager_register(sub, child1, root, NULL); + efl_ui_focus_manager_register(sub, child3, root, NULL); + efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL); + eina_list_free(unregistered); + unregistered = NULL; + eina_list_free(registered); + registered = NULL; + + //test unregister stuff + efl_ui_focus_manager_unregister(sub, child3); + efl_ui_focus_manager_register(sub, child2, root, NULL); + efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL); + fail_if(!_set_equal(registered, set_add)); + fail_if(!_set_equal(unregistered, set_del)); + + efl_del(sub); + efl_del(manager); + efl_del(root); + efl_del(child1); + efl_del(child2); + efl_del(child3); + elm_shutdown(); +} +END_TEST + +void elm_test_focus_sub(TCase *tc) +{ + tcase_add_test(tc, correct_register); + tcase_add_test(tc, correct_unregister); + tcase_add_test(tc, correct_un_register); +} \ No newline at end of file diff --git a/src/tests/elementary/focus_test.eo b/src/tests/elementary/focus_test.eo new file mode 100644 index 0000000000..7417cace1c --- /dev/null +++ b/src/tests/elementary/focus_test.eo @@ -0,0 +1,14 @@ +class Focus.Test(Efl.Object, Efl.Ui.Focus.Object) { + methods { + size { + params { + rect : Eina.Rectangle; + } + } + } + implements { + Efl.Object.constructor; + Efl.Ui.Focus.Object.geometry_get; + Efl.Ui.Focus.Object.focus.set; + } +} \ No newline at end of file diff --git a/src/tests/elementary/focus_test_sub.eo b/src/tests/elementary/focus_test_sub.eo new file mode 100644 index 0000000000..355acb95ef --- /dev/null +++ b/src/tests/elementary/focus_test_sub.eo @@ -0,0 +1,8 @@ +class Focus.Test.Sub(Efl.Ui.Focus.Manager.Sub) { + implements { + Efl.Ui.Focus.Manager.Sub.select_set; + Efl.Ui.Focus.Object.geometry_get; + Efl.Ui.Focus.Object.focus.get; + Efl.Ui.Focus.User.manager.get; + } +} \ No newline at end of file