efl_ui_table: refactor layout_update
authorYeongjong Lee <cleanlyj@naver.com>
Wed, 27 Feb 2019 19:45:27 +0000 (14:45 -0500)
committerWonki Kim <wonki_.kim@samsung.com>
Fri, 8 Mar 2019 11:49:36 +0000 (20:49 +0900)
Summary:
There are three reasons to refactor layout_update of Efl.Ui.Table.

=== 1. Inconsistency of hint behavior. ===

Some hint property is often not respected. for example, hint_min is ignored in
Table when it is used with hint_max even if hint_weight is 0. hint_aspect is
always ignored in Table.
The ambiguous behavior make it hard to layout widgets in container. of course,
we documented 'it's just a hint that should be used whenever appropriate.' but i
don't think it means that 'hint API is sometimes respected and we also don't
know when that API is respected.'. at least there is rule for consistent
behavior and we should be able to explain why a widget is located here and
why some hint property is ignored.

So, i'll suggest priority of hint property. this refactoring support following
priority.
1) HintMin
2) HintMin + HintAspect
3) HintMargin
4) HintMax
5) HintAspect
6) HintWeight, HintFill
7) HintAlign

ref T5487
Please check with unit test D7840

=== 2. To Enhance usability. ===

Efl.Ui.Table is using homogeneous mode of evas_table which have same columns,
rows size. but i think a table can generally change columns, rows size and
we can provide homogeneous mode option.(D7892)

In this patch
 - table columns(rows) min size is decided by maximum size among its cells
width(height) min size.
 - table columns(rows) weight is decided by maximum weight among its cells
horizontal(vertical) weight.

Also, pack_align is implemented. it is used if no item has a weight.

=== 3. To remove internal evas_table. ===

This is low priority work. however, i guess is is necessary for lightweight
container widget. there are two size_hint callback to adjust table size and
efl_canvas_group_calculate is called twice when it is resized.
This patch is first step to remove internal evas_table.

Test Plan:
make check
elementary_test -to 'efl.ui.table'

Reviewers: jpeg, Jaehyun_Cho, zmike

Reviewed By: zmike

Subscribers: zmike, cedric, #reviewers, #committers

Tags: #efl

Maniphest Tasks: T5487

Differential Revision: https://phab.enlightenment.org/D7841

src/Makefile_Elementary.am
src/bin/elementary/test_ui_table.c
src/lib/elementary/efl_ui_box_layout.c
src/lib/elementary/efl_ui_container_layout.c [new file with mode: 0644]
src/lib/elementary/efl_ui_container_layout.h [new file with mode: 0644]
src/lib/elementary/efl_ui_table.c
src/lib/elementary/efl_ui_table.eo
src/lib/elementary/efl_ui_table_layout.c [new file with mode: 0644]
src/lib/elementary/efl_ui_table_private.h
src/lib/elementary/meson.build

index 0386d3d..67936bc 100644 (file)
@@ -851,6 +851,8 @@ lib_elementary_libelementary_la_SOURCES = \
        lib/elementary/els_cursor.c \
        lib/elementary/els_tooltip.c \
        lib/elementary/elu_ews_wm.c \
+       lib/elementary/efl_ui_container_layout.c \
+       lib/elementary/efl_ui_container_layout.h \
        lib/elementary/efl_ui_box.c \
        lib/elementary/efl_ui_box_flow.c \
        lib/elementary/efl_ui_box_stack.c \
@@ -863,6 +865,7 @@ lib_elementary_libelementary_la_SOURCES = \
        lib/elementary/efl_ui_anchor_popup.c \
        lib/elementary/efl_ui_table.c \
        lib/elementary/efl_ui_table_static.c \
+       lib/elementary/efl_ui_table_layout.c \
        lib/elementary/efl_ui_table_private.h \
        lib/elementary/efl_ui_text.c \
        lib/elementary/efl_ui_text_factory_images.c \
index 098fe13..2f698f1 100644 (file)
@@ -34,31 +34,31 @@ weights_cb(void *data, const Efl_Event *event)
      {
       case NONE:
         efl_gfx_hint_align_set(table, 0.5, 0.5);
-        for (int i = 0; i < 7; i++)
+        for (int i = 1; i < 7; i++)
           efl_gfx_hint_weight_set(objects[i], 0, 0);
         break;
       case NONE_BUT_FILL:
         efl_gfx_hint_fill_set(table, EINA_TRUE, EINA_TRUE);
-        for (int i = 0; i < 7; i++)
+        for (int i = 1; i < 7; i++)
           efl_gfx_hint_weight_set(objects[i], 0, 0);
         break;
       case EQUAL:
         efl_gfx_hint_align_set(table, 0.5, 0.5);
-        for (int i = 0; i < 7; i++)
+        for (int i = 1; i < 7; i++)
           efl_gfx_hint_weight_set(objects[i], 1, 1);
         break;
       case ONE:
         efl_gfx_hint_align_set(table, 0.5, 0.5);
-        for (int i = 0; i < 6; i++)
+        for (int i = 1; i < 7; i++)
           efl_gfx_hint_weight_set(objects[i], 0, 0);
-        efl_gfx_hint_weight_set(objects[6], 1, 1);
+        efl_gfx_hint_weight_set(objects[2], 1, 1);
         break;
       case TWO:
         efl_gfx_hint_align_set(table, 0.5, 0.5);
-        for (int i = 0; i < 5; i++)
+        for (int i = 1; i < 7; i++)
           efl_gfx_hint_weight_set(objects[i], 0, 0);
-        efl_gfx_hint_weight_set(objects[5], 1, 1);
-        efl_gfx_hint_weight_set(objects[6], 1, 1);
+        efl_gfx_hint_weight_set(objects[2], 1, 1);
+        efl_gfx_hint_weight_set(objects[3], 1, 1);
         break;
       case CUSTOM:
         efl_object_override(table, &custom_layout_ops);
@@ -89,11 +89,11 @@ margin_slider_cb(void *data, const Efl_Event *event)
 }
 
 static void
-btnmargins_slider_cb(void *data, const Efl_Event *event)
+btnmargins_slider_cb(void *data EINA_UNUSED, const Efl_Event *event)
 {
    int val = elm_slider_value_get(event->object);
    for (int i = 1; i < 7; i++)
-     efl_gfx_hint_margin_set(data, val, val, val, val);
+     efl_gfx_hint_margin_set(objects[i], val, val, val, val);
 }
 
 static void
@@ -333,7 +333,7 @@ test_ui_table(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_
    efl_event_callback_add(o, EFL_UI_SLIDER_EVENT_CHANGED, padding_slider_cb, table);
    elm_slider_min_max_set(o, 0, 40);
    elm_slider_inverted_set(o, 1);
-   elm_slider_value_set(o, 10);
+   elm_slider_value_set(o, 0);
    efl_pack(bx, o);
    efl_gfx_entity_visible_set(o, 1);
 
@@ -359,7 +359,7 @@ test_ui_table(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_
    efl_event_callback_add(o, EFL_UI_SLIDER_EVENT_CHANGED, margin_slider_cb, table);
    elm_slider_min_max_set(o, 0, 40);
    elm_slider_inverted_set(o, 1);
-   elm_slider_value_set(o, 10);
+   elm_slider_value_set(o, 0);
    efl_pack(bx, o);
    efl_gfx_entity_visible_set(o, 1);
 
@@ -386,7 +386,7 @@ test_ui_table(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_
    efl_event_callback_add(o, EFL_UI_SLIDER_EVENT_CHANGED, btnmargins_slider_cb, table);
    elm_slider_min_max_set(o, 0, 40);
    elm_slider_inverted_set(o, 1);
-   elm_slider_value_set(o, 10);
+   elm_slider_value_set(o, 0);
    efl_pack(bx, o);
    efl_gfx_entity_visible_set(o, 1);
 
index 90c5d1b..bc9ea63 100644 (file)
@@ -1,6 +1,7 @@
 #define EFL_GFX_HINT_PROTECTED
 
 #include "efl_ui_box_private.h"
+#include "efl_ui_container_layout.h"
 
 // FIXME: handle RTL? just invert the horizontal order?
 
@@ -11,65 +12,19 @@ struct _Item_Calc
    EINA_INLIST;
 
    Evas_Object *obj;
-   double weight[2];
-   double align[2];
-   double space[2];
-   double comp_factor;
-   Eina_Bool fill[2];
-   Eina_Size2D max, min, aspect;
-   int pad[4];
-   Efl_Gfx_Hint_Aspect aspect_type;
-   int id;
+   double weight_factor;
+   Efl_Ui_Container_Item_Hints hints[2]; /* 0 is x-axis, 1 is y-axis */
 };
 
 static int
-weight_sort_cb(const void *l1, const void *l2)
+_weight_sort_cb(const void *l1, const void *l2)
 {
    Item_Calc *it1, *it2;
 
    it1 = EINA_INLIST_CONTAINER_GET(l1, Item_Calc);
    it2 = EINA_INLIST_CONTAINER_GET(l2, Item_Calc);
 
-   return it2->comp_factor <= it1->comp_factor ? -1 : 1;
-}
-
-static inline void
-_min_max_calc(Item_Calc *item, int *cw, int *ch, Eina_Bool aspect_check)
-{
-   int w = *cw, h = *ch;
-
-   if (aspect_check)
-     {
-        w = h * item->aspect.w / item->aspect.h;
-        if (w > *cw)
-          {
-             w = *cw;
-             h = w * item->aspect.h / item->aspect.w;
-          }
-     }
-
-   if (w > item->max.w)
-     {
-        w = item->max.w;
-        if (aspect_check) h = w * item->aspect.h / item->aspect.w;
-     }
-   if (h > item->max.h)
-     {
-        h = item->max.h;
-        if (aspect_check) w = h * item->aspect.w / item->aspect.h;
-     }
-   if (w < item->min.w)
-     {
-        w = item->min.w;
-        if (aspect_check) h = w * item->aspect.h / item->aspect.w;
-     }
-   if (h < item->min.h)
-     {
-        h = item->min.h;
-        if (aspect_check) w = h * item->aspect.w / item->aspect.h;
-     }
-   *cw = w;
-   *ch = h;
+   return it2->weight_factor <= it1->weight_factor ? -1 : 1;
 }
 
 void
@@ -77,37 +32,17 @@ _efl_ui_box_custom_layout(Efl_Ui_Box *ui_box, Evas_Object_Box_Data *bd)
 {
    Efl_Ui_Box_Data *pd = efl_data_scope_get(ui_box, EFL_UI_BOX_CLASS);
    Evas_Object_Box_Option *opt;
-   Evas_Object *o;
    Eina_List *li;
    Eina_Inlist *inlist = NULL;
-   int wantw = 0, wanth = 0; // requested size
-   Eina_Rect boxs;
    Item_Calc *items, *item;
-   Eina_Bool horiz = efl_ui_dir_is_horizontal(pd->dir, EINA_FALSE);
-   int id = 0, count, boxl = 0, boxr = 0, boxt = 0, boxb = 0;
-   int length, want, pad;
-   double cur_pos, weight[2] = { 0, 0 }, scale, mmin = 0;
-   double box_align[2];
-   Eina_Bool box_fill[2] = { EINA_FALSE, EINA_FALSE };
-
-   boxs = efl_gfx_entity_geometry_get(ui_box);
-   efl_gfx_hint_margin_get(ui_box, &boxl, &boxr, &boxt, &boxb);
-   scale = efl_gfx_entity_scale_get(ui_box);
-
-   // Box align: used if "item has max size and fill" or "no item has a weight"
-   // Note: cells always expand on the orthogonal direction
-   box_align[0] = pd->align.h;
-   box_align[1] = pd->align.v;
-   if (box_align[0] < 0)
-     {
-        box_fill[0] = EINA_TRUE;
-        box_align[0] = 0.5;
-     }
-   if (box_align[1] < 0)
-     {
-        box_fill[1] = EINA_TRUE;
-        box_align[1] = 0.5;
-     }
+   Efl_Ui_Container_Item_Hints *hints, *hint;
+   Eina_Bool axis = !efl_ui_dir_is_horizontal(pd->dir, EINA_FALSE);
+   Eina_Bool r_axis = !axis;
+   int want[2] = { 0, 0 };
+   int count, i = 0;
+   double cur_pos, mmin = 0, weight_sum = 0;
+   Efl_Ui_Container_Layout_Calc box_calc[2]; /* 0 is x-axis, 1 is y-axis */
+
 
    count = eina_list_count(bd->children);
    if (!count)
@@ -116,240 +51,153 @@ _efl_ui_box_custom_layout(Efl_Ui_Box *ui_box, Evas_Object_Box_Data *bd)
         return;
      }
 
+   _efl_ui_container_layout_init(ui_box, box_calc);
+
    items = alloca(count * sizeof(*items));
 #ifdef DEBUG
    memset(items, 0, count * sizeof(*items));
 #endif
 
-   // box outer margin
-   boxs.w -= boxl + boxr;
-   boxs.h -= boxt + boxb;
-   boxs.x += boxl;
-   boxs.y += boxt;
-
    // scan all items, get their properties, calculate total weight & min size
    EINA_LIST_FOREACH(bd->children, li, opt)
      {
-        item = &items[id];
-        o = item->obj = opt->obj;
-
-        efl_gfx_hint_weight_get(o, &item->weight[0], &item->weight[1]);
-        efl_gfx_hint_align_get(o, &item->align[0], &item->align[1]);
-        efl_gfx_hint_margin_get(o, &item->pad[0], &item->pad[1], &item->pad[2], &item->pad[3]);
-        efl_gfx_hint_fill_get(o, &item->fill[0], &item->fill[1]);
-        item->max = efl_gfx_hint_size_max_get(o);
-        item->min = efl_gfx_hint_size_combined_min_get(o);
-        efl_gfx_hint_aspect_get(o, &item->aspect_type, &item->aspect);
-
-        if (horiz && (box_fill[0] || pd->homogeneous)) item->weight[0] = 1;
-        else if (item->weight[0] < 0) item->weight[0] = 0;
-        if (!horiz && (box_fill[1] || pd->homogeneous)) item->weight[1] = 1;
-        else if (item->weight[1] < 0) item->weight[1] = 0;
-
-        if (EINA_DBL_EQ(item->align[0], -1))
-          {
-             item->align[0] = 0.5;
-             item->fill[0] = EINA_TRUE;
-          }
-        else if (item->align[0] < 0) item->align[0] = 0;
-        else if (item->align[0] > 1) item->align[0] = 1;
-        if (EINA_DBL_EQ(item->align[1], -1))
-          {
-             item->align[1] = 0.5;
-             item->fill[1] = EINA_TRUE;
-          }
-        else if (item->align[1] < 0) item->align[1] = 0;
-        else if (item->align[1] > 1) item->align[1] = 1;
+        item = &items[i++];
+        item->obj = opt->obj;
+        hints = item->hints;
 
-        if (item->min.w < 0) item->min.w = 0;
-        if (item->min.h < 0) item->min.h = 0;
+        _efl_ui_container_layout_item_init(item->obj, hints);
 
-        if (item->max.w < 0) item->max.w = INT_MAX;
-        if (item->max.h < 0) item->max.h = INT_MAX;
+        if (pd->homogeneous || box_calc[0].fill)
+          hints[0].weight = 1;
+        else if (hints[0].weight < 0)
+          hints[0].weight = 0;
 
-        weight[0] += item->weight[0];
-        weight[1] += item->weight[1];
+        if (pd->homogeneous || box_calc[1].fill)
+          hints[1].weight = 1;
+        else if (hints[1].weight < 0)
+          hints[1].weight = 0;
 
-        if ((item->aspect.w <= 0) || (item->aspect.h <= 0))
-          {
-             if ((item->aspect.w <= 0) ^ (item->aspect.h <= 0))
-               {
-                  ERR("Invalid aspect parameter for obj: %p", item->obj);
-                  item->aspect.w = item->aspect.h = 0;
-                  item->aspect_type = EFL_GFX_HINT_ASPECT_NONE;
-               }
-          }
-        else
-          {
-             _min_max_calc(item, &item->min.w, &item->min.h, EINA_TRUE);
-          }
+        weight_sum += hints[axis].weight;
 
-        item->space[0] = item->min.w + item->pad[0] + item->pad[1];
-        item->space[1] = item->min.h + item->pad[2] + item->pad[3];
+        if (hints[r_axis].space > want[r_axis])
+          want[r_axis] = hints[r_axis].space;
 
-        if (horiz)
+        if (pd->homogeneous)
           {
-             if (item->space[1] > wanth)
-               wanth = item->space[1];
-             if (pd->homogeneous)
-               {
-                  if (item->space[0] > mmin)
-                    mmin = item->space[0];
-               }
-             else
-               {
-                  wantw += item->space[0];
-               }
+             if (hints[axis].space > mmin)
+               mmin = hints[axis].space;
           }
         else
           {
-             if (item->space[0] > wantw)
-               wantw = item->space[0];
-             if (pd->homogeneous)
-               {
-                  if (item->space[1] > mmin)
-                    mmin = item->space[1];
-               }
-             else
-               {
-                  wanth += item->space[1];
-               }
+             want[axis] += hints[axis].space;
           }
-
-        item->id = id++;
      }
 
    // total space & available space
-   if (horiz)
-     {
-        if (pd->homogeneous)
-          wantw = mmin * count;
-        want = wantw;
-        length = boxs.w;
-        pad = pd->pad.scalable ? (pd->pad.h * scale) : pd->pad.h;
-        if (boxs.h < wanth)
-          boxs.h = wanth;
-     }
-   else
-     {
-        if (pd->homogeneous)
-          wanth = mmin * count;
-        want = wanth;
-        length = boxs.h;
-        pad = pd->pad.scalable ? (pd->pad.v * scale) : pd->pad.v;
-        if (boxs.w < wantw)
-          boxs.w = wantw;
-     }
+   if (pd->homogeneous)
+     want[axis] = mmin * count;
+
+   if (box_calc[r_axis].size < want[r_axis])
+     box_calc[r_axis].size = want[r_axis];
 
    // padding can not be squeezed (note: could make it an option)
-   length -= pad * (count - 1);
-   cur_pos = horiz ? boxs.x : boxs.y;
+   box_calc[axis].size -= (box_calc[axis].pad * (count - 1));
+   box_calc[r_axis].pad = 0;
+   cur_pos = box_calc[axis].pos;
 
-   // calculate weight length
-   if (!pd->homogeneous && (length > want) && (weight[!horiz] > 0))
+   // calculate weight size
+   if (!pd->homogeneous && (box_calc[axis].size > want[axis]) && (weight_sum > 0))
      {
-        int orig_length = length;
-        double orig_weight = weight[!horiz];
+        int orig_size, calc_size;
+        double orig_weight = weight_sum;
+
+        calc_size = orig_size = box_calc[axis].size;
 
-        for (id = 0; id < count; id++)
+        for (i = 0; i < count; i++)
           {
              double denom;
-             item = &items[id];
+             hint = &items[i].hints[axis];
 
-             denom = (item->weight[!horiz] * orig_length) -
-                     (orig_weight * item->space[!horiz]);
+             denom = (hint->weight * orig_size) - (orig_weight * hint->space);
              if (denom > 0)
                {
-                  item->comp_factor = (item->weight[!horiz] * orig_length) / denom;
-                  inlist = eina_inlist_sorted_insert(inlist, EINA_INLIST_GET(item),
-                                                     weight_sort_cb);
+                  items[i].weight_factor = (hint->weight * orig_size) / denom;
+                  inlist = eina_inlist_sorted_insert(inlist, EINA_INLIST_GET(&items[i]),
+                                                     _weight_sort_cb);
+
                }
              else
                {
-                  length -= item->space[!horiz];
-                  weight[!horiz] -= item->weight[!horiz];
+                  calc_size -= hint->space;
+                  weight_sum -= hint->weight;
                }
           }
 
         EINA_INLIST_FOREACH(inlist, item)
           {
              double weight_len;
+             hint = &item->hints[axis];
 
-             weight_len = (length * item->weight[!horiz]) / weight[!horiz];
-             if (item->space[!horiz] < weight_len)
+             weight_len = (calc_size * hint->weight) / weight_sum;
+             if (hint->space < weight_len)
                {
-                  item->space[!horiz] = weight_len;
+                  hint->space = weight_len;
                }
              else
                {
-                  weight[!horiz] -= item->weight[!horiz];
-                  length -= item->space[!horiz];
+                  weight_sum -= hint->weight;
+                  calc_size -= hint->space;
                }
           }
      }
 
    // calculate item geometry
      {
-        int x, y, w, h, sw, sh;
+        int item_size[2], item_pos[2], sw, sh;
 
-        if (length > want)
+        if (box_calc[axis].size > want[axis])
           {
              if (pd->homogeneous)
-               mmin = (double)length / count;
-             else if (EINA_DBL_EQ(weight[!horiz], 0))
-               cur_pos += (length - want) * box_align[!horiz];
+               mmin = (double)box_calc[axis].size / count;
+             else if (EINA_DBL_EQ(weight_sum, 0))
+               cur_pos += (box_calc[axis].size - want[axis]) * box_calc[axis].align;
           }
 
-        for (id = 0; id < count; id++)
+        for (i = 0; i < count; i++)
           {
-             item = &items[id];
+             hints = items[i].hints;
+
              if (pd->homogeneous)
-               item->space[!horiz] = mmin;
-             item->space[horiz] = horiz ? boxs.h : boxs.w;
-             sw = item->space[0] - item->pad[0] - item->pad[1];
-             sh = item->space[1] - item->pad[2] - item->pad[3];
+               hints[axis].space = mmin;
+             hints[r_axis].space = box_calc[r_axis].size;
+             sw = hints[0].space - (hints[0].margin[0] + hints[0].margin[1]);
+             sh = hints[1].space - (hints[1].margin[0] + hints[1].margin[1]);
 
-             if ((item->weight[0] > 0) && item->fill[0])
-               w = sw;
-             else
-               w = 0;
+             item_size[0] = ((hints[0].weight > 0) && hints[0].fill) ? sw : 0;
+             item_size[1] = ((hints[1].weight > 0) && hints[1].fill) ? sh : 0;
 
-             if ((item->weight[1] > 0) && item->fill[1])
-               h = sh;
-             else
-               h = 0;
+             _efl_ui_container_layout_min_max_calc(hints, &item_size[0], &item_size[1],
+                                (hints[0].aspect > 0) && (hints[1].aspect > 0));
 
-             _min_max_calc(item, &w, &h, (item->aspect.w > 0) &&
-                                         (item->aspect.h > 0));
-             if (horiz)
-               {
-                  x = cur_pos + 0.5;
-                  y = boxs.y;
-               }
-             else
-               {
-                  x = boxs.x;
-                  y = cur_pos + 0.5;
-               }
-             x += item->pad[0] + ((sw - w) * item->align[0]);
-             y += item->pad[2] + ((sh - h) * item->align[1]);
+             item_pos[axis] = cur_pos + 0.5;
+             item_pos[r_axis] = box_calc[r_axis].pos;
+
+             item_pos[0] += (hints[0].margin[0] +
+                             ((sw - item_size[0]) * hints[0].align));
+             item_pos[1] += (hints[1].margin[0] +
+                             ((sh - item_size[1]) * hints[1].align));
 
-             cur_pos += item->space[!horiz] + pad;
+             cur_pos += hints[axis].space + box_calc[axis].pad;
 
-             efl_gfx_entity_geometry_set(item->obj, EINA_RECT(x, y, w, h));
+             efl_gfx_entity_geometry_set(items[i].obj,
+                                         EINA_RECT(item_pos[0], item_pos[1],
+                                                   item_size[0], item_size[1]));
           }
      }
+   want[0] += (box_calc[0].margin[0] + box_calc[0].margin[1]) +
+              (box_calc[0].pad * (count - 1));
+   want[1] += (box_calc[1].margin[0] + box_calc[1].margin[1]) +
+              (box_calc[1].pad * (count - 1));
 
-   if (horiz)
-     {
-        efl_gfx_hint_size_min_set(ui_box, EINA_SIZE2D(
-                                  wantw + boxl + boxr + pad * (count - 1),
-                                  wanth + boxt + boxb));
-     }
-   else
-     {
-        efl_gfx_hint_size_min_set(ui_box, EINA_SIZE2D(
-                                  wantw + boxl + boxr,
-                                  wanth + pad * (count - 1) + boxt + boxb));
-     }
+   efl_gfx_hint_size_min_set(ui_box, EINA_SIZE2D(want[0], want[1]));
 }
diff --git a/src/lib/elementary/efl_ui_container_layout.c b/src/lib/elementary/efl_ui_container_layout.c
new file mode 100644 (file)
index 0000000..e8c5979
--- /dev/null
@@ -0,0 +1,158 @@
+#include "efl_ui_container_layout.h"
+
+void
+_efl_ui_container_layout_min_max_calc(Efl_Ui_Container_Item_Hints *item, int *cw, int *ch, Eina_Bool aspect_check)
+{
+   int w = *cw, h = *ch;
+   Eina_Size2D aspect = { item[0].aspect, item[1].aspect };
+
+   if (aspect_check)
+     {
+        w = h * aspect.w / aspect.h;
+        if (w > *cw)
+          {
+             w = *cw;
+             h = w * aspect.h / aspect.w;
+          }
+     }
+
+   if (w > item[0].max)
+     {
+        w = item[0].max;
+        if (aspect_check) h = w * aspect.h / aspect.w;
+     }
+   if (h > item[1].max)
+     {
+        h = item[1].max;
+        if (aspect_check) w = h * aspect.w / aspect.h;
+     }
+   if (w < item[0].min)
+     {
+        w = item[0].min;
+        if (aspect_check) h = w * aspect.h / aspect.w;
+     }
+   if (h < item[1].min)
+     {
+        h = item[1].min;
+        if (aspect_check) w = h * aspect.w / aspect.h;
+     }
+   *cw = w;
+   *ch = h;
+}
+
+void
+_efl_ui_container_layout_item_init(Eo* o, Efl_Ui_Container_Item_Hints *item)
+{
+   Eina_Size2D max;
+   Eina_Size2D min;
+   Eina_Size2D aspect;
+   Efl_Gfx_Hint_Aspect aspect_type;
+
+   efl_gfx_hint_weight_get(o, &item[0].weight, &item[1].weight);
+   efl_gfx_hint_align_get(o, &item[0].align, &item[1].align);
+   efl_gfx_hint_margin_get(o, &item[0].margin[0], &item[0].margin[1],
+                                   &item[1].margin[0], &item[1].margin[1]);
+   efl_gfx_hint_fill_get(o, &item[0].fill, &item[1].fill);
+   max = efl_gfx_hint_size_max_get(o);
+   min = efl_gfx_hint_size_combined_min_get(o);
+   efl_gfx_hint_aspect_get(o, &aspect_type, &aspect);
+   item[0].aspect = aspect.w;
+   item[1].aspect = aspect.h;
+   item[0].aspect_type = aspect_type;
+   item[1].aspect_type = aspect_type;
+
+   if (EINA_DBL_EQ(item[0].align, -1))
+     {
+        item[0].align = 0.5;
+        item[0].fill = EINA_TRUE;
+     }
+   else if (item[0].align < 0) item[0].align = 0;
+   else if (item[0].align > 1) item[0].align = 1;
+   if (EINA_DBL_EQ(item[1].align, -1))
+     {
+        item[1].align = 0.5;
+        item[1].fill = EINA_TRUE;
+     }
+   else if (item[1].align < 0) item[1].align = 0;
+   else if (item[1].align > 1) item[1].align = 1;
+
+   if (min.w < 0) min.w = 0;
+   if (min.h < 0) min.h = 0;
+
+   if (max.w < 0) max.w = INT_MAX;
+   if (max.h < 0) max.h = INT_MAX;
+
+   item[0].max = max.w;
+   item[1].max = max.h;
+   item[0].min = min.w;
+   item[1].min = min.h;
+
+   if ((item[0].aspect <= 0) || (item[1].aspect_type <= 0))
+     {
+        if ((item[0].aspect <= 0) ^ (item[1].aspect_type <= 0))
+          {
+             ERR("Invalid aspect parameter for obj(%p)", o);
+             item[0].aspect = item[1].aspect_type = 0;
+             item[0].aspect_type = item[1].aspect_type = EFL_GFX_HINT_ASPECT_NONE;
+          }
+     }
+   else
+     {
+        _efl_ui_container_layout_min_max_calc(item, &item[0].min, &item[1].min,
+                                              EINA_TRUE);
+     }
+
+
+   item[0].space = item[0].min + item[0].margin[0] + item[0].margin[1];
+   item[1].space = item[1].min + item[1].margin[0] + item[1].margin[1];
+}
+
+void
+_efl_ui_container_layout_init(Eo* obj, Efl_Ui_Container_Layout_Calc *calc)
+{
+   Eina_Rect geom;
+   Eina_Bool pad_scalable;
+
+   geom = efl_gfx_entity_geometry_get(obj);
+   efl_gfx_hint_margin_get(obj, &calc[0].margin[0], &calc[0].margin[1],
+                                     &calc[1].margin[0], &calc[1].margin[1]);
+   calc[0].scale = calc[1].scale = efl_gfx_entity_scale_get(obj);
+
+   efl_pack_padding_get(obj, &calc[0].pad, &calc[1].pad, &pad_scalable);
+   calc[0].pad = pad_scalable ? (calc[0].pad * calc[0].scale) : calc[0].pad;
+   calc[1].pad = pad_scalable ? (calc[1].pad * calc[1].scale) : calc[1].pad;
+
+   // pack align is used if "no item has a weight"
+   efl_pack_align_get(obj, &calc[0].align, &calc[1].align);
+   if (calc[0].align < 0)
+     {
+        calc[0].fill = EINA_TRUE;
+        calc[0].align = 0.5;
+     }
+   else if (calc[0].align > 1)
+     {
+        calc[0].align = 1;
+     }
+   else
+     {
+        calc[0].fill = EINA_FALSE;
+     }
+
+   if (calc[1].align < 0)
+     {
+        calc[1].fill = EINA_TRUE;
+        calc[1].align = 0.5;
+     }
+   else if (calc[1].align > 1)
+     {
+        calc[1].align = 1;
+     }
+   else
+     {
+        calc[1].fill = EINA_FALSE;
+     }
+   calc[0].pos = geom.x + calc[0].margin[0];
+   calc[1].pos = geom.y + calc[1].margin[0];
+   calc[0].size = geom.w - calc[0].margin[0] - calc[0].margin[1];
+   calc[1].size = geom.h - calc[1].margin[0] - calc[1].margin[1];
+}
diff --git a/src/lib/elementary/efl_ui_container_layout.h b/src/lib/elementary/efl_ui_container_layout.h
new file mode 100644 (file)
index 0000000..097abee
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef _EFL_UI_CONTAINER_HELPER_H_
+#define _EFL_UI_CONTAINER_HELPER_H_
+
+#ifdef HAVE_CONFIG_H
+# include "elementary_config.h"
+#endif
+
+#include <Elementary.h>
+#include "elm_priv.h"
+
+typedef struct _Efl_Ui_Container_Item_Hints Efl_Ui_Container_Item_Hints;
+typedef struct _Efl_Ui_Container_Layout_Calc Efl_Ui_Container_Layout_Calc;
+
+struct _Efl_Ui_Container_Item_Hints
+{
+   int max;
+   int min;
+   int aspect;
+   int margin[2]; // start, end
+   Efl_Gfx_Hint_Aspect aspect_type;
+   double weight;
+   double align;
+   double space;
+   Eina_Bool fill;
+};
+
+struct _Efl_Ui_Container_Layout_Calc
+{
+   int pos;
+   int size;
+   int margin[2];
+   double align;
+   double scale;
+   double pad;
+   Eina_Bool fill : 1;
+};
+
+void _efl_ui_container_layout_min_max_calc(Efl_Ui_Container_Item_Hints *item, int *cw, int *ch, Eina_Bool aspect_check);
+void _efl_ui_container_layout_item_init(Eo* o, Efl_Ui_Container_Item_Hints *item);
+void _efl_ui_container_layout_init(Eo* obj, Efl_Ui_Container_Layout_Calc *calc);
+
+#endif
index 889dc42..5f42043 100644 (file)
@@ -74,7 +74,16 @@ _table_size_hints_changed(void *data, Evas *e EINA_UNUSED,
 {
    Efl_Ui_Table_Data *pd = efl_data_scope_get(data, MY_CLASS);
 
-   _sizing_eval(data, pd);
+   if (table == data)
+     efl_pack_layout_request(data);
+   else
+     _sizing_eval(data, pd);
+}
+
+static void
+_efl_ui_table_size_hints_changed_cb(void *data EINA_UNUSED, const Efl_Event *ev)
+{
+   efl_pack_layout_request(ev->object);
 }
 
 /* Custom table class: overrides smart_calculate. */
@@ -116,10 +125,7 @@ _custom_table_calc(Eo *obj, Custom_Table_Data *pd)
 EOLIAN static void
 _efl_ui_table_efl_pack_layout_layout_update(Eo *obj, Efl_Ui_Table_Data *pd)
 {
-   ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
-
-   _sizing_eval(obj, pd);
-   efl_canvas_group_calculate(efl_super(wd->resize_obj, CUSTOM_TABLE_CLASS));
+   _efl_ui_table_custom_layout(obj, pd);
 }
 
 EOLIAN void
@@ -148,6 +154,8 @@ _efl_ui_table_efl_canvas_group_group_add(Eo *obj, Efl_Ui_Table_Data *pd)
 
    evas_object_event_callback_add
          (table, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _table_size_hints_changed, obj);
+   efl_event_callback_add(obj, EFL_GFX_ENTITY_EVENT_HINTS_CHANGED,
+                          _efl_ui_table_size_hints_changed_cb, NULL);
 
    efl_canvas_group_add(efl_super(obj, MY_CLASS));
 
@@ -168,6 +176,8 @@ _efl_ui_table_efl_canvas_group_group_del(Eo *obj, Efl_Ui_Table_Data *pd EINA_UNU
    evas_object_event_callback_del_full
          (wd->resize_obj, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
           _table_size_hints_changed, obj);
+   efl_event_callback_del(obj, EFL_GFX_ENTITY_EVENT_HINTS_CHANGED,
+                          _efl_ui_table_size_hints_changed_cb, NULL);
 
    /* let's make our table object the *last* to be processed, since it
     * may (smart) parent other sub objects here */
@@ -197,6 +207,8 @@ _efl_ui_table_efl_object_constructor(Eo *obj, Efl_Ui_Table_Data *pd)
    pd->last_row = -1;
    pd->req_cols = 0;
    pd->req_rows = 0;
+   pd->align.h = 0.5;
+   pd->align.v = 0.5;
 
    return obj;
 }
@@ -232,6 +244,28 @@ _efl_ui_table_efl_pack_pack_padding_get(const Eo *obj, Efl_Ui_Table_Data *pd EIN
    if (v) *v = pd->pad.v;
 }
 
+EOLIAN static void
+_efl_ui_table_efl_pack_pack_align_set(Eo *obj, Efl_Ui_Table_Data *pd, double h, double v)
+{
+   ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
+
+   if (h < 0) h = -1;
+   if (v < 0) v = -1;
+   if (h > 1) h = 1;
+   if (v > 1) v = 1;
+   pd->align.h = h;
+   pd->align.v = v;
+
+   efl_pack_layout_request(obj);
+}
+
+EOLIAN static void
+_efl_ui_table_efl_pack_pack_align_get(const Eo *obj EINA_UNUSED, Efl_Ui_Table_Data *pd, double *h, double *v)
+{
+   if (h) *h = pd->align.h;
+   if (v) *v = pd->align.v;
+}
+
 static void
 _subobj_del_cb(void *data, const Efl_Event *event)
 {
index c62612a..f3c067f 100644 (file)
@@ -15,6 +15,7 @@ class @beta Efl.Ui.Table extends Efl.Ui.Widget implements Efl.Pack_Table, Efl.Pa
       Efl.Pack.unpack;
       Efl.Pack.pack;
       Efl.Pack.pack_padding { get; set; }
+      Efl.Pack.pack_align { get; set; }
       Efl.Pack_Table.pack_table;
       Efl.Pack_Table.table_content_get;
       Efl.Pack_Table.table_contents_get;
diff --git a/src/lib/elementary/efl_ui_table_layout.c b/src/lib/elementary/efl_ui_table_layout.c
new file mode 100644 (file)
index 0000000..0a21384
--- /dev/null
@@ -0,0 +1,294 @@
+#include "efl_ui_table_private.h"
+#include "efl_ui_container_layout.h"
+
+typedef struct _Item_Calc Item_Calc;
+typedef struct _Cell_Calc Cell_Calc;
+typedef struct _Table_Calc Table_Calc;
+
+struct _Item_Calc
+{
+   Evas_Object *obj;
+   int cell_span[2];
+   int cell_index[2];
+   Efl_Ui_Container_Item_Hints hints[2]; /* 0 is x-axis, 1 is y-axis */
+};
+
+struct _Cell_Calc
+{
+   EINA_INLIST;
+
+   int       index;
+   int       next;
+   double    acc;
+   double    space;
+   double    weight;
+   double    weight_factor;
+   Eina_Bool occupied : 1;
+};
+
+struct _Table_Calc
+{
+   /* 0 is x-axis, 1 is y-axis */
+
+   int                           rows;
+   int                           cols;
+   int                           want[2];
+   double                        weight_sum[2];
+   Cell_Calc                    *cell_calc[2];
+   Efl_Ui_Container_Layout_Calc  layout_calc[2];
+};
+
+static int
+_weight_sort_cb(const void *l1, const void *l2)
+{
+   Cell_Calc *cc1, *cc2;
+
+   cc1 = EINA_INLIST_CONTAINER_GET(l1, Cell_Calc);
+   cc2 = EINA_INLIST_CONTAINER_GET(l2, Cell_Calc);
+
+   return cc2->weight_factor <= cc1->weight_factor ? -1 : 1;
+}
+
+static void
+_cell_weight_calc(Table_Calc *table_calc, Eina_Bool axis)
+{
+   int i, count, layout_size, calc_size;
+   double denom, weight_sum, calc_weight;
+   Eina_Inlist *inlist = NULL;
+   Cell_Calc *cell_calc, *cc;
+
+   layout_size = calc_size = table_calc->layout_calc[axis].size;
+   weight_sum = calc_weight = table_calc->weight_sum[axis];
+   cell_calc = table_calc->cell_calc[axis];
+   count = axis ? table_calc->rows : table_calc->cols;
+
+   for (i = 0; i < count; i = cell_calc[i].next)
+     {
+        denom = (cell_calc[i].weight * layout_size) -
+                (weight_sum * cell_calc[i].space);
+        if (denom > 0)
+          {
+             cell_calc[i].weight_factor = (cell_calc[i].weight * layout_size) / denom;
+             inlist =  eina_inlist_sorted_insert(inlist,
+                                                 EINA_INLIST_GET(&cell_calc[i]),
+                                                 _weight_sort_cb);
+          }
+        else
+          {
+             calc_size -= cell_calc[i].space;
+             calc_weight -= cell_calc[i].weight;
+          }
+     }
+
+   EINA_INLIST_FOREACH(inlist, cc)
+     {
+        double weight_len;
+
+        weight_len = (calc_size * cc->weight) / calc_weight;
+        if (cc->space < weight_len)
+          {
+             cc->space = weight_len;
+          }
+        else
+          {
+             calc_size -= cc->space;
+             calc_weight -= cc->weight;
+          }
+     }
+}
+
+static void
+_efl_ui_table_regular_cell_init(Table_Calc *table_calc, Eina_Bool axis)
+{
+   int i, index = 0, acc, want = 0, count;
+   double weight_sum = 0;
+   Cell_Calc *prev_cell = NULL, *cell_calc;
+   Efl_Ui_Container_Layout_Calc *layout_calc;
+
+   layout_calc = &(table_calc->layout_calc[axis]);
+   cell_calc = table_calc->cell_calc[axis];
+   count = axis ? table_calc->rows : table_calc->cols;
+
+   for (i = 0; i < count; i++)
+     {
+        if (!cell_calc[i].occupied) continue;
+
+        cell_calc[i].index = index++;
+        want += cell_calc[i].space;
+        weight_sum += cell_calc[i].weight;
+
+        if (prev_cell)
+          prev_cell->next = i;
+
+        prev_cell = &cell_calc[i];
+     }
+   if (prev_cell)
+     prev_cell->next = count;
+
+   table_calc->want[axis] = want;
+   table_calc->weight_sum[axis] = weight_sum;
+   table_calc->layout_calc[axis].size -= (table_calc->layout_calc[axis].pad
+                                          * (index - 1));
+
+   if ((layout_calc->size > want) && (weight_sum > 0))
+     _cell_weight_calc(table_calc, axis);
+   if (EINA_DBL_EQ(weight_sum, 0.0))
+     layout_calc->pos += (layout_calc->size - want) * layout_calc->align;
+
+   for (i = 0, acc = 0; i < count; acc += cell_calc[i].space, i = cell_calc[i].next)
+     cell_calc[i].acc = acc;
+}
+
+static inline int
+_efl_ui_table_regular_item_pos_get(Table_Calc *table_calc, Item_Calc *item, Eina_Bool axis)
+{
+   return 0.5 + table_calc->layout_calc[axis].pos
+          + table_calc->cell_calc[axis][item->cell_index[axis]].acc
+          + (table_calc->cell_calc[axis][item->cell_index[axis]].index *
+             table_calc->layout_calc[axis].pad);
+}
+
+static inline int
+_efl_ui_table_regular_item_size_get(Table_Calc *table_calc, Item_Calc *item, Eina_Bool axis)
+{
+   int start, end;
+
+   start = item->cell_index[axis];
+   end = start + item->cell_span[axis] - 1;
+
+   return table_calc->cell_calc[axis][end].acc
+          - table_calc->cell_calc[axis][start].acc
+          + table_calc->cell_calc[axis][end].space
+          + ((item->cell_span[axis] - 1) * table_calc->layout_calc[axis].pad)
+          - item->hints[axis].margin[0] - item->hints[axis].margin[1];
+}
+
+void
+_efl_ui_table_custom_layout(Efl_Ui_Table *ui_table, Efl_Ui_Table_Data *pd)
+{
+   Table_Item *ti;
+   Item_Calc *items, *item;
+   Efl_Ui_Container_Item_Hints *hints;
+   int i = 0, rows, cols;
+
+   Table_Calc table_calc;
+
+   if (!pd->count)
+     {
+        efl_gfx_hint_size_min_set(ui_table, EINA_SIZE2D(0, 0));
+        return;
+     }
+
+   _efl_ui_container_layout_init(ui_table, table_calc.layout_calc);
+
+   table_calc.want[0] = table_calc.want[1] = 0;
+   table_calc.weight_sum[0] = table_calc.weight_sum[1] = 0;
+
+   efl_pack_table_size_get(ui_table, &cols, &rows);
+
+   table_calc.cell_calc[0] = alloca(cols * sizeof(Cell_Calc));
+   table_calc.cell_calc[1] = alloca(rows * sizeof(Cell_Calc));
+
+   memset(table_calc.cell_calc[0], 0, cols * sizeof(Cell_Calc));
+   memset(table_calc.cell_calc[1], 0, rows * sizeof(Cell_Calc));
+
+   items = alloca(pd->count * sizeof(*items));
+#ifdef DEBUG
+   memset(items, 0, pd->count * sizeof(*items));
+#endif
+
+   table_calc.cols = cols;
+   table_calc.rows = rows;
+   // scan all items, get their properties, calculate total weight & min size
+   EINA_INLIST_FOREACH(pd->items, ti)
+     {
+        if (((ti->col + ti->col_span) > cols) ||
+            ((ti->row + ti->row_span) > rows))
+          {
+             efl_gfx_entity_visible_set(ti->object, EINA_FALSE);
+             continue;
+          }
+
+        item = &items[i++];
+        item->obj = ti->object;
+        hints = item->hints;
+
+        _efl_ui_container_layout_item_init(item->obj, hints);
+
+        if (table_calc.layout_calc[0].fill)
+          hints[0].weight = 1;
+        else if (hints[0].weight < 0)
+          hints[0].weight = 0;
+
+        if (table_calc.layout_calc[1].fill)
+          hints[1].weight = 1;
+        else if (hints[1].weight < 0)
+          hints[1].weight = 0;
+
+        item->cell_index[0] = ti->col;
+        item->cell_index[1] = ti->row;
+        item->cell_span[0] = ti->col_span;
+        item->cell_span[1] = ti->row_span;
+
+        if (ti->col_span == 1)
+          {
+             table_calc.cell_calc[0][ti->col].occupied = EINA_TRUE;
+
+             if (table_calc.cell_calc[0][ti->col].space < hints[0].space)
+               table_calc.cell_calc[0][ti->col].space = hints[0].space;
+             if (table_calc.cell_calc[0][ti->col].weight < hints[0].weight)
+               table_calc.cell_calc[0][ti->col].weight = hints[0].weight;
+          }
+
+        if (ti->row_span == 1)
+          {
+             table_calc.cell_calc[1][ti->row].occupied = EINA_TRUE;
+
+             if (table_calc.cell_calc[1][ti->row].space < hints[1].space)
+               table_calc.cell_calc[1][ti->row].space = hints[1].space;
+             if (table_calc.cell_calc[1][ti->row].weight < hints[1].weight)
+               table_calc.cell_calc[1][ti->row].weight = hints[1].weight;
+          }
+     }
+
+   _efl_ui_table_regular_cell_init(&table_calc, 0);
+   _efl_ui_table_regular_cell_init(&table_calc, 1);
+
+   for (i = 0; i < pd->count; i++)
+     {
+        Eina_Rect space, item_geom;
+        item = &items[i];
+        hints = items[i].hints;
+
+        space.x = _efl_ui_table_regular_item_pos_get(&table_calc, item, 0);
+        space.y = _efl_ui_table_regular_item_pos_get(&table_calc, item, 1);
+        space.w = _efl_ui_table_regular_item_size_get(&table_calc, item, 0);
+        space.h = _efl_ui_table_regular_item_size_get(&table_calc, item, 1);
+
+        item_geom.w = hints[0].fill ? space.w : hints[0].min;
+        item_geom.h = hints[1].fill ? space.h : hints[1].min;
+
+        _efl_ui_container_layout_min_max_calc(hints, &item_geom.w, &item_geom.h,
+                                (hints[0].aspect > 0) && (hints[1].aspect > 0));
+
+        item_geom.x = space.x + ((space.w - item_geom.w) * hints[0].align)
+                      + hints[0].margin[0];
+        item_geom.y = space.y + ((space.h - item_geom.h) * hints[1].align)
+                      + hints[1].margin[0];
+
+        efl_gfx_entity_geometry_set(item->obj, item_geom);
+     }
+
+   table_calc.want[0] += table_calc.layout_calc[0].margin[0]
+                         + table_calc.layout_calc[0].margin[1]
+                         + (table_calc.layout_calc[0].pad *
+                            table_calc.cell_calc[0][cols - 1].index);
+
+   table_calc.want[1] += table_calc.layout_calc[1].margin[0]
+                         + table_calc.layout_calc[1].margin[1]
+                         + (table_calc.layout_calc[1].pad *
+                            table_calc.cell_calc[1][rows - 1].index);
+
+   efl_gfx_hint_size_min_set(ui_table, EINA_SIZE2D(table_calc.want[0],
+                                                   table_calc.want[1]));
+}
index e7c3821..0ab08e5 100644 (file)
@@ -14,6 +14,8 @@ typedef struct _Efl_Ui_Table_Data Efl_Ui_Table_Data;
 typedef struct _Table_Item_Iterator Table_Item_Iterator;
 typedef struct _Table_Item Table_Item;
 
+void _efl_ui_table_custom_layout(Efl_Ui_Table *ui_table, Efl_Ui_Table_Data *pd);
+
 #define TABLE_ITEM_KEY "__table_item"
 
 struct _Table_Item
@@ -39,6 +41,9 @@ struct _Efl_Ui_Table_Data
       double h, v;
       Eina_Bool scalable: 1;
    } pad;
+   struct {
+      double h, v;
+   } align;
    Eina_Bool linear_recalc : 1;
 };
 
index 58c8dc6..731743e 100644 (file)
@@ -851,6 +851,8 @@ elementary_src = [
   'els_cursor.c',
   'els_tooltip.c',
   'elu_ews_wm.c',
+  'efl_ui_container_layout.c',
+  'efl_ui_container_layout.h',
   'efl_ui_box.c',
   'efl_ui_box_flow.c',
   'efl_ui_box_stack.c',
@@ -863,6 +865,7 @@ elementary_src = [
   'efl_ui_anchor_popup.c',
   'efl_ui_table.c',
   'efl_ui_table_static.c',
+  'efl_ui_table_layout.c',
   'efl_ui_table_private.h',
   'efl_ui_text.c',
   'efl_ui_text_factory_images.c',