Efl.Ui.Grid: Add implementation of a grid/table

This is only a partial implementation, work in progress.

The grid object should be a replacement for evas and elementary's
table and grid. Only the layout of items inside the container
is different, otherwise the API should be the same.

For now, this widget is based on an evas table but the full
layout logic will need to be reimplemented, eventually.

@feature
This commit is contained in:
Jean-Philippe Andre 2016-04-12 14:10:47 +09:00
parent b18a993dc0
commit 2343aa7900
2 changed files with 684 additions and 0 deletions

View File

@ -0,0 +1,634 @@
#ifdef HAVE_CONFIG_H
# include "elementary_config.h"
#endif
#define EFL_PACK_PROTECTED
#include <Elementary.h>
#include "elm_priv.h"
#include "efl_ui_grid.eo.h"
#define MY_CLASS EFL_UI_GRID_CLASS
#define MY_CLASS_NAME "Efl.Ui.Grid"
typedef struct _Efl_Ui_Grid_Data Efl_Ui_Grid_Data;
typedef struct _Grid_Item_Iterator Grid_Item_Iterator;
typedef struct _Grid_Item Grid_Item;
struct _Grid_Item
{
EINA_INLIST;
Efl_Pack_Item *object;
int colspan, rowspan;
int col, row; // if linear, this may change
Eina_Bool linear : 1;
};
struct _Efl_Ui_Grid_Data
{
Grid_Item *items;
int cols, rows; // requested
int max_span;
int lastcol, lastrow;
Efl_Orient dir1, dir2;
struct {
double h, v;
Eina_Bool scalable: 1;
} pad;
Eina_Bool linear_recalc : 1;
};
struct _Grid_Item_Iterator
{
Eina_List *list;
Eina_Iterator iterator;
Eina_Iterator *real_iterator;
Efl_Ui_Grid *object;
};
static inline Eina_Bool
_horiz(Efl_Orient dir)
{
return dir % 180 == 0;
}
EOLIAN static Eina_Bool
_efl_ui_grid_elm_widget_focus_next_manager_is(Eo *obj EINA_UNUSED, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
return EINA_TRUE;
}
EOLIAN static Eina_Bool
_efl_ui_grid_elm_widget_focus_next(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED, Elm_Focus_Direction dir, Evas_Object **next, Elm_Object_Item **next_item)
{
Eina_Bool int_ret;
const Eina_List *items;
Eina_List *(*list_free)(Eina_List *list);
void *(*list_data_get)(const Eina_List *list);
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd, EINA_FALSE);
/* Focus chain */
/* TODO: Change this to use other chain */
if ((items = elm_widget_focus_custom_chain_get(obj)))
{
list_data_get = eina_list_data_get;
list_free = NULL;
}
else
{
items = evas_object_table_children_get
(wd->resize_obj);
list_data_get = eina_list_data_get;
list_free = eina_list_free;
if (!items) return EINA_FALSE;
}
int_ret = elm_widget_focus_list_next_get(obj, items, list_data_get, dir, next, next_item);
if (list_free) list_free((Eina_List *)items);
return int_ret;
}
EOLIAN static Eina_Bool
_efl_ui_grid_elm_widget_focus_direction_manager_is(Eo *obj EINA_UNUSED, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
return EINA_TRUE;
}
EOLIAN static Eina_Bool
_efl_ui_grid_elm_widget_focus_direction(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED, const Evas_Object *base, double degree, Evas_Object **direction, Elm_Object_Item **direction_item, double *weight)
{
Eina_Bool int_ret;
const Eina_List *items;
Eina_List *(*list_free)(Eina_List *list);
void *(*list_data_get)(const Eina_List *list);
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd, EINA_FALSE);
/* Focus chain */
/* TODO: Change this to use other chain */
if ((items = elm_widget_focus_custom_chain_get(obj)))
{
list_data_get = eina_list_data_get;
list_free = NULL;
}
else
{
items = evas_object_table_children_get
(wd->resize_obj);
list_data_get = eina_list_data_get;
list_free = eina_list_free;
if (!items) return EINA_FALSE;
}
int_ret = elm_widget_focus_list_direction_get
(obj, base, items, list_data_get, degree, direction, direction_item, weight);
if (list_free)
list_free((Eina_List *)items);
return int_ret;
}
static void
_mirrored_set(Evas_Object *obj, Eina_Bool rtl)
{
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
evas_object_table_mirrored_set(wd->resize_obj, rtl);
}
EOLIAN static Eina_Bool
_efl_ui_grid_elm_widget_theme_apply(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
Eina_Bool int_ret = EINA_FALSE;
int_ret = elm_obj_widget_theme_apply(eo_super(obj, MY_CLASS));
if (!int_ret) return EINA_FALSE;
_mirrored_set(obj, elm_widget_mirrored_get(obj));
return EINA_TRUE;
}
static void
_sizing_eval(Evas_Object *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
Evas_Coord minw = 0, minh = 0, maxw = -1, maxh = -1;
Evas_Coord w, h;
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
evas_object_size_hint_min_get
(wd->resize_obj, &minw, &minh);
evas_object_size_hint_max_get
(wd->resize_obj, &maxw, &maxh);
evas_object_size_hint_min_set(obj, minw, minh);
evas_object_size_hint_max_set(obj, maxw, maxh);
evas_object_geometry_get(obj, NULL, NULL, &w, &h);
if (w < minw) w = minw;
if (h < minh) h = minh;
if ((maxw >= 0) && (w > maxw)) w = maxw;
if ((maxh >= 0) && (h > maxh)) h = maxh;
evas_object_resize(obj, w, h);
}
static void
_on_size_hints_changed(void *data,
Evas *e EINA_UNUSED,
Evas_Object *obj EINA_UNUSED,
void *event_info EINA_UNUSED)
{
Efl_Ui_Grid_Data *pd = eo_data_scope_get(data, MY_CLASS);
_sizing_eval(data, pd);
}
EOLIAN static void
_efl_ui_grid_evas_object_smart_add(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
Evas_Object *table;
elm_widget_sub_object_parent_add(obj);
table = evas_object_table_add(evas_object_evas_get(obj));
elm_widget_resize_object_set(obj, table, EINA_TRUE);
evas_object_event_callback_add
(table, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _on_size_hints_changed, obj);
evas_obj_smart_add(eo_super(obj, MY_CLASS));
elm_widget_can_focus_set(obj, EINA_FALSE);
elm_widget_highlight_ignore_set(obj, EINA_FALSE);
elm_obj_widget_theme_apply(obj);
}
EOLIAN static void
_efl_ui_grid_evas_object_smart_del(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
Eina_List *l;
Evas_Object *child;
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
evas_object_event_callback_del_full
(wd->resize_obj, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
_on_size_hints_changed, obj);
/* let's make our table object the *last* to be processed, since it
* may (smart) parent other sub objects here */
EINA_LIST_FOREACH(wd->subobjs, l, child)
{
if (child == wd->resize_obj)
{
wd->subobjs =
eina_list_demote_list(wd->subobjs, l);
break;
}
}
evas_obj_smart_del(eo_super(obj, MY_CLASS));
}
EOLIAN static Eo *
_efl_ui_grid_eo_base_constructor(Eo *obj, Efl_Ui_Grid_Data *pd)
{
obj = eo_constructor(eo_super(obj, MY_CLASS));
evas_obj_type_set(obj, MY_CLASS_NAME);
elm_interface_atspi_accessible_role_set(obj, ELM_ATSPI_ROLE_FILLER);
pd->dir1 = EFL_ORIENT_RIGHT;
pd->dir2 = EFL_ORIENT_DOWN;
return obj;
}
EOLIAN static void
_efl_ui_grid_efl_pack_padding_set(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED, double h, double v, Eina_Bool scalable)
{
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
if (h < 0) h = 0;
if (v < 0) v = 0;
pd->pad.h = h;
pd->pad.v = v;
pd->pad.scalable = !!scalable;
if (pd->pad.scalable)
{
double scale = elm_object_scale_get(obj);
evas_object_table_padding_set(wd->resize_obj, h * scale, v * scale);
}
else
evas_object_table_padding_set(wd->resize_obj, h, v);
}
EOLIAN static void
_efl_ui_grid_efl_pack_padding_get(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED, double *h, double *v, Eina_Bool *scalable)
{
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
if (scalable) *scalable = pd->pad.scalable;
if (h) *h = pd->pad.h;
if (v) *v = pd->pad.v;
}
EOLIAN static void
_efl_ui_grid_efl_pack_grid_pack_grid(Eo *obj, Efl_Ui_Grid_Data *pd, Efl_Pack_Item *subobj, int col, int row, int colspan, int rowspan)
{
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
if (col < 0) col = 0;
if (row < 0) row = 0;
if (colspan < 1) colspan = 1;
if (rowspan < 1) rowspan = 1;
if ((0xffff - col) < colspan)
{
ERR("col + colspan > 0xffff. adjusted");
colspan = 0xffff - col;
}
if ((0xffff - row) < rowspan)
{
ERR("row + rowspan > 0xffff, adjusted");
rowspan = 0xffff - row;
}
if ((col + colspan) >= 0x7ffff)
WRN("col + colspan getting rather large (>32767)");
if ((row + rowspan) >= 0x7ffff)
WRN("row + rowspan getting rather large (>32767)");
if ((col + colspan - 1) > pd->lastcol)
pd->lastcol = col + colspan;
if ((row + rowspan - 1) > pd->lastrow)
pd->lastrow = row + rowspan;
elm_widget_sub_object_add(obj, subobj);
evas_object_table_pack(wd->resize_obj, subobj, col, row, colspan, rowspan);
}
EOLIAN static void
_efl_ui_grid_efl_pack_grid_grid_child_position_set(Eo *obj, Efl_Ui_Grid_Data *pd, Evas_Object *subobj, int col, int row, int colspan, int rowspan)
{
_efl_ui_grid_efl_pack_grid_pack_grid(obj, pd, subobj, col, row, colspan, rowspan);
}
EOLIAN static void
_efl_ui_grid_efl_pack_grid_grid_child_position_get(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED, Evas_Object *subobj, int *col, int *row, int *colspan, int *rowspan)
{
unsigned short icol, irow, icolspan, irowspan;
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
evas_object_table_pack_get
(wd->resize_obj, subobj, &icol, &irow, &icolspan, &irowspan);
if (col) *col = icol;
if (row) *row = irow;
if (colspan) *colspan = icolspan;
if (rowspan) *rowspan = irowspan;
}
EOLIAN static Eina_Bool
_efl_ui_grid_efl_pack_unpack(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED, Efl_Pack_Item *subobj)
{
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd, EINA_FALSE);
if (elm_widget_sub_object_del(obj, subobj))
{
evas_object_table_unpack(wd->resize_obj, subobj);
return EINA_TRUE;
}
return EINA_FALSE;
}
EOLIAN static void
_efl_ui_grid_efl_pack_clear(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
evas_object_table_clear(wd->resize_obj, EINA_TRUE);
}
EOLIAN static void
_efl_ui_grid_efl_pack_unpack_all(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
evas_object_table_clear(wd->resize_obj, EINA_FALSE);
}
EOLIAN void
_efl_ui_grid_evas_object_smart_calculate(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
evas_object_smart_calculate(wd->resize_obj);
}
EOLIAN void
_efl_ui_grid_efl_pack_layout_update(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
_sizing_eval(obj, pd);
}
EOLIAN void
_efl_ui_grid_efl_pack_layout_request(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
evas_object_smart_need_recalculate_set(obj, EINA_TRUE);
}
static Eina_Bool
_grid_item_iterator_next(Grid_Item_Iterator *it, void **data)
{
Efl_Pack_Item *sub;
if (!eina_iterator_next(it->real_iterator, (void **) &sub))
return EINA_FALSE;
if (data) *data = sub;
return EINA_TRUE;
}
static Elm_Layout *
_grid_item_iterator_get_container(Grid_Item_Iterator *it)
{
return it->object;
}
static void
_grid_item_iterator_free(Grid_Item_Iterator *it)
{
eina_iterator_free(it->real_iterator);
eina_list_free(it->list);
free(it);
}
EOLIAN static Eina_Iterator *
_efl_ui_grid_efl_pack_contents_iterate(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
Grid_Item_Iterator *it;
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd, NULL);
it = calloc(1, sizeof(*it));
if (!it) return NULL;
EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR);
it->list = evas_object_table_children_get(wd->resize_obj);
it->real_iterator = eina_list_iterator_new(it->list);
it->iterator.version = EINA_ITERATOR_VERSION;
it->iterator.next = FUNC_ITERATOR_NEXT(_grid_item_iterator_next);
it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER(_grid_item_iterator_get_container);
it->iterator.free = FUNC_ITERATOR_FREE(_grid_item_iterator_free);
it->object = obj;
return &it->iterator;
}
EOLIAN static int
_efl_ui_grid_efl_pack_contents_count(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED)
{
/* FIXME */
Eina_Iterator *it;
Efl_Pack_Item *pack;
int k = 0;
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd, 0);
it = evas_object_table_iterator_new(obj);
EINA_ITERATOR_FOREACH(it, pack)
k++;
eina_iterator_free(it);
return k;
}
EOLIAN static Eina_List *
_efl_ui_grid_efl_pack_grid_grid_children_at(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED, int col, int row)
{
Eina_List *l = NULL;
ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd, NULL);
/* FIXME: only one item returned */
return eina_list_append(l, evas_object_table_child_get(wd->resize_obj, col, row));
}
EOLIAN static void
_efl_ui_grid_efl_pack_linear_direction_set(Eo *obj, Efl_Ui_Grid_Data *pd, Efl_Orient orient)
{
EINA_SAFETY_ON_FALSE_RETURN((orient % 90) == 0);
pd->dir1 = orient;
/* if both orientations are either horizontal or vertical, need to adjust
* secondary orientation (dir2) */
if (_horiz(pd->dir1) == _horiz(pd->dir2))
{
if (!_horiz(pd->dir1))
pd->dir2 = EFL_ORIENT_RIGHT;
else
pd->dir2 = EFL_ORIENT_DOWN;
}
efl_pack_layout_request(obj);
}
EOLIAN static Efl_Orient
_efl_ui_grid_efl_pack_linear_direction_get(Eo *obj EINA_UNUSED, Efl_Ui_Grid_Data *pd)
{
return pd->dir1;
}
EOLIAN static void
_efl_ui_grid_efl_pack_grid_directions_set(Eo *obj, Efl_Ui_Grid_Data *pd, Efl_Orient primary, Efl_Orient secondary)
{
EINA_SAFETY_ON_FALSE_RETURN((primary % 90) == 0);
EINA_SAFETY_ON_FALSE_RETURN((secondary % 90) == 0);
pd->dir1 = primary;
pd->dir2 = secondary;
if (_horiz(pd->dir1) == _horiz(pd->dir2))
{
ERR("specified two orientations in the same axis, secondary orientation "
" is reset to a valid default");
switch (pd->dir1)
{
case EFL_ORIENT_DOWN:
case EFL_ORIENT_UP:
pd->dir2 = EFL_ORIENT_RIGHT;
break;
default:
pd->dir2 = EFL_ORIENT_DOWN;
break;
}
}
efl_pack_layout_request(obj);
}
EOLIAN static void
_efl_ui_grid_efl_pack_grid_directions_get(Eo *obj EINA_UNUSED, Efl_Ui_Grid_Data *pd, Efl_Orient *primary, Efl_Orient *secondary)
{
if (primary) *primary = pd->dir1;
if (secondary) *secondary = pd->dir2;
}
EOLIAN static void
_efl_ui_grid_efl_pack_grid_grid_size_set(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED, int cols, int rows)
{
/* FIXME: what's the behaviour if items were packed OUTSIDE this box? */
if (cols < 0) cols = 0;
if (rows < 0) rows = 0;
efl_pack_columns_set(obj, cols);
efl_pack_rows_set(obj, rows);
}
EOLIAN static void
_efl_ui_grid_efl_pack_grid_grid_size_get(Eo *obj EINA_UNUSED, Efl_Ui_Grid_Data *pd EINA_UNUSED, int *cols, int *rows)
{
if (cols) *cols = efl_pack_columns_get(obj);
if (rows) *rows = efl_pack_rows_get(obj);
}
EOLIAN static void
_efl_ui_grid_efl_pack_grid_max_span_set(Eo *obj, Efl_Ui_Grid_Data *pd, int maxx)
{
/* FIXME: what's the behaviour if items were packed OUTSIDE this range? */
if (maxx < 0) maxx = 0;
pd->max_span = maxx;
efl_pack_layout_request(obj);
}
EOLIAN static int
_efl_ui_grid_efl_pack_grid_max_span_get(Eo *obj EINA_UNUSED, Efl_Ui_Grid_Data *pd)
{
return pd->max_span;
}
EOLIAN static void
_efl_ui_grid_efl_pack_grid_columns_set(Eo *obj, Efl_Ui_Grid_Data *pd, int columns)
{
/* FIXME: what's the behaviour if items were packed OUTSIDE this range? */
pd->cols = columns;
efl_pack_layout_request(obj);
}
EOLIAN static int
_efl_ui_grid_efl_pack_grid_columns_get(Eo *obj EINA_UNUSED, Efl_Ui_Grid_Data *pd)
{
return pd->cols;
}
EOLIAN static void
_efl_ui_grid_efl_pack_grid_rows_set(Eo *obj, Efl_Ui_Grid_Data *pd, int rows)
{
/* FIXME: what's the behaviour if items were packed OUTSIDE this range? */
pd->rows = rows;
efl_pack_layout_request(obj);
}
EOLIAN static int
_efl_ui_grid_efl_pack_grid_rows_get(Eo *obj EINA_UNUSED, Efl_Ui_Grid_Data *pd)
{
return pd->rows;
}
EOLIAN static void
_efl_ui_grid_efl_pack_pack(Eo *obj, Efl_Ui_Grid_Data *pd EINA_UNUSED, Efl_Pack_Item *subobj)
{
/* this is just an alias */
efl_pack_end(obj, subobj);
}
EOLIAN static void
_efl_ui_grid_efl_pack_linear_pack_end(Eo *obj, Efl_Ui_Grid_Data *pd, Efl_Pack_Item *subobj)
{
EINA_SAFETY_ON_NULL_RETURN(subobj);
int col = pd->lastcol;
int row = pd->lastrow;
if (_horiz(pd->dir1))
{
col++;
if (pd->max_span && (col >= pd->max_span))
{
col = 0;
row++;
}
}
else
{
row++;
if (pd->max_span && (row >= pd->max_span))
{
row = 0;
col++;
}
}
DBG("packing new obj at %d,%d", col, row);
efl_pack_grid(obj, subobj, col, row, 1, 1);
}
#include "efl_ui_grid.eo.c"

View File

@ -0,0 +1,50 @@
class Efl.Ui.Grid (Elm.Widget, Efl.Pack_Grid)
{
methods {
}
implements {
Eo.Base.constructor;
Evas.Object_Smart.add;
Evas.Object_Smart.del;
Evas.Object_Smart.calculate;
Elm.Widget.focus_direction;
Elm.Widget.focus_next_manager_is;
Elm.Widget.focus_direction_manager_is;
Elm.Widget.focus_next;
Elm.Widget.theme_apply;
Efl.Pack.contents_iterate;
Efl.Pack.contents_count;
Efl.Pack.clear;
Efl.Pack.unpack_all;
Efl.Pack.unpack;
Efl.Pack.pack;
Efl.Pack.padding.get;
Efl.Pack.padding.set;
Efl.Pack.layout_update;
Efl.Pack.layout_request;
Efl.Pack_Grid.pack_grid;
Efl.Pack_Grid.grid_children_at;
Efl.Pack_Grid.grid_child_position.set;
Efl.Pack_Grid.grid_child_position.get;
Efl.Pack_Grid.grid_size.set;
Efl.Pack_Grid.grid_size.get;
Efl.Pack_Grid.max_span.set;
Efl.Pack_Grid.max_span.get;
Efl.Pack_Grid.columns.set;
Efl.Pack_Grid.columns.get;
Efl.Pack_Grid.rows.set;
Efl.Pack_Grid.rows.get;
Efl.Pack_Linear.pack_end;
//Efl.Pack_Linear.child_at.get;
//Efl.Pack_Linear.child_at.set;
//Efl.Pack_Linear.child_index.get;
//Efl.Pack_Linear.child_index.set;
Efl.Pack_Linear.direction.set;
Efl.Pack_Linear.direction.get;
Efl.Pack_Grid.directions.set;
Efl.Pack_Grid.directions.get;
}
}