efl/src/lib/elementary/efl_ui_box_layout.c

329 lines
9.3 KiB
C

#define EFL_GFX_SIZE_HINT_PROTECTED
#include "efl_ui_box_private.h"
// 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, 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)
{
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;
double box_align[2];
Eina_Bool box_fill[2] = { EINA_FALSE, EINA_FALSE };
boxs = efl_gfx_entity_geometry_get(ui_box);
efl_gfx_size_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;
}
count = eina_list_count(bd->children);
if (!count)
{
efl_gfx_size_hint_min_set(ui_box, EINA_SIZE2D(0, 0));
return;
}
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_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]);
efl_gfx_size_hint_fill_get(o, &item->fill[0], &item->fill[1]);
item->max = efl_gfx_size_hint_max_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 (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] > 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;
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;
weight[0] += item->weight[0];
weight[1] += item->weight[1];
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_SIZE_HINT_ASPECT_NONE;
}
}
else
{
_min_max_calc(item, &item->min.w, &item->min.h, EINA_TRUE);
}
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 (horiz)
{
if (item->space[1] > wanth)
wanth = item->space[1];
wantw += item->space[0];
}
else
{
if (item->space[0] > wantw)
wantw = item->space[0];
wanth += item->space[1];
}
item->id = id++;
}
// total space & available space
if (horiz)
{
want = wantw;
length = boxs.w;
pad = pd->pad.scalable ? (pd->pad.h * scale) : pd->pad.h;
if (boxs.h < wanth)
boxs.h = wanth;
}
else
{
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;
// calculate weight length
if ((length > want) && (weight[!horiz] > 0))
{
int orig_length = length;
double orig_weight = weight[!horiz];
for (id = 0; id < count; id++)
{
double denom;
item = &items[id];
denom = (item->weight[!horiz] * orig_length) -
(orig_weight * item->space[!horiz]);
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);
}
else
{
length -= item->space[!horiz];
weight[!horiz] -= item->weight[!horiz];
}
}
EINA_INLIST_FOREACH(inlist, item)
{
double weight_len;
weight_len = (length * item->weight[!horiz]) / weight[!horiz];
if (item->space[!horiz] < weight_len)
{
item->space[!horiz] = weight_len;
}
else
{
weight[!horiz] -= item->weight[!horiz];
length -= item->space[!horiz];
}
}
}
// calculate item geometry
{
int x, y, w, h, sw, sh;
if ((length > want) && EINA_DBL_EQ(weight[!horiz], 0))
cur_pos += (length - want) * box_align[!horiz];
for (id = 0; id < count; id++)
{
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
w = 0;
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)
{
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]);
cur_pos += item->space[!horiz] + pad;
efl_gfx_entity_geometry_set(item->obj, EINA_RECT(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));
}
}