box: implement aspected item layout

tl;dr: if you haven't used aspect hints on objects packed into boxes,
this commit has no effect on anything you've done

in some cases, using weight+align hints are not sufficient in order to
do a simple box packing of objects.
one example of this is when a box's size is not explicitly determined,
such as if it is packed with EXPAND into another container and contains
only objects with no min size hint, such as images. in this case, the box
will determine that its min size is extremely small, resulting in nearly-hidden
contents inside the box.

using aspect hints, this outcome is noticeably different. the expanded/filled
potential size of a box item is used to calculate the aspected size, and this
size, if it fits within min/max constraints, is then used to calculate the min
size of the box.

as a more concrete example:
* create box with maximum size 100x50
* pack 2x image into box
 - set VERTICAL aspect, EXPAND, FILL
have 2x 50x50 images

also I added some docs

@feature
This commit is contained in:
Mike Blumenkrantz 2015-12-31 15:47:51 -05:00
parent d36f570bc1
commit 80fc568aed
1 changed files with 300 additions and 87 deletions

View File

@ -7,27 +7,259 @@
#include "elm_priv.h"
#include "els_box.h"
/* calculate an object's aspected size */
static Eina_Bool
_box_object_aspect_calc(int *ow, int *oh, int minw, int minh, int maxw, int maxh,
int fw /* fill width */, int fh /* fill height */,
int ww /* "maximum" width */, int hh /* "maximum" height */,
Evas_Aspect_Control aspect, double ratio)
{
if (*ow < minw) *ow = minw;
if (*oh < minh) *oh = minh;
switch (aspect)
{
case EVAS_ASPECT_CONTROL_HORIZONTAL:
/* set height using aspect+width */
if (fw) *ow = ww;
if ((maxw >= 0) && (maxw < *ow)) *ow = maxw;
*oh = ratio / *ow;
/* apply min/max */
if ((maxh >= 0) && (maxh < *oh)) *oh = maxh;
else if ((minh >= 0) && (minh > *oh)) *oh = minh;
else return EINA_TRUE;
return EINA_FALSE;
case EVAS_ASPECT_CONTROL_VERTICAL:
/* set width using aspect+height */
if (fh) *oh = hh;
if ((maxh >= 0) && (maxh < *oh)) *oh = maxh;
*ow = ratio * *oh;
/* apply min/max */
if ((maxw >= 0) && (maxw < *ow)) *ow = maxw;
else if ((minw >= 0) && (minw > *ow)) *ow = minw;
else return EINA_TRUE;
return EINA_FALSE;
case EVAS_ASPECT_CONTROL_BOTH:
/* try width then height */
/* set width using aspect+height */
if (fh) *oh = hh;
if ((maxh >= 0) && (maxh < *oh)) *oh = maxh;
*ow = ratio * *oh;
/* apply min/max */
if ((maxw >= 0) && (maxw < *ow)) *ow = maxw;
else if ((minw >= 0) && (minw > *ow)) *ow = minw;
else return EINA_TRUE;
/* set height using aspect+width */
if (fw) *ow = ww;
if ((maxw >= 0) && (maxw < *ow)) *ow = maxw;
*oh = ratio / *ow;
/* apply min/max */
if ((maxh >= 0) && (maxh < *oh)) *oh = maxh;
else if ((minh >= 0) && (minh > *oh)) *oh = minh;
else return EINA_TRUE;
/* fallthrough on BOTH failure */
default: break;
}
/* on failure or ASPECT_NONE, use default size calc:
* - apply fill
* - apply max size constraints
*/
if (fw) *ow = ww;
if ((maxw >= 0) && (maxw < *ow)) *ow = maxw;
if (fh) *oh = hh;
if ((maxh >= 0) && (maxh < *oh)) *oh = maxh;
return EINA_FALSE;
}
/* add box w/h padding to min/max totals */
static void
_smart_extents_calculate(Evas_Object *box, Evas_Object_Box_Data *priv, Eina_Bool horizontal, Eina_Bool homogeneous)
_smart_extents_padding_calc(Evas_Object_Box_Data *priv, int *minw, int *minh, int *maxw, int *maxh, Eina_Bool horizontal)
{
int c;
if ((*maxw >= 0) && (*minw > *maxw)) *maxw = *minw;
if ((*maxh >= 0) && (*minh > *maxh)) *maxh = *minh;
c = eina_list_count(priv->children) - 1;
if (c > 0)
{
if (horizontal)
{
*minw += priv->pad.h * c;
if (*maxw != -1) *maxw += priv->pad.h * c;
}
else
{
*minh += priv->pad.v * c;
if (*maxh != -1) *maxh += priv->pad.v * c;
}
}
}
/* calculate extents for non-homogeneous layout;
* called twice if aspected items exist
*/
static Eina_Bool
_smart_extents_non_homogeneous_calc(Evas_Object_Box_Data *priv, int w, int h, int *minw, int *minh, int *maxw, int *maxh, double expand, Eina_Bool horizontal, Eina_Bool do_aspect)
{
const Eina_List *l;
Evas_Object_Box_Option *opt;
int mnw, mnh, mxw, mxh, cminw, cminh;
Evas_Coord *rw, *rh, *rxw, *rxh, *rminw, *rminh, *rmaxw, *rmaxh;
Eina_Bool max = EINA_TRUE, asp = EINA_FALSE;
cminw = *minw, cminh = *minh;
*minw = *minh = *maxw = *maxh = 0;
/* use pointers to values to simplify horizontal vs vertical calculations into
* a single algorithm for both orientations
*/
if (!horizontal)
{
rw = &mnw;
rh = &mnh;
rxw = &mxw;
rxh = &mxh;
rminw = minw;
rminh = minh;
rmaxw = maxw;
rmaxh = maxh;
}
else
{
rw = &mnh;
rh = &mnw;
rxw = &mxh;
rxh = &mxw;
rminw = minh;
rminh = minw;
rmaxw = maxh;
rmaxh = maxw;
}
EINA_LIST_FOREACH(priv->children, l, opt)
{
Evas_Aspect_Control aspect = EVAS_ASPECT_CONTROL_NONE;
int asx, asy, ow = 0, oh = 0, *rrw, *rrh;
if (!horizontal)
rrw = &ow, rrh = &oh;
else
rrw = &oh, rrh = &ow;
evas_object_size_hint_min_get(opt->obj, &mnw, &mnh);
if (*rminw < *rw) *rminw = *rw;
*rminh += *rh;
evas_object_size_hint_aspect_get(opt->obj, &aspect, &asx, &asy);
if (aspect && ((asx < 1) || (asy < 1)))
{
aspect = EVAS_ASPECT_CONTROL_NONE;
ERR("Invalid aspect specified!");
}
/* return whether any aspected items exist */
asp |= !!aspect;
evas_object_size_hint_max_get(opt->obj, &mxw, &mxh);
if (*rxh < 0)
{
*rmaxh = -1;
max = EINA_FALSE;
}
if (max) *rmaxh += *rxh;
if (do_aspect && aspect)
{
int ww, hh, fw, fh;
double wx, wy, ax, ay;
evas_object_size_hint_weight_get(opt->obj, &wx, &wy);
if (horizontal)
{
/* use min size to start */
ww = *rw;
if ((expand > 0) && (wx > 0.0))
{
/* add remaining container value after applying weight hint */
ow = ((w - cminw) * wx) / expand;
ww += ow;
}
hh = h;
}
else
{
hh = *rh;
if ((expand > 0) && (wy > 0.0))
{
oh = ((h - cminh) * wy) / expand;
hh += oh;
}
ww = w;
}
evas_object_size_hint_align_get(opt->obj, &ax, &ay);
if (ax < 0) fw = 1;
if (ay < 0) fh = 1;
/* if aspecting succeeds, use aspected size for min size */
if (_box_object_aspect_calc(rrw, rrh, mnw, mnh, mxw, mxh,
fw, fh, ww, hh, aspect, asx / (double)asy))
*rminh += (*rrh - *rh);
}
if (*rxw >= 0)
{
if (*rmaxw == -1) *rmaxw = *rxw;
else if (*rmaxw > *rxw) *rmaxw = *rxw;
}
}
return asp;
}
static void
_smart_extents_calculate(Evas_Object *box, Evas_Object_Box_Data *priv, int w, int h, double expand, Eina_Bool horizontal, Eina_Bool homogeneous)
{
Evas_Coord minw, minh, mnw, mnh, maxw, maxh;
const Eina_List *l;
Evas_Object_Box_Option *opt;
Eina_Bool max = EINA_TRUE;
int c;
minw = 0;
minh = 0;
maxw = -1;
maxh = -1;
c = eina_list_count(priv->children);
if (homogeneous)
{
Evas_Aspect_Control paspect = -1; //causes overflow
int pasx = -1, pasy = -1;
EINA_LIST_FOREACH(priv->children, l, opt)
{
Evas_Aspect_Control aspect = EVAS_ASPECT_CONTROL_NONE;
int asx, asy, ow = 0, oh = 0, fw, fh, ww, hh;
double ax, ay;
evas_object_size_hint_align_get(opt->obj, &ax, &ay);
if (ax < 0) fw = 1;
if (ay < 0) fh = 1;
evas_object_size_hint_min_get(opt->obj, &mnw, &mnh);
if (minh < mnh) minh = mnh;
if (minw < mnw) minw = mnw;
evas_object_size_hint_aspect_get(opt->obj, &aspect, &asx, &asy);
if (aspect && ((asx < 1) || (asy < 1)))
{
aspect = EVAS_ASPECT_CONTROL_NONE;
ERR("Invalid aspect specified!");
}
if (paspect < 100) //value starts overflowed as UINT_MAX
{
/* this condition can cause some items to not be the same size,
* resulting in a non-homogeneous homogeneous layout
*/
if ((aspect != paspect) || (asx != pasx) || (asy != pasy))
ERR("Homogeneous box with differently-aspected items!");
}
evas_object_size_hint_max_get(opt->obj, &mnw, &mnh);
if (mnh >= 0)
{
@ -39,81 +271,57 @@ _smart_extents_calculate(Evas_Object *box, Evas_Object_Box_Data *priv, Eina_Bool
if (maxw == -1) maxw = mnw;
else if (maxw > mnw) maxw = mnw;
}
if (aspect)
{
if (horizontal)
{
ww = ((w - (c - 1) * priv->pad.h) / c);
hh = h;
}
else
{
hh = ((h - (c - 1) * priv->pad.v) / c);
ww = w;
}
if (_box_object_aspect_calc(&ow, &oh, mnw, mnh, maxw, maxh,
fw, fh, ww, hh, aspect, asx / (double)asy))
{
if ((oh > mnh) && (minh < oh)) minh = oh;
if ((ow > mnw) && (minw < ow)) minw = ow;
}
}
paspect = aspect;
pasx = asx, pasy = asy;
}
if (horizontal)
{
minw *= eina_list_count(priv->children);
minw *= c;
if (maxw != -1)
maxw *= eina_list_count(priv->children);
maxw *= c;
else maxw = -1;
}
else
{
minh *= eina_list_count(priv->children);
minh *= c;
if (maxh != -1)
maxh *= eina_list_count(priv->children);
maxh *= c;
else maxh = -1;
}
}
else
{
/* calculate but after switched w and h for horizontal mode */
Evas_Coord *rw, *rh, *rminw, *rminh, *rmaxw, *rmaxh;
if (!horizontal)
/* returns true if at least one item has aspect hint */
if (_smart_extents_non_homogeneous_calc(priv, w, h, &minw, &minh, &maxw, &maxh, expand, horizontal, 0))
{
rw = &mnw;
rh = &mnh;
rminw = &minw;
rminh = &minh;
rmaxw = &maxw;
rmaxh = &maxh;
}
else
{
rw = &mnh;
rh = &mnw;
rminw = &minh;
rminh = &minw;
rmaxw = &maxh;
rmaxh = &maxw;
}
EINA_LIST_FOREACH(priv->children, l, opt)
{
evas_object_size_hint_min_get(opt->obj, &mnw, &mnh);
if (*rminw < *rw) *rminw = *rw;
*rminh += *rh;
evas_object_size_hint_max_get(opt->obj, &mnw, &mnh);
if (*rh < 0)
{
*rmaxh = -1;
max = EINA_FALSE;
}
if (max) *rmaxh += *rh;
if (*rw >= 0)
{
if (*rmaxw == -1) *rmaxw = *rw;
else if (*rmaxw > *rw) *rmaxw = *rw;
}
}
}
if ((maxw >= 0) && (minw > maxw)) maxw = minw;
if ((maxh >= 0) && (minh > maxh)) maxh = minh;
c = eina_list_count(priv->children) - 1;
if (c > 0)
{
if (horizontal)
{
minw += priv->pad.h * c;
if (maxw != -1) maxw += priv->pad.h * c;
}
else
{
minh += priv->pad.v * c;
if (maxh != -1) maxh += priv->pad.v * c;
/* aspect can only be accurately calculated after the full (non-aspected) min size of the box has
* been calculated due to the use of this min size during aspect calculations
*/
_smart_extents_padding_calc(priv, &minw, &minh, &maxw, &maxh, horizontal);
_smart_extents_non_homogeneous_calc(priv, w, h, &minw, &minh, &maxw, &maxh, expand, horizontal, 1);
}
}
_smart_extents_padding_calc(priv, &minw, &minh, &maxw, &maxh, horizontal);
evas_object_size_hint_min_set(box, minw, minh);
evas_object_size_hint_max_set(box, maxw, maxh);
}
@ -132,12 +340,22 @@ _els_box_layout(Evas_Object *o, Evas_Object_Box_Data *priv, Eina_Bool horizontal
double *rwy;
Evas_Object_Box_Option *opt;
_smart_extents_calculate(o, priv, horizontal, homogeneous);
evas_object_geometry_get(o, &x, &y, &w, &h);
/* accummulate expand after switched x and y for horizontal mode */
if (!horizontal)
rwy = &wy;
else
rwy = &wx;
EINA_LIST_FOREACH(priv->children, l, opt)
{
evas_object_size_hint_weight_get(opt->obj, &wx, &wy);
if (*rwy > 0.0) expand += *rwy;
}
_smart_extents_calculate(o, priv, w, h, expand, horizontal, homogeneous);
evas_object_size_hint_min_get(o, &minw, &minh);
evas_object_box_align_get(o, &ax, &ay);
/* if object size is less than min, apply align to trigger viewporting */
if (w < minw)
{
x = x + ((w - minw) * (1.0 - ax));
@ -150,16 +368,6 @@ _els_box_layout(Evas_Object *o, Evas_Object_Box_Data *priv, Eina_Bool horizontal
}
count = eina_list_count(priv->children);
/* accummulate expand as same way but after switched x and y for horizontal mode */
if (!horizontal)
rwy = &wy;
else
rwy = &wx;
EINA_LIST_FOREACH(priv->children, l, opt)
{
evas_object_size_hint_weight_get(opt->obj, &wx, &wy);
if (*rwy > 0.0) expand += *rwy;
}
if (!expand)
{
if (rtl) ax = 1.0 - ax;
@ -179,15 +387,21 @@ _els_box_layout(Evas_Object *o, Evas_Object_Box_Data *priv, Eina_Bool horizontal
EINA_LIST_FOREACH(priv->children, l, opt)
{
Evas_Coord mnw, mnh, mxw, mxh;
int fw, fh, xw, xh;
int fw, fh, xw, xh;//fillw, fillw, expandw, expandh
Evas_Aspect_Control aspect = EVAS_ASPECT_CONTROL_NONE;
int asx, asy;
obj = opt->obj;
evas_object_size_hint_align_get(obj, &ax, &ay);
evas_object_size_hint_weight_get(obj, &wx, &wy);
evas_object_size_hint_min_get(obj, &mnw, &mnh);
evas_object_size_hint_max_get(obj, &mxw, &mxh);
evas_object_size_hint_aspect_get(obj, &aspect, &asx, &asy);
if (aspect && ((asx < 1) || (asy < 1)))
aspect = EVAS_ASPECT_CONTROL_NONE;
fw = fh = 0;
xw = xh = 0;
/* align(-1) means fill to maximum apportioned size */
if (ax == -1.0) {fw = 1; ax = 0.5;}
if (ay == -1.0) {fh = 1; ay = 0.5;}
if (rtl) ax = 1.0 - ax;
@ -195,7 +409,7 @@ _els_box_layout(Evas_Object *o, Evas_Object_Box_Data *priv, Eina_Bool horizontal
if (wy > 0.0) xh = 1;
if (horizontal)
{
Evas_Coord ww, hh, ow, oh;
Evas_Coord ww, hh, ow = 0, oh = 0;
if (homogeneous)
{
@ -211,12 +425,13 @@ _els_box_layout(Evas_Object *o, Evas_Object_Box_Data *priv, Eina_Bool horizontal
}
}
hh = h;
ow = mnw;
if (fw) ow = ww;
if ((mxw >= 0) && (mxw < ow)) ow = mxw;
oh = mnh;
if (fh) oh = hh;
if ((mxh >= 0) && (mxh < oh)) oh = mxh;
_box_object_aspect_calc(&ow, &oh, mnw, mnh, mxw, mxh, fw, fh, ww, hh, aspect, asx / (double)asy);
/* non-homogeneous, aspected, expending items are calculated based on object size
* during extents calc, so use this for positioning during layout as well
*/
if (xw && aspect && (!homogeneous))
ww = ow;
evas_object_move(obj,
((!rtl) ? (xx) : (x + (w - (xx - x) - ww)))
+ (Evas_Coord)(((double)(ww - ow)) * ax),
@ -227,7 +442,7 @@ _els_box_layout(Evas_Object *o, Evas_Object_Box_Data *priv, Eina_Bool horizontal
}
else
{
Evas_Coord ww, hh, ow, oh;
Evas_Coord ww, hh, ow = 0, oh = 0;
if (homogeneous)
{
@ -243,12 +458,10 @@ _els_box_layout(Evas_Object *o, Evas_Object_Box_Data *priv, Eina_Bool horizontal
}
}
ww = w;
ow = mnw;
if (fw) ow = ww;
if ((mxw >= 0) && (mxw < ow)) ow = mxw;
oh = mnh;
if (fh) oh = hh;
if ((mxh >= 0) && (mxh < oh)) oh = mxh;
_box_object_aspect_calc(&ow, &oh, mnw, mnh, mxw, mxh, fw, fh, ww, hh, aspect, asx / (double)asy);
if (xh && aspect && (!homogeneous))
hh = oh;
evas_object_move(obj,
xx + (Evas_Coord)(((double)(ww - ow)) * ax),
yy + (Evas_Coord)(((double)(hh - oh)) * ay));