From 2343aa79005bb0d9de69f375047b033298bb8953 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Andre Date: Tue, 12 Apr 2016 14:10:47 +0900 Subject: [PATCH] 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 --- src/lib/elementary/efl_ui_grid.c | 634 ++++++++++++++++++++++++++++++++++++++ src/lib/elementary/efl_ui_grid.eo | 50 +++ 2 files changed, 684 insertions(+) create mode 100644 src/lib/elementary/efl_ui_grid.c create mode 100644 src/lib/elementary/efl_ui_grid.eo diff --git a/src/lib/elementary/efl_ui_grid.c b/src/lib/elementary/efl_ui_grid.c new file mode 100644 index 0000000..5181d30 --- /dev/null +++ b/src/lib/elementary/efl_ui_grid.c @@ -0,0 +1,634 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define EFL_PACK_PROTECTED + +#include +#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" diff --git a/src/lib/elementary/efl_ui_grid.eo b/src/lib/elementary/efl_ui_grid.eo new file mode 100644 index 0000000..accbddc --- /dev/null +++ b/src/lib/elementary/efl_ui_grid.eo @@ -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; + } +} -- 2.7.4