summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCedric BAIL <cedric.bail@free.fr>2019-07-05 14:03:13 -0700
committerMarcel Hollerbach <mail@marcel-hollerbach.de>2019-09-10 10:08:07 +0200
commitbfb6bc40b21c60a536349d65d4b7e9ab327fb173 (patch)
treee40774a8216c030e6e023535078208737e369bc2
parentaabcf28a91944c8b8bbe7184cbe417a55845543a (diff)
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.
-rw-r--r--src/lib/elementary/Efl_Ui.h6
-rw-r--r--src/lib/elementary/efl_ui_collection_view.c1802
-rw-r--r--src/lib/elementary/efl_ui_collection_view.eo58
-rw-r--r--src/lib/elementary/efl_ui_collection_view_focus_manager.eo7
-rw-r--r--src/lib/elementary/meson.build3
5 files changed, 1876 insertions, 0 deletions
diff --git a/src/lib/elementary/Efl_Ui.h b/src/lib/elementary/Efl_Ui.h
index 9db87890f9..29243dec06 100644
--- a/src/lib/elementary/Efl_Ui.h
+++ b/src/lib/elementary/Efl_Ui.h
@@ -154,6 +154,7 @@ EAPI void efl_ui_focus_relation_free(Efl_Ui_Focus_Relations *rel);
154# include <efl_ui_focus_manager_calc.eo.h> 154# include <efl_ui_focus_manager_calc.eo.h>
155# include <efl_ui_focus_manager_sub.eo.h> 155# include <efl_ui_focus_manager_sub.eo.h>
156# include <efl_ui_focus_manager_root_focus.eo.h> 156# include <efl_ui_focus_manager_root_focus.eo.h>
157# include <efl_ui_widget_focus_manager.eo.h>
157# include <efl_ui_focus_util.eo.h> 158# include <efl_ui_focus_util.eo.h>
158# include <efl_ui_textpath.eo.h> 159# include <efl_ui_textpath.eo.h>
159# include <efl_ui_l10n.eo.h> 160# include <efl_ui_l10n.eo.h>
@@ -168,6 +169,9 @@ EAPI void efl_ui_focus_relation_free(Efl_Ui_Focus_Relations *rel);
168# include <efl_access_editable_text.eo.h> 169# include <efl_access_editable_text.eo.h>
169# include <efl_access_selection.eo.h> 170# include <efl_access_selection.eo.h>
170# include <efl_access_value.eo.h> 171# include <efl_access_value.eo.h>
172# include <efl_access_selection.eo.h>
173# include <efl_access_object.eo.h>
174# include <efl_access_widget_action.eo.h>
171 175
172# include <efl_ui_theme.eo.h> 176# include <efl_ui_theme.eo.h>
173# include <efl_config_global.eo.h> 177# include <efl_config_global.eo.h>
@@ -278,6 +282,8 @@ typedef Eo Efl_Ui_Spotlight_Indicator;
278# include <efl_ui_tab_pager.eo.h> 282# include <efl_ui_tab_pager.eo.h>
279# include <efl_ui_select_model.eo.h> 283# include <efl_ui_select_model.eo.h>
280# include <efl_ui_view_model.eo.h> 284# include <efl_ui_view_model.eo.h>
285# include <efl_ui_collection_view.eo.h>
286
281/** 287/**
282 * Initialize Elementary 288 * Initialize Elementary
283 * 289 *
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..731a4e2431
--- /dev/null
+++ b/src/lib/elementary/efl_ui_collection_view.c
@@ -0,0 +1,1802 @@
1#ifdef HAVE_CONFIG_H
2# include "elementary_config.h"
3#endif
4
5#define ELM_LAYOUT_PROTECTED
6#define EFL_UI_SCROLL_MANAGER_PROTECTED
7#define EFL_UI_SCROLLBAR_PROTECTED
8#define EFL_UI_WIDGET_FOCUS_MANAGER_PROTECTED
9
10#include <Efl_Ui.h>
11#include <Elementary.h>
12#include "elm_widget.h"
13#include "elm_priv.h"
14
15#include "efl_ui_collection_view_focus_manager.eo.h"
16
17typedef struct _Efl_Ui_Collection_View_Data Efl_Ui_Collection_View_Data;
18typedef struct _Efl_Ui_Collection_Viewport Efl_Ui_Collection_Viewport;
19typedef struct _Efl_Ui_Collection_View_Focus_Manager_Data Efl_Ui_Collection_View_Focus_Manager_Data;
20typedef struct _Efl_Ui_Collection_Item Efl_Ui_Collection_Item;
21typedef struct _Efl_Ui_Collection_Item_Lookup Efl_Ui_Collection_Item_Lookup;
22typedef struct _Efl_Ui_Collection_Request Efl_Ui_Collection_Request;
23
24struct _Efl_Ui_Collection_Item
25{
26 Efl_Gfx_Entity *entity;
27 Efl_Model *model;
28};
29
30struct _Efl_Ui_Collection_Item_Lookup
31{
32 EINA_RBTREE;
33
34 uint64_t index;
35 Efl_Ui_Collection_Item item;
36};
37
38struct _Efl_Ui_Collection_Viewport
39{
40 Efl_Ui_Collection_Item *items;
41
42 uint64_t offset;
43 uint16_t count;
44};
45
46struct _Efl_Ui_Collection_Request
47{
48 Eina_Future *f;
49
50 uint64_t offset;
51 uint64_t length;
52
53 Eina_Bool model_requested : 1;
54 Eina_Bool entity_requested : 1;
55};
56
57struct _Efl_Ui_Collection_View_Data
58{
59 Efl_Ui_Factory *factory;
60 Efl_Ui_Position_Manager_Entity *manager;
61 Efl_Ui_Scroll_Manager *scroller;
62 Efl_Ui_Pan *pan;
63 Efl_Gfx_Entity *sizer;
64 Efl_Model *model;
65
66 Efl_Ui_Collection_Viewport *viewport[3];
67 Eina_Rbtree *cache;
68
69 Eina_List *requests; // Array of Efl_Ui_Collection_Request in progress
70
71 uint64_t start_id;
72 uint64_t end_id;
73
74 Eina_Size2D last_base;
75 Eina_Size2D content_min_size;
76
77 Efl_Ui_Layout_Orientation direction;
78 Efl_Ui_Select_Mode mode;
79
80 struct {
81 Eina_Bool w : 1;
82 Eina_Bool h : 1;
83 } match_content;
84};
85
86struct _Efl_Ui_Collection_View_Focus_Manager_Data
87{
88 Efl_Ui_Collection_View *collection;
89};
90
91static const char *COLLECTION_VIEW_MANAGED = "_collection_view.managed";
92static const char *COLLECTION_VIEW_MANAGED_YES = "yes";
93
94#define MY_CLASS EFL_UI_COLLECTION_VIEW_CLASS
95
96#define MY_DATA_GET(obj, pd) \
97 Efl_Ui_Collection_View_Data *pd = efl_data_scope_get(obj, MY_CLASS);
98
99static int
100_cache_tree_lookup(const Eina_Rbtree *node, const void *key,
101 int length EINA_UNUSED, void *data EINA_UNUSED)
102{
103 const Efl_Ui_Collection_Item_Lookup *n = (Efl_Ui_Collection_Item_Lookup *)node;
104 const uint64_t *index = key;
105
106 return n->index - *index;
107}
108
109static Eina_Rbtree_Direction
110_cache_tree_cmp(const Eina_Rbtree *left, const Eina_Rbtree *right, void *data EINA_UNUSED)
111{
112 Efl_Ui_Collection_Item_Lookup *l = (Efl_Ui_Collection_Item_Lookup *)left;
113 Efl_Ui_Collection_Item_Lookup *r = (Efl_Ui_Collection_Item_Lookup *)right;
114
115 return l->index < r->index ? EINA_RBTREE_LEFT : EINA_RBTREE_RIGHT;
116}
117
118static void
119_item_cleanup(Efl_Ui_Factory *factory, Efl_Ui_Collection_Item *item)
120{
121 Efl_Gfx_Entity *entity;
122
123 efl_replace(&item->model, NULL);
124 entity = item->entity;
125 if (!entity) return ;
126
127 efl_ui_view_model_set(entity, NULL);
128 efl_replace(&item->entity, NULL);
129 efl_ui_factory_release(factory, entity);
130}
131
132static void
133_cache_item_free(Eina_Rbtree *node, void *data)
134{
135 Efl_Ui_Collection_Item_Lookup *n = (void*) node;
136 Efl_Ui_Collection_View_Data *pd = data;
137
138 _item_cleanup(pd->factory, &n->item);
139 free(n);
140}
141
142static void
143_cache_cleanup(Efl_Ui_Collection_View_Data *pd)
144{
145 eina_rbtree_delete(pd->cache, _cache_item_free, pd);
146}
147
148static void
149_all_cleanup(Efl_Ui_Collection_View_Data *pd)
150{
151 Efl_Ui_Collection_Request *request;
152 Eina_List *l, *ll;
153 unsigned int i;
154
155 _cache_cleanup(pd);
156 for (i = 0; i < 3; i++)
157 {
158 unsigned int j;
159
160 if (!pd->viewport[i]) continue;
161
162 for (j = 0; j < pd->viewport[i]->count; j++)
163 _item_cleanup(pd->factory, &(pd->viewport[i]->items[j]));
164 }
165
166 EINA_LIST_FOREACH_SAFE(pd->requests, l, ll, request)
167 eina_future_cancel(request->f);
168}
169
170static inline Eina_Bool
171_size_from_model(Efl_Model *model, Eina_Size2D *r, const char *width, const char *height)
172{
173 Eina_Value *vw, *vh;
174 Eina_Bool success = EINA_FALSE;
175
176 vw = efl_model_property_get(model, width);
177 vh = efl_model_property_get(model, height);
178
179 if (eina_value_type_get(vw) == EINA_VALUE_TYPE_ERROR ||
180 eina_value_type_get(vh) == EINA_VALUE_TYPE_ERROR)
181 goto on_error;
182
183 if (!eina_value_int_convert(vw, &(r->w))) r->w = 0;
184 if (!eina_value_int_convert(vh, &(r->h))) r->h = 0;
185
186 success = EINA_TRUE;
187
188 on_error:
189 eina_value_free(vw);
190 eina_value_free(vh);
191
192 return success;
193}
194
195static inline void
196_size_to_model(Efl_Model *model, Eina_Size2D state)
197{
198 Eina_Value vw, vh;
199
200 vw = eina_value_int_init(state.w);
201 vh = eina_value_int_init(state.h);
202
203 efl_model_property_set(model, "self.width", &vw);
204 efl_model_property_set(model, "selft.height", &vh);
205
206 eina_value_flush(&vw);
207 eina_value_flush(&vh);
208}
209
210#define ITEM_BASE_SIZE_FROM_MODEL(Model, Size) _size_from_model(Model, &Size, "item.width", "item.height")
211#define ITEM_SIZE_FROM_MODEL(Model, Size) _size_from_model(Model, &Size, "self.width", "self.height")
212
213static Eina_List *
214_request_add(Eina_List *requests, Efl_Ui_Collection_Request **request,
215 uint64_t index, Eina_Bool need_entity)
216{
217 if (!(*request)) goto create;
218
219 if ((*request)->offset + (*request)->length == index)
220 {
221 if (need_entity) (*request)->entity_requested = EINA_TRUE;
222 (*request)->length += 1;
223 return requests;
224 }
225
226 requests = eina_list_append(requests, request);
227
228 create:
229 *request = calloc(1, sizeof (Efl_Ui_Collection_Request));
230 if (!(*request)) return requests;
231 (*request)->offset = index;
232 (*request)->length = 1;
233 // At this point, we rely on the model caching ability to avoid recreating model
234 (*request)->model_requested = EINA_TRUE;
235 (*request)->entity_requested = !!need_entity;
236
237 return requests;
238}
239
240static Eina_Value
241_model_fetched_cb(Eo *obj, void *data, const Eina_Value v)
242{
243 MY_DATA_GET(obj, pd);
244 Efl_Ui_Collection_Request *request = data;
245 Efl_Model *child;
246 unsigned int i, len;
247
248 EINA_VALUE_ARRAY_FOREACH(&v, len, i, child)
249 {
250 Efl_Ui_Collection_Item_Lookup *insert;
251 unsigned int v;
252
253 for (v = 0; v < 3; ++v)
254 {
255 if (!pd->viewport[v]) continue;
256
257 if ((pd->viewport[v]->offset <= request->offset + i) &&
258 (request->offset + i < pd->viewport[v]->offset + pd->viewport[v]->count))
259 {
260 uint64_t index = request->offset + i - pd->viewport[v]->offset;
261
262 efl_replace(&pd->viewport[v]->items[index].model, child);
263 child = NULL;
264 break;
265 }
266 }
267
268 // When requesting a model, it should not be in the cache prior to the request
269 if (!child) continue;
270
271 insert = calloc(1, sizeof (Efl_Ui_Collection_Item_Lookup));
272 if (!insert) continue;
273
274 insert->index = request->offset + i;
275 insert->item.model = efl_ref(child);
276
277 pd->cache = eina_rbtree_inline_insert(pd->cache, EINA_RBTREE_GET(insert), _cache_tree_cmp, NULL);
278 }
279
280 return v;
281}
282
283static void
284_model_free_cb(Eo *o, void *data, const Eina_Future *dead_future EINA_UNUSED)
285{
286 MY_DATA_GET(o, pd);
287 Efl_Ui_Collection_Request *request = data;
288
289 if (request->entity_requested) return;
290 pd->requests = eina_list_remove(pd->requests, request);
291 free(request);
292}
293
294static Eina_Value
295_entity_fetch_cb(Eo *obj, void *data EINA_UNUSED, const Eina_Value v)
296{
297 MY_DATA_GET(obj, pd);
298 Efl_Model *child;
299 Eina_Future *r;
300 Eina_Array tmp;
301 unsigned int i, len;
302
303 eina_array_step_set(&tmp, sizeof (Eina_Array), 4);
304
305 EINA_VALUE_ARRAY_FOREACH(&v, len, i, child)
306 {
307 eina_array_push(&tmp, child);
308 }
309
310 r = efl_ui_view_factory_create_with_event(pd->factory, eina_array_iterator_new(&tmp), obj);
311
312 eina_array_flush(&tmp);
313
314 return eina_future_as_value(r);
315}
316
317static inline Eina_Bool
318_entity_propagate(Efl_Model *model, Efl_Gfx_Entity *entity)
319{
320 Eina_Size2D item_size;
321
322 if (ITEM_SIZE_FROM_MODEL(model, item_size)) return EINA_FALSE;
323
324 item_size = efl_gfx_hint_size_min_get(entity);
325 _size_to_model(model, item_size);
326 return EINA_TRUE;
327}
328
329static Eina_Value
330_entity_fetched_cb(Eo *obj, void *data, const Eina_Value v)
331{
332 MY_DATA_GET(obj, pd);
333 Efl_Ui_Collection_Request *request = data;
334 Efl_Gfx_Entity *child;
335 unsigned int i, len;
336 uint64_t updated_start_id;
337 Eina_Bool updated = EINA_FALSE;
338
339 EINA_VALUE_ARRAY_FOREACH(&v, len, i, child)
340 {
341 Efl_Ui_Collection_Item_Lookup *lookup;
342 uint64_t search_index;
343 unsigned int v;
344
345 efl_key_data_set(child, COLLECTION_VIEW_MANAGED, COLLECTION_VIEW_MANAGED_YES);
346
347 for (v = 0; v < 3; ++v)
348 {
349 if (!pd->viewport[v]) continue;
350
351 if ((pd->viewport[v]->offset <= request->offset + i) &&
352 (request->offset + i < pd->viewport[v]->offset + pd->viewport[v]->count))
353 {
354 uint64_t index = request->offset + i - pd->viewport[v]->offset;
355
356 efl_replace(&pd->viewport[v]->items[index].entity, child);
357 if (_entity_propagate(pd->viewport[v]->items[index].model, child))
358 {
359 if (!updated)
360 {
361 updated = EINA_TRUE;
362 updated_start_id = index;
363 }
364 }
365 else
366 {
367 if (updated)
368 {
369 efl_ui_position_manager_entity_item_size_changed(pd->manager,
370 updated_start_id,
371 index - 1);
372 updated = EINA_FALSE;
373 }
374 }
375 child = NULL;
376 break;
377 }
378 }
379
380 // When requesting an entity, the model should already be in the cache
381 if (!child) continue;
382
383 search_index = request->offset + i;
384
385 lookup = (void*) eina_rbtree_inline_lookup(pd->cache, &search_index,
386 sizeof (search_index), _cache_tree_lookup,
387 NULL);
388
389 if (!lookup) continue;
390
391 lookup->item.entity = efl_ref(child);
392
393 if (_entity_propagate(lookup->item.model, child))
394 {
395 if (!updated)
396 {
397 updated = EINA_TRUE;
398 updated_start_id = search_index;
399 }
400 }
401 else
402 {
403 if (updated)
404 {
405 efl_ui_position_manager_entity_item_size_changed(pd->manager,
406 updated_start_id,
407 search_index - 1);
408 updated = EINA_FALSE;
409 }
410 }
411 }
412
413 return v;
414}
415
416static void
417_entity_free_cb(Eo *o, void *data, const Eina_Future *dead_future EINA_UNUSED)
418{
419 MY_DATA_GET(o, pd);
420 Efl_Ui_Collection_Request *request = data;
421
422 pd->requests = eina_list_remove(pd->requests, request);
423 free(request);
424}
425
426static Eina_List *
427_cache_size_fetch(Eina_List *requests, Efl_Ui_Collection_Request **request,
428 Efl_Ui_Collection_View_Data *pd,
429 uint64_t search_index,
430 Efl_Ui_Position_Manager_Batch_Size_Access *target,
431 Eina_Size2D item_base)
432{
433 Efl_Ui_Collection_Item_Lookup *lookup;
434 Efl_Model *model;
435 Eina_Size2D item_size;
436
437 if (!pd->cache) goto not_found;
438
439 lookup = (void*) eina_rbtree_inline_lookup(pd->cache, &search_index,
440 sizeof (search_index), _cache_tree_lookup,
441 NULL);
442 if (!lookup) goto not_found;
443
444 // In the cache we should always have model, so no need to check for it
445 model = lookup->item.model;
446
447 // If we do not know the size
448 if (!ITEM_SIZE_FROM_MODEL(model, item_size))
449 {
450 // But can calculate it now
451 if (!lookup->item.entity) goto not_found;
452
453 item_size = efl_gfx_hint_size_min_get(lookup->item.entity);
454 _size_to_model(model, item_size);
455 }
456
457 target->size = item_size;
458 target->group = EFL_UI_POSITION_MANAGER_BATCH_GROUP_STATE_NO_GROUP;
459
460 return requests;
461
462 not_found:
463 requests = _request_add(requests, request, search_index, EINA_FALSE);
464
465 target->size = item_base;
466 target->group = EFL_UI_POSITION_MANAGER_BATCH_GROUP_STATE_NO_GROUP;
467
468 return requests;
469}
470
471static Eina_List *
472_cache_entity_fetch(Eina_List *requests, Efl_Ui_Collection_Request **request,
473 Efl_Ui_Collection_View_Data *pd,
474 uint64_t search_index,
475 Efl_Ui_Position_Manager_Batch_Entity_Access *target)
476{
477 Efl_Ui_Collection_Item_Lookup *lookup;
478
479 if (!pd->cache) goto not_found;
480
481 lookup = (void*) eina_rbtree_inline_lookup(pd->cache, &search_index,
482 sizeof (search_index), _cache_tree_lookup,
483 NULL);
484 if (!lookup) goto not_found;
485 if (!lookup->item.entity) goto not_found;
486
487 target->entity = lookup->item.entity;
488 target->group = EFL_UI_POSITION_MANAGER_BATCH_GROUP_STATE_NO_GROUP;
489
490 return requests;
491
492 not_found:
493 requests = _request_add(requests, request, search_index, EINA_TRUE);
494
495 target->entity = NULL;
496 target->group = EFL_UI_POSITION_MANAGER_BATCH_GROUP_STATE_NO_GROUP;
497
498 return requests;
499}
500
501static void
502_entity_request(Efl_Ui_Collection_View *obj, Efl_Ui_Collection_Request *request)
503{
504 request->f = efl_future_then(obj, request->f,
505 .success_type = EINA_VALUE_TYPE_ARRAY,
506 .success = _entity_fetch_cb);
507 request->f = efl_future_then(obj, request->f,
508 .success_type = EINA_VALUE_TYPE_ARRAY,
509 .success = _entity_fetched_cb,
510 .data = request,
511 .free = _entity_free_cb);
512}
513
514static inline void
515_entity_inflight_request(Efl_Ui_Collection_View *obj,
516 Efl_Ui_Collection_Request *request,
517 Efl_Ui_Collection_Request *inflight)
518{
519 if (request->entity_requested == EINA_FALSE) return ;
520 if (request->entity_requested == inflight->entity_requested) return ;
521
522 _entity_request(obj, inflight);
523 inflight->entity_requested = EINA_TRUE;
524}
525
526static Eina_List *
527_batch_request_flush(Eina_List *requests,
528 Efl_Ui_Collection_View *obj,
529 Efl_Ui_Collection_View_Data *pd)
530{
531 Efl_Ui_Collection_Request *request;
532
533 EINA_LIST_FREE(requests, request)
534 {
535 // Check request intersection with all pending request
536 Efl_Ui_Collection_Request *inflight;
537 Efl_Model *model;
538 Eina_List *l;
539
540 EINA_LIST_FOREACH(pd->requests, l, inflight)
541 {
542 uint64_t istart = inflight->offset;
543 uint64_t iend = inflight->offset + inflight->length;
544 uint64_t rstart = request->offset;
545 uint64_t rend = request->offset + request->length;
546
547 // Way before
548 if (rend < istart) continue;
549 // Way after
550 if (rstart >= iend) continue;
551
552 // request included in current inflight request
553 if (rstart >= istart && rend < iend)
554 {
555 _entity_inflight_request(obj, request, inflight);
556
557 // In this case no need to start a request
558 free(request);
559 request = NULL;
560 break;
561 }
562
563 // request overflow left and right
564 if (rstart < istart && iend < rend)
565 {
566 // Remove the center portion of the request by emitting a new one
567 Efl_Ui_Collection_Request *rn;
568
569 rn = calloc(1, sizeof (Efl_Ui_Collection_Request));
570 if (!rn) break;
571
572 rn->offset = iend;
573 rn->length = rend - iend;
574 rn->model_requested = request->model_requested;
575 rn->entity_requested = request->entity_requested;
576
577 requests = eina_list_append(requests, rn);
578
579 request->length = istart - rstart;
580 _entity_inflight_request(obj, request, inflight);
581
582 continue;
583 }
584
585 // request overflow left
586 if (rstart < istart && iend > istart && rend < iend)
587 {
588 request->length = istart - rstart;
589 _entity_inflight_request(obj, request, inflight);
590 continue;
591 }
592
593 // request overflow right
594 if (rstart >= istart && rstart < rend && iend < rend)
595 {
596 request->offset = iend;
597 request->length = rend - iend;
598 _entity_inflight_request(obj, request, inflight);
599 continue;
600 }
601 }
602
603 if (!request) continue;
604
605 model = pd->model;
606 // Are we ready yet
607 if (!model)
608 {
609 free(request);
610 continue;
611 }
612 // Is the request inside the limit of the model?
613 if (request->offset >= efl_model_children_count_get(model))
614 {
615 free(request);
616 continue;
617 }
618 // Is its limit outside the model limit?
619 if (request->offset + request->length >= efl_model_children_count_get(model))
620 {
621 request->length = efl_model_children_count_get(model) - request->offset;
622 }
623
624 // We now have a request, time to trigger a fetch
625 // We assume here that we are always fetching the model (model_requested must be true)
626 if (!request->model_requested)
627 {
628 ERR("Someone forgot to set model_requested for %lu to %lu.",
629 request->offset, request->offset + request->length);
630 request->model_requested = EINA_TRUE;
631 }
632 request->f = efl_model_children_slice_get(model, request->offset, request->length);
633 request->f = efl_future_then(obj, request->f,
634 .success = _model_fetched_cb,
635 .data = request,
636 .free = _model_free_cb);
637
638 if (request->entity_requested)
639 _entity_request(obj, request);
640
641 pd->requests = eina_list_append(pd->requests, request);
642 }
643
644 return NULL;
645}
646
647static Efl_Ui_Position_Manager_Batch_Result
648_batch_size_cb(void *data, int start_id, Eina_Rw_Slice memory)
649{
650 MY_DATA_GET(data, pd);
651 Efl_Ui_Position_Manager_Batch_Size_Access *sizes;
652 Efl_Ui_Collection_Request *request = NULL;
653 Efl_Ui_Position_Manager_Batch_Result result = {-1, 0};
654 Efl_Model *parent;
655 Eina_List *requests = NULL;
656 Eina_Size2D item_base;
657 unsigned int i, count, limit;
658 unsigned int idx = 0;
659
660 // get the approximate value from the tree node
661 parent = pd->model;
662 if (!ITEM_BASE_SIZE_FROM_MODEL(parent, item_base))
663 {
664 item_base.w = 0;
665 item_base.h = 0;
666 }
667 pd->last_base = item_base;
668
669 sizes = memory.mem;
670 count = efl_model_children_count_get(parent);
671 limit = MIN(count - start_id, memory.len);
672
673 // Look in the temporary cache now for the beginning of the buffer
674 if (pd->viewport[0] && ((uint64_t)(start_id + idx) < pd->viewport[0]->offset))
675 {
676 while ((uint64_t)(start_id + idx) < pd->viewport[0]->offset)
677 {
678 uint64_t search_index = start_id + idx;
679
680 requests = _cache_size_fetch(requests, &request, pd,
681 search_index, &sizes[idx], item_base);
682
683 idx++;
684 }
685 }
686
687 // Then look in our buffer view if the needed information can be found there
688 for (i = 0; i < 3; ++i)
689 {
690 if (!pd->viewport[i]) continue;
691
692 while (idx < limit &&
693 (pd->viewport[i]->offset <= start_id + idx) &&
694 (start_id + idx < (pd->viewport[i]->offset + pd->viewport[i]->count)))
695 {
696 uint16_t offset = start_id + idx - pd->viewport[i]->offset;
697 Efl_Model *model = pd->viewport[i]->items[offset].model;
698 Efl_Gfx_Entity *entity = pd->viewport[i]->items[offset].entity;
699 Eina_Bool entity_request = EINA_FALSE;
700
701 if (!model)
702 {
703 Eina_Size2D item_size;
704 Eina_Bool found = EINA_FALSE;
705
706 if (ITEM_SIZE_FROM_MODEL(model, item_size))
707 found = EINA_TRUE;
708 if (!found && entity)
709 {
710 item_size = efl_gfx_hint_size_min_get(entity);
711 _size_to_model(model, item_size);
712 found = EINA_TRUE;
713 }
714
715 if (found)
716 {
717 sizes[idx].size = item_size;
718 sizes[idx].group = EFL_UI_POSITION_MANAGER_BATCH_GROUP_STATE_NO_GROUP;
719 goto done;
720 }
721
722 // We will need an entity to calculate this size
723 entity_request = EINA_TRUE;
724 }
725
726 // No data, add to the requests
727 requests = _request_add(requests, &request, start_id + idx, entity_request);
728
729 sizes[idx].size = item_base;
730 sizes[idx].group = EFL_UI_POSITION_MANAGER_BATCH_GROUP_STATE_NO_GROUP;
731
732 done:
733 idx++;
734 }
735 }
736
737 // Look in the temporary cache now for the end of the buffer
738 while (idx < limit)
739 {
740 uint64_t search_index = start_id + idx;
741
742 requests = _cache_size_fetch(requests, &request, pd,
743 search_index, &sizes[idx], item_base);
744
745 idx++;
746 }
747
748 // Done, but flush request first
749 if (request) requests = eina_list_append(requests, request);
750
751 requests = _batch_request_flush(requests, data, pd);
752
753 // Get the amount of filled item
754 result.filled_items = limit;
755
756 return result;
757}
758
759static Efl_Ui_Position_Manager_Batch_Result
760_batch_entity_cb(void *data, int start_id, Eina_Rw_Slice memory)
761{
762 MY_DATA_GET(data, pd);
763 Efl_Ui_Position_Manager_Batch_Entity_Access *entities;
764 Efl_Ui_Collection_Request *request = NULL;
765 Efl_Ui_Position_Manager_Batch_Result result = {-1, 0};
766 Eina_List *requests = NULL;
767 Efl_Model *parent;
768 unsigned int i, count, limit;
769 unsigned int idx = 0;
770
771 parent = pd->model;
772
773 entities = memory.mem;
774 count = efl_model_children_count_get(parent);
775 limit = MIN(count - start_id, memory.len);
776
777 // Look in the temporary cache now for the beginning of the buffer
778 if (pd->viewport[0] && ((uint64_t)(start_id + idx) < pd->viewport[0]->offset))
779 {
780 while ((uint64_t)(start_id + idx) < pd->viewport[0]->offset)
781 {
782 uint64_t search_index = start_id + idx;
783
784 requests = _cache_entity_fetch(requests, &request, pd,
785 search_index, &entities[idx]);
786
787 idx++;
788 }
789 }
790
791 // Then look in our buffer view if the needed information can be found there
792 for (i = 0; i < 3; ++i)
793 {
794 if (!pd->viewport[i]) continue;
795
796 while (idx < limit &&
797 (pd->viewport[i]->offset <= start_id + idx) &&
798 (start_id + idx < (pd->viewport[i]->offset + pd->viewport[i]->count)))
799 {
800 uint16_t offset = start_id + idx - pd->viewport[i]->offset;
801 Efl_Gfx_Entity *entity = pd->viewport[i]->items[offset].entity;
802
803 if (!entity)
804 {
805 // No data, add to the requests
806 requests = _request_add(requests, &request, start_id + idx, EINA_TRUE);
807
808 entities[idx].entity = NULL;
809 entities[idx].group = EFL_UI_POSITION_MANAGER_BATCH_GROUP_STATE_NO_GROUP;
810 }
811 else
812 {
813 entities[idx].entity = entity;
814 entities[idx].group = EFL_UI_POSITION_MANAGER_BATCH_GROUP_STATE_NO_GROUP;
815 }
816
817 idx++;
818 }
819 }
820
821 // Look in the temporary cache now for the end of the buffer
822 while (idx < limit)
823 {
824 uint64_t search_index = start_id + idx;
825
826 requests = _cache_entity_fetch(requests, &request, pd,
827 search_index, &entities[idx]);
828
829 idx++;
830 }
831
832 // Done, but flush request first
833 if (request) requests = eina_list_append(requests, request);
834
835 requests = _batch_request_flush(requests, data, pd);
836
837 // Get the amount of filled item
838 result.filled_items = limit;
839
840 return result;
841}
842
843static void
844_batch_free_cb(void *data)
845{
846 efl_unref(data);
847}
848
849static void
850flush_min_size(Eo *obj, Efl_Ui_Collection_View_Data *pd)
851{
852 Eina_Size2D tmp = pd->content_min_size;
853
854 if (!pd->match_content.w)
855 tmp.w = -1;
856
857 if (!pd->match_content.h)
858 tmp.h = -1;
859
860 efl_gfx_hint_size_min_set(obj, tmp);
861}
862
863static void
864_manager_content_size_changed_cb(void *data, const Efl_Event *ev)
865{
866 Eina_Size2D *size = ev->info;
867 MY_DATA_GET(data, pd);
868
869 efl_gfx_entity_size_set(pd->sizer, *size);
870}
871
872static void
873_manager_content_min_size_changed_cb(void *data, const Efl_Event *ev)
874{
875 Eina_Size2D *size = ev->info;
876 MY_DATA_GET(data, pd);
877
878 pd->content_min_size = *size;
879
880 flush_min_size(data, pd);
881}
882
883static Eina_List *
884_viewport_walk_fill(Eina_List *requests,
885 Efl_Ui_Collection_View *obj EINA_UNUSED,
886 Efl_Ui_Collection_View_Data *pd,
887 Efl_Ui_Collection_Viewport *viewport)
888{
889 Efl_Ui_Collection_Request *current = NULL;
890 unsigned int j;
891
892 for (j = 0; j < viewport->count; j++)
893 {
894 Efl_Ui_Collection_Item_Lookup *lookup;
895 uint64_t index = viewport->offset + j;
896
897 if (viewport->items[j].model) goto check_entity;
898
899 lookup = (void*) eina_rbtree_inline_lookup(pd->cache, &index,
900 sizeof (index), _cache_tree_lookup,
901 NULL);
902
903 if (lookup)
904 {
905 efl_replace(&viewport->items[j].model, lookup->item.model);
906 efl_replace(&viewport->items[j].entity, lookup->item.entity);
907 efl_replace(&lookup->item.entity, NULL); // Necessary to avoid premature release
908
909 pd->cache = eina_rbtree_inline_remove(pd->cache, EINA_RBTREE_GET(lookup),
910 _cache_tree_cmp, NULL);
911 _cache_item_free(EINA_RBTREE_GET(lookup), pd);
912 }
913
914 check_entity:
915 if (viewport->items[j].entity) continue ;
916
917 requests = _request_add(requests, &current, index, EINA_TRUE);
918 }
919
920 // We do break request per viewport, just in case we generate to big batch at once
921 if (current) requests = eina_list_append(requests, current);
922
923 return requests;
924}
925
926static void
927_manager_content_visible_range_changed_cb(void *data, const Efl_Event *ev)
928{
929 Efl_Ui_Position_Manager_Range_Update *event = ev->info;
930 MY_DATA_GET(data, pd);
931 Eina_List *requests = NULL;
932 unsigned int baseid;
933 unsigned int delta, marginup, margindown;
934 uint64_t upperlimit_offset, lowerlimit_offset;
935 unsigned int i;
936
937 pd->start_id = event->start_id;
938 pd->end_id = event->end_id;
939
940 delta = pd->end_id - pd->start_id;
941
942 // First time setting up the viewport, so trigger request as we see fit
943 if (!pd->viewport[0])
944 {
945 Eina_List *requests = NULL;
946
947 baseid = (pd->start_id < delta) ? 0 : pd->start_id - delta;
948
949 for (i = 0; i < 3; i++)
950 {
951 pd->viewport[i] = calloc(1, sizeof (Efl_Ui_Collection_Viewport));
952 if (!pd->viewport[i]) continue;
953
954 pd->viewport[i]->offset = baseid + delta * i;
955 pd->viewport[i]->count = delta;
956 pd->viewport[i]->items = calloc(delta, sizeof (Efl_Ui_Collection_Item));
957 if (!pd->viewport[i]->items) continue ;
958
959 requests = _viewport_walk_fill(requests, data, pd, pd->viewport[i]);
960 }
961
962 goto flush_requests;
963 }
964
965 // Compute limit offset
966 upperlimit_offset = delta * 3 + pd->viewport[0]->offset;
967 lowerlimit_offset = 0;
968
969 // Adjust the viewport for size or to much offset change in two step
970
971 // Trying to resize first if there size is in bigger/smaller than 25% of the original size
972 margindown = delta * 75 / 100;
973 marginup = delta * 125 / 100;
974 if (margindown < pd->viewport[0]->count &&
975 pd->viewport[0]->count < marginup)
976 {
977 // Trying to do the resize in an optimized way is complex, let's do it simple
978 Efl_Ui_Collection_Item *items[3];
979 unsigned int j = 0, t = 1;
980
981 for (i = 0; i < 3; i++)
982 {
983 unsigned int m;
984
985 items[i] = calloc(delta, sizeof (Efl_Ui_Collection_Item));
986 if (!items[i]) continue;
987
988 for (m = 0; m < delta && t < 3; m++)
989 {
990 items[i][m] = pd->viewport[t]->items[j];
991
992 j++;
993 if (j < pd->viewport[t]->count) continue;
994
995 j = 0;
996 t++;
997 if (t == 3) break;
998 }
999
1000 // Preserve last updated index to later build a request
1001 if (t == 3)
1002 {
1003 upperlimit_offset = pd->viewport[0]->offset + i * delta + m;
1004
1005 t = 4; // So that we never come back here again
1006 }
1007 }
1008
1009 // For now destroy leftover object, could be cached
1010 for (i = t; i < 3; i++)
1011 {
1012 for (; j < pd->viewport[i]->count; j++)
1013 {
1014 _item_cleanup(pd->factory, &pd->viewport[i]->items[j]);
1015 }
1016 j = 0;
1017 }
1018
1019 // And now define viewport back
1020 for (i = 0; i < 3; i++)
1021 {
1022 free(pd->viewport[i]->items);
1023 pd->viewport[i]->items = items[i];
1024 pd->viewport[i]->count = delta;
1025 pd->viewport[i]->offset = pd->viewport[0]->offset + delta * i;
1026 }
1027 }
1028
1029 // We decided that resizing was unecessary
1030 delta = pd->viewport[0]->count;
1031
1032 // Try to keep the visual viewport in between half of the first and last viewport
1033
1034 // start_id is in the first half of the first viewport, assume upward move
1035 // start_id + delta is in the second half of the last viewport, assume upward move
1036 if (pd->viewport[0]->offset + delta / 2 < pd->start_id ||
1037 pd->start_id + delta > pd->viewport[2]->offset + delta / 2)
1038 {
1039 // We could optimize this to actually just move viewport around in most cases
1040 Efl_Ui_Collection_Item *items[3];
1041 unsigned int j = 0, t = 0;
1042 uint64_t target, current;
1043
1044 // Case where are at the top
1045 if (pd->start_id < delta && pd->viewport[0]->offset == 0) goto build_request;
1046
1047 // Trying to adjust the offset to maintain it in the center viewport +/- delta/2
1048 baseid = (pd->start_id < delta) ? 0 : pd->start_id - delta;
1049
1050 // Lookup for starting point
1051 lowerlimit_offset = pd->viewport[0]->offset;
1052 target = baseid;
1053
1054 // cleanup before target
1055 for (current = pd->viewport[0]->offset; current < target; current++)
1056 {
1057 _item_cleanup(pd->factory, &pd->viewport[t]->items[j]);
1058
1059 j++;
1060 if (j < pd->viewport[t]->count) continue;
1061
1062 j = 0;
1063 t++;
1064 if (t == 3) break;
1065 }
1066
1067 // Allocation and copy
1068 for (i = 0; i < 3; i++)
1069 {
1070 unsigned int m;
1071
1072 items[i] = calloc(delta, sizeof (Efl_Ui_Collection_Item));
1073 if (!items[i]) continue;
1074
1075 for (m = 0; m < delta && t < 3; m++, target++)
1076 {
1077 if (target < pd->viewport[t]->offset) continue ;
1078 items[i][m] = pd->viewport[t]->items[j];
1079
1080 j++;
1081 if (j < pd->viewport[t]->count) continue;
1082
1083 j = 0;
1084 t++;
1085 if (t == 3) break;
1086 }
1087
1088 // Preserve last updated index to later build a request
1089 if (t == 3)
1090 {
1091 if (upperlimit_offset > pd->viewport[0]->offset + i * delta + m)
1092 {
1093 upperlimit_offset = pd->viewport[0]->offset + i * delta + m;
1094 }
1095
1096 t = 4; // So that we never come back here again
1097 }
1098 }
1099
1100 // For now destroy leftover object, could be cached
1101 for (i = t; i < 3; i++)
1102 {
1103 for (; j < pd->viewport[i]->count; j++)
1104 {
1105 _item_cleanup(pd->factory, &pd->viewport[i]->items[j]);
1106 }
1107 j = 0;
1108 }
1109
1110 // And now define viewport back
1111 for (i = 0; i < 3; i++)
1112 {
1113 free(pd->viewport[i]->items);
1114 pd->viewport[i]->items = items[i];
1115 pd->viewport[i]->offset = baseid + delta * i;
1116 }
1117 }
1118
1119 build_request:
1120 // Check if the first viewport has all the lower part of it filled with objects
1121 if (pd->viewport[0]->offset < lowerlimit_offset)
1122 {
1123 Efl_Ui_Collection_Request *request;
1124
1125 request = calloc(1, sizeof (Efl_Ui_Collection_Request));
1126 if (request) return ;
1127
1128 request->offset = lowerlimit_offset;
1129 // This length work over multiple viewport as they are contiguous
1130 request->length = lowerlimit_offset - pd->viewport[0]->offset;
1131 request->model_requested = EINA_TRUE;
1132 request->entity_requested = EINA_TRUE;
1133
1134 requests = eina_list_append(requests, request);
1135 }
1136
1137 // Check if the last viewport has all the upper part of it filler with objects
1138 if (pd->viewport[2]->offset + pd->viewport[2]->count > upperlimit_offset)
1139 {
1140 Efl_Ui_Collection_Request *request;
1141
1142 request = calloc(1, sizeof (Efl_Ui_Collection_Request));
1143 if (request) return ;
1144
1145 request->offset = upperlimit_offset;
1146 // This length work over multiple viewport as they are contiguous
1147 request->length = pd->viewport[2]->offset + pd->viewport[2]->count - upperlimit_offset;
1148 request->model_requested = EINA_TRUE;
1149 request->entity_requested = EINA_TRUE;
1150
1151 requests = eina_list_append(requests, request);
1152 }
1153
1154 flush_requests:
1155 requests = _batch_request_flush(requests, data, pd);
1156}
1157
1158EFL_CALLBACKS_ARRAY_DEFINE(manager_cbs,
1159 { EFL_UI_POSITION_MANAGER_ENTITY_EVENT_CONTENT_SIZE_CHANGED, _manager_content_size_changed_cb },
1160 { EFL_UI_POSITION_MANAGER_ENTITY_EVENT_CONTENT_MIN_SIZE_CHANGED, _manager_content_min_size_changed_cb },
1161 { EFL_UI_POSITION_MANAGER_ENTITY_EVENT_VISIBLE_RANGE_CHANGED, _manager_content_visible_range_changed_cb }
1162)
1163
1164static void
1165_item_scroll_internal(Eo *obj EINA_UNUSED,
1166 Efl_Ui_Collection_View_Data *pd,
1167 uint64_t index,
1168 double align EINA_UNUSED,
1169 Eina_Bool anim)
1170{
1171 Eina_Rect ipos, view;
1172 Eina_Position2D vpos;
1173
1174 if (!pd->scroller) return;
1175
1176 ipos = efl_ui_position_manager_entity_position_single_item(pd->manager, index);
1177 view = efl_ui_scrollable_viewport_geometry_get(pd->scroller);
1178 vpos = efl_ui_scrollable_content_pos_get(pd->scroller);
1179
1180 ipos.x = ipos.x + vpos.x - view.x;
1181 ipos.y = ipos.y + vpos.y - view.y;
1182
1183 //FIXME scrollable needs some sort of align, the docs do not even garantee to completly move in the element
1184 efl_ui_scrollable_scroll(pd->scroller, ipos, anim);
1185}
1186
1187// Exported function
1188
1189static void
1190_efl_ui_collection_view_factory_set(Eo *obj EINA_UNUSED, Efl_Ui_Collection_View_Data *pd,
1191 Efl_Ui_Factory *factory)
1192{
1193 efl_replace(&pd->factory, factory);
1194}
1195
1196static Efl_Ui_Factory *
1197_efl_ui_collection_view_factory_get(const Eo *obj EINA_UNUSED, Efl_Ui_Collection_View_Data *pd)
1198{
1199 return pd->factory;
1200}
1201
1202static void
1203_efl_ui_collection_view_position_manager_set(Eo *obj, Efl_Ui_Collection_View_Data *pd,
1204 Efl_Ui_Position_Manager_Entity *manager)
1205{
1206 Efl_Model *model;
1207 unsigned int count;
1208
1209 if (manager)
1210 EINA_SAFETY_ON_FALSE_RETURN(efl_isa(manager, EFL_UI_POSITION_MANAGER_ENTITY_INTERFACE));
1211
1212 if (pd->manager)
1213 {
1214 efl_event_callback_array_del(pd->manager, manager_cbs(), obj);
1215 efl_ui_position_manager_entity_data_access_set(pd->manager,
1216 NULL, NULL, NULL,
1217 NULL, NULL, NULL,
1218 0);
1219 efl_del(pd->manager);
1220 }
1221 pd->manager = manager;
1222 if (!pd->manager) return;
1223
1224 // Start watching change on model from here on
1225 model = pd->model;
1226 count = model ? efl_model_children_count_get(model) : 0;
1227
1228 efl_parent_set(pd->manager, obj);
1229 efl_event_callback_array_add(pd->manager, manager_cbs(), obj);
1230 efl_ui_position_manager_entity_data_access_set(pd->manager,
1231 efl_ref(obj), _batch_entity_cb, _batch_free_cb,
1232 efl_ref(obj), _batch_size_cb, _batch_free_cb,
1233 count);
1234 efl_ui_position_manager_entity_viewport_set(pd->manager, efl_ui_scrollable_viewport_geometry_get(obj));
1235 efl_ui_layout_orientation_set(pd->manager, pd->direction);
1236}
1237
1238static Efl_Ui_Position_Manager_Entity *
1239_efl_ui_collection_view_position_manager_get(const Eo *obj EINA_UNUSED,
1240 Efl_Ui_Collection_View_Data *pd)
1241{
1242 return pd->manager;
1243}
1244
1245static void
1246_efl_model_count_changed(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED)
1247{
1248 // We are not triggering efl_ui_position_manager_entity_data_access_set as it is can
1249 // only be slow, we rely on child added/removed instead (If we were to not rely on
1250 // child added/removed we could maybe use count changed)
1251}
1252
1253static void
1254_efl_model_properties_changed(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED)
1255{
1256 // We could here watch if the global base size item change and notify of a global change
1257 // But I can not find a proper way to do it for the object that are not visible, which
1258 // is kind of the point...
1259}
1260
1261static void
1262_efl_model_child_added(void *data, const Efl_Event *event)
1263{
1264 // At the moment model only append child, but let's try to handle it theorically correct
1265 Efl_Model_Children_Event *ev = event->info;
1266 MY_DATA_GET(data, pd);
1267 Eina_List *requests = NULL;
1268 unsigned int i;
1269
1270 _cache_cleanup(pd);
1271
1272 // Check if we really have something to do
1273 if (!pd->viewport[0]) goto notify_manager;
1274
1275 // Insert the child in the viewport if necessary
1276 for (i = 0; i < 3; i++)
1277 {
1278 Efl_Ui_Collection_Request *request;
1279 unsigned int o;
1280 unsigned int j;
1281
1282 if (ev->index < pd->viewport[i]->offset)
1283 {
1284 pd->viewport[i]->offset++;
1285 continue;
1286 }
1287 if (pd->viewport[i]->offset + pd->viewport[i]->count < ev->index)
1288 {
1289 continue;
1290 }
1291
1292 for (j = 2; j > i; j--)
1293 {
1294 _item_cleanup(pd->factory, &pd->viewport[j]->items[pd->viewport[j]->count - 1]);
1295 memmove(&pd->viewport[j]->items[1],
1296 &pd->viewport[j]->items[0],
1297 (pd->viewport[j]->count - 1) * sizeof (Efl_Ui_Collection_Item));
1298 pd->viewport[j]->items[0] = pd->viewport[j - 1]->items[pd->viewport[j - 1]->count - 1];
1299 pd->viewport[j - 1]->items[pd->viewport[j - 1]->count - 1].entity = NULL;
1300 pd->viewport[j - 1]->items[pd->viewport[j - 1]->count - 1].model = NULL;
1301 }
1302 o = ev->index - pd->viewport[i]->offset;
1303 memmove(&pd->viewport[j]->items[o],
1304 &pd->viewport[j]->items[o + 1],
1305 (pd->viewport[j]->count - 1 - o) * sizeof (Efl_Ui_Collection_Item));
1306 pd->viewport[j]->items[o].entity = NULL;
1307 pd->viewport[j]->items[o].model = efl_ref(ev->child);
1308
1309 request = calloc(1, sizeof (Efl_Ui_Collection_Request));
1310 if (!request) break;
1311 request->offset = ev->index;
1312 request->length = 1;
1313 request->model_requested = EINA_TRUE;
1314 request->entity_requested = EINA_TRUE;
1315
1316 requests = eina_list_append(requests, request);
1317
1318 requests = _batch_request_flush(requests, data, pd);
1319
1320 break;
1321 }
1322
1323 notify_manager:
1324 efl_ui_position_manager_entity_item_added(pd->manager, ev->index, NULL);
1325}
1326
1327static void
1328_efl_model_child_removed(void *data, const Efl_Event *event)
1329{
1330 Efl_Model_Children_Event *ev = event->info;
1331 MY_DATA_GET(data, pd);
1332 Eina_List *requests = NULL;
1333 unsigned int i;
1334
1335 _cache_cleanup(pd);
1336
1337 // Check if we really have something to do
1338 if (!pd->viewport[0]) goto notify_manager;
1339
1340 // Insert the child in the viewport if necessary
1341 for (i = 0; i < 3; i++)
1342 {
1343 Efl_Ui_Collection_Request *request;
1344 unsigned int o;
1345
1346 if (ev->index < pd->viewport[i]->offset)
1347 {
1348 pd->viewport[i]->offset--;
1349 continue;
1350 }
1351 if (pd->viewport[i]->offset + pd->viewport[i]->count < ev->index)
1352 {
1353 continue;
1354 }
1355
1356 o = ev->index - pd->viewport[i]->offset;
1357 _item_cleanup(pd->factory, &pd->viewport[i]->items[o]);
1358 for (; i < 3; i++)
1359 {
1360 memmove(&pd->viewport[i]->items[o],
1361 &pd->viewport[i]->items[o + 1],
1362 (pd->viewport[i]->count - 1 - o) * sizeof (Efl_Ui_Collection_Item));
1363 if (i + 1 < 3)
1364 {
1365 pd->viewport[i]->items[pd->viewport[i]->count - 1] = pd->viewport[i + 1]->items[0];
1366 }
1367 else
1368 {
1369 pd->viewport[i]->items[pd->viewport[i]->count - 1].entity = NULL;
1370 pd->viewport[i]->items[pd->viewport[i]->count - 1].model = NULL;
1371 }
1372 o = 0;
1373 }
1374
1375 request = calloc(1, sizeof (Efl_Ui_Collection_Request));
1376 if (!request) break;
1377 request->offset = pd->viewport[2]->offset + pd->viewport[i]->count - 1;
1378 request->length = 1;
1379 request->model_requested = EINA_TRUE;
1380 request->entity_requested = EINA_TRUE;
1381
1382 requests = eina_list_append(requests, request);
1383
1384 requests = _batch_request_flush(requests, data, pd);
1385
1386 break;
1387 }
1388
1389 notify_manager:
1390 efl_ui_position_manager_entity_item_removed(pd->manager, ev->index, NULL);
1391}
1392
1393EFL_CALLBACKS_ARRAY_DEFINE(model_cbs,
1394 { EFL_MODEL_EVENT_CHILDREN_COUNT_CHANGED, _efl_model_count_changed },
1395 { EFL_MODEL_EVENT_PROPERTIES_CHANGED, _efl_model_properties_changed },
1396 { EFL_MODEL_EVENT_CHILD_ADDED, _efl_model_child_added },
1397 { EFL_MODEL_EVENT_CHILD_REMOVED, _efl_model_child_removed })
1398
1399static void
1400_efl_ui_collection_view_model_changed(void *data, const Efl_Event *event)
1401{
1402 Efl_Model_Changed_Event *ev = event->info;
1403 Eina_List *requests = NULL;
1404 MY_DATA_GET(data, pd);
1405 Eina_Iterator *it;
1406 const char *property;
1407 Efl_Model *model = NULL;
1408 unsigned int i, count;
1409 Eina_Bool selection = EINA_FALSE, sizing = EINA_FALSE;
1410
1411 if (ev->previous) efl_event_callback_array_del(ev->previous, model_cbs(), data);
1412 if (ev->current) efl_event_callback_array_add(ev->current, model_cbs(), data);
1413
1414 // Cleanup all object, pending request and refetch everything
1415 _all_cleanup(pd);
1416
1417 efl_del(pd->model);
1418 pd->model = NULL;
1419
1420 if (!ev->current) return ;
1421
1422 it = efl_model_properties_get(ev->current);
1423 EINA_ITERATOR_FOREACH(it, property)
1424 {
1425 // Check if the model provide selection
1426 if (eina_streq(property, "child.selected"))
1427 selection = EINA_TRUE;
1428 // Check if the model provide sizing logic
1429 else if (eina_streq(property, _efl_model_property_itemw) ||
1430 eina_streq(property, _efl_model_property_itemh))
1431 sizing = EINA_TRUE;
1432 }
1433 eina_iterator_free(it);
1434
1435 // Push selection model first
1436 if (!selection) model = efl_add(EFL_SELECT_MODEL_CLASS, data,
1437 efl_ui_view_model_set(efl_added, ev->current));
1438 if (!sizing) model = efl_add(EFL_UI_HOMOGENEOUS_MODEL_CLASS, data,
1439 efl_ui_view_model_set(efl_added, model ? model : ev->current));
1440 if (!model) model = efl_add(EFL_VIEW_MODEL_CLASS, data,
1441 efl_ui_view_model_set(efl_added, ev->current));
1442
1443 count = efl_model_children_count_get(model);
1444 efl_ui_position_manager_entity_data_access_set(pd->manager,
1445 efl_ref(data), _batch_entity_cb, _batch_free_cb,
1446 efl_ref(data), _batch_size_cb, _batch_free_cb,
1447 count);
1448
1449 for (i = 0; i < 3; i++)
1450 {
1451 Efl_Ui_Collection_Request *request;
1452
1453 if (!pd->viewport[i]) continue ;
1454 if (pd->viewport[i]->count == 0) continue ;
1455
1456 request = calloc(1, sizeof (Efl_Ui_Collection_Request));
1457 if (!request) continue ;
1458
1459 request->offset = pd->viewport[i]->offset;
1460 request->length = pd->viewport[i]->count;
1461 request->model_requested = EINA_TRUE;
1462 request->entity_requested = EINA_TRUE;
1463
1464 requests = eina_list_append(requests, request);
1465 }
1466
1467 requests = _batch_request_flush(requests, data, pd);
1468}
1469
1470static void
1471_pan_viewport_changed_cb(void *data, const Efl_Event *ev EINA_UNUSED)
1472{
1473 MY_DATA_GET(data, pd);
1474 Eina_Rect rect = efl_ui_scrollable_viewport_geometry_get(data);
1475
1476 efl_ui_position_manager_entity_viewport_set(pd->manager, rect);
1477}
1478
1479static void
1480_pan_position_changed_cb(void *data, const Efl_Event *ev EINA_UNUSED)
1481{
1482 MY_DATA_GET(data, pd);
1483 Eina_Position2D pos = efl_ui_pan_position_get(pd->pan);
1484 Eina_Position2D max = efl_ui_pan_position_max_get(pd->pan);
1485 Eina_Vector2 rpos = {0.0, 0.0};
1486
1487 if (max.x > 0.0)
1488 rpos.x = (double)pos.x/(double)max.x;
1489 if (max.y > 0.0)
1490 rpos.y = (double)pos.y/(double)max.y;
1491
1492 efl_ui_position_manager_entity_scroll_position_set(pd->manager, rpos.x, rpos.y);
1493}
1494
1495EFL_CALLBACKS_ARRAY_DEFINE(pan_events_cb,
1496 {EFL_UI_PAN_EVENT_PAN_POSITION_CHANGED, _pan_position_changed_cb},
1497 {EFL_UI_PAN_EVENT_PAN_VIEWPORT_CHANGED, _pan_viewport_changed_cb},
1498)
1499
1500static Efl_Object *
1501_efl_ui_collection_view_efl_object_constructor(Eo *obj, Efl_Ui_Collection_View_Data *pd)
1502{
1503 obj = efl_constructor(efl_super(obj, EFL_UI_COLLECTION_VIEW_CLASS));
1504
1505 if (!elm_widget_theme_klass_get(obj))
1506 elm_widget_theme_klass_set(obj, "collection");
1507
1508 efl_wref_add(efl_add(EFL_CANVAS_RECTANGLE_CLASS, evas_object_evas_get(obj)), &pd->sizer);
1509 efl_gfx_color_set(pd->sizer, 0, 0, 0, 0);
1510
1511 efl_wref_add(efl_add(EFL_UI_PAN_CLASS, obj), &pd->pan);
1512 efl_content_set(pd->pan, pd->sizer);
1513 efl_event_callback_array_add(pd->pan, pan_events_cb(), obj);
1514
1515 efl_wref_add(efl_add(EFL_UI_SCROLL_MANAGER_CLASS, obj), &pd->scroller);
1516 efl_composite_attach(obj, pd->scroller);
1517 efl_ui_mirrored_set(pd->scroller, efl_ui_mirrored_get(obj));
1518 efl_ui_scroll_manager_pan_set(pd->scroller, pd->pan);
1519
1520 efl_ui_scroll_connector_bind(obj, pd->scroller);
1521
1522 efl_event_callback_add(obj, EFL_UI_VIEW_EVENT_MODEL_CHANGED,
1523 _efl_ui_collection_view_model_changed, obj);
1524
1525 return obj;
1526}
1527
1528static void
1529_efl_ui_collection_view_efl_object_invalidate(Eo *obj,
1530 Efl_Ui_Collection_View_Data *pd)
1531{
1532 efl_ui_collection_view_position_manager_set(obj, NULL);
1533 efl_event_callback_del(obj, EFL_UI_VIEW_EVENT_MODEL_CHANGED,
1534 _efl_ui_collection_view_model_changed, obj);
1535
1536 _all_cleanup(pd);
1537
1538 efl_invalidate(efl_super(obj, EFL_UI_COLLECTION_VIEW_CLASS));
1539}
1540
1541static void
1542_efl_ui_collection_view_efl_ui_layout_orientable_orientation_set(Eo *obj EINA_UNUSED,
1543 Efl_Ui_Collection_View_Data *pd,
1544 Efl_Ui_Layout_Orientation dir)
1545{
1546 if (pd->direction == dir) return;
1547
1548 pd->direction = dir;
1549 if (pd->manager) efl_ui_layout_orientation_set(pd->manager, dir);
1550}
1551
1552static Efl_Ui_Layout_Orientation
1553_efl_ui_collection_view_efl_ui_layout_orientable_orientation_get(const Eo *obj EINA_UNUSED,
1554 Efl_Ui_Collection_View_Data *pd)
1555{
1556 return pd->direction;
1557}
1558
1559static Eina_Error
1560_efl_ui_collection_view_efl_ui_widget_theme_apply(Eo *obj, Efl_Ui_Collection_View_Data *pd)
1561{
1562 Eina_Error res;
1563
1564 ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd, EFL_UI_THEME_APPLY_ERROR_GENERIC);
1565 res = efl_ui_widget_theme_apply(efl_super(obj, MY_CLASS));
1566 if (res == EFL_UI_THEME_APPLY_ERROR_GENERIC) return res;
1567 efl_ui_mirrored_set(pd->scroller, efl_ui_mirrored_get(obj));
1568 efl_content_set(efl_part(wd->resize_obj, "efl.content"), pd->pan);
1569
1570 return res;
1571}
1572
1573static void
1574_efl_ui_collection_view_efl_ui_scrollable_interactive_match_content_set(Eo *obj, Efl_Ui_Collection_View_Data *pd, Eina_Bool w, Eina_Bool h)
1575{
1576 if (pd->match_content.w == w && pd->match_content.h == h)
1577 return;
1578
1579 pd->match_content.w = w;
1580 pd->match_content.h = h;
1581
1582 efl_ui_scrollable_match_content_set(pd->scroller, w, h);
1583 flush_min_size(obj, pd);
1584}
1585
1586static void
1587_efl_ui_collection_view_efl_ui_multi_selectable_select_mode_set(Eo *obj EINA_UNUSED,
1588 Efl_Ui_Collection_View_Data *pd,
1589 Efl_Ui_Select_Mode mode)
1590{
1591 pd->mode = mode;
1592}
1593
1594static Efl_Ui_Select_Mode
1595_efl_ui_collection_view_efl_ui_multi_selectable_select_mode_get(const Eo *obj EINA_UNUSED,
1596 Efl_Ui_Collection_View_Data *pd)
1597{
1598 return pd->mode;
1599}
1600
1601static Efl_Ui_Focus_Manager *
1602_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)
1603{
1604 Efl_Ui_Collection_View_Focus_Manager_Data *mpd;
1605 Eo *manager = efl_add(EFL_UI_COLLECTION_VIEW_FOCUS_MANAGER_CLASS, obj,
1606 efl_ui_focus_manager_root_set(efl_added, root));
1607
1608 mpd = efl_data_scope_get(manager, EFL_UI_COLLECTION_VIEW_FOCUS_MANAGER_CLASS);
1609 mpd->collection = obj;
1610
1611 return manager;
1612}
1613
1614static Efl_Ui_Focus_Object *
1615_efl_ui_collection_view_efl_ui_focus_manager_move(Eo *obj, Efl_Ui_Collection_View_Data *pd, Efl_Ui_Focus_Direction direction)
1616{
1617 Eo *new_obj, *focus;
1618 Eina_Size2D step;
1619
1620 new_obj = efl_ui_focus_manager_move(efl_super(obj, MY_CLASS), direction);
1621 focus = efl_ui_focus_manager_focus_get(obj);
1622 step = efl_gfx_hint_size_min_get(focus);
1623 if (!new_obj)
1624 {
1625 Eina_Rect pos = efl_gfx_entity_geometry_get(focus);
1626 Eina_Rect view = efl_ui_scrollable_viewport_geometry_get(pd->scroller);
1627 Eina_Position2D vpos = efl_ui_scrollable_content_pos_get(pd->scroller);
1628
1629 pos.x = pos.x + vpos.x - view.x;
1630 pos.y = pos.y + vpos.y - view.y;
1631 Eina_Position2D max = efl_ui_pan_position_max_get(pd->pan);
1632
1633 if (direction == EFL_UI_FOCUS_DIRECTION_RIGHT)
1634 {
1635 if (pos.x < max.x)
1636 {
1637 pos.x = MIN(max.x, pos.x + step.w);
1638 efl_ui_scrollable_scroll(obj, pos, EINA_TRUE);
1639 new_obj = focus;
1640 }
1641 }
1642 else if (direction == EFL_UI_FOCUS_DIRECTION_LEFT)
1643 {
1644 if (pos.x > 0)
1645 {
1646 pos.x = MAX(0, pos.x - step.w);
1647 efl_ui_scrollable_scroll(obj, pos, EINA_TRUE);
1648 new_obj = focus;
1649 }
1650 }
1651 else if (direction == EFL_UI_FOCUS_DIRECTION_UP)
1652 {
1653 if (pos.y > 0)
1654 {
1655 pos.y = MAX(0, pos.y - step.h);
1656 efl_ui_scrollable_scroll(obj, pos, EINA_TRUE);
1657 new_obj = focus;
1658 }
1659 }
1660 else if (direction == EFL_UI_FOCUS_DIRECTION_DOWN)
1661 {
1662 if (pos.y < max.y)
1663 {
1664 pos.y = MAX(0, pos.y + step.h);
1665 efl_ui_scrollable_scroll(obj, pos, EINA_TRUE);
1666 new_obj = focus;
1667 }
1668 }
1669 }
1670 else
1671 {
1672 Efl_Model *model;
1673 Eina_Value *vindex;
1674 uint64_t index;
1675
1676 model = efl_ui_view_model_get(new_obj);
1677 vindex = efl_model_property_get(model, "child.index");
1678 if (eina_value_uint64_convert(vindex, &index))
1679 _item_scroll_internal(obj, pd, index, .0, EINA_TRUE);
1680 eina_value_free(vindex);
1681 }
1682
1683 return new_obj;
1684}
1685
1686#include "efl_ui_collection_view.eo.c"
1687
1688#define ITEM_IS_OUTSIDE_VISIBLE(id) id < cpd->start_id || id > cpd->end_id
1689
1690static Efl_Ui_Item *
1691_find_item(Eo *obj EINA_UNUSED, Efl_Ui_Collection_View_Data *pd EINA_UNUSED, Eo *focused_element)
1692{
1693 if (!focused_element) return NULL;
1694
1695 while (focused_element &&
1696 efl_key_data_get(focused_element, COLLECTION_VIEW_MANAGED) != COLLECTION_VIEW_MANAGED_YES)
1697 {
1698 focused_element = efl_ui_widget_parent_get(focused_element);
1699 }
1700
1701 return focused_element;
1702}
1703
1704static inline void
1705_assert_item_available(Eo *item, int new_id, Efl_Ui_Collection_View_Data *pd)
1706{
1707 efl_gfx_entity_visible_set(item, EINA_TRUE);
1708 efl_gfx_entity_geometry_set(item, efl_ui_position_manager_entity_position_single_item(pd->manager, new_id));
1709}
1710static void
1711_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)
1712{
1713 MY_DATA_GET(pd->collection, cpd);
1714 Efl_Ui_Item *item = NULL;
1715 uint64_t item_id;
1716
1717 if (focus == efl_ui_focus_manager_root_get(obj))
1718 {
1719 // Find last item
1720 item_id = efl_model_children_count_get(cpd->model) - 1;
1721 }
1722 else
1723 {
1724 Efl_Model *model;
1725 Eina_Value *vindex;
1726
1727 item = _find_item(obj, cpd, focus);
1728 if (!item) return ;
1729
1730 model = efl_ui_view_model_get(item);
1731 vindex = efl_model_property_get(model, "child.index");
1732 if (!eina_value_uint64_convert(vindex, &item_id)) return;
1733 eina_value_free(vindex);
1734 }
1735
1736 // If this is NULL then we are before finalize, we cannot serve any sane value here
1737 if (!cpd->manager) return ;
1738
1739 if (ITEM_IS_OUTSIDE_VISIBLE(item_id))
1740 {
1741 _assert_item_available(item, item_id, cpd);
1742 }
1743 efl_ui_focus_manager_focus_set(efl_super(obj, EFL_UI_COLLECTION_VIEW_FOCUS_MANAGER_CLASS), focus);
1744}
1745
1746static Efl_Ui_Focus_Object *
1747_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)
1748{
1749 MY_DATA_GET(pd->collection, cpd);
1750 Efl_Ui_Item *new_item, *item;
1751 unsigned int item_id;
1752
1753 if (!child)
1754 child = efl_ui_focus_manager_focus_get(obj);
1755
1756 item = _find_item(obj, cpd, child);
1757
1758 //if this is NULL then we are before finalize, we cannot serve any sane value here
1759 if (!cpd->manager) return NULL;
1760 if (!item) return NULL;
1761
1762 item_id = efl_ui_item_index_get(item);
1763
1764 if (ITEM_IS_OUTSIDE_VISIBLE(item_id))
1765 {
1766 int new_id;
1767
1768 new_id = efl_ui_position_manager_entity_relative_item(cpd->manager,
1769 efl_ui_item_index_get(item),
1770 direction);
1771 if (new_id == -1)
1772 {
1773 new_item = NULL;
1774 }
1775 else
1776 {
1777 unsigned int i;
1778
1779 for (i = 0; i < 3; i++)
1780 {
1781 if (!cpd->viewport[i]) continue;
1782
1783 if (!((cpd->viewport[i]->offset <= (unsigned int) new_id) &&
1784 ((unsigned int) new_id < cpd->viewport[i]->offset + cpd->viewport[i]->count)))
1785 continue;
1786
1787 new_item = cpd->viewport[i]->items[new_id - cpd->viewport[i]->offset].entity;
1788 // We shouldn't get in a case where the available item is NULL
1789 if (!new_item) break; // Just in case
1790 _assert_item_available(new_item, new_id, cpd);
1791 }
1792 }
1793 }
1794 else
1795 {
1796 new_item = efl_ui_focus_manager_request_move(efl_super(obj, EFL_UI_COLLECTION_VIEW_FOCUS_MANAGER_CLASS), direction, child, logical);
1797 }
1798
1799 return new_item;
1800}
1801
1802#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..a0e4312a2d
--- /dev/null
+++ b/src/lib/elementary/efl_ui_collection_view.eo
@@ -0,0 +1,58 @@
1struct @beta Efl.Ui.Collection_View_Item_Event
2{
3 item: Efl.Ui.Widget;
4 model: Efl.Model;
5}
6
7class @beta Efl.Ui.Collection_View extends Efl.Ui.Layout_Base implements
8 Efl.Ui.Scrollable_Interactive,
9 Efl.Ui.Scrollbar,
10 Efl.Ui.Layout_Orientable,
11 Efl.Ui.Selectable,
12 Efl.Ui.Multi_Selectable,
13 Efl.Ui.Focus.Manager_Sub,
14 Efl.Ui.Widget_Focus_Manager
15
16{
17 methods {
18 @property factory {
19 [[Define the factory used to create all the items.]]
20 get {}
21 set {}
22 values {
23 factory: Efl.Ui.Factory; [[The factory.]]
24 }
25 }
26 @property position_manager {
27 [[Position manager object that handles placement of items.]]
28 values {
29 position_manager : Efl.Ui.Position_Manager.Entity @owned; [[The objects ownership is passed to the item container.]]
30 }
31 }
32 }
33 implements {
34 Efl.Object.constructor;
35 Efl.Object.invalidate;
36
37 Efl.Ui.Layout_Orientable.orientation { get; set; }
38
39 Efl.Ui.Widget.theme_apply;
40
41 Efl.Ui.Scrollable_Interactive.match_content { set; }
42 Efl.Ui.Multi_Selectable.select_mode {get; set;}
43 Efl.Ui.Widget_Focus_Manager.focus_manager_create;
44 Efl.Ui.Focus.Manager.move;
45 }
46 events {
47 item,realized : Efl.Ui.Collection_View_Item_Event;
48 item,unrealized : Efl.Ui.Collection_View_Item_Event;
49 item,focused : Efl.Ui.Collection_View_Item_Event;
50 item,unfocused : Efl.Ui.Collection_View_Item_Event;
51 item,highlighted : Efl.Ui.Collection_View_Item_Event;
52 item,unhighlighted : Efl.Ui.Collection_View_Item_Event;
53 }
54 composite {
55 Efl.Ui.Scrollable_Interactive;
56 Efl.Ui.Scrollbar;
57 }
58}
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 @@
1class @beta Efl.Ui.Collection_View_Focus_Manager extends Efl.Ui.Focus.Manager_Calc {
2 [[Internal class which implements collection specific behaviour, cannot be used outside of collection]]
3 implements {
4 Efl.Ui.Focus.Manager.manager_focus { set; }
5 Efl.Ui.Focus.Manager.request_move;
6 }
7}
diff --git a/src/lib/elementary/meson.build b/src/lib/elementary/meson.build
index a848f693dc..a8ed2eea50 100644
--- a/src/lib/elementary/meson.build
+++ b/src/lib/elementary/meson.build
@@ -184,6 +184,8 @@ pub_eo_files = [
184 'efl_ui_tab_bar_default_item.eo', 184 'efl_ui_tab_bar_default_item.eo',
185 'efl_ui_select_model.eo', 185 'efl_ui_select_model.eo',
186 'efl_ui_view_model.eo', 186 'efl_ui_view_model.eo',
187 'efl_ui_collection_view.eo',
188 'efl_ui_collection_view_focus_manager.eo',
187] 189]
188 190
189foreach eo_file : pub_eo_files 191foreach eo_file : pub_eo_files
@@ -944,6 +946,7 @@ elementary_src = [
944 'efl_ui_tab_bar_default_item.c', 946 'efl_ui_tab_bar_default_item.c',
945 'efl_ui_select_model.c', 947 'efl_ui_select_model.c',
946 'efl_ui_view_model.c', 948 'efl_ui_view_model.c',
949 'efl_ui_collection_view.c',
947] 950]
948 951
949elementary_deps = [emile, eo, efl, edje, ethumb, ethumb_client, emotion, ecore_imf, ecore_con, eldbus, efreet, efreet_mime, efreet_trash, eio, atspi, dl, intl] 952elementary_deps = [emile, eo, efl, edje, ethumb, ethumb_client, emotion, ecore_imf, ecore_con, eldbus, efreet, efreet_mime, efreet_trash, eio, atspi, dl, intl]