elementary: add an interface for asynchronous multi selection.

This interface is currently focusing on being use by widget like
Efl.Ui.Collection_View or Efl.Ui.Select_Model. I have decided that
it should not be available along with Efl.Ui.Multi_Selectable.

Differential Revision: https://phab.enlightenment.org/D9957
This commit is contained in:
Cedric Bail 2019-09-15 12:50:27 -07:00
parent 1c0916ef03
commit 3c9a46372e
9 changed files with 408 additions and 63 deletions

View File

@ -326,6 +326,7 @@ typedef Eo Efl_Ui_Spotlight_Indicator;
# include <efl_ui_tab_bar.eo.h>
# include <efl_ui_tab_page.eo.h>
# include <efl_ui_tab_pager.eo.h>
# include <efl_ui_multi_selectable_async.eo.h>
# include <efl_ui_select_model.eo.h>
# include "efl_ui_list_view_types.eot.h"

View File

@ -96,5 +96,13 @@ enum @beta Efl.Ui.Widget_Orientation_Mode
not change according to the window or screen orientation.]]
}
enum @beta Efl.Ui.Select_Mode {
[[Type of multi selectable object.]]
single, [[Only single child is selected. If a child is selected,
previous selected child will be unselected.]]
multi, [[Allow multiple selection of children.]]
none [[No child can be selected at all.]]
}
/* Types for A11Y (internal/beta API) */
type @beta @extern Efl.Access.Action_Data: __undefined_type; [[Internal struct for accesssibility.]]

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

View File

@ -1,10 +1,4 @@
enum @beta Efl.Ui.Select_Mode {
[[Type of multi selectable object.]]
single, [[Only single child is selected. If a child is selected,
previous selected child will be unselected.]]
multi, [[Allow multiple selection of children.]]
none [[No child can be selected at all.]]
}
import efl_ui;
interface @beta Efl.Ui.Multi_Selectable extends Efl.Ui.Single_Selectable
{

View File

@ -0,0 +1,63 @@
import efl_ui;
interface @beta Efl.Ui.Multi_Selectable_Async extends Efl.Ui.Single_Selectable
{
[[Interface for getting access to a range of selected items for widgets that provide them asynchronously.
The implementor of this interface provides the possibility to select multiple @Efl.Ui.Selectable.
If not, only @Efl.Ui.Single_Selectable should be implemented.
A widget can only provide either this interface or @Efl.Ui.Multi_Selectable, but not both.]]
methods
{
@property select_mode {
[[The mode type for children selection.]]
set {}
get {}
values {
mode: Efl.Ui.Select_Mode; [[Type of selection of children]]
}
}
selected_iterator_new {
[[Gets an iterator of all the selected child of this model.
]]
return: iterator<uint64> @move @no_unused; [[The iterator gives indices of selected children.
It is valid until any change is made on the model.]]
}
unselected_iterator_new {
[[Gets an iterator of all the child of this model that are not selected.
]]
return: iterator<uint64> @move @no_unused; [[The iterator gives indices of unselected children.
It is valid until any change is made on the model.]]
}
select_range {
[[Select a range of @Efl.Ui.Selectable.
This will select the range of selectables from $a to $b or from $b to $a depending on which one comes first.
If $a or $b are not in the range the widget, an error is returned, and no change is applied.
Both of the passed values will also be selected.
]]
params {
a : uint64; [[One side of the range.]]
b : uint64; [[The other side of the range.]]
}
}
unselect_range {
[[Unselect a range of @Efl.Ui.Selectable.
This will unselect the range of selectables from $a to $b or from $b to $a depending on which one comes first.
If $a or $b are not in the range of the widget, an error is returned, and no change is applied.
Both of the passed values will also be unselected.
]]
params {
a : uint64; [[One side of the range.]]
b : uint64; [[The other side of the range.]]
}
}
select_all {
[[Select all @Efl.Ui.Selectable]]
}
unselect_all {
[[Unselect all @Efl.Ui.Selectable]]
}
}
}

View File

@ -2,6 +2,10 @@
# include <config.h>
#endif
// Note: we do not rely on reflection here to implement select as it require to asynchronously acces
// children. Could be done differently by implementing the children select in the parent instead of
// in the children. For later optimization.
#include <Elementary.h>
#include "elm_priv.h"
@ -13,15 +17,21 @@ typedef struct _Efl_Ui_Select_Model_Data Efl_Ui_Select_Model_Data;
struct _Efl_Ui_Select_Model_Data
{
Efl_Ui_Select_Model_Data *parent;
Eina_Future *pending_selection_event;
Efl_Ui_Select_Model *fallback_model;
Efl_Ui_Select_Model *last_model;
unsigned long last;
Eina_Bool single_selection : 1;
Efl_Ui_Select_Mode selection;
Eina_Bool none : 1;
};
static Eo*
_efl_ui_select_model_efl_object_constructor(Eo *obj,
Efl_Ui_Select_Model_Data *pd EINA_UNUSED)
Efl_Ui_Select_Model_Data *pd)
{
Eo *parent;
@ -29,7 +39,9 @@ _efl_ui_select_model_efl_object_constructor(Eo *obj,
efl_boolean_model_boolean_add(obj, "selected", EINA_FALSE);
pd->last = -1;
efl_replace(&pd->last_model, NULL);
pd->last = 0;
pd->none = EINA_TRUE;
parent = efl_parent_get(obj);
if (efl_isa(parent, EFL_UI_SELECT_MODEL_CLASS))
@ -38,10 +50,61 @@ _efl_ui_select_model_efl_object_constructor(Eo *obj,
return obj;
}
static void
_efl_ui_select_model_efl_object_invalidate(Eo *obj,
Efl_Ui_Select_Model_Data *pd)
{
efl_replace(&pd->fallback_model, NULL);
efl_replace(&pd->last_model, NULL);
pd->none = EINA_TRUE;
efl_invalidate(efl_super(obj, EFL_UI_SELECT_MODEL_CLASS));
}
static void
_efl_ui_select_model_fallback(Efl_Ui_Select_Model_Data *pd)
{
Eina_Value selected;
if (!pd->parent) return;
if (!pd->parent->none) return;
if (!pd->parent->fallback_model) return;
// I think it only make sense to trigger the fallback on single mode
if (pd->parent->selection != EFL_UI_SELECT_MODE_SINGLE) return;
selected = eina_value_bool_init(EINA_TRUE);
efl_model_property_set(pd->parent->fallback_model, "self.selected", &selected);
eina_value_flush(&selected);
}
static Eina_Value
_select_notification_cb(Eo *o, void *data EINA_UNUSED, const Eina_Value v)
{
Efl_Ui_Select_Model_Data *pd = efl_data_scope_get(o, EFL_UI_SELECT_MODEL_CLASS);
efl_event_callback_call(o, EFL_UI_SINGLE_SELECTABLE_EVENT_SELECTION_CHANGED, NULL);
pd->pending_selection_event = NULL;
return v;
}
static void
_efl_ui_select_model_selection_notification(Eo *parent, Efl_Ui_Select_Model_Data *pd)
{
if (!pd) return;
if (pd->pending_selection_event) return;
pd->pending_selection_event = efl_future_then(parent,
efl_loop_job(efl_loop_get(parent)),
.success = _select_notification_cb);
}
static Eina_Value
_commit_change(Eo *child, void *data EINA_UNUSED, const Eina_Value v)
{
Efl_Ui_Select_Model_Data *pd;
Eo *parent;
Eina_Value *selected = NULL;
Eina_Bool selflag = EINA_FALSE;
@ -50,7 +113,8 @@ _commit_change(Eo *child, void *data EINA_UNUSED, const Eina_Value v)
selected = efl_model_property_get(child, "selected");
pd = efl_data_scope_get(efl_parent_get(child), EFL_UI_SELECT_MODEL_CLASS);
parent = efl_parent_get(child);
pd = efl_data_scope_get(parent, EFL_UI_SELECT_MODEL_CLASS);
if (!pd) goto on_error;
eina_value_bool_get(selected, &selflag);
@ -59,6 +123,7 @@ _commit_change(Eo *child, void *data EINA_UNUSED, const Eina_Value v)
// select case
pd->none = EINA_FALSE;
pd->last = efl_composite_model_index_get(child);
efl_replace(&pd->last_model, child);
efl_event_callback_call(child, EFL_UI_SELECT_MODEL_EVENT_SELECTED, child);
}
else
@ -69,11 +134,17 @@ _commit_change(Eo *child, void *data EINA_UNUSED, const Eina_Value v)
last = efl_composite_model_index_get(child);
if (pd->last == last)
{
efl_replace(&pd->last_model, NULL);
pd->last = 0;
pd->none = EINA_TRUE;
// Just in case we need to refill the fallback
_efl_ui_select_model_fallback(pd);
}
efl_event_callback_call(child, EFL_UI_SELECT_MODEL_EVENT_UNSELECTED, child);
}
_efl_ui_select_model_selection_notification(parent, pd);
efl_model_properties_changed(child, "self.selected");
on_error:
eina_value_free(selected);
@ -239,7 +310,7 @@ _untangle_free(void *data,
static Eina_Iterator *
_efl_ui_select_model_efl_model_properties_get(const Eo *obj,
Efl_Ui_Select_Model_Data *pd EINA_UNUSED)
Efl_Ui_Select_Model_Data *pd EINA_UNUSED)
{
EFL_COMPOSITE_MODEL_PROPERTIES_SUPER(props,
obj, EFL_UI_SELECT_MODEL_CLASS,
@ -250,22 +321,23 @@ _efl_ui_select_model_efl_model_properties_get(const Eo *obj,
static Eina_Future *
_efl_ui_select_model_efl_model_property_set(Eo *obj,
Efl_Ui_Select_Model_Data *pd,
const char *property, Eina_Value *value)
Efl_Ui_Select_Model_Data *pd,
const char *property, Eina_Value *value)
{
Eina_Value vf = EINA_VALUE_EMPTY;
if (eina_streq("single_selection", property))
{
Eina_Bool single_selection = pd->single_selection;
Eina_Bool single_selection = pd->selection == EFL_UI_SELECT_MODE_SINGLE;
Eina_Bool new_selection;
Eina_Bool changed;
vf = eina_value_bool_init(single_selection);
eina_value_convert(value, &vf);
eina_value_bool_get(&vf, &single_selection);
if (!eina_value_bool_get(value, &new_selection))
return efl_loop_future_rejected(obj, EINVAL);
changed = (!pd->single_selection != !single_selection);
pd->single_selection = !!single_selection;
changed = (!!new_selection != !!single_selection);
if (new_selection) pd->selection = EFL_UI_SELECT_MODE_SINGLE;
else pd->selection = EFL_UI_SELECT_MODE_MULTI;
if (changed) efl_model_properties_changed(obj, "single_selection");
@ -276,6 +348,9 @@ _efl_ui_select_model_efl_model_property_set(Eo *obj,
{
unsigned long l = 0;
if (pd->selection == EFL_UI_SELECT_MODE_NONE)
return efl_loop_future_rejected(obj, EFL_MODEL_ERROR_READ_ONLY);
if (!eina_value_ulong_convert(value, &l))
return efl_loop_future_rejected(obj, EFL_MODEL_ERROR_INCORRECT_VALUE);
@ -293,6 +368,9 @@ _efl_ui_select_model_efl_model_property_set(Eo *obj,
Eina_Value *prev;
Eina_Future *chain;
if (pd->parent->selection == EFL_UI_SELECT_MODE_NONE)
return efl_loop_future_rejected(obj, EFL_MODEL_ERROR_INCORRECT_VALUE);
prev = efl_model_property_get(efl_super(obj, EFL_UI_SELECT_MODEL_CLASS), "selected");
success = eina_value_bool_get(prev, &prevflag);
success &= eina_value_bool_convert(value, &newflag);
@ -303,7 +381,7 @@ _efl_ui_select_model_efl_model_property_set(Eo *obj,
if (newflag == prevflag)
return efl_loop_future_resolved(obj, eina_value_bool_init(newflag));
single_selection = pd->parent->single_selection;
single_selection = !!(pd->parent->selection == EFL_UI_SELECT_MODE_SINGLE);
// First store the new value in the boolean model we inherit from
chain = efl_model_property_set(efl_super(obj, EFL_UI_SELECT_MODEL_CLASS),
@ -321,7 +399,12 @@ _efl_ui_select_model_efl_model_property_set(Eo *obj,
i = efl_composite_model_index_get(obj);
if (pd->parent->last == i && !newflag)
pd->parent->none = EINA_TRUE;
{
efl_replace(&pd->last_model, NULL);
pd->parent->none = EINA_TRUE;
_efl_ui_select_model_fallback(pd);
}
}
else
{
@ -362,7 +445,7 @@ static Eina_Value *
_efl_ui_select_model_efl_model_property_get(const Eo *obj, Efl_Ui_Select_Model_Data *pd, const char *property)
{
if (eina_streq("single_selection", property))
return eina_value_bool_new(pd->single_selection);
return eina_value_bool_new(pd->selection == EFL_UI_SELECT_MODE_SINGLE);
// Last selected child
if (eina_streq("child.selected", property))
{
@ -374,34 +457,239 @@ _efl_ui_select_model_efl_model_property_get(const Eo *obj, Efl_Ui_Select_Model_D
// Redirect to are we ourself selected
if (pd->parent && eina_streq("self.selected", property))
{
if (pd->parent->selection == EFL_UI_SELECT_MODE_NONE)
return eina_value_bool_new(EINA_FALSE);
return efl_model_property_get(efl_super(obj, EFL_UI_SELECT_MODEL_CLASS), "selected");
}
return efl_model_property_get(efl_super(obj, EFL_UI_SELECT_MODEL_CLASS), property);
}
static void
_efl_ui_select_model_single_selection_set(Eo *obj EINA_UNUSED, Efl_Ui_Select_Model_Data *pd, Eina_Bool enable)
{
pd->single_selection = enable;
}
static Eina_Bool
_efl_ui_select_model_single_selection_get(const Eo *obj EINA_UNUSED, Efl_Ui_Select_Model_Data *pd)
{
return pd->single_selection;
}
static Eina_Iterator *
_efl_ui_select_model_selected_get(Eo *obj, Efl_Ui_Select_Model_Data *pd EINA_UNUSED)
_efl_ui_select_model_efl_ui_multi_selectable_async_selected_iterator_new(Eo *obj,
Efl_Ui_Select_Model_Data *pd)
{
if (pd->parent && pd->parent->selection == EFL_UI_SELECT_MODE_NONE)
return eina_list_iterator_new(NULL); // Quick hack to get a valid empty iterator
return efl_boolean_model_boolean_iterator_get(obj, "selected", EINA_TRUE);
}
static Eina_Iterator *
_efl_ui_select_model_unselected_get(Eo *obj, Efl_Ui_Select_Model_Data *pd EINA_UNUSED)
_efl_ui_select_model_efl_ui_multi_selectable_async_unselected_iterator_new(Eo *obj,
Efl_Ui_Select_Model_Data *pd EINA_UNUSED)
{
return efl_boolean_model_boolean_iterator_get(obj, "selected", EINA_FALSE);
}
static Efl_Ui_Selectable *
_efl_ui_select_model_efl_ui_single_selectable_last_selected_get(const Eo *obj EINA_UNUSED,
Efl_Ui_Select_Model_Data *pd)
{
return pd->last_model;
}
static void
_efl_ui_select_model_efl_ui_multi_selectable_async_select_mode_set(Eo *obj,
Efl_Ui_Select_Model_Data *pd,
Efl_Ui_Select_Mode mode)
{
switch (mode)
{
case EFL_UI_SELECT_MODE_SINGLE:
mode = EFL_UI_SELECT_MODE_SINGLE;
if (pd->selection == EFL_UI_SELECT_MODE_MULTI)
efl_ui_multi_selectable_async_unselect_all(obj);
break;
case EFL_UI_SELECT_MODE_NONE:
if (pd->selection == EFL_UI_SELECT_MODE_MULTI)
efl_ui_multi_selectable_async_unselect_all(obj);
else if (pd->last_model)
{
Eina_Value unselect = eina_value_bool_init(EINA_FALSE);
efl_model_property_set(pd->last_model, "self.selected", &unselect);
eina_value_flush(&unselect);
}
break;
case EFL_UI_SELECT_MODE_MULTI:
break;
default:
ERR("Unknown select mode passed to %s: %i.", efl_debug_name_get(obj), mode);
return;
}
pd->selection = mode;
efl_model_properties_changed(obj, "single_selection", "child.selected");
}
static Efl_Ui_Select_Mode
_efl_ui_select_model_efl_ui_multi_selectable_async_select_mode_get(const Eo *obj EINA_UNUSED,
Efl_Ui_Select_Model_Data *pd)
{
return pd->selection;
}
static void
_efl_ui_select_model_efl_ui_multi_selectable_async_select_all(Eo *obj,
Efl_Ui_Select_Model_Data *pd EINA_UNUSED)
{
unsigned long count, i;
// Not the fastest way to implement it, but will reuse more code and be easier as a v1.
// It also make it not very async which could be noticable.
count = efl_model_children_count_get(obj);
for (i = 0; i < count; i++)
{
Eina_Value p = eina_value_ulong_init(i);
efl_model_property_set(obj, "child.selected", &p);
eina_value_flush(&p);
}
}
static void
_efl_ui_select_model_efl_ui_multi_selectable_async_unselect_all(Eo *obj,
Efl_Ui_Select_Model_Data *pd EINA_UNUSED)
{
uint64_t count = efl_model_children_count_get(obj);
efl_ui_multi_selectable_async_unselect_range(obj, 0, count - 1);
}
static void
_efl_ui_select_model_efl_ui_multi_selectable_async_select_range(Eo *obj,
Efl_Ui_Select_Model_Data *pd EINA_UNUSED,
uint64_t a, uint64_t b)
{
unsigned long count, i;
// Not the fastest way to implement it, but will reuse more code and be easier as a v1.
// It also make it not very async which could be noticable.
count = MIN(efl_model_children_count_get(obj), b + 1);
for (i = a; i < count; i++)
{
Eina_Value p = eina_value_ulong_init(i);
efl_model_property_set(obj, "child.selected", &p);
eina_value_flush(&p);
}
}
static Eina_Value
_children_unselect_then(Eo *o EINA_UNUSED, void *data EINA_UNUSED, const Eina_Value v)
{
Eo *target;
Eina_Value unselect;
unsigned int i, len;
unselect = eina_value_bool_init(EINA_FALSE);
EINA_VALUE_ARRAY_FOREACH(&v, len, i, target)
{
efl_model_property_set(target, "self.selected", &unselect);
}
eina_value_flush(&unselect);
return v;
}
#define BATCH_MAX 100
static void
_efl_ui_select_model_efl_ui_multi_selectable_async_unselect_range(Eo *obj,
Efl_Ui_Select_Model_Data *pd EINA_UNUSED,
uint64_t a, uint64_t b)
{
unsigned int count, batch, i;
count = MIN(efl_model_children_count_get(obj), b + 1);
// Fetch group request of children in batches not to big to allow for throttling
// In the callback edit said object property to be unselected
i = a;
batch = 0;
while (i < count)
{
Eina_Future *f;
batch = MIN(i + BATCH_MAX, count);
batch -= i;
f = efl_model_children_slice_get(obj, i, batch);
efl_future_then(obj, f, .success_type = EINA_VALUE_TYPE_ARRAY,
.success = _children_unselect_then);
i += batch;
}
}
static void
_efl_ui_select_model_efl_ui_single_selectable_fallback_selection_set(Eo *obj,
Efl_Ui_Select_Model_Data *pd,
Efl_Ui_Selectable *fallback)
{
Eina_Value *index;
if (!efl_isa(fallback, EFL_UI_SELECT_MODEL_CLASS))
{
ERR("Class of object '%s' does not provide the necessary interface for Efl.Ui.Select_Model.fallback.", efl_debug_name_get(fallback));
return;
}
if (efl_parent_get(fallback) != obj)
{
ERR("Provided object '%s' for fallback isn't a child of '%s'.",
efl_debug_name_get(fallback), efl_debug_name_get(obj));
return;
}
efl_replace(&pd->fallback_model, fallback);
if (!pd->none) return ;
// When we provide a fallback, we should use it!
index = efl_model_property_get(fallback, EFL_COMPOSITE_MODEL_CHILD_INDEX);
efl_model_property_set(obj, "child.selected", index);
eina_value_free(index);
}
static Efl_Ui_Selectable *
_efl_ui_select_model_efl_ui_single_selectable_fallback_selection_get(const Eo *obj EINA_UNUSED,
Efl_Ui_Select_Model_Data *pd)
{
return pd->fallback_model;
}
static void
_efl_ui_select_model_efl_ui_selectable_selected_set(Eo *obj,
Efl_Ui_Select_Model_Data *pd EINA_UNUSED,
Eina_Bool selected)
{
Eina_Value set = eina_value_bool_init(selected);
efl_model_property_set(obj, "self.selected", &set);
eina_value_flush(&set);
}
static Eina_Bool
_efl_ui_select_model_efl_ui_selectable_selected_get(const Eo *obj,
Efl_Ui_Select_Model_Data *pd EINA_UNUSED)
{
Eina_Value *selected;
Eina_Bool r = EINA_FALSE;
selected = efl_model_property_get(obj, "self.selected");
eina_value_bool_convert(selected, &r);
eina_value_free(selected);
return r;
}
#include "efl_ui_select_model.eo.c"
#include "efl_ui_multi_selectable_async.eo.c"

View File

@ -1,33 +1,23 @@
class @beta Efl.Ui.Select_Model extends Efl.Boolean_Model
implements Efl.Ui.Multi_Selectable_Async,
Efl.Ui.Selectable
{
[[Efl ui select model class]]
methods {
selected_get {
[[Gets an iterator of all the selected child of this model.
]]
return: iterator<uint64>; [[The iterator gives indices of selected children.
It is valid until any change is made on the model.]]
}
unselected_get {
[[Gets an iterator of all the child of this model that are not selected.
]]
return: iterator<uint64>; [[The iterator gives indices of unselected children.
It is valid until any change is made on the model.]]
}
@property single_selection {
[[Defines if we support only one exclusive selection at a time when set to $true.
If disable with $false, it will have the behavior of a multi select mode.
]]
values {
enable: bool; [[$true will enable the exclusive mode.]]
}
}
}
implements {
Efl.Object.constructor;
Efl.Object.invalidate;
Efl.Model.property { get; set; }
Efl.Model.properties { get; }
Efl.Ui.Single_Selectable.last_selected { get; }
Efl.Ui.Multi_Selectable_Async.selected_iterator_new;
Efl.Ui.Multi_Selectable_Async.unselected_iterator_new;
Efl.Ui.Multi_Selectable_Async.select_mode {get; set;}
Efl.Ui.Multi_Selectable_Async.select_all;
Efl.Ui.Multi_Selectable_Async.unselect_all;
Efl.Ui.Multi_Selectable_Async.select_range;
Efl.Ui.Multi_Selectable_Async.unselect_range;
Efl.Ui.Single_Selectable.fallback_selection {get; set;}
Efl.Ui.Selectable.selected {get; set;}
}
events {
/* FIXME: The object is emitted in the event_info. This is redundant. */

View File

@ -180,6 +180,7 @@ pub_eo_files = [
'efl_ui_position_manager_grid.eo',
'efl_ui_selectable.eo',
'efl_ui_multi_selectable.eo',
'efl_ui_multi_selectable_async.eo',
'efl_ui_single_selectable.eo',
'efl_ui_position_manager_data_access_v1.eo',
'efl_ui_tab_bar_default_item.eo',

View File

@ -111,12 +111,12 @@ EFL_START_TEST(efl_test_select_model)
ecore_main_loop_begin();
it = efl_ui_select_model_selected_get(model);
it = efl_ui_multi_selectable_async_selected_iterator_new(model);
EINA_ITERATOR_FOREACH(it, index)
fail_if(*index != 2);
eina_iterator_free(it);
it = efl_ui_select_model_unselected_get(model);
it = efl_ui_multi_selectable_async_unselected_iterator_new(model);
EINA_ITERATOR_FOREACH(it, index)
fail_if(*index == 2);
eina_iterator_free(it);