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 <zmike@samsung.com>
Co-authored-by: Marcel Hollerbach <mail@marcel-hollerbach.de>

Differential Revision: https://phab.enlightenment.org/D9958
This commit is contained in:
Cedric BAIL 2019-07-05 14:03:13 -07:00 committed by Cedric Bail
parent 832d300e20
commit 2e1317baed
15 changed files with 2555 additions and 36 deletions

View File

@ -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 <Efl_Ui.h>
#include <Elementary.h>
#include <Efl.h>
#include <Eio.h>
#include <stdio.h>
#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()

View File

@ -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',

View File

@ -333,7 +333,7 @@ typedef Eo Efl_Ui_Spotlight_Indicator;
# include <efl_ui_list_view.eo.h>
# include <efl_ui_list_view_model.eo.h>
# include <efl_ui_view_model.eo.h>
# include <efl_ui_collection_view.eo.h>
# include <efl_ui_scroller.eo.h>
# include <efl_ui_pan.eo.h>

View File

@ -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.]]

File diff suppressed because it is too large Load Diff

View File

@ -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.]]
}
}

View File

@ -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;
}
}

View File

@ -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"

View File

@ -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; }
}
}

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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]