ui.box: refactor layout_update
authorYeongjong Lee <yj34.lee@samsung.com>
Wed, 13 Feb 2019 13:38:58 +0000 (08:38 -0500)
committerWonki Kim <wonki_.kim@samsung.com>
Fri, 8 Mar 2019 11:49:33 +0000 (20:49 +0900)
Summary:
Current layout_update doesn't consider max hint priority. for example, if it
has hint_max, the size is hint_max value regardless of weight or fill.
moreover, if it has hint_aspect with hint_max, hint_min can be ignored.
(test that aspect(1,3) max(50,150), min(70, 70) then, size has (50, 150))
It seems that hint_max is considered as high priority. however, if hint_min
greater than hint_max, hint_max is ignored.

In order to resolve the hint priority conflict, this refactoring supports
following hint priority.
1) HintMin
2) HintMin + HintAspect
3) HintMargin
4) HintMax
5) HintAspect
6) HintWeight, HintFill
7) HintAlign

ref T5487

Specific unit test is D7463

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

Reviewers: jpeg, Jaehyun_Cho, zmike

Reviewed By: zmike

Subscribers: segfaultxavi, zmike, cedric, #reviewers, #committers

Tags: #efl

Maniphest Tasks: T5487

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

src/lib/elementary/efl_ui_box_layout.c

index c42dd74..8f8cc4d 100644 (file)
@@ -2,23 +2,76 @@
 
 #include "efl_ui_box_private.h"
 
-// FIXME: Aspect support is not implemented
 // FIXME: handle RTL? just invert the horizontal order?
 
 typedef struct _Item_Calc Item_Calc;
 
 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, want, aspect;
+   Eina_Size2D max, min, aspect;
    int pad[4];
    Efl_Gfx_Size_Hint_Aspect aspect_type;
    int id;
 };
 
+static int
+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;
+}
+
 void
 _efl_ui_box_custom_layout(Efl_Ui_Box *ui_box, Evas_Object_Box_Data *bd)
 {
@@ -26,20 +79,20 @@ _efl_ui_box_custom_layout(Efl_Ui_Box *ui_box, Evas_Object_Box_Data *bd)
    Evas_Object_Box_Option *opt;
    Evas_Object *o;
    Eina_List *li;
+   Eina_Inlist *inlist = NULL;
    int wantw = 0, wanth = 0; // requested size
-   int boxx, boxy, boxw, boxh;
+   Eina_Rect boxs;
    Item_Calc *items, *item;
    Eina_Bool horiz = efl_ui_dir_is_horizontal(pd->dir, EINA_FALSE);
-   Eina_Bool zeroweight = EINA_FALSE;
    int id = 0, count, boxl = 0, boxr = 0, boxt = 0, boxb = 0;
-   int length, want, pad, extra = 0, rounding = 0;
-   double cur_pos = 0, weight[2] = { 0, 0 }, scale;
+   int length, want, pad;
+   double cur_pos, weight[2] = { 0, 0 }, scale;
    double box_align[2];
    Eina_Bool box_fill[2] = { EINA_FALSE, EINA_FALSE };
 
-   evas_object_geometry_get(ui_box, &boxx, &boxy, &boxw, &boxh);
+   boxs = efl_gfx_entity_geometry_get(ui_box);
    efl_gfx_size_hint_margin_get(ui_box, &boxl, &boxr, &boxt, &boxb);
-   scale = evas_object_scale_get(ui_box);
+   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
@@ -59,7 +112,7 @@ _efl_ui_box_custom_layout(Efl_Ui_Box *ui_box, Evas_Object_Box_Data *bd)
    count = eina_list_count(bd->children);
    if (!count)
      {
-        evas_object_size_hint_min_set(ui_box, 0, 0);
+        efl_gfx_size_hint_min_set(ui_box, EINA_SIZE2D(0, 0));
         return;
      }
 
@@ -68,6 +121,12 @@ _efl_ui_box_custom_layout(Efl_Ui_Box *ui_box, Evas_Object_Box_Data *bd)
    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)
      {
@@ -79,321 +138,191 @@ _efl_ui_box_custom_layout(Efl_Ui_Box *ui_box, Evas_Object_Box_Data *bd)
         efl_gfx_size_hint_margin_get(o, &item->pad[0], &item->pad[1], &item->pad[2], &item->pad[3]);
         efl_gfx_size_hint_fill_get(o, &item->fill[0], &item->fill[1]);
         item->max = efl_gfx_size_hint_max_get(o);
-        item->want = efl_gfx_size_hint_combined_min_get(o);
+        item->min = efl_gfx_size_hint_combined_min_get(o);
         efl_gfx_size_hint_aspect_get(o, &item->aspect_type, &item->aspect);
 
-        if (item->weight[0] < 0) item->weight[0] = 0;
-        if (item->weight[1] < 0) item->weight[1] = 0;
+        if (horiz && box_fill[0]) item->weight[0] = 1;
+        else if (item->weight[0] < 0) item->weight[0] = 0;
+        if (!horiz && box_fill[1]) 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] < 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;
-          }
-        if (item->align[0] > 1) item->align[0] = 1;
-        if (item->align[1] > 1) item->align[1] = 1;
+        else if (item->align[1] < 0) item->align[1] = 0;
+        else if (item->align[1] > 1) item->align[1] = 1;
 
-        if (item->want.w < 0) item->want.w = 0;
-        if (item->want.h < 0) item->want.h = 0;
+        if (item->min.w < 0) item->min.w = 0;
+        if (item->min.h < 0) item->min.h = 0;
 
         if (item->max.w < 0) item->max.w = INT_MAX;
         if (item->max.h < 0) item->max.h = INT_MAX;
 
-        if (item->aspect.w <= 0 || item->aspect.h <= 0)
-          {
-             if (item->aspect_type >= EFL_GFX_SIZE_HINT_ASPECT_HORIZONTAL)
-               ERR("Invalid aspect parameter for obj: %p", item->obj);
-             item->aspect.w = item->aspect.h = 0;
-             item->aspect_type = EFL_GFX_SIZE_HINT_ASPECT_NONE;
-          }
+        weight[0] += item->weight[0];
+        weight[1] += item->weight[1];
 
-        if (item->aspect_type >= EFL_GFX_SIZE_HINT_ASPECT_HORIZONTAL)
+        if ((item->aspect.w <= 0) || (item->aspect.h <= 0))
           {
-             double w, h;
-
-             w = item->want.w;
-             h = w * item->aspect.h / item->aspect.w;
-             if (h < item->want.h)
+             if ((item->aspect.w <= 0) ^ (item->aspect.h <= 0))
                {
-                  h = item->want.h;
-                  w = h * item->aspect.w / item->aspect.h;
+                  ERR("Invalid aspect parameter for obj: %p", item->obj);
+                  item->aspect.w = item->aspect.h = 0;
+                  item->aspect_type = EFL_GFX_SIZE_HINT_ASPECT_NONE;
                }
-
-             if (horiz)
-               {
-                  if (item->fill[1] && (h < boxh))
-                    {
-                       double w1, h1;
-                       h1 = item->max.h > 0 ? MIN(boxh, item->max.h) : boxh;
-                       h1 = MAX(h, h1);
-                       w1 = h1 * item->aspect.w / item->aspect.h;
-                       w = item->max.w > 0 ? MIN(w1, item->max.w) : w1;
-                    }
-               }
-             else
-               {
-                  if (item->fill[0] && (w < boxw))
-                    {
-                       double w1, h1;
-                       w1 = item->max.w > 0 ? MIN(boxw, item->max.w) : boxw;
-                       w1 = MAX(w, w1);
-                       h1 = w1 * item->aspect.h / item->aspect.w;
-                       h = item->max.h > 0 ? MIN(h1, item->max.h) : h1;
-                    }
-               }
-             item->want.w = w;
-             item->want.h = h;
           }
-        if (item->max.w < item->want.w) item->max.w = item->want.w;
-        if (item->max.h < item->want.h) item->max.h = item->want.h;
+        else
+          {
+             _min_max_calc(item, &item->min.w, &item->min.h, EINA_TRUE);
+          }
 
-        item->want.w += item->pad[0] + item->pad[1];
-        item->want.h += item->pad[2] + item->pad[3];
+        item->space[0] = item->min.w + item->pad[0] + item->pad[1];
+        item->space[1] = item->min.h + item->pad[2] + item->pad[3];
 
-        weight[0] += item->weight[0];
-        weight[1] += item->weight[1];
         if (horiz)
           {
-             wantw += item->want.w;
-             if (item->want.h > wanth)
-               wanth = item->want.h;
+             if (item->space[1] > wanth)
+               wanth = item->space[1];
+             wantw += item->space[0];
           }
         else
           {
-             wanth += item->want.h;
-             if (item->want.w > wantw)
-               wantw = item->want.w;
+             if (item->space[0] > wantw)
+               wantw = item->space[0];
+             wanth += item->space[1];
           }
 
         item->id = id++;
      }
 
-   // box outer margin
-   boxw -= boxl + boxr;
-   boxh -= boxt + boxb;
-   boxx += boxl;
-   boxy += boxt;
-
    // total space & available space
    if (horiz)
      {
-        length = boxw;
         want = wantw;
+        length = boxs.w;
         pad = pd->pad.scalable ? (pd->pad.h * scale) : pd->pad.h;
+        if (boxs.h < wanth)
+          boxs.h = wanth;
      }
    else
      {
-        length = boxh;
         want = wanth;
+        length = boxs.h;
         pad = pd->pad.scalable ? (pd->pad.v * scale) : pd->pad.v;
+        if (boxs.w < wantw)
+          boxs.w = wantw;
      }
 
    // padding can not be squeezed (note: could make it an option)
    length -= pad * (count - 1);
+   cur_pos = horiz ? boxs.x : boxs.y;
 
-   // available space. if <0 we overflow
-   extra = length - want;
-
-   if (horiz)
-     {
-        evas_object_size_hint_min_set(ui_box,
-                                      wantw + boxl + boxr + pad * (count - 1),
-                                      wanth + boxt + boxb);
-     }
-   else
+   // calculate weight length
+   if ((length > want) && (weight[!horiz] > 0))
      {
-        evas_object_size_hint_min_set(ui_box,
-                                      wantw + boxl + boxr,
-                                      wanth + pad * (count - 1) + boxt + boxb);
-     }
-
-   if (extra < 0) extra = 0;
+        int orig_length = length;
+        double orig_weight = weight[!horiz];
 
-   if (EINA_DBL_EQ(weight[!horiz], 0))
-     {
-        if (box_fill[!horiz])
+        for (id = 0; id < count; id++)
           {
-             // box is filled, set all weights to be equal
-             zeroweight = EINA_TRUE;
-          }
-        else
-          {
-             // move bounding box according to box align
-             cur_pos = extra * box_align[!horiz];
-          }
-        weight[!horiz] = count;
-     }
-
-   for (id = 0; id < count; id++)
-     {
-        double cx, cy, cw, ch, x, y, w, h;
-        item = &items[id];
+             double denom;
+             item = &items[id];
 
-        // extra rounding up (compensate cumulative error)
-        if ((item->id == (count - 1)) && (cur_pos - floor(cur_pos) >= 0.5))
-          rounding = 1;
-
-        if (horiz)
-          {
-             cx = boxx + cur_pos;
-             cy = boxy;
-             cw = item->want.w + rounding + (zeroweight ? 1.0 : item->weight[0]) * extra / weight[0];
-             ch = boxh;
-             cur_pos += cw + pad;
-          }
-        else
-          {
-             cx = boxx;
-             cy = boxy + cur_pos;
-             cw = boxw;
-             ch = item->want.h + rounding + (zeroweight ? 1.0 : item->weight[1]) * extra / weight[1];
-             cur_pos += ch + pad;
-          }
-
-        if (item->aspect_type >= EFL_GFX_SIZE_HINT_ASPECT_HORIZONTAL)
-          {
-             if (horiz)
+             denom = (item->weight[!horiz] * orig_length) -
+                     (orig_weight * item->space[!horiz]);
+             if (denom > 0)
                {
-                  w = item->want.w;
-                  h = item->want.h;
-                  if (weight[0] > 0)
-                    w = item->want.w + extra * item->weight[0] / weight[0];
-                  h = w * item->aspect.h / item->aspect.w;
-
-                  if (item->aspect_type == EFL_GFX_SIZE_HINT_ASPECT_BOTH ||
-                      item->aspect_type == EFL_GFX_SIZE_HINT_ASPECT_VERTICAL)
-                    {
-                       if (h > boxh)
-                         {
-                            h = boxh;
-                            w = h * item->aspect.w / item->aspect.h;
-                         }
-                    }
+                  item->comp_factor = (item->weight[!horiz] * orig_length) / denom;
+                  inlist = eina_inlist_sorted_insert(inlist, EINA_INLIST_GET(item),
+                                                     weight_sort_cb);
                }
              else
                {
-                  w = item->want.w;
-                  h = item->want.h;
-                  if (weight[1] > 0)
-                    h = item->want.h + extra * item->weight[1] / weight[1];
-                  w = h * item->aspect.w / item->aspect.h;
-
-                  if (item->aspect_type == EFL_GFX_SIZE_HINT_ASPECT_BOTH ||
-                      item->aspect_type == EFL_GFX_SIZE_HINT_ASPECT_HORIZONTAL)
-                    {
-                       if (w > boxw)
-                         {
-                            w = boxw;
-                            h = w * item->aspect.h / item->aspect.w;
-                         }
-                    }
+                  length -= item->space[!horiz];
+                  weight[!horiz] -= item->weight[!horiz];
                }
+          }
 
-             if ((item->max.w != INT_MAX) && (item->max.h != INT_MAX))
-               {
-                  double mar, ar;
-                  mar = item->max.w / (double)item->max.h;
-                  ar = item->aspect.w / (double)item->aspect.h;
-                  if (ar < mar)
-                    {
-                       if (h > item->max.h)
-                         {
-                            h = item->max.h;
-                            w = h * item->aspect.w / item->aspect.h;
-                         }
-                    }
-                  else
-                    {
-                       if (w > item->max.w)
-                         {
-                            w = item->max.w;
-                            h = w * item->aspect.h / item->aspect.w;
-                         }
-                    }
-               }
-             else if (item->max.w != INT_MAX)
+        EINA_INLIST_FOREACH(inlist, item)
+          {
+             double weight_len;
+
+             weight_len = (length * item->weight[!horiz]) / weight[!horiz];
+             if (item->space[!horiz] < weight_len)
                {
-                  w = MIN(w, MAX(item->want.w, item->max.w));
-                  h = w * item->aspect.h / item->aspect.w;
+                  item->space[!horiz] = weight_len;
                }
              else
                {
-                  h = MIN(h, MAX(item->want.h, item->max.h));
-                  w = h * item->aspect.w / item->aspect.h;
+                  weight[!horiz] -= item->weight[!horiz];
+                  length -= item->space[!horiz];
                }
-             w = w - item->pad[0] - item->pad[1];
-             h = h - item->pad[2] - item->pad[3];
+          }
+     }
 
-             if (item->fill[0])
-               x = cx + (cw - w) * 0.5 + item->pad[0];
-             else
-               x = cx + (cw - w) * item->align[0] + item->pad[0];
+   // calculate item geometry
+     {
+        int x, y, w, h, sw, sh;
 
-             if (item->fill[1])
-               y = cy + (ch - h) * 0.5 + item->pad[2];
-             else
-               y = cy + (ch - h) * item->align[1] + item->pad[2];
-          }
-        else
+        if ((length > want) && EINA_DBL_EQ(weight[!horiz], 0))
+          cur_pos += (length - want) * box_align[!horiz];
+
+        for (id = 0; id < count; id++)
           {
-             // horizontally
-             if (item->max.w < INT_MAX)
-               {
-                  w = MIN(MAX(item->want.w - item->pad[0] - item->pad[1], item->max.w), cw);
-                  x = cx + ((cw - w) * item->align[0]) + item->pad[0];
-               }
-             else if (item->fill[0])
-               {
-                  // fill x
-                  w = cw - item->pad[0] - item->pad[1];
-                  x = cx + item->pad[0];
-               }
+             item = &items[id];
+             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];
+
+             if ((item->weight[0] > 0) && item->fill[0])
+               w = sw;
              else
-               {
-                  if (horiz && item->weight[0] > 0)
-                    w = cw - item->pad[0] - item->pad[1];
-                  else
-                    w = item->want.w - item->pad[0] - item->pad[1];
-                  x = cx + ((cw - w) * item->align[0]) + item->pad[0];
-               }
+               w = 0;
 
-             // vertically
-             if (item->max.h < INT_MAX)
-               {
-                  h = MIN(MAX(item->want.h - item->pad[2] - item->pad[3], item->max.h), ch);
-                  y = cy + ((ch - h) * item->align[1]) + item->pad[2];
-               }
-             else if (item->fill[1])
+             if ((item->weight[1] > 0) && item->fill[1])
+               h = sh;
+             else
+               h = 0;
+
+             _min_max_calc(item, &w, &h, (item->aspect.w > 0) &&
+                                         (item->aspect.h > 0));
+             if (horiz)
                {
-                  // fill y
-                  h = ch - item->pad[2] - item->pad[3];
-                  y = cy + item->pad[2];
+                  x = cur_pos + 0.5;
+                  y = boxs.y;
                }
              else
                {
-                  if (!horiz && item->weight[1] > 0)
-                    h = ch - item->pad[2] - item->pad[3];
-                  else
-                    h = item->want.h - item->pad[2] - item->pad[3];
-                  y = cy + ((ch - h) * item->align[1]) + item->pad[2];
+                  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]);
+
+             cur_pos += item->space[!horiz] + pad;
+
+             efl_gfx_entity_geometry_set(item->obj, EINA_RECT(x, y, w, h));
           }
+     }
 
-        //DBG("[%2d/%2d] cell: %.0f,%.0f %.0fx%.0f item: %.0f,%.0f %.0fx%.0f",
-        //    id, count, cx, cy, cw, ch, x, y, w, h);
-        evas_object_geometry_set(item->obj, x, y, w, h);
+   if (horiz)
+     {
+        efl_gfx_size_hint_min_set(ui_box, EINA_SIZE2D(
+                                  wantw + boxl + boxr + pad * (count - 1),
+                                  wanth + boxt + boxb));
+     }
+   else
+     {
+        efl_gfx_size_hint_min_set(ui_box, EINA_SIZE2D(
+                                  wantw + boxl + boxr,
+                                  wanth + pad * (count - 1) + boxt + boxb));
      }
 }