From 2e1317baedb844a490e96b574fcec3c1ef52c6d9 Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Fri, 5 Jul 2019 14:03:13 -0700 Subject: [PATCH] elementary: introduce Efl.Ui.CollectionView a generic listing View. The idea of this widget is to provide to MVVM what Efl.Ui.Collection provide and leverage the same shared logic for layout. Co-authored-by: Mike Blumenkrantz Co-authored-by: Marcel Hollerbach Differential Revision: https://phab.enlightenment.org/D9958 --- .../efl_ui_collection_view_example_1.c | 107 + src/examples/elementary/meson.build | 1 + src/lib/elementary/Efl_Ui.h | 2 +- .../elementary/efl_ui_collection_events.eo | 2 +- src/lib/elementary/efl_ui_collection_view.c | 2301 +++++++++++++++++ src/lib/elementary/efl_ui_collection_view.eo | 61 + .../efl_ui_collection_view_focus_manager.eo | 7 + src/lib/elementary/efl_ui_item.c | 23 + src/lib/elementary/efl_ui_item.eo | 12 + src/lib/elementary/efl_ui_item_private.h | 1 + .../elementary/efl_ui_position_manager_list.c | 35 +- src/lib/elementary/efl_ui_widget.c | 2 - src/lib/elementary/efl_ui_widget_factory.c | 33 +- src/lib/elementary/elm_priv.h | 1 + src/lib/elementary/meson.build | 3 + 15 files changed, 2555 insertions(+), 36 deletions(-) create mode 100644 src/examples/elementary/efl_ui_collection_view_example_1.c create mode 100644 src/lib/elementary/efl_ui_collection_view.c create mode 100644 src/lib/elementary/efl_ui_collection_view.eo create mode 100644 src/lib/elementary/efl_ui_collection_view_focus_manager.eo diff --git a/src/examples/elementary/efl_ui_collection_view_example_1.c b/src/examples/elementary/efl_ui_collection_view_example_1.c new file mode 100644 index 0000000000..72960651fb --- /dev/null +++ b/src/examples/elementary/efl_ui_collection_view_example_1.c @@ -0,0 +1,107 @@ +// gcc -o efl_ui_collection_view_example_1 efl_ui_collection_view_example_1.c `pkg-config --cflags --libs efl-ui + +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#else +# define EFL_BETA_API_SUPPORT 1 +#endif + +#include +#include +#include +#include +#include + +#define NUM_ITEMS 400 + +static Efl_Model* +_make_model(Evas_Object *win) +{ + Eina_Value vtext; + Efl_Generic_Model *model, *child; + unsigned int i; + char buf[256]; + + model = efl_add(EFL_GENERIC_MODEL_CLASS, win); + eina_value_setup(&vtext, EINA_VALUE_TYPE_STRING); + + for (i = 0; i < (NUM_ITEMS); i++) + { + child = efl_model_child_add(model); + snprintf(buf, sizeof(buf), "Item # %i", i); + eina_value_set(&vtext, buf); + efl_model_property_set(child, "title", &vtext); + } + + eina_value_flush(&vtext); + return model; +} + +static void +_item_constructing(void *data EINA_UNUSED, const Efl_Event *ev) +{ + Efl_Gfx_Entity *item = ev->info; + + if (!efl_ui_item_calc_locked_get(item)) + efl_gfx_hint_size_min_set(item, EINA_SIZE2D(50, 50)); +} + +EAPI_MAIN void +efl_main(void *data EINA_UNUSED, const Efl_Event *ev) +{ + Efl_Ui_Factory *factory; + Evas_Object *win, *li; + Eo *model; + Eo *position_manager; + Efl_App *app = ev->object; + Eina_Accessor *ac; + Eina_Bool list = EINA_TRUE, multi = EINA_FALSE, none = EINA_FALSE; + Efl_Ui_Select_Mode mode = EFL_UI_SELECT_MODE_SINGLE; + const char *arg; + unsigned int i; + + ac = efl_core_command_line_command_access(app); + EINA_ACCESSOR_FOREACH(ac, i, arg) + { + if (eina_streq(arg, "grid")) list = EINA_FALSE; + if (eina_streq(arg, "multi")) multi = EINA_TRUE; + if (eina_streq(arg, "none")) none = EINA_TRUE; + } + eina_accessor_free(ac); + + if (multi) mode = EFL_UI_SELECT_MODE_MULTI; + if (none) mode = EFL_UI_SELECT_MODE_NONE; + + win = efl_add(EFL_UI_WIN_CLASS, app, + efl_ui_win_type_set(efl_added, EFL_UI_WIN_TYPE_BASIC), + efl_ui_win_autohide_set(efl_added, EINA_TRUE)); + elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_HIDDEN); + model = _make_model(win); + + factory = efl_add(EFL_UI_LAYOUT_FACTORY_CLASS, win); + efl_ui_property_bind(factory, "text", "title"); + + if (list) + { + position_manager = efl_new(EFL_UI_POSITION_MANAGER_LIST_CLASS); + efl_ui_widget_factory_item_class_set(factory, EFL_UI_LIST_DEFAULT_ITEM_CLASS); + } + else + { + position_manager = efl_new(EFL_UI_POSITION_MANAGER_GRID_CLASS); + efl_ui_widget_factory_item_class_set(factory, EFL_UI_GRID_DEFAULT_ITEM_CLASS); + efl_event_callback_add(factory, EFL_UI_FACTORY_EVENT_ITEM_CONSTRUCTING, _item_constructing, NULL); + } + + li = efl_add(EFL_UI_COLLECTION_VIEW_CLASS, win, + efl_ui_collection_view_position_manager_set(efl_added, position_manager), + efl_ui_view_model_set(efl_added, model), + efl_ui_multi_selectable_async_select_mode_set(efl_added, mode), + efl_ui_collection_view_factory_set(efl_added, factory)); + + efl_content_set(win, li); + + //showall + efl_gfx_entity_size_set(win, EINA_SIZE2D(320, 320)); +} +EFL_MAIN() diff --git a/src/examples/elementary/meson.build b/src/examples/elementary/meson.build index 53d1213d4c..53489299cb 100644 --- a/src/examples/elementary/meson.build +++ b/src/examples/elementary/meson.build @@ -114,6 +114,7 @@ examples = [ 'efl_ui_list_view_example_1', 'efl_ui_list_view_example_2', 'efl_ui_list_view_example_3', + 'efl_ui_collection_view_example_1', 'efl_canvas_layout_text', 'efl_ui_theme_example_01', 'efl_ui_theme_example_02', diff --git a/src/lib/elementary/Efl_Ui.h b/src/lib/elementary/Efl_Ui.h index 2c899fa20c..baa945c24b 100644 --- a/src/lib/elementary/Efl_Ui.h +++ b/src/lib/elementary/Efl_Ui.h @@ -333,7 +333,7 @@ typedef Eo Efl_Ui_Spotlight_Indicator; # include # include # include - +# include # include # include diff --git a/src/lib/elementary/efl_ui_collection_events.eo b/src/lib/elementary/efl_ui_collection_events.eo index 9ef6a991df..653172564b 100644 --- a/src/lib/elementary/efl_ui_collection_events.eo +++ b/src/lib/elementary/efl_ui_collection_events.eo @@ -1,6 +1,6 @@ interface @beta Efl.Ui.Collection_Events { - [[Shared sets of events between @Efl.Ui.Collection and Efl.Ui.Collection_View.]] + [[Shared sets of events between @Efl.Ui.Collection and @Efl.Ui.Collection_View.]] event_prefix: efl_ui; events { item,pressed : Efl.Ui.Item; [[A $press event occurred over an item.]] diff --git a/src/lib/elementary/efl_ui_collection_view.c b/src/lib/elementary/efl_ui_collection_view.c new file mode 100644 index 0000000000..d597c46d0b --- /dev/null +++ b/src/lib/elementary/efl_ui_collection_view.c @@ -0,0 +1,2301 @@ +// Note: @1.23 Initial release has infrastructure to support more mode than homogeneous, but isn't exposed in the API nor supported. + +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_LAYOUT_PROTECTED +#define EFL_UI_SCROLL_MANAGER_PROTECTED +#define EFL_UI_SCROLLBAR_PROTECTED +#define EFL_UI_WIDGET_FOCUS_MANAGER_PROTECTED + +#include +#include +#include "elm_widget.h" +#include "elm_priv.h" + +#include "efl_ui_collection_view_focus_manager.eo.h" + +#ifndef VIEWPORT_ENABLE +# undef VIEWPORT_ENABLE +#endif + +typedef struct _Efl_Ui_Collection_View_Data Efl_Ui_Collection_View_Data; +typedef struct _Efl_Ui_Collection_Viewport Efl_Ui_Collection_Viewport; +typedef struct _Efl_Ui_Collection_View_Focus_Manager_Data Efl_Ui_Collection_View_Focus_Manager_Data; +typedef struct _Efl_Ui_Collection_Item Efl_Ui_Collection_Item; +typedef struct _Efl_Ui_Collection_Item_Lookup Efl_Ui_Collection_Item_Lookup; +typedef struct _Efl_Ui_Collection_Request Efl_Ui_Collection_Request; + +struct _Efl_Ui_Collection_Item +{ + Efl_Gfx_Entity *entity; + Efl_Model *model; +}; + +struct _Efl_Ui_Collection_Item_Lookup +{ + EINA_RBTREE; + + uint64_t index; + Efl_Ui_Collection_Item item; +}; + +struct _Efl_Ui_Collection_Viewport +{ + Efl_Ui_Collection_Item *items; + + uint64_t offset; + uint16_t count; +}; + +struct _Efl_Ui_Collection_Request +{ + Eina_Future *f; + + uint64_t offset; + uint64_t length; + + Eina_Bool model_requested : 1; + Eina_Bool model_fetched : 1; + Eina_Bool need_entity : 1; + Eina_Bool entity_requested : 1; +}; + +struct _Efl_Ui_Collection_View_Data +{ + Efl_Ui_Factory *factory; + Efl_Ui_Position_Manager_Entity *manager; + Efl_Ui_Scroll_Manager *scroller; + Efl_Ui_Pan *pan; + Efl_Gfx_Entity *sizer; + Efl_Model *model; + Efl_Model *multi_selectable_async_model; + +#ifdef VIEWPORT_ENABLE + Efl_Ui_Collection_Viewport *viewport[3]; +#endif + Eina_Rbtree *cache; + + Eina_List *requests; // Array of Efl_Ui_Collection_Request in progress + + uint64_t start_id; + uint64_t end_id; + + Eina_Size2D content_min_size; + + Efl_Ui_Layout_Orientation direction; + Efl_Ui_Select_Mode mode; + + struct { + Eina_Bool w : 1; + Eina_Bool h : 1; + } match_content; + + Efl_Ui_Position_Manager_Request_Range current_range; +}; + +struct _Efl_Ui_Collection_View_Focus_Manager_Data +{ + Efl_Ui_Collection_View *collection; +}; + +static const char *COLLECTION_VIEW_MANAGED = "_collection_view.managed"; +static const char *COLLECTION_VIEW_MANAGED_YES = "yes"; + +#define MY_CLASS EFL_UI_COLLECTION_VIEW_CLASS + +#define MY_DATA_GET(obj, pd) \ + Efl_Ui_Collection_View_Data *pd = efl_data_scope_get(obj, MY_CLASS); + +static void _entity_request(Efl_Ui_Collection_View *obj, Efl_Ui_Collection_Request *request); +static void _idle_cb(void *data, const Efl_Event *event); + +static int +_cache_tree_lookup(const Eina_Rbtree *node, const void *key, + int length EINA_UNUSED, void *data EINA_UNUSED) +{ + const Efl_Ui_Collection_Item_Lookup *n = (Efl_Ui_Collection_Item_Lookup *)node; + const uint64_t *index = key; + + if (n->index > *index) + return 1; + if (n->index < *index) + return -1; + return 0; +} + +static Eina_Rbtree_Direction +_cache_tree_cmp(const Eina_Rbtree *left, const Eina_Rbtree *right, void *data EINA_UNUSED) +{ + Efl_Ui_Collection_Item_Lookup *l = (Efl_Ui_Collection_Item_Lookup *)left; + Efl_Ui_Collection_Item_Lookup *r = (Efl_Ui_Collection_Item_Lookup *)right; + + return l->index < r->index ? EINA_RBTREE_LEFT : EINA_RBTREE_RIGHT; +} + +static Eina_Value +_undo_item_selected_then(Eo *item, void *data EINA_UNUSED, Eina_Error err) +{ + Eina_Value *get; + Eina_Bool item_selected = efl_ui_selectable_selected_get(item); + Eina_Bool model_selected = EINA_FALSE; + + get = efl_model_property_get(efl_ui_view_model_get(item), "self.selected"); + eina_value_bool_get(get, &model_selected); + eina_value_free(get); + + if ((!!model_selected) != (!!item_selected)) + efl_ui_selectable_selected_set(item, model_selected); + + return eina_value_error_init(err); +} + +static void +_selected_item_cb(void *data EINA_UNUSED, const Efl_Event *ev) +{ + // Link back property to model, maybe just trigger event on the item should be enough + Eina_Value *get; + Eina_Bool item_selected = efl_ui_selectable_selected_get(ev->object); + Eina_Bool model_selected = EINA_FALSE; + Eina_Value set = eina_value_bool_init(!!item_selected); + + get = efl_model_property_get(efl_ui_view_model_get(ev->object), "self.selected"); + eina_value_bool_get(get, &model_selected); + eina_value_free(get); + + if ((!!model_selected) != (!!item_selected)) + { + Eina_Future *f; + + f = efl_model_property_set(efl_ui_view_model_get(ev->object), "self.selected", &set); + + // In case the mode is preventing the change, we need to update the UI back. So handle error case + efl_future_then(ev->object, f, + .error = _undo_item_selected_then); + } + + eina_value_flush(&set); +} + +static void +_redirect_item_cb(void *data, const Efl_Event *ev) +{ + Eo *obj = data; + +#define REDIRECT_EVT(item_evt, item) \ + if (item_evt == ev->desc) efl_event_callback_call(obj, item, ev->object); + REDIRECT_EVT(EFL_INPUT_EVENT_PRESSED, EFL_UI_EVENT_ITEM_PRESSED); + REDIRECT_EVT(EFL_INPUT_EVENT_UNPRESSED, EFL_UI_EVENT_ITEM_UNPRESSED); + REDIRECT_EVT(EFL_INPUT_EVENT_LONGPRESSED, EFL_UI_EVENT_ITEM_LONGPRESSED); + REDIRECT_EVT(EFL_INPUT_EVENT_CLICKED_ANY, EFL_UI_EVENT_ITEM_CLICKED_ANY); + REDIRECT_EVT(EFL_INPUT_EVENT_CLICKED, EFL_UI_EVENT_ITEM_CLICKED); +#undef REDIRECT_EVT +} + +EFL_CALLBACKS_ARRAY_DEFINE(active_item_cbs, + { EFL_UI_EVENT_SELECTED_CHANGED, _selected_item_cb }, + { EFL_INPUT_EVENT_PRESSED, _redirect_item_cb }, + { EFL_INPUT_EVENT_UNPRESSED, _redirect_item_cb }, + { EFL_INPUT_EVENT_LONGPRESSED, _redirect_item_cb }, + { EFL_INPUT_EVENT_CLICKED, _redirect_item_cb }, + { EFL_INPUT_EVENT_CLICKED_ANY, _redirect_item_cb }); + +static void +_entity_cleanup(Efl_Ui_Collection_View *obj, Efl_Ui_Factory *factory, + Efl_Ui_Collection_Item *item, Eina_Array *scheduled_release) +{ + Efl_Gfx_Entity *entities[1]; + + entities[0] = item->entity; + if (!entities[0]) return ; + + efl_event_callback_array_del(entities[0], active_item_cbs(), obj); + efl_replace(&item->entity, NULL); + efl_event_callback_call(obj, EFL_UI_COLLECTION_VIEW_EVENT_ITEM_UNREALIZED, entities[0]); + if (!scheduled_release) + { + efl_ui_factory_release(factory, EINA_C_ARRAY_ITERATOR_NEW(entities)); + } + else + { + eina_array_push(scheduled_release, entities[0]); + } +} + +static void +_item_cleanup(Efl_Ui_Collection_View *obj, Efl_Ui_Factory *factory, + Efl_Ui_Collection_Item *item, Eina_Array *scheduled_release) +{ + efl_replace(&item->model, NULL); + + _entity_cleanup(obj, factory, item, scheduled_release); +} + +static void +_cache_item_free(Eina_Rbtree *node, void *data) +{ + Efl_Ui_Collection_Item_Lookup *n = (void*) node; + MY_DATA_GET(data, pd); + + _item_cleanup(data, pd->factory, &n->item, NULL); + free(n); +} + +static void +_cache_cleanup(Efl_Ui_Collection_View *obj, Efl_Ui_Collection_View_Data *pd) +{ + eina_rbtree_delete(pd->cache, _cache_item_free, obj); + pd->cache = NULL; +} + +static void +_all_cleanup(Efl_Ui_Collection_View *obj, Efl_Ui_Collection_View_Data *pd) +{ + Efl_Ui_Collection_Request *request; + Eina_List *l, *ll; +#ifdef VIEWPORT_ENABLE + unsigned int i; +#endif + + _cache_cleanup(obj, pd); +#ifdef VIEWPORT_ENABLE + for (i = 0; i < 3; i++) + { + unsigned int j; + + if (!pd->viewport[i]) continue; + + for (j = 0; j < pd->viewport[i]->count; j++) + _item_cleanup(obj, pd->factory, &(pd->viewport[i]->items[j])); + } +#endif + + EINA_LIST_FOREACH_SAFE(pd->requests, l, ll, request) + eina_future_cancel(request->f); +} + +static inline Eina_Bool +_size_from_model(Efl_Model *model, Eina_Size2D *r, const char *width, const char *height) +{ + Eina_Value *vw, *vh; + Eina_Bool success = EINA_FALSE; + + EINA_SAFETY_ON_NULL_RETURN_VAL(model, EINA_FALSE); + + vw = efl_model_property_get(model, width); + vh = efl_model_property_get(model, height); + + if (eina_value_type_get(vw) == EINA_VALUE_TYPE_ERROR || + eina_value_type_get(vh) == EINA_VALUE_TYPE_ERROR) + goto on_error; + + if (!eina_value_int_convert(vw, &(r->w))) r->w = 0; + if (!eina_value_int_convert(vh, &(r->h))) r->h = 0; + + success = EINA_TRUE; + + on_error: + eina_value_free(vw); + eina_value_free(vh); + + return success; +} + +static inline void +_size_to_model(Efl_Model *model, Eina_Size2D state) +{ + Eina_Value vw, vh; + + vw = eina_value_int_init(state.w); + vh = eina_value_int_init(state.h); + + efl_model_property_set(model, "self.width", &vw); + efl_model_property_set(model, "self.height", &vh); + + eina_value_flush(&vw); + eina_value_flush(&vh); +} + +#define ITEM_BASE_SIZE_FROM_MODEL(Model, Size) _size_from_model(Model, &Size, "item.width", "item.height") +#define ITEM_SIZE_FROM_MODEL(Model, Size) _size_from_model(Model, &Size, "self.width", "self.height") + +static Eina_List * +_request_add(Eina_List *requests, Efl_Ui_Collection_Request **request, + uint64_t index, Eina_Bool need_entity) +{ + if (!(*request)) goto create; + + if ((*request)->offset + (*request)->length == index) + { + if (need_entity) (*request)->need_entity = EINA_TRUE; + (*request)->length += 1; + return requests; + } + + requests = eina_list_append(requests, *request); + + create: + *request = calloc(1, sizeof (Efl_Ui_Collection_Request)); + if (!(*request)) return requests; + (*request)->offset = index; + (*request)->length = 1; + // At this point, we rely on the model caching ability to avoid recreating model + (*request)->model_requested = EINA_TRUE; + (*request)->need_entity = !!need_entity; + + return requests; +} + +static Eina_Value +_model_fetched_cb(Eo *obj, void *data, const Eina_Value v) +{ + MY_DATA_GET(obj, pd); + Efl_Ui_Collection_Request *request = data; + Efl_Model *child; + unsigned int i, len; + Eina_Bool request_entity = EINA_FALSE; + + request->model_fetched = EINA_TRUE; + EINA_VALUE_ARRAY_FOREACH(&v, len, i, child) + { + Efl_Ui_Collection_Item_Lookup *insert; + Eina_Size2D item_size; +#ifdef VIEWPORT_ENABLE + unsigned int v; + + for (v = 0; v < 3; ++v) + { + if (!pd->viewport[v]) continue; + + if ((pd->viewport[v]->offset <= request->offset + i) && + (request->offset + i < pd->viewport[v]->offset + pd->viewport[v]->count)) + { + uint64_t index = request->offset + i - pd->viewport[v]->offset; + + efl_replace(&pd->viewport[v]->items[index].model, child); + child = NULL; + break; + } + } +#endif + + // When requesting a model, it should not be in the cache prior to the request + if (!child) continue; + + uint64_t search_index = request->offset + i; + + insert = (void*) eina_rbtree_inline_lookup(pd->cache, &search_index, + sizeof (search_index), _cache_tree_lookup, + NULL); + if (insert) + { + if (!insert->item.entity && request->need_entity) + { + //drop the old model here, overwrite with model + view + efl_replace(&insert->item.model, child); + } + else + ERR("Inserting a model that was already fetched, dropping new model %lu", search_index); + } + else + { + insert = calloc(1, sizeof (Efl_Ui_Collection_Item_Lookup)); + if (!insert) continue; + insert->index = request->offset + i; + insert->item.model = efl_ref(child); + pd->cache = eina_rbtree_inline_insert(pd->cache, EINA_RBTREE_GET(insert), _cache_tree_cmp, NULL); + } + + if (!ITEM_SIZE_FROM_MODEL(insert->item.model, item_size)) + request_entity = EINA_TRUE; + } + + if (request_entity) + { + request->need_entity = EINA_TRUE; + _entity_request(obj, request); + } + + return v; +} + +static void +_model_free_cb(Eo *o, void *data, const Eina_Future *dead_future EINA_UNUSED) +{ + MY_DATA_GET(o, pd); + Efl_Ui_Collection_Request *request = data; + + if (request->need_entity) + { + if (!request->entity_requested) + _entity_request(o, request); + } + else + { + pd->requests = eina_list_remove(pd->requests, request); + free(request); + } +} + +static Eina_Value +_entity_fetch_cb(Eo *obj, void *data EINA_UNUSED, const Eina_Value v) +{ + MY_DATA_GET(obj, pd); + Efl_Model *child; + Eina_Future *r; + Eina_Array tmp; + unsigned int i, len; + + eina_array_step_set(&tmp, sizeof (Eina_Array), 4); + + EINA_VALUE_ARRAY_FOREACH(&v, len, i, child) + { + eina_array_push(&tmp, child); + } + + r = efl_ui_view_factory_create_with_event(pd->factory, eina_array_iterator_new(&tmp)); + + eina_array_flush(&tmp); + + return eina_future_as_value(r); +} + +static inline Eina_Bool +_entity_propagate(Efl_Model *model, Efl_Gfx_Entity *entity) +{ + Eina_Size2D item_size; + + if (efl_key_data_get(entity, "efl.ui.widget.factory.size_set")) + { + return EINA_FALSE; + } + + if (ITEM_SIZE_FROM_MODEL(model, item_size)) + { + efl_gfx_hint_size_min_set(entity, item_size); + efl_canvas_group_need_recalculate_set(entity, EINA_FALSE); + if (efl_isa(entity, EFL_UI_ITEM_CLASS)) efl_ui_item_calc_locked_set(entity, EINA_TRUE); + return EINA_FALSE; + } + + efl_canvas_group_calculate(entity); + item_size = efl_gfx_hint_size_combined_min_get(entity); + efl_canvas_group_need_recalculate_set(entity, EINA_FALSE); + + _size_to_model(model, item_size); + return EINA_TRUE; +} + +static Eina_Value +_entity_fetched_cb(Eo *obj, void *data, const Eina_Value v) +{ + MY_DATA_GET(obj, pd); + Efl_Ui_Collection_Request *request = data; + Efl_Gfx_Entity *child; + unsigned int i, len; + uint64_t updated_size_start_id, updated_entity_start_id; + Eina_Bool updated_size = EINA_FALSE, updated_entity = EINA_FALSE; + + EINA_VALUE_ARRAY_FOREACH(&v, len, i, child) + { + Efl_Ui_Collection_Item_Lookup *lookup; + uint64_t search_index; + //unsigned int v; + + efl_key_data_set(child, COLLECTION_VIEW_MANAGED, COLLECTION_VIEW_MANAGED_YES); + /* fix eventing in scroller by ensuring collection items are in the scroller hierarchy */ + efl_ui_item_container_set(child, obj); + efl_ui_widget_sub_object_add(obj, child); + efl_canvas_group_member_add(pd->pan, child); + efl_ui_widget_focus_allow_set(child, EINA_FALSE); + efl_gfx_entity_visible_set(child, EINA_FALSE); + +#ifdef VIEWPORT_ENABLE + for (v = 0; v < 3; ++v) + { + if (!pd->viewport[v]) continue; + + if ((pd->viewport[v]->offset <= request->offset + i) && + (request->offset + i < pd->viewport[v]->offset + pd->viewport[v]->count)) + { + uint64_t index = request->offset + i - pd->viewport[v]->offset; + + if (pd->viewport[v]->items[index].entity) + { + ERR("Entity already existing for id %d", i); + efl_unref(pd->viewport[v]->items[index].entity); + efl_del(pd->viewport[v]->items[index].entity); + pd->viewport[v]->items[index].entity = NULL; + } + + efl_replace(&pd->viewport[v]->items[index].entity, child); + if (_entity_propagate(pd->viewport[v]->items[index].model, child)) + { + if (!updated_size) + { + updated_size = EINA_TRUE; + updated_size_start_id = index; + } + } + else + { + if (updated_size) + { + efl_ui_position_manager_entity_item_size_changed(pd->manager, + updated_size_start_id, + index - 1); + updated_size = EINA_FALSE; + } + } + child = NULL; + break; + } + } +#endif + + // When requesting an entity, the model should already be in the cache + if (!child) continue; + + search_index = request->offset + i; + + lookup = (void*) eina_rbtree_inline_lookup(pd->cache, &search_index, + sizeof (search_index), _cache_tree_lookup, + NULL); + + if (!lookup) + { + Efl_Gfx_Entity *entities[1] = { child }; + efl_ui_factory_release(pd->factory, EINA_C_ARRAY_ITERATOR_NEW(entities)); + continue; + } + if (lookup->item.entity) + { + ERR("Entity already existing for id %lu", search_index); + _entity_cleanup(obj, pd->factory, &lookup->item, NULL); + } + + lookup->item.entity = efl_ref(child); + efl_event_callback_array_add(child, active_item_cbs(), obj); + efl_event_callback_call(obj, EFL_UI_COLLECTION_VIEW_EVENT_ITEM_REALIZED, child); + + if (!updated_entity) + { + updated_entity = EINA_TRUE; + updated_entity_start_id = search_index; + } + + if (_entity_propagate(lookup->item.model, child)) + { + if (!updated_size) + { + updated_size = EINA_TRUE; + updated_size_start_id = search_index; + } + } + else + { + if (updated_size) + { + efl_ui_position_manager_entity_item_size_changed(pd->manager, + updated_size_start_id, + search_index - 1); + updated_size = EINA_FALSE; + } + }} + ; + + // Currently position manager will flush its entire size cache on update, so only do + // it when necessary to improve performance. + if (updated_size) + { + efl_ui_position_manager_entity_item_size_changed(pd->manager, + updated_size_start_id, + i - 1); + updated_size = EINA_FALSE; + } + + // Notify the position manager that new entity are ready to display + if (updated_entity) + { + efl_ui_position_manager_entity_entities_ready(pd->manager, + updated_entity_start_id, + i - 1); + + efl_event_callback_del(efl_main_loop_get(), EFL_LOOP_EVENT_IDLE, _idle_cb, obj); + efl_event_callback_add(efl_main_loop_get(), EFL_LOOP_EVENT_IDLE, _idle_cb, obj); + } + return v; +} + +static void +_entity_free_cb(Eo *o, void *data, const Eina_Future *dead_future EINA_UNUSED) +{ + MY_DATA_GET(o, pd); + Efl_Ui_Collection_Request *request = data; + + pd->requests = eina_list_remove(pd->requests, request); + free(request); +} + +static Eina_List * +_cache_size_fetch(Eina_List *requests, Efl_Ui_Collection_Request **request, + Efl_Ui_Collection_View_Data *pd, + uint64_t search_index, + Efl_Ui_Position_Manager_Size_Batch_Entity *target, + Eina_Size2D item_base) +{ + Efl_Ui_Collection_Item_Lookup *lookup; + Efl_Model *model; + Eina_Size2D item_size = item_base; + + if (!pd->cache) goto not_found; + + lookup = (void*) eina_rbtree_inline_lookup(pd->cache, &search_index, + sizeof (search_index), _cache_tree_lookup, + NULL); + if (!lookup) goto not_found; + + // In the cache we should always have model, so no need to check for it + model = lookup->item.model; + + // If we do not know the size + if (!ITEM_SIZE_FROM_MODEL(model, item_size)) + { + if (lookup->item.entity) + { + ERR("Got a model '%s' and an item '%s', but no size. Recalculating.", + efl_debug_name_get(model), efl_debug_name_get(lookup->item.entity)); + _entity_propagate(model, lookup->item.entity); + if (!ITEM_SIZE_FROM_MODEL(model, item_size)) + { + CRI("No size for itme '%s' after recalculating. This is bad.", + efl_debug_name_get(lookup->item.entity)); + } + } + else if (!ITEM_BASE_SIZE_FROM_MODEL(pd->model, item_size)) + { + INF("No base size yet available. Making things up."); + item_size.w = 1; + item_size.h = 1; + } + } + + target->size = item_size; + target->element_depth = 0; + target->depth_leader = EINA_FALSE; + return requests; + + not_found: + requests = _request_add(requests, request, search_index, EINA_FALSE); + + target->size = item_size; + target->element_depth = 0; + target->depth_leader = EINA_FALSE; + return requests; +} + +static Eina_List * +_cache_entity_fetch(Eina_List *requests, Efl_Ui_Collection_Request **request, + Efl_Ui_Collection_View_Data *pd, + uint64_t search_index, + Efl_Ui_Position_Manager_Object_Batch_Entity *target) +{ + Efl_Ui_Collection_Item_Lookup *lookup; + + if (!pd->cache) goto not_found; + + lookup = (void*) eina_rbtree_inline_lookup(pd->cache, &search_index, + sizeof (search_index), _cache_tree_lookup, + NULL); + if (!lookup) goto not_found; + if (!lookup->item.entity) goto not_found; + + if (target) target->entity = lookup->item.entity; + goto finish; + + not_found: + requests = _request_add(requests, request, search_index, EINA_TRUE); + + if (target) target->entity = NULL; + finish: + if (!target) return requests; + + target->element_depth = 0; + target->depth_leader = EINA_FALSE; + + return requests; +} + +static void +_entity_request(Efl_Ui_Collection_View *obj, Efl_Ui_Collection_Request *request) +{ + if (request->model_requested && (!request->model_fetched)) return; + request->f = efl_future_then(obj, request->f, + .success_type = EINA_VALUE_TYPE_ARRAY, + .success = _entity_fetch_cb); + request->f = efl_future_then(obj, request->f, + .success_type = EINA_VALUE_TYPE_ARRAY, + .success = _entity_fetched_cb, + .data = request, + .free = _entity_free_cb); + request->entity_requested = EINA_TRUE; +} + +static inline void +_entity_inflight_request(Efl_Ui_Collection_View *obj, + Efl_Ui_Collection_Request *request, + Efl_Ui_Collection_Request *inflight) +{ + if (request->need_entity == EINA_FALSE) return ; + if (inflight->entity_requested) return ; + + _entity_request(obj, inflight); +} + +static Eina_List * +_batch_request_flush(Eina_List *requests, + Efl_Ui_Collection_View *obj, + Efl_Ui_Collection_View_Data *pd) +{ + Efl_Ui_Collection_Request *request; + Eina_List *ll, *next_list_item; + + EINA_LIST_FOREACH_SAFE(requests, ll, next_list_item, request) + { + // Check request intersection with all pending request + Efl_Ui_Collection_Request *inflight; + Efl_Model *model; + Eina_List *l; + + EINA_LIST_FOREACH(pd->requests, l, inflight) + { + uint64_t istart = inflight->offset; + uint64_t iend = inflight->offset + inflight->length; + uint64_t rstart = request->offset; + uint64_t rend = request->offset + request->length; + + // Way before + if (rend < istart) continue; + // Way after + if (rstart >= iend) continue; + + // request included in current inflight request + if (rstart >= istart && rend <= iend) + { + _entity_inflight_request(obj, request, inflight); + + // In this case no need to start a request + requests = eina_list_remove_list(requests, ll); + free(request); + request = NULL; + break; + } + + // request overflow left and right + if (rstart < istart && iend < rend) + { + // Remove the center portion of the request by emitting a new one + Efl_Ui_Collection_Request *rn; + + rn = calloc(1, sizeof (Efl_Ui_Collection_Request)); + if (!rn) break; + + rn->offset = iend; + rn->length = rend - iend; + rn->model_requested = request->model_requested; + rn->need_entity = request->need_entity; + + requests = eina_list_append(requests, rn); + + request->length = istart - rstart; + _entity_inflight_request(obj, request, inflight); + continue; + } + + // request overflow left + if (rstart < istart && rend > istart && rend <= iend) + { + request->length = istart - rstart; + _entity_inflight_request(obj, request, inflight); + continue; + } + + // request overflow right + if (rstart >= istart && rstart < iend && iend <= rend) + { + request->offset = iend; + request->length = rend - iend; + _entity_inflight_request(obj, request, inflight); + continue; + } + } + + if (!request) continue; + + model = pd->model; + // Are we ready yet + if (!model) + { + requests = eina_list_remove_list(requests, ll); + free(request); + continue; + } + // Is the request inside the limit of the model? + if (request->offset >= efl_model_children_count_get(model)) + { + requests = eina_list_remove_list(requests, ll); + free(request); + continue; + } + // Is its limit outside the model limit? + if (request->offset + request->length >= efl_model_children_count_get(model)) + { + request->length = efl_model_children_count_get(model) - request->offset; + } + + // We now have a request, time to trigger a fetch + // We assume here that we are always fetching the model (model_requested must be true) + if (!request->model_requested) + { + CRI("Someone forgot to set model_requested for %lu to %lu.", + request->offset, request->offset + request->length); + request->model_requested = EINA_TRUE; + } + request->f = efl_model_children_slice_get(model, request->offset, request->length); + request->f = efl_future_then(obj, request->f, + .success = _model_fetched_cb, + .data = request, + .free = _model_free_cb); + + eina_list_move_list(&pd->requests, &requests, ll); + } + return NULL; +} + +static Efl_Ui_Position_Manager_Size_Batch_Result +_batch_size_cb(void *data, Efl_Ui_Position_Manager_Size_Call_Config conf, Eina_Rw_Slice memory) +{ + MY_DATA_GET(data, pd); + Efl_Ui_Position_Manager_Size_Batch_Entity *sizes; + Efl_Ui_Collection_Request *request = NULL; + Efl_Ui_Position_Manager_Size_Batch_Result result = {0}; + Efl_Model *parent; + Eina_List *requests = NULL; + Eina_Size2D item_base = {0}; + unsigned int limit; + unsigned int idx = 0; + + // get the approximate value from the tree node + parent = pd->model; + + sizes = memory.mem; + //count = efl_model_children_count_get(parent); + limit = conf.range.end_id - conf.range.start_id; + ITEM_BASE_SIZE_FROM_MODEL(parent, item_base); + + // Look in the temporary cache now for the beginning of the buffer +#ifdef VIEWPORT_ENABLE + if (pd->viewport[0] && ((uint64_t)(conf.range.start_id + idx) < pd->viewport[0]->offset)) + { + while ((uint64_t)(conf.range.start_id + idx) < pd->viewport[0]->offset && idx < limit) + { + uint64_t search_index = conf.range.start_id + idx; + requests = _cache_size_fetch(requests, &request, pd, + search_index, &sizes[idx], item_base); + idx++; + } + } + + // Then look in our buffer view if the needed information can be found there + for (i = 0; i < 3; ++i) + { + if (!pd->viewport[i]) continue; + + while (idx < limit && + (pd->viewport[i]->offset <= conf.range.start_id + idx) && + (conf.range.start_id + idx < (pd->viewport[i]->offset + pd->viewport[i]->count))) + { + uint64_t offset = conf.range.start_id + idx - pd->viewport[i]->offset; + Efl_Model *model = pd->viewport[i]->items[offset].model; + Efl_Gfx_Entity *entity = pd->viewport[i]->items[offset].entity; + Eina_Bool entity_request = EINA_FALSE; + + if (model) + { + Eina_Size2D item_size; + Eina_Bool found = EINA_FALSE; + + if (ITEM_SIZE_FROM_MODEL(model, item_size)) + found = EINA_TRUE; + if (!found && entity) + { + item_size = efl_gfx_hint_size_combined_min_get(entity); + //if the size is 0 here, then we are running into trouble, + //fetch size from the parent model, where some fallback is defined + if (item_size.h == 0 && item_size.w == 0) + { + item_size = item_base; + found = EINA_TRUE; + } + else + { + _size_to_model(model, item_size); + found = EINA_TRUE; + } + + } + + if (found) + { + sizes[idx].size = item_size; + sizes[idx].element_depth = 0; + sizes[idx].depth_leader = EINA_FALSE; + goto done; + } + + // We will need an entity to calculate this size + entity_request = EINA_TRUE; + } + // No data, add to the requests + requests = _request_add(requests, &request, conf.range.start_id + idx, entity_request); + + sizes[idx].size = item_base; + sizes[idx].element_depth = 0; + sizes[idx].depth_leader = EINA_FALSE; + + done: + idx++; + } + } + + // Look in the temporary cache now for the end of the buffer + while (idx < limit) + { + uint64_t search_index = conf.range.start_id + idx; + requests = _cache_size_fetch(requests, &request, pd, + search_index, &sizes[idx], item_base); + idx++; + } +#endif + + /* if (conf.cache_request) */ + /* { */ + /* printf("CACHING SIZE CALL\n"); */ + /* while (idx < limit) */ + /* { */ + /* sizes[idx].depth_leader = EINA_FALSE; */ + /* sizes[idx].element_depth = 0; */ + /* sizes[idx].size = pd->last_base; */ + /* idx++; */ + /* } */ + /* fprintf(stderr, "read with no fetch\n"); */ + /* } */ + /* else */ + { + while (idx < limit) + { + uint64_t search_index = conf.range.start_id + idx; + requests = _cache_size_fetch(requests, &request, pd, + search_index, &sizes[idx], item_base); + idx++; + } + + + // Done, but flush request first + if (request) requests = eina_list_append(requests, request); + + requests = _batch_request_flush(requests, data, pd); + } + + // Get the amount of filled item + result.filled_items = limit; + + return result; +} + +static Efl_Ui_Position_Manager_Object_Batch_Result +_batch_entity_cb(void *data, Efl_Ui_Position_Manager_Request_Range range, Eina_Rw_Slice memory) +{ + MY_DATA_GET(data, pd); + Efl_Ui_Position_Manager_Object_Batch_Entity *entities; + Efl_Ui_Collection_Request *request = NULL; + Efl_Ui_Position_Manager_Object_Batch_Result result = {0}; + Eina_List *requests = NULL; +#ifdef VIEWPORT_ENABLE + Efl_Model *parent; +#endif + unsigned int limit; + unsigned int idx = 0; + + //parent = pd->model; + + entities = memory.mem; + //count = efl_model_children_count_get(parent); + limit = range.end_id - range.start_id;; + + // Look in the temporary cache now for the beginning of the buffer +#ifdef VIEWPORT_ENABLE + if (pd->viewport[0] && ((uint64_t)(range.start_id + idx) < pd->viewport[0]->offset)) + { + while (idx < limit && (uint64_t)(range.start_id + idx) < pd->viewport[0]->offset) + { + uint64_t search_index = range.start_id + idx; + + requests = _cache_entity_fetch(requests, &request, pd, + search_index, &entities[idx]); + + idx++; + } + } + + // Then look in our buffer view if the needed information can be found there + for (i = 0; i < 3; ++i) + { + if (!pd->viewport[i]) continue; + + while (idx < limit && + (pd->viewport[i]->offset <= range.start_id + idx) && + (range.start_id + idx < (pd->viewport[i]->offset + pd->viewport[i]->count))) + { + uint64_t offset = range.start_id + idx - pd->viewport[i]->offset; + Efl_Gfx_Entity *entity = pd->viewport[i]->items[offset].entity; + + if (!entity) + { + // No data, add to the requests + requests = _request_add(requests, &request, range.start_id + idx, EINA_TRUE); + + entities[idx].entity = NULL; + entities[idx].depth_leader = EINA_FALSE; + entities[idx].element_depth = 0; + } + else + { + entities[idx].entity = entity; + entities[idx].depth_leader = EINA_FALSE; + entities[idx].element_depth = 0; + } + + idx++; + } + } +#endif + + // Look in the temporary cache now for the end of the buffer + while (idx < limit) + { + uint64_t search_index = range.start_id + idx; + + requests = _cache_entity_fetch(requests, &request, pd, + search_index, &entities[idx]); + + idx++; + } + // Done, but flush request first + if (request) + { + requests = eina_list_append(requests, request); + } + + requests = _batch_request_flush(requests, data, pd); + + // Get the amount of filled item + result.filled_items = limit; + + return result; +} + + +#if 0 +static void +_batch_free_cb(void *data) +{ + efl_unref(data); +} +#endif + +static void +flush_min_size(Eo *obj, Efl_Ui_Collection_View_Data *pd) +{ + Eina_Size2D tmp = pd->content_min_size; + + if (!pd->match_content.w) + tmp.w = -1; + + if (!pd->match_content.h) + tmp.h = -1; + + efl_gfx_hint_size_restricted_min_set(obj, tmp); +} + +static void +_manager_content_size_changed_cb(void *data, const Efl_Event *ev) +{ + Eina_Size2D *size = ev->info; + MY_DATA_GET(data, pd); + + efl_gfx_entity_size_set(pd->sizer, *size); +} + +static void +_manager_content_min_size_changed_cb(void *data, const Efl_Event *ev) +{ + Eina_Size2D *size = ev->info; + MY_DATA_GET(data, pd); + + pd->content_min_size = *size; + + flush_min_size(data, pd); +} + +#ifdef VIEWPORT_ENABLE +static Eina_List * +_viewport_walk_fill(Eina_List *requests, + Efl_Ui_Collection_View *obj, + Efl_Ui_Collection_View_Data *pd, + Efl_Ui_Collection_Viewport *viewport) +{ + Efl_Ui_Collection_Request *current = NULL; + unsigned int j; + + for (j = 0; j < viewport->count; j++) + { + Efl_Ui_Collection_Item_Lookup *lookup; + uint64_t index = viewport->offset + j; + + if (viewport->items[j].model) goto check_entity; + + lookup = (void*) eina_rbtree_inline_lookup(pd->cache, &index, + sizeof (index), _cache_tree_lookup, + NULL); + + if (lookup) + { + efl_replace(&viewport->items[j].model, lookup->item.model); + efl_replace(&viewport->items[j].entity, lookup->item.entity); + efl_replace(&lookup->item.entity, NULL); // Necessary to avoid premature release + + pd->cache = eina_rbtree_inline_remove(pd->cache, EINA_RBTREE_GET(lookup), + _cache_tree_cmp, NULL); + _cache_item_free(EINA_RBTREE_GET(lookup), obj); + } + + check_entity: + if (viewport->items[j].entity) continue ; + requests = _request_add(requests, ¤t, index, EINA_TRUE); + } + + // We do break request per viewport, just in case we generate to big batch at once + if (current) requests = eina_list_append(requests, current); + + return requests; +} + +#endif + +// An RbTree has the nice property of sorting content. The smaller than the root being in +// son[1] and the greater than the root in son[0]. Using this we can efficiently walk the +// tree once to take note of all the item that need cleaning. +static void +_mark_lesser(Efl_Ui_Collection_Item_Lookup *root, Eina_Array *mark, const unsigned int lower) +{ + if (!root) return ; + + if (root->index < lower) + { + eina_array_push(mark, root); + _mark_lesser((void*) EINA_RBTREE_GET(root)->son[1], mark, lower); + } + else + { + _mark_lesser((void*) EINA_RBTREE_GET(root)->son[0], mark, lower); + _mark_lesser((void*) EINA_RBTREE_GET(root)->son[1], mark, lower); + } +} + +static void +_mark_ge(Efl_Ui_Collection_Item_Lookup *root, Eina_Array *mark, const unsigned int upper) +{ + if (!root) return ; + + if (root->index >= upper) + { + eina_array_push(mark, root); + _mark_ge((void*) EINA_RBTREE_GET(root)->son[0], mark, upper); + _mark_ge((void*) EINA_RBTREE_GET(root)->son[1], mark, upper); + } + else + { + _mark_ge((void*) EINA_RBTREE_GET(root)->son[0], mark, upper); + } +} + +// we walk the tree twice, once for everything below the limit and once for everything above +// then we do free each item individually. +static void +_idle_cb(void *data, const Efl_Event *event EINA_UNUSED) +{ + Efl_Ui_Collection_Item_Lookup *lookup; + Eina_Array mark; + Eina_Array scheduled_release; + MY_DATA_GET(data, pd); + const unsigned int length = pd->current_range.end_id - pd->current_range.start_id; + const unsigned int lower_end = MAX((long)pd->current_range.start_id - (long)length/2, 0); + const unsigned int upper_end = pd->current_range.end_id + length/2; + Eina_Array_Iterator iterator; + unsigned int i; + + eina_array_step_set(&mark, sizeof (Eina_Array), 16); + eina_array_step_set(&scheduled_release, sizeof (Eina_Array), 16); + + _mark_lesser((void*) pd->cache, &mark, lower_end); + _mark_ge((void*) pd->cache, &mark, upper_end); + + EINA_ARRAY_ITER_NEXT(&mark, i, lookup, iterator) + { + pd->cache = (void*) eina_rbtree_inline_remove(pd->cache, + EINA_RBTREE_GET(lookup), + _cache_tree_cmp, NULL); + _item_cleanup(data, pd->factory, &lookup->item, &scheduled_release); + free(lookup); + } + eina_array_flush(&mark); + + efl_ui_factory_release(pd->factory, eina_array_iterator_new(&scheduled_release)); + eina_array_flush(&scheduled_release); + + efl_event_callback_del(efl_main_loop_get(), EFL_LOOP_EVENT_IDLE, _idle_cb, data); +} + +#ifndef VIEWPORT_ENABLE +static void +_manager_content_visible_range_changed_cb(void *data, const Efl_Event *ev) +{ + Efl_Ui_Position_Manager_Range_Update *event = ev->info; + unsigned int count; + unsigned int lower_end; + unsigned int upper_end; + long length; + Efl_Ui_Collection_Request *request = NULL; + Eina_List *requests = NULL; + unsigned int idx; + MY_DATA_GET(data, pd); + + pd->current_range.start_id = event->start_id; + pd->current_range.end_id = event->end_id; + + count = efl_model_children_count_get(efl_ui_view_model_get(data)); + + length = pd->current_range.end_id - pd->current_range.start_id; + lower_end = MAX((long)pd->current_range.start_id - (length / 2), 0); + upper_end = MIN(pd->current_range.end_id + (length / 2), count); + + idx = lower_end; + while (idx < upper_end) + { + uint64_t search_index = idx; + + requests = _cache_entity_fetch(requests, &request, pd, + search_index, NULL); + + idx++; + } + // Done, but flush request first + if (request) requests = eina_list_append(requests, request); + + requests = _batch_request_flush(requests, data, pd); +} +#endif + +#ifdef VIEWPORT_ENABLE +static void +_manager_content_visible_range_changed_cb(void *data, const Efl_Event *ev) +{ + Efl_Ui_Position_Manager_Range_Update *event = ev->info; + MY_DATA_GET(data, pd); + Eina_List *requests = NULL; + long baseid; + unsigned int delta, marginup, margindown; + uint64_t upperlimit_offset, lowerlimit_offset; + unsigned int i; + + pd->start_id = event->start_id; + pd->end_id = event->end_id; + + delta = pd->end_id - pd->start_id; + + // First time setting up the viewport, so trigger request as we see fit + if (!pd->viewport[0]) + { + baseid = pd->start_id - delta; + + for (i = 0; i < 3; i++) + { + pd->viewport[i] = calloc(1, sizeof (Efl_Ui_Collection_Viewport)); + if (!pd->viewport[i]) continue; + + pd->viewport[i]->offset = MAX(baseid + delta * i, 0); + pd->viewport[i]->count = delta; + pd->viewport[i]->items = calloc(delta, sizeof (Efl_Ui_Collection_Item)); + if (!pd->viewport[i]->items) continue ; + + requests = _viewport_walk_fill(requests, data, pd, pd->viewport[i]); + } + + goto flush_requests; + } + + // Compute limit offset + upperlimit_offset = delta * 3 + pd->viewport[0]->offset; + lowerlimit_offset = 0; + + // Adjust the viewport for size or to much offset change in two step + + // Trying to resize first if there size is in bigger/smaller than 25% of the original size + margindown = delta * 75 / 100; + marginup = delta * 125 / 100; + if (margindown < pd->viewport[0]->count && + pd->viewport[0]->count < marginup) + { + // Trying to do the resize in an optimized way is complex, let's do it simple + Efl_Ui_Collection_Item *items[3]; + unsigned int j = 0, t = 1; + + for (i = 0; i < 3; i++) + { + unsigned int m; + + items[i] = calloc(delta, sizeof (Efl_Ui_Collection_Item)); + if (!items[i]) continue; + + for (m = 0; m < delta && t < 3; m++) + { + items[i][m] = pd->viewport[t]->items[j]; + + j++; + if (j < pd->viewport[t]->count) continue; + + j = 0; + t++; + if (t == 3) break; + } + + // Preserve last updated index to later build a request + if (t == 3) + { + upperlimit_offset = pd->viewport[0]->offset + i * delta + m; + + t = 4; // So that we never come back here again + } + } + + // For now destroy leftover object, could be cached + for (i = t; i < 3; i++) + { + for (; j < pd->viewport[i]->count; j++) + { + _item_cleanup(pd->factory, &pd->viewport[i]->items[j]); + } + j = 0; + } + + // And now define viewport back + for (i = 0; i < 3; i++) + { + free(pd->viewport[i]->items); + pd->viewport[i]->items = items[i]; + pd->viewport[i]->count = delta; + pd->viewport[i]->offset = pd->viewport[0]->offset + delta * i; + } + } + + // We decided that resizing was unecessary + delta = pd->viewport[0]->count; + + // Try to keep the visual viewport in between half of the first and last viewport + + // start_id is in the first half of the first viewport, assume upward move + // start_id + delta is in the second half of the last viewport, assume upward move + if (pd->viewport[0]->offset + delta / 2 < pd->start_id || + pd->start_id + delta > pd->viewport[2]->offset + delta / 2) + { + // We could optimize this to actually just move viewport around in most cases + Efl_Ui_Collection_Item *items[3]; + unsigned int j = 0, t = 0; + uint64_t target, current; + + // Case where are at the top + if (pd->start_id < delta && pd->viewport[0]->offset == 0) goto build_request; + + // Trying to adjust the offset to maintain it in the center viewport +/- delta/2 + baseid = (pd->start_id < delta) ? 0 : pd->start_id - delta; + + // Lookup for starting point + lowerlimit_offset = pd->viewport[0]->offset; + target = baseid; + + // cleanup before target + for (current = pd->viewport[t]->offset; current < target; current++) + { + _item_cleanup(pd->factory, &pd->viewport[t]->items[j]); + + j++; + if (j < pd->viewport[t]->count) continue; + + j = 0; + t++; + if (t == 3) break; + } + + // Allocation and copy + for (i = 0; i < 3; i++) + { + unsigned int m; + + items[i] = calloc(delta, sizeof (Efl_Ui_Collection_Item)); + if (!items[i]) continue; + + for (m = 0; m < delta && t < 3; m++, target++) + { + if (target < pd->viewport[t]->offset) continue ; + items[i][m] = pd->viewport[t]->items[j]; + + j++; + if (j < pd->viewport[t]->count) continue; + + j = 0; + t++; + if (t == 3) break; + } + + // Preserve last updated index to later build a request + if (t == 3) + { + if (upperlimit_offset > pd->viewport[0]->offset + i * delta + m) + { + upperlimit_offset = pd->viewport[0]->offset + i * delta + m; + } + + t = 4; // So that we never come back here again + } + } + + // For now destroy leftover object, could be cached + for (i = t; i < 3; i++) + { + for (; j < pd->viewport[i]->count; j++) + { + _item_cleanup(pd->factory, &pd->viewport[i]->items[j]); + } + j = 0; + } + + // And now define viewport back + for (i = 0; i < 3; i++) + { + free(pd->viewport[i]->items); + pd->viewport[i]->items = items[i]; + pd->viewport[i]->offset = baseid + delta * i; + } + } + + build_request: + // Check if the first viewport has all the lower part of it filled with objects + if (pd->viewport[0]->offset < lowerlimit_offset) + { + Efl_Ui_Collection_Request *request; + + request = calloc(1, sizeof (Efl_Ui_Collection_Request)); + if (request) return ; + + request->offset = lowerlimit_offset; + // This length work over multiple viewport as they are contiguous + request->length = lowerlimit_offset - pd->viewport[0]->offset; + request->model_requested = EINA_TRUE; + request->need_entity = EINA_TRUE; + + requests = eina_list_append(requests, request); + } + + // Check if the last viewport has all the upper part of it filler with objects + if (pd->viewport[2]->offset + pd->viewport[2]->count > upperlimit_offset) + { + Efl_Ui_Collection_Request *request; + + request = calloc(1, sizeof (Efl_Ui_Collection_Request)); + if (request) return ; + + request->offset = upperlimit_offset; + // This length work over multiple viewport as they are contiguous + request->length = pd->viewport[2]->offset + pd->viewport[2]->count - upperlimit_offset; + request->model_requested = EINA_TRUE; + request->need_entity = EINA_TRUE; + + requests = eina_list_append(requests, request); + } + + flush_requests: + requests = _batch_request_flush(requests, data, pd); +} +#endif + +EFL_CALLBACKS_ARRAY_DEFINE(manager_cbs, + { EFL_UI_POSITION_MANAGER_ENTITY_EVENT_CONTENT_SIZE_CHANGED, _manager_content_size_changed_cb }, + { EFL_UI_POSITION_MANAGER_ENTITY_EVENT_CONTENT_MIN_SIZE_CHANGED, _manager_content_min_size_changed_cb }, + { EFL_UI_POSITION_MANAGER_ENTITY_EVENT_VISIBLE_RANGE_CHANGED, _manager_content_visible_range_changed_cb } +) + +static void +_item_scroll_internal(Eo *obj EINA_UNUSED, + Efl_Ui_Collection_View_Data *pd, + uint64_t index, + double align EINA_UNUSED, + Eina_Bool anim) +{ + Eina_Rect ipos, view; + Eina_Position2D vpos; + + if (!pd->scroller) return; + + ipos = efl_ui_position_manager_entity_position_single_item(pd->manager, index); + view = efl_ui_scrollable_viewport_geometry_get(pd->scroller); + vpos = efl_ui_scrollable_content_pos_get(pd->scroller); + + ipos.x = ipos.x + vpos.x - view.x; + ipos.y = ipos.y + vpos.y - view.y; + + //FIXME scrollable needs some sort of align, the docs do not even garantee to completly move in the element + efl_ui_scrollable_scroll(pd->scroller, ipos, anim); +} + +// Exported function + +EOLIAN static void +_efl_ui_collection_view_factory_set(Eo *obj EINA_UNUSED, Efl_Ui_Collection_View_Data *pd, + Efl_Ui_Factory *factory) +{ + if (pd->factory) efl_ui_property_bind(pd->factory, "selected", NULL); + efl_replace(&pd->factory, factory); + if (pd->factory) efl_ui_property_bind(pd->factory, "selected", "self.selected"); +} + +EOLIAN static Efl_Ui_Factory * +_efl_ui_collection_view_factory_get(const Eo *obj EINA_UNUSED, Efl_Ui_Collection_View_Data *pd) +{ + return pd->factory; +} + +static void +_unref_cb(void *data) +{ + Eo *obj = data; + + efl_unref(obj); +} + +EOLIAN static void +_efl_ui_collection_view_position_manager_set(Eo *obj, Efl_Ui_Collection_View_Data *pd, + Efl_Ui_Position_Manager_Entity *manager) +{ + Efl_Model *model; + unsigned int count; + + if (manager) + EINA_SAFETY_ON_FALSE_RETURN(efl_isa(manager, EFL_UI_POSITION_MANAGER_ENTITY_INTERFACE)); + + if (pd->manager) + { + efl_event_callback_array_del(pd->manager, manager_cbs(), obj); + efl_del(pd->manager); + } + pd->manager = manager; + if (!pd->manager) return; + + // Start watching change on model from here on + model = pd->model; + count = model ? efl_model_children_count_get(model) : 0; + + efl_parent_set(pd->manager, obj); + efl_event_callback_array_add(pd->manager, manager_cbs(), obj); + switch(efl_ui_position_manager_entity_version(pd->manager, 1)) + { + case 1: + efl_ui_position_manager_data_access_v1_data_access_set(pd->manager, + efl_ref(obj), _batch_entity_cb, _unref_cb, + efl_ref(obj), _batch_size_cb, _unref_cb, + count); + break; + } + + if (efl_finalized_get(obj)) + efl_ui_position_manager_entity_viewport_set(pd->manager, efl_ui_scrollable_viewport_geometry_get(obj)); + efl_ui_layout_orientation_set(pd->manager, pd->direction); +} + +EOLIAN static Efl_Ui_Position_Manager_Entity * +_efl_ui_collection_view_position_manager_get(const Eo *obj EINA_UNUSED, + Efl_Ui_Collection_View_Data *pd) +{ + return pd->manager; +} + +static void +_efl_model_count_changed(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED) +{ + // We are not triggering efl_ui_position_manager_entity_data_access_set as it is can + // only be slow, we rely on child added/removed instead (If we were to not rely on + // child added/removed we could maybe use count changed) +} + +static void +_efl_model_properties_changed(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED) +{ + // We could here watch if the global base size item change and notify of a global change + // But I can not find a proper way to do it for the object that are not visible, which + // is kind of the point... +} + +static void +_cache_cleanup_above(Efl_Ui_Collection_View *obj, Efl_Ui_Collection_View_Data *pd, unsigned int index) +{ + Efl_Ui_Collection_Item_Lookup *lookup; + Eina_Array scheduled_release; + Eina_Array mark; + Eina_Array_Iterator iterator; + unsigned int i; + + eina_array_step_set(&mark, sizeof (Eina_Array), 16); + eina_array_step_set(&scheduled_release, sizeof (Eina_Array), 16); + + _mark_ge((void*) pd->cache, &mark, index); + + EINA_ARRAY_ITER_NEXT(&mark, i, lookup, iterator) + { + pd->cache = (void*) eina_rbtree_inline_remove(pd->cache, + EINA_RBTREE_GET(lookup), + _cache_tree_cmp, NULL); + _item_cleanup(obj, pd->factory, &lookup->item, &scheduled_release); + free(lookup); + } + eina_array_flush(&mark); + + efl_ui_factory_release(pd->factory, eina_array_iterator_new(&scheduled_release)); + eina_array_flush(&scheduled_release); +} + +static void +_efl_model_child_added(void *data, const Efl_Event *event) +{ + // At the moment model only append child, but let's try to handle it theorically correct + Efl_Model_Children_Event *ev = event->info; + MY_DATA_GET(data, pd); +#ifdef VIEWPORT_ENABLE + Eina_List *requests = NULL; + unsigned int i; +#endif + + _cache_cleanup_above(data, pd, ev->index); + + // Check if we really have something to do +#ifdef VIEWPORT_ENABLE + if (!pd->viewport[0]) goto notify_manager; + + // Insert the child in the viewport if necessary + for (i = 0; i < 3; i++) + { + Efl_Ui_Collection_Request *request; + unsigned int o; + unsigned int j; + + if (ev->index < pd->viewport[i]->offset) + { + pd->viewport[i]->offset++; + continue; + } + if (pd->viewport[i]->offset + pd->viewport[i]->count < ev->index) + { + continue; + } + + for (j = 2; j > i; j--) + { + _item_cleanup(pd->factory, &pd->viewport[j]->items[pd->viewport[j]->count - 1]); + memmove(&pd->viewport[j]->items[1], + &pd->viewport[j]->items[0], + (pd->viewport[j]->count - 1) * sizeof (Efl_Ui_Collection_Item)); + pd->viewport[j]->items[0] = pd->viewport[j - 1]->items[pd->viewport[j - 1]->count - 1]; + pd->viewport[j - 1]->items[pd->viewport[j - 1]->count - 1].entity = NULL; + pd->viewport[j - 1]->items[pd->viewport[j - 1]->count - 1].model = NULL; + } + o = ev->index - pd->viewport[i]->offset; + memmove(&pd->viewport[j]->items[o], + &pd->viewport[j]->items[o + 1], + (pd->viewport[j]->count - 1 - o) * sizeof (Efl_Ui_Collection_Item)); + pd->viewport[j]->items[o].entity = NULL; + pd->viewport[j]->items[o].model = efl_ref(ev->child); + + request = calloc(1, sizeof (Efl_Ui_Collection_Request)); + if (!request) break; + request->offset = ev->index; + request->length = 1; + request->model_requested = EINA_TRUE; + request->need_entity = EINA_TRUE; + + requests = eina_list_append(requests, request); + + requests = _batch_request_flush(requests, data, pd); + + break; + } + + notify_manager: +#endif + // FIXME this function must be called with an entity + efl_ui_position_manager_entity_item_added(pd->manager, ev->index, NULL); +} + +static void +_efl_model_child_removed(void *data, const Efl_Event *event) +{ + Efl_Model_Children_Event *ev = event->info; + MY_DATA_GET(data, pd); + Eina_List *requests = NULL; + Efl_Ui_Collection_Request *request = NULL; +#ifdef VIEWPORT_ENABLE + Eina_List *requests = NULL; + unsigned int i; +#endif + unsigned int upper_end; + long length; + unsigned int count; + + // FIXME: later optimization, instead of reloading everyone, we could actually track index and self + // update would be more efficient, but it is also more tricky + _cache_cleanup_above(data, pd, ev->index); + + count = efl_model_children_count_get(event->object); + length = pd->current_range.end_id - pd->current_range.start_id; + upper_end = MIN(pd->current_range.end_id + (length / 2), count); + + // Check if we really have something to do +#ifdef VIEWPORT_ENABLE + if (!pd->viewport[0]) goto notify_manager; + + // Insert the child in the viewport if necessary + for (i = 0; i < 3; i++) + { + Efl_Ui_Collection_Request *request; + unsigned int o; + + if (ev->index < pd->viewport[i]->offset) + { + pd->viewport[i]->offset--; + continue; + } + if (pd->viewport[i]->offset + pd->viewport[i]->count < ev->index) + { + continue; + } + + o = ev->index - pd->viewport[i]->offset; + _item_cleanup(pd->factory, &pd->viewport[i]->items[o]); + for (; i < 3; i++) + { + memmove(&pd->viewport[i]->items[o], + &pd->viewport[i]->items[o + 1], + (pd->viewport[i]->count - 1 - o) * sizeof (Efl_Ui_Collection_Item)); + if (i + 1 < 3) + { + pd->viewport[i]->items[pd->viewport[i]->count - 1] = pd->viewport[i + 1]->items[0]; + } + else + { + pd->viewport[i]->items[pd->viewport[i]->count - 1].entity = NULL; + pd->viewport[i]->items[pd->viewport[i]->count - 1].model = NULL; + } + o = 0; + } + + request = calloc(1, sizeof (Efl_Ui_Collection_Request)); + if (!request) break; + request->offset = pd->viewport[2]->offset + pd->viewport[i]->count - 1; + request->length = 1; + request->model_requested = EINA_TRUE; + request->need_entity = EINA_TRUE; + + requests = eina_list_append(requests, request); + + requests = _batch_request_flush(requests, data, pd); + + break; + } + + notify_manager: +#endif + requests = _request_add(requests, &request, ev->index, EINA_TRUE); + request->length = upper_end - ev->index; + + if (request->length > 0) + { + requests = eina_list_append(requests, request); + requests = _batch_request_flush(requests, data, pd); + } + + efl_ui_position_manager_entity_item_removed(pd->manager, ev->index, NULL); +} + +EFL_CALLBACKS_ARRAY_DEFINE(model_cbs, + { EFL_MODEL_EVENT_CHILDREN_COUNT_CHANGED, _efl_model_count_changed }, + { EFL_MODEL_EVENT_PROPERTIES_CHANGED, _efl_model_properties_changed }, + { EFL_MODEL_EVENT_CHILD_ADDED, _efl_model_child_added }, + { EFL_MODEL_EVENT_CHILD_REMOVED, _efl_model_child_removed }) + +static void +_efl_ui_collection_view_model_changed(void *data, const Efl_Event *event) +{ + Efl_Model_Changed_Event *ev = event->info; + Eina_List *requests = NULL; + MY_DATA_GET(data, pd); + Eina_Iterator *it; + const char *property; + Efl_Model *model = NULL; + unsigned int count; + Efl_Model *mselect = NULL; + Eina_Bool selection = EINA_FALSE, sizing = EINA_FALSE; + + if (ev->previous) efl_event_callback_array_del(ev->previous, model_cbs(), data); + if (ev->current) efl_event_callback_array_add(ev->current, model_cbs(), data); + + // Cleanup all object, pending request and refetch everything + _all_cleanup(data, pd); + + efl_replace(&pd->model, NULL); + + if (!ev->current) + { + if (pd->multi_selectable_async_model) + { + efl_event_callback_forwarder_del(pd->multi_selectable_async_model, + EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, + data); + efl_composite_detach(data, pd->multi_selectable_async_model); + efl_replace(&pd->multi_selectable_async_model, NULL); + } + return ; + } + + it = efl_model_properties_get(ev->current); + EINA_ITERATOR_FOREACH(it, property) + { + // Check if the model provide selection + if (eina_streq(property, "child.selected")) + selection = EINA_TRUE; + // Check if the model provide sizing logic + else if (eina_streq(property, _efl_model_property_itemw) || + eina_streq(property, _efl_model_property_itemh)) + sizing = EINA_TRUE; + } + eina_iterator_free(it); + + if (selection) + { + // Search the composition of model for the one providing MULTI_SELECTABLE_ASYNC + mselect = ev->current; + while (mselect && + !efl_isa(mselect, EFL_UI_MULTI_SELECTABLE_ASYNC_INTERFACE) && + efl_isa(mselect, EFL_COMPOSITE_MODEL_CLASS)) + mselect = efl_ui_view_model_get(mselect); + + if (!efl_isa(mselect, EFL_UI_MULTI_SELECTABLE_ASYNC_INTERFACE)) + { + mselect = NULL; + selection = EINA_FALSE; + } + } + + // Try to build the minimal chain of necessary model for collection view + model = ev->current; + + // Build and connect the selection model properly + if (!mselect) + { + mselect = model = efl_add(EFL_UI_SELECT_MODEL_CLASS, data, + efl_ui_view_model_set(efl_added, model)); + } + if (pd->multi_selectable_async_model) + { + efl_event_callback_forwarder_del(pd->multi_selectable_async_model, + EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, + data); + efl_composite_detach(data, pd->multi_selectable_async_model); + } + efl_replace(&pd->multi_selectable_async_model, mselect); + efl_composite_attach(data, pd->multi_selectable_async_model); + efl_event_callback_forwarder_add(pd->multi_selectable_async_model, + EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, + data); + + if (!sizing) model = efl_add(EFL_UI_HOMOGENEOUS_MODEL_CLASS, data, + efl_ui_view_model_set(efl_added, model)); + + count = efl_model_children_count_get(model); + +#ifdef VIEWPORT_ENABLE + for (i = 0; i < 3; i++) + { + Efl_Ui_Collection_Request *request; + + if (!pd->viewport[i]) continue ; + if (pd->viewport[i]->count == 0) continue ; + + request = calloc(1, sizeof (Efl_Ui_Collection_Request)); + if (!request) continue ; + + request->offset = pd->viewport[i]->offset; + request->length = pd->viewport[i]->count; + request->model_requested = EINA_TRUE; + request->need_entity = EINA_TRUE; + + requests = eina_list_append(requests, request); + } +#endif + requests = _batch_request_flush(requests, data, pd); + + pd->model = model; + efl_ui_position_manager_entity_item_size_changed(pd->manager, 0, count - 1); + switch(efl_ui_position_manager_entity_version(pd->manager, 1)) + { + case 1: + efl_ui_position_manager_data_access_v1_data_access_set(pd->manager, + efl_ref(data), _batch_entity_cb, _unref_cb, + efl_ref(data), _batch_size_cb, _unref_cb, + count); + break; + } + + +} + +static void +_pan_viewport_changed_cb(void *data, const Efl_Event *ev EINA_UNUSED) +{ + MY_DATA_GET(data, pd); + Eina_Rect rect = efl_ui_scrollable_viewport_geometry_get(data); + + efl_ui_position_manager_entity_viewport_set(pd->manager, rect); +} + +static void +_pan_position_changed_cb(void *data, const Efl_Event *ev EINA_UNUSED) +{ + MY_DATA_GET(data, pd); + Eina_Position2D pos = efl_ui_pan_position_get(pd->pan); + Eina_Position2D max = efl_ui_pan_position_max_get(pd->pan); + Eina_Vector2 rpos = {0.0, 0.0}; + + if (max.x > 0.0) + rpos.x = (double)pos.x/(double)max.x; + if (max.y > 0.0) + rpos.y = (double)pos.y/(double)max.y; + + efl_ui_position_manager_entity_scroll_position_set(pd->manager, rpos.x, rpos.y); +} + +EFL_CALLBACKS_ARRAY_DEFINE(pan_events_cb, + {EFL_UI_PAN_EVENT_PAN_CONTENT_POSITION_CHANGED, _pan_position_changed_cb}, + {EFL_GFX_ENTITY_EVENT_SIZE_CHANGED, _pan_viewport_changed_cb}, + {EFL_GFX_ENTITY_EVENT_POSITION_CHANGED, _pan_viewport_changed_cb}, +) + +EOLIAN static Efl_Object * +_efl_ui_collection_view_efl_object_constructor(Eo *obj, Efl_Ui_Collection_View_Data *pd) +{ + pd->direction = EFL_UI_LAYOUT_ORIENTATION_VERTICAL; + obj = efl_constructor(efl_super(obj, EFL_UI_COLLECTION_VIEW_CLASS)); + + if (!elm_widget_theme_klass_get(obj)) + elm_widget_theme_klass_set(obj, "collection"); + + efl_wref_add(efl_add(EFL_CANVAS_RECTANGLE_CLASS, evas_object_evas_get(obj)), &pd->sizer); + efl_gfx_color_set(pd->sizer, 0, 0, 0, 0); + + efl_wref_add(efl_add(EFL_UI_PAN_CLASS, obj), &pd->pan); + efl_content_set(pd->pan, pd->sizer); + efl_event_callback_array_add(pd->pan, pan_events_cb(), obj); + + efl_wref_add(efl_add(EFL_UI_SCROLL_MANAGER_CLASS, obj), &pd->scroller); + efl_composite_attach(obj, pd->scroller); + efl_ui_mirrored_set(pd->scroller, efl_ui_mirrored_get(obj)); + efl_ui_scroll_manager_pan_set(pd->scroller, pd->pan); + + efl_ui_scroll_connector_bind(obj, pd->scroller); + + efl_event_callback_add(obj, EFL_UI_VIEW_EVENT_MODEL_CHANGED, + _efl_ui_collection_view_model_changed, obj); + + return obj; +} + +EOLIAN static void +_efl_ui_collection_view_efl_object_invalidate(Eo *obj, + Efl_Ui_Collection_View_Data *pd) +{ + efl_ui_collection_view_position_manager_set(obj, NULL); + efl_event_callback_del(obj, EFL_UI_VIEW_EVENT_MODEL_CHANGED, + _efl_ui_collection_view_model_changed, obj); + + _all_cleanup(obj, pd); + + efl_invalidate(efl_super(obj, EFL_UI_COLLECTION_VIEW_CLASS)); +} + +EOLIAN static void +_efl_ui_collection_view_efl_ui_layout_orientable_orientation_set(Eo *obj EINA_UNUSED, + Efl_Ui_Collection_View_Data *pd, + Efl_Ui_Layout_Orientation dir) +{ + if (pd->direction == dir) return; + + pd->direction = dir; + if (pd->manager) efl_ui_layout_orientation_set(pd->manager, dir); +} + +EOLIAN static Efl_Ui_Layout_Orientation +_efl_ui_collection_view_efl_ui_layout_orientable_orientation_get(const Eo *obj EINA_UNUSED, + Efl_Ui_Collection_View_Data *pd) +{ + return pd->direction; +} + +EOLIAN static Eina_Error +_efl_ui_collection_view_efl_ui_widget_theme_apply(Eo *obj, Efl_Ui_Collection_View_Data *pd) +{ + Eina_Error res; + + ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd, EFL_UI_THEME_APPLY_ERROR_GENERIC); + res = efl_ui_widget_theme_apply(efl_super(obj, MY_CLASS)); + if (res == EFL_UI_THEME_APPLY_ERROR_GENERIC) return res; + efl_ui_mirrored_set(pd->scroller, efl_ui_mirrored_get(obj)); + efl_content_set(efl_part(wd->resize_obj, "efl.content"), pd->pan); + + return res; +} + +EOLIAN static void +_efl_ui_collection_view_efl_ui_scrollable_match_content_set(Eo *obj, Efl_Ui_Collection_View_Data *pd, Eina_Bool w, Eina_Bool h) +{ + if (pd->match_content.w == w && pd->match_content.h == h) + return; + + pd->match_content.w = w; + pd->match_content.h = h; + + efl_ui_scrollable_match_content_set(pd->scroller, w, h); + flush_min_size(obj, pd); +} + +EOLIAN static Efl_Ui_Focus_Manager * +_efl_ui_collection_view_efl_ui_widget_focus_manager_focus_manager_create(Eo *obj, Efl_Ui_Collection_View_Data *pd EINA_UNUSED, Efl_Ui_Focus_Object *root) +{ + Efl_Ui_Collection_View_Focus_Manager_Data *mpd; + Eo *manager = efl_add(EFL_UI_COLLECTION_VIEW_FOCUS_MANAGER_CLASS, obj, + efl_ui_focus_manager_root_set(efl_added, root)); + + mpd = efl_data_scope_get(manager, EFL_UI_COLLECTION_VIEW_FOCUS_MANAGER_CLASS); + mpd->collection = obj; + + return manager; +} + +EOLIAN static Efl_Ui_Focus_Object * +_efl_ui_collection_view_efl_ui_focus_manager_move(Eo *obj, Efl_Ui_Collection_View_Data *pd, Efl_Ui_Focus_Direction direction) +{ + Eo *new_obj, *focus; + Eina_Size2D step; + + new_obj = efl_ui_focus_manager_move(efl_super(obj, MY_CLASS), direction); + focus = efl_ui_focus_manager_focus_get(obj); + step = efl_gfx_hint_size_combined_min_get(focus); + if (!new_obj) + { + Eina_Rect pos = efl_gfx_entity_geometry_get(focus); + Eina_Rect view = efl_ui_scrollable_viewport_geometry_get(pd->scroller); + Eina_Position2D vpos = efl_ui_scrollable_content_pos_get(pd->scroller); + + pos.x = pos.x + vpos.x - view.x; + pos.y = pos.y + vpos.y - view.y; + Eina_Position2D max = efl_ui_pan_position_max_get(pd->pan); + + if (direction == EFL_UI_FOCUS_DIRECTION_RIGHT) + { + if (pos.x < max.x) + { + pos.x = MIN(max.x, pos.x + step.w); + efl_ui_scrollable_scroll(obj, pos, EINA_TRUE); + new_obj = focus; + } + } + else if (direction == EFL_UI_FOCUS_DIRECTION_LEFT) + { + if (pos.x > 0) + { + pos.x = MAX(0, pos.x - step.w); + efl_ui_scrollable_scroll(obj, pos, EINA_TRUE); + new_obj = focus; + } + } + else if (direction == EFL_UI_FOCUS_DIRECTION_UP) + { + if (pos.y > 0) + { + pos.y = MAX(0, pos.y - step.h); + efl_ui_scrollable_scroll(obj, pos, EINA_TRUE); + new_obj = focus; + } + } + else if (direction == EFL_UI_FOCUS_DIRECTION_DOWN) + { + if (pos.y < max.y) + { + pos.y = MAX(0, pos.y + step.h); + efl_ui_scrollable_scroll(obj, pos, EINA_TRUE); + new_obj = focus; + } + } + } + else + { + Efl_Model *model; + Eina_Value *vindex; + uint64_t index; + + model = efl_ui_view_model_get(new_obj); + vindex = efl_model_property_get(model, "child.index"); + if (eina_value_uint64_convert(vindex, &index)) + _item_scroll_internal(obj, pd, index, .0, EINA_TRUE); + eina_value_free(vindex); + } + + return new_obj; +} + +#include "efl_ui_collection_view.eo.c" + +#define ITEM_IS_OUTSIDE_VISIBLE(id) id < cpd->start_id || id > cpd->end_id + +static Efl_Ui_Item * +_find_item(Eo *obj EINA_UNUSED, Efl_Ui_Collection_View_Data *pd EINA_UNUSED, Eo *focused_element) +{ + if (!focused_element) return NULL; + + while (focused_element && + efl_key_data_get(focused_element, COLLECTION_VIEW_MANAGED) != COLLECTION_VIEW_MANAGED_YES) + { + focused_element = efl_ui_widget_parent_get(focused_element); + } + + return focused_element; +} + +static inline void +_assert_item_available(Eo *item, int new_id, Efl_Ui_Collection_View_Data *pd) +{ + efl_gfx_entity_visible_set(item, EINA_TRUE); + efl_gfx_entity_geometry_set(item, efl_ui_position_manager_entity_position_single_item(pd->manager, new_id)); +} +EOLIAN static void +_efl_ui_collection_view_focus_manager_efl_ui_focus_manager_manager_focus_set(Eo *obj, Efl_Ui_Collection_View_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *focus) +{ + MY_DATA_GET(pd->collection, cpd); + Efl_Ui_Item *item = NULL; + uint64_t item_id; + + if (focus == efl_ui_focus_manager_root_get(obj)) + { + // Find last item + item_id = efl_model_children_count_get(cpd->model) - 1; + } + else + { + Efl_Model *model; + Eina_Value *vindex; + + item = _find_item(obj, cpd, focus); + if (!item) return ; + + model = efl_ui_view_model_get(item); + vindex = efl_model_property_get(model, "child.index"); + if (!eina_value_uint64_convert(vindex, &item_id)) return; + eina_value_free(vindex); + } + + // If this is NULL then we are before finalize, we cannot serve any sane value here + if (!cpd->manager) return ; + + if (ITEM_IS_OUTSIDE_VISIBLE(item_id)) + { + _assert_item_available(item, item_id, cpd); + } + efl_ui_focus_manager_focus_set(efl_super(obj, EFL_UI_COLLECTION_VIEW_FOCUS_MANAGER_CLASS), focus); +} + +EOLIAN static Efl_Ui_Focus_Object * +_efl_ui_collection_view_focus_manager_efl_ui_focus_manager_request_move(Eo *obj, Efl_Ui_Collection_View_Focus_Manager_Data *pd, Efl_Ui_Focus_Direction direction, Efl_Ui_Focus_Object *child, Eina_Bool logical) +{ + MY_DATA_GET(pd->collection, cpd); + Efl_Ui_Item *new_item, *item; + unsigned int item_id; + + if (!child) + child = efl_ui_focus_manager_focus_get(obj); + + item = _find_item(obj, cpd, child); + + //if this is NULL then we are before finalize, we cannot serve any sane value here + if (!cpd->manager) return NULL; + if (!item) return NULL; + + item_id = efl_ui_item_index_get(item); + + if (ITEM_IS_OUTSIDE_VISIBLE(item_id)) + { + int new_id; + + new_id = efl_ui_position_manager_entity_relative_item(cpd->manager, + efl_ui_item_index_get(item), + direction); + if (new_id == -1) + { + new_item = NULL; + } + else + { +#ifdef VIEWPORT_ENABLE + unsigned int i; + + for (i = 0; i < 3; i++) + { + if (!cpd->viewport[i]) continue; + + if (!((cpd->viewport[i]->offset <= (unsigned int) new_id) && + ((unsigned int) new_id < cpd->viewport[i]->offset + cpd->viewport[i]->count))) + continue; + + new_item = cpd->viewport[i]->items[new_id - cpd->viewport[i]->offset].entity; + // We shouldn't get in a case where the available item is NULL + if (!new_item) break; // Just in case + _assert_item_available(new_item, new_id, cpd); + } +#endif + } + } + else + { + new_item = efl_ui_focus_manager_request_move(efl_super(obj, EFL_UI_COLLECTION_VIEW_FOCUS_MANAGER_CLASS), direction, child, logical); + } + + return new_item; +} + +#include "efl_ui_collection_view_focus_manager.eo.c" diff --git a/src/lib/elementary/efl_ui_collection_view.eo b/src/lib/elementary/efl_ui_collection_view.eo new file mode 100644 index 0000000000..d0efbf347a --- /dev/null +++ b/src/lib/elementary/efl_ui_collection_view.eo @@ -0,0 +1,61 @@ +class @beta Efl.Ui.Collection_View extends Efl.Ui.Layout_Base implements + Efl.Ui.Layout_Orientable, + Efl.Ui.Selectable, + Efl.Ui.Multi_Selectable_Async, + Efl.Ui.Focus.Manager_Sub, + Efl.Ui.Widget_Focus_Manager, + Efl.Ui.Collection_Events + composites Efl.Ui.Scrollable, Efl.Ui.Scrollbar, Efl.Ui.Multi_Selectable_Async +{ + [[This widget displays a list of items in an arrangement controlled by an external @.position_manager + object. By using different @.position_manager objects this widget can show unidimensional lists or + two-dimensional grids of items, for example. + + This class is intended to act as a base for widgets like List_View or Grid_View, + which hide this complexity from the user. + + Items are generated by the @Efl.Ui.Factory defined with .factory.set to match the content of the + @Efl.Model defined with @Efl.Ui.View.model.set. They are dynamically created/destroyed to only have + the one that are necessary to display all the one that are to far out of the viewport will not be + created to lighten the usage for very large list. + + The direction of the arrangement can be controlled through @Efl.Ui.Layout_Orientable.orientation. + + If all items do not fit in the current widget size scrolling facilities are provided. + + Items inside this widget can be selected according to the @Efl.Ui.Multi_Selectable_Async.select_mode + policy, and the selection can be retrieved with @Efl.Ui.Multi_Selectable_Async.selected_iterator_new. + ]] + methods { + @property factory { + [[Define the factory used to create all the items.]] + get {} + set {} + values { + factory: Efl.Ui.Factory; [[The factory.]] + } + } + @property position_manager { + [[Position manager object that handles placement of items.]] + values { + position_manager : Efl.Ui.Position_Manager.Entity @move; [[The objects ownership is passed to the item container.]] + } + } + } + implements { + Efl.Object.constructor; + Efl.Object.invalidate; + + Efl.Ui.Layout_Orientable.orientation { get; set; } + + Efl.Ui.Widget.theme_apply; + + Efl.Ui.Scrollable.match_content { set; } + Efl.Ui.Widget_Focus_Manager.focus_manager_create; + Efl.Ui.Focus.Manager.move; + } + events { + item,realized : Efl.Ui.Item; [[Event triggered when an @Efl.Ui.Item has been provided by the @Efl.Ui.Factory and is about to be used.]] + item,unrealized : Efl.Ui.Item; [[Event triggered when the @Efl.Ui.Collection_View is about to give an @Efl.Ui.Item back to the @Efl.Ui.Factory.]] + } +} diff --git a/src/lib/elementary/efl_ui_collection_view_focus_manager.eo b/src/lib/elementary/efl_ui_collection_view_focus_manager.eo new file mode 100644 index 0000000000..bd4e27727b --- /dev/null +++ b/src/lib/elementary/efl_ui_collection_view_focus_manager.eo @@ -0,0 +1,7 @@ +class @beta Efl.Ui.Collection_View_Focus_Manager extends Efl.Ui.Focus.Manager_Calc { + [[Internal class which implements collection specific behaviour, cannot be used outside of collection]] + implements { + Efl.Ui.Focus.Manager.manager_focus { set; } + Efl.Ui.Focus.Manager.request_move; + } +} diff --git a/src/lib/elementary/efl_ui_item.c b/src/lib/elementary/efl_ui_item.c index b454555497..7cdfe16424 100644 --- a/src/lib/elementary/efl_ui_item.c +++ b/src/lib/elementary/efl_ui_item.c @@ -208,6 +208,29 @@ _efl_ui_item_item_parent_get(const Eo *obj EINA_UNUSED, Efl_Ui_Item_Data *pd) return pd->parent; } +EOLIAN static void +_efl_ui_item_calc_locked_set(Eo *obj EINA_UNUSED, Efl_Ui_Item_Data *pd, Eina_Bool locked) +{ + pd->locked = !!locked; +} + +EOLIAN static Eina_Bool +_efl_ui_item_calc_locked_get(const Eo *obj EINA_UNUSED, Efl_Ui_Item_Data *pd) +{ + return pd->locked; +} + +EOLIAN static void +_efl_ui_item_efl_canvas_group_group_need_recalculate_set(Eo *obj, Efl_Ui_Item_Data *pd EINA_UNUSED, Eina_Bool value) +{ + // Prevent recalc when the item are stored in the cache + // As due to async behavior, we can still have text updated from future that just finished after + // we have left the releasing stage of factories. This is the simplest way to prevent those later + // update. + if (pd->locked) return; + efl_canvas_group_need_recalculate_set(efl_super(obj, EFL_UI_ITEM_CLASS), value); +} + ELM_WIDGET_KEY_DOWN_DEFAULT_IMPLEMENT(efl_ui_item, Efl_Ui_Item_Data) #include "efl_ui_item.eo.c" diff --git a/src/lib/elementary/efl_ui_item.eo b/src/lib/elementary/efl_ui_item.eo index 0e841f5df9..9f05b1ea1f 100644 --- a/src/lib/elementary/efl_ui_item.eo +++ b/src/lib/elementary/efl_ui_item.eo @@ -54,6 +54,17 @@ abstract Efl.Ui.Item extends Efl.Ui.Layout_Base implements Efl.Ui.Selectable, Ef parent : Efl.Ui.Item; } } + @property calc_locked { + [[If the item has its calc locked it will not trigger @Efl.Canvas.Group.group_need_recalculate.set done. + + This is done automatically by @Efl.Ui.Widget_Factory, but you can use this information to meaningfully set the hint when items are not @.calc_locked. + ]] + set {} + get {} + values { + locked: bool; [[If set to $true, no more @Efl.Canvas.Group.group_need_recalculate.set]] + } + } } implements { Efl.Object.constructor; @@ -61,5 +72,6 @@ abstract Efl.Ui.Item extends Efl.Ui.Layout_Base implements Efl.Ui.Selectable, Ef Efl.Object.destructor; Efl.Ui.Selectable.selected {get; set;} Efl.Ui.Widget.widget_input_event_handler; + Efl.Canvas.Group.group_need_recalculate { set; } } } diff --git a/src/lib/elementary/efl_ui_item_private.h b/src/lib/elementary/efl_ui_item_private.h index cd8300b6a2..5c1ef268bf 100644 --- a/src/lib/elementary/efl_ui_item_private.h +++ b/src/lib/elementary/efl_ui_item_private.h @@ -11,6 +11,7 @@ typedef struct _Efl_Ui_Item_Data // Boolean Data Eina_Bool selected : 1; /* State for item selected */ + Eina_Bool locked : 1; } Efl_Ui_Item_Data; diff --git a/src/lib/elementary/efl_ui_position_manager_list.c b/src/lib/elementary/efl_ui_position_manager_list.c index cee6730137..0adb498322 100644 --- a/src/lib/elementary/efl_ui_position_manager_list.c +++ b/src/lib/elementary/efl_ui_position_manager_list.c @@ -36,6 +36,14 @@ typedef struct { * Every other walk of the items is at max the maximum number of items you get into the maximum distance between the average item size and a actaul item size. */ +static void +cache_invalidate(Eo *obj EINA_UNUSED, Efl_Ui_Position_Manager_List_Data *pd) +{ + if (pd->size_cache) + free(pd->size_cache); + pd->size_cache = NULL; +} + static void cache_require(Eo *obj EINA_UNUSED, Efl_Ui_Position_Manager_List_Data *pd) { @@ -82,19 +90,15 @@ cache_require(Eo *obj EINA_UNUSED, Efl_Ui_Position_Manager_List_Data *pd) } pd->size_cache[i + 1] = pd->size_cache[i] + step; pd->maximum_min_size = MAX(pd->maximum_min_size, min); + /* no point iterating further if size calc can't be done yet */ + //if ((!i) && (!pd->maximum_min_size)) break; } pd->average_item_size = pd->size_cache[pd->size]/pd->size; + if ((!pd->average_item_size) && (!pd->maximum_min_size)) + cache_invalidate(obj, pd); } -static void -cache_invalidate(Eo *obj EINA_UNUSED, Efl_Ui_Position_Manager_List_Data *pd) -{ - if (pd->size_cache) - free(pd->size_cache); - pd->size_cache = NULL; -} - -static inline int +static int cache_access(Eo *obj EINA_UNUSED, Efl_Ui_Position_Manager_List_Data *pd, unsigned int idx) { EINA_SAFETY_ON_FALSE_RETURN_VAL(idx <= pd->size, 0); @@ -105,7 +109,12 @@ static void recalc_absolut_size(Eo *obj, Efl_Ui_Position_Manager_List_Data *pd) { Eina_Size2D min_size = EINA_SIZE2D(-1, -1); + Eina_Size2D pabs_size = pd->abs_size; + int pmin_size = pd->maximum_min_size; + cache_require(obj, pd); + /* deferred */ + if (!pd->size_cache) return; pd->abs_size = pd->viewport.size; @@ -116,8 +125,8 @@ recalc_absolut_size(Eo *obj, Efl_Ui_Position_Manager_List_Data *pd) else pd->abs_size.w = MAX(cache_access(obj, pd, pd->size), pd->abs_size.w); } - - efl_event_callback_call(obj, EFL_UI_POSITION_MANAGER_ENTITY_EVENT_CONTENT_SIZE_CHANGED, &pd->abs_size); + if ((pabs_size.w != pd->abs_size.w) || (pabs_size.h != pd->abs_size.h)) + efl_event_callback_call(obj, EFL_UI_POSITION_MANAGER_ENTITY_EVENT_CONTENT_SIZE_CHANGED, &pd->abs_size); if (pd->dir == EFL_UI_LAYOUT_ORIENTATION_VERTICAL) { @@ -127,8 +136,8 @@ recalc_absolut_size(Eo *obj, Efl_Ui_Position_Manager_List_Data *pd) { min_size.h = pd->maximum_min_size; } - - efl_event_callback_call(obj, EFL_UI_POSITION_MANAGER_ENTITY_EVENT_CONTENT_MIN_SIZE_CHANGED, &min_size); + if ((pd->maximum_min_size > 0) && (pmin_size > 0) && (pd->maximum_min_size != pmin_size)) + efl_event_callback_call(obj, EFL_UI_POSITION_MANAGER_ENTITY_EVENT_CONTENT_MIN_SIZE_CHANGED, &min_size); } static inline Vis_Segment diff --git a/src/lib/elementary/efl_ui_widget.c b/src/lib/elementary/efl_ui_widget.c index 6835688c52..d98ee78fa1 100644 --- a/src/lib/elementary/efl_ui_widget.c +++ b/src/lib/elementary/efl_ui_widget.c @@ -5816,8 +5816,6 @@ _efl_ui_property_bind_get(Eo *obj, Efl_Ui_Widget_Data *pd, Efl_Ui_Property_Bound value = efl_model_property_get(pd->properties.model, prop->property); target = prop->part ? efl_part(obj, prop->part) : obj; - fprintf(stderr, "setting: %s for %s from %s\n", - eina_value_to_string(value), prop->property, efl_debug_name_get(pd->properties.model)); err = efl_property_reflection_set(target, prop->key, eina_value_reference_copy(value)); eina_value_free(value); diff --git a/src/lib/elementary/efl_ui_widget_factory.c b/src/lib/elementary/efl_ui_widget_factory.c index 0be3e302d2..b1c339d4bc 100644 --- a/src/lib/elementary/efl_ui_widget_factory.c +++ b/src/lib/elementary/efl_ui_widget_factory.c @@ -95,6 +95,9 @@ _efl_ui_widget_factory_constructing(void *data EINA_UNUSED, const Efl_Event *ev) model = efl_ui_view_model_get(ui_view); + // Enable recalculate in case we do not know the size of the item + efl_canvas_group_need_recalculate_set(ui_view, EINA_TRUE); + // Fetch min size from model if available to avoid recalculcating it width = efl_model_property_get(model, "self.width"); height = efl_model_property_get(model, "self.height"); @@ -106,12 +109,14 @@ _efl_ui_widget_factory_constructing(void *data EINA_UNUSED, const Efl_Event *ev) if (!eina_value_int_convert(width, &s.w)) s.w = 0; if (!eina_value_int_convert(height, &s.h)) s.h = 0; - /* efl_event_freeze(ui_view); */ - efl_key_data_set(ui_view, "efl.ui.widget.factory.size_set", (void*)EINA_TRUE); efl_gfx_hint_size_min_set(ui_view, s); + efl_canvas_group_need_recalculate_set(ui_view, EINA_FALSE); + if (efl_isa(ui_view, EFL_UI_ITEM_CLASS)) efl_ui_item_calc_locked_set(ui_view, EINA_TRUE); } eina_value_free(width); eina_value_free(height); + + efl_key_data_set(ui_view, "efl.ui.widget.factory.size_check", (void*)EINA_TRUE); } @@ -121,7 +126,7 @@ _efl_ui_widget_factory_building(void *data, const Efl_Event *ev) Efl_Gfx_Entity *ui_view = ev->info; Efl_Ui_Widget_Factory_Data *pd = data; const Efl_Model *model; - Eina_Value *property, *width, *height; + Eina_Value *property; Efl_Ui_Bind_Part_Data *bpd; Eina_Iterator *it; char *style; @@ -146,22 +151,6 @@ _efl_ui_widget_factory_building(void *data, const Efl_Event *ev) } eina_iterator_free(it); - // Fetch min size from model if available to avoid recalculcating it - width = efl_model_property_get(model, "self.width"); - height = efl_model_property_get(model, "self.height"); - if (eina_value_type_get(width) != EINA_VALUE_TYPE_ERROR && - eina_value_type_get(height) != EINA_VALUE_TYPE_ERROR) - { - Eina_Size2D s; - - if (!eina_value_int_convert(width, &s.w)) s.w = 0; - if (!eina_value_int_convert(height, &s.h)) s.h = 0; - - efl_gfx_hint_size_min_set(ui_view, s); - } - eina_value_free(width); - eina_value_free(height); - // As we have already waited for the property to be ready, we should get the right style now if (!pd->style) return ; @@ -173,6 +162,8 @@ _efl_ui_widget_factory_building(void *data, const Efl_Event *ev) free(style); eina_value_free(property); + + efl_key_data_set(ui_view, "efl.ui.widget.factory.cached", NULL); } static void @@ -185,6 +176,7 @@ _efl_ui_widget_factory_releasing(void *data, const Efl_Event *ev) efl_key_data_set(ui_view, "efl.ui.widget.factory.size_set", NULL); efl_key_data_set(ui_view, "efl.ui.widget.factory.size_check", NULL); + if (efl_isa(ui_view, EFL_UI_ITEM_CLASS)) efl_ui_item_calc_locked_set(ui_view, EINA_TRUE); // Bind all property before the object is finalize it = eina_hash_iterator_data_new(pd->parts); @@ -200,6 +192,9 @@ _efl_ui_widget_factory_releasing(void *data, const Efl_Event *ev) eina_iterator_free(it); efl_ui_view_model_set(ui_view, NULL); + + // Prevent any recalc to happen when an object is in the cache or during shutdown of the object + efl_canvas_group_need_recalculate_set(ui_view, EINA_FALSE); } EFL_CALLBACKS_ARRAY_DEFINE(item_callbacks, diff --git a/src/lib/elementary/elm_priv.h b/src/lib/elementary/elm_priv.h index f1b959b69f..f7223ae6b4 100644 --- a/src/lib/elementary/elm_priv.h +++ b/src/lib/elementary/elm_priv.h @@ -165,6 +165,7 @@ # include "efl_ui_focus_parent_provider_standard.eo.h" # include "efl_ui_selection_manager.eo.h" # include "efl_datetime_manager.eo.h" + extern const char *_efl_model_property_itemw; extern const char *_efl_model_property_itemh; extern const char *_efl_model_property_selfw; diff --git a/src/lib/elementary/meson.build b/src/lib/elementary/meson.build index 5ef2b05be1..2e48c5a39a 100644 --- a/src/lib/elementary/meson.build +++ b/src/lib/elementary/meson.build @@ -186,6 +186,8 @@ pub_eo_files = [ 'efl_ui_tab_bar_default_item.eo', 'efl_ui_select_model.eo', 'efl_ui_view_model.eo', + 'efl_ui_collection_view.eo', + 'efl_ui_collection_view_focus_manager.eo', ] foreach eo_file : pub_eo_files @@ -947,6 +949,7 @@ elementary_src = [ 'efl_ui_tab_bar_default_item.c', 'efl_ui_select_model.c', 'efl_ui_view_model.c', + 'efl_ui_collection_view.c', ] elementary_deps = [emile, eo, efl, edje, ethumb, ethumb_client, emotion, ecore_imf, ecore_con, eldbus, efreet, efreet_mime, efreet_trash, eio, atspi, dl, intl]