efl/src/lib/elementary/efl_ui_box_layout.c

394 lines
13 KiB
C

#define EFL_GFX_SIZE_HINT_PROTECTED
#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
{
Evas_Object *obj;
double weight[2];
double align[2];
Eina_Size2D max, want, aspect;
int pad[4];
Efl_Gfx_Size_Hint_Aspect aspect_type;
int id;
};
void
_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;
int wantw = 0, wanth = 0; // requested size
int boxx, boxy, boxw, boxh;
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;
double box_align[2];
Eina_Bool box_fill[2] = { EINA_FALSE, EINA_FALSE };
evas_object_geometry_get(ui_box, &boxx, &boxy, &boxw, &boxh);
efl_gfx_size_hint_margin_get(ui_box, &boxl, &boxr, &boxt, &boxb);
scale = evas_object_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;
}
count = eina_list_count(bd->children);
if (!count)
{
evas_object_size_hint_min_set(ui_box, 0, 0);
return;
}
items = alloca(count * sizeof(*items));
#ifdef DEBUG
memset(items, 0, count * sizeof(*items));
#endif
// 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_size_hint_weight_get(o, &item->weight[0], &item->weight[1]);
efl_gfx_size_hint_align_get(o, &item->align[0], &item->align[1]);
efl_gfx_size_hint_margin_get(o, &item->pad[0], &item->pad[1], &item->pad[2], &item->pad[3]);
item->max = efl_gfx_size_hint_max_get(o);
item->want = efl_gfx_size_hint_combined_min_get(o);
efl_gfx_size_hint_aspect_get(o, &item->aspect_type, &item->aspect);
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;
}
if (item->aspect_type >= EFL_GFX_SIZE_HINT_ASPECT_HORIZONTAL)
{
double w, h;
w = item->want.w;
h = w * item->aspect.h / item->aspect.w;
if (h < item->want.h)
{
h = item->want.h;
w = h * item->aspect.w / item->aspect.h;
}
if (horiz)
{
if ((item->align[1] < 0) && (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->align[0] < 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->weight[0] < 0) item->weight[0] = 0;
if (item->weight[1] < 0) item->weight[1] = 0;
if (item->align[0] < 0) item->align[0] = -1;
if (item->align[1] < 0) item->align[1] = -1;
if (item->align[0] > 1) item->align[0] = 1;
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->max.w < 0) item->max.w = INT_MAX;
if (item->max.h < 0) item->max.h = INT_MAX;
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;
item->want.w += item->pad[0] + item->pad[1];
item->want.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;
}
else
{
wanth += item->want.h;
if (item->want.w > wantw)
wantw = item->want.w;
}
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;
pad = pd->pad.scalable ? (pd->pad.h * scale) : pd->pad.h;
}
else
{
length = boxh;
want = wanth;
pad = pd->pad.scalable ? (pd->pad.v * scale) : pd->pad.v;
}
// padding can not be squeezed (note: could make it an option)
length -= pad * (count - 1);
// 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
{
evas_object_size_hint_min_set(ui_box,
wantw + boxl + boxr,
wanth + pad * (count - 1) + boxt + boxb);
}
if (extra < 0) extra = 0;
if (EINA_DBL_EQ(weight[!horiz], 0))
{
if (box_fill[!horiz])
{
// 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];
// 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)
{
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;
}
}
}
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;
}
}
}
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)
{
w = MIN(w, MAX(item->want.w, item->max.w));
h = w * item->aspect.h / item->aspect.w;
}
else
{
h = MIN(h, MAX(item->want.h, item->max.h));
w = h * item->aspect.w / item->aspect.h;
}
w = w - item->pad[0] - item->pad[1];
h = h - item->pad[2] - item->pad[3];
if (item->align[0] < 0)
x = cx + (cw - w) * 0.5 + item->pad[0];
else
x = cx + (cw - w) * item->align[0] + item->pad[0];
if (item->align[1] < 0)
y = cy + (ch - h) * 0.5 + item->pad[2];
else
y = cy + (ch - h) * item->align[1] + item->pad[2];
}
else
{
// horizontally
if (item->max.w < INT_MAX)
{
w = MIN(MAX(item->want.w - item->pad[0] - item->pad[1], item->max.w), cw);
if (item->align[0] < 0)
{
// bad case: fill+max are not good together
x = cx + ((cw - w) * box_align[0]) + item->pad[0];
}
else
x = cx + ((cw - w) * item->align[0]) + item->pad[0];
}
else if (item->align[0] < 0)
{
// fill x
w = cw - item->pad[0] - item->pad[1];
x = cx + item->pad[0];
}
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];
}
// vertically
if (item->max.h < INT_MAX)
{
h = MIN(MAX(item->want.h - item->pad[2] - item->pad[3], item->max.h), ch);
if (item->align[1] < 0)
{
// bad case: fill+max are not good together
y = cy + ((ch - h) * box_align[1]) + item->pad[2];
}
else
y = cy + ((ch - h) * item->align[1]) + item->pad[2];
}
else if (item->align[1] < 0)
{
// fill y
h = ch - item->pad[2] - item->pad[3];
y = cy + item->pad[2];
}
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];
}
}
//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);
}
}