New maximisation algorithm.

Based on patches by Daniel Manjarres.

SVN revision: 61190
This commit is contained in:
Kim Woelders 2011-07-10 06:23:57 +00:00
parent b84d22e594
commit 8989b80e80
1 changed files with 655 additions and 89 deletions

View File

@ -33,6 +33,8 @@
#define Dprintf(fmt...)
#endif
#define ENABLE_SMART_MAXIMISE 1
#define MAX_ABSOLUTE 0 /* Fill screen */
#define MAX_AVAILABLE 1 /* Expand until don't cover */
#define MAX_CONSERVATIVE 2 /* Expand until something */
@ -108,116 +110,678 @@ _get_span_y(const EWin * ewin, int type, EWin * const *lst, int num,
*py2 = sy2;
}
static void
_get_span_xy(const EWin * ewin, int type, EWin * const *lst, int num,
int *px1, int *px2, int *py1, int *py2)
{
int i;
EWin *pe;
int x, y, w, h, x1, x2, y1, y2;
#if ENABLE_SMART_MAXIMISE
/*
* Smart window sizing algorithm.
* Based on pareto frontiers and constraint unification.
* Blame it on Dan Manjarres.
*
* The algorithm is to treat the center of the window being maximized as the
* center of a cartesian coordinate system, and to find a pareto frontier
* in each quadrant of the cartesian plane. The frontier is found from points
* that are the corners of other windows or the nearest point on the edge of a
* window that spans 2 quadrants. Windows that span more than 2 quadrants are
* ignored since they already overlap with the original window.
*
* The frontiers encode a discrete set of possible window edges, which are sorted
* and tested in sequence until the one with the largest area is found.
*
* Filtering is done to ignore windows that are visually "under"
* a window that is overlapped so that hidden windows are not counted as
* constraining maximization. Windows that have the never use flag are not
* ignored in this manner. Windows with aspect ratio hints are respected, and
* placed as close as possible to their original position. All windows may be
* slightly shifted to get more area, as the only point that represents the
* window is its center, around which the constraints are placed. The new
* center may be located anywhere in region of the screen defined by partial
* orders of windows along x and y, and conflicts between a windows existing edges
* and resizing the window are completely bypassed. In other words you don't have
* to grab the mouse to move the window before maximizing it, and you don't need the
* mouse to maximizing it either. Alt-Tab and <maximize key command> are all you need.
*
* computation proceeeds as
*
* 1) list all windows
* 2) filter out windows that overlap maximizing window, and all windows under them.
* 3) record the 4 corners of each window in a constraint array
* 4) compute the nearest point of any window that crosses the x or y axis and store
* it as a constraint.
* 5) make 4 copies of the array and adjust for different search directions
* 6) for each copy filter in preparation for pareto criteria for each corner of the window
* 6a) for upper corners ignore windows that are below window center
* 6b) for lower corners ignore windows that are above below window center
* 6c) for left corners ignore windows that are right of window center
* 6d) for right corners ignore windows that are left of window center
* 7) perform pareto frontier filtering for each quadrant
* after this step the remaining points are the pareto frontier for each corner
* of the window in isolation, and form a not-too-nonconvex hull topologically equal
* to a circle around the original window's center.
*
* Each discrete x value and y value represents a potential location
* for sides of the window, and each window side will touch
* at least one of its potential corner points.
*
* 8) combine the adjacent corners for each side into arrays holding potential
* window edge locations and maximum spans sorted by distance from center.
* 9) for each pair of top and bottom constraints find the available area from
* the left and right constraints.
* 10) adjust for aspect ratio.
* 11) pick the biggest one
* 12) and on the seventh day rest
* */
typedef struct {
int dx;
int dy;
int dominated;
} point;
static int
_gti(const void *p1, const void *p2)
{
return (*(int *)p1) - *((int *)p2);
}
static int
_lti(const void *p1, const void *p2)
{
return (*(int *)p2) - *((int *)p1);
}
static int
_gtx(const void *p1, const void *p2)
{
return ((point *) p1)->dx - ((point *) p2)->dx;
}
static int
_ltx(const void *p1, const void *p2)
{
return ((point *) p2)->dx - ((point *) p1)->dx;
}
#if 0
static int
_gty(const void *p1, const void *p2)
{
return ((point *) p1)->dy - ((point *) p2)->dy;
}
static int
_lty(const void *p1, const void *p2)
{
return ((point *) p2)->dy - ((point *) p1)->dy;
}
#endif
static void
sort_ints(int *is, int n, int ascending)
{
if (ascending >= 0)
qsort(is, n, sizeof(int), _gti);
else
qsort(is, n, sizeof(int), _lti);
}
static void
sort_points_x(point * ps, int n, int ascending)
{
if (ascending >= 0)
qsort(ps, n, sizeof(point), _gtx);
else
qsort(ps, n, sizeof(point), _ltx);
}
#if 0
static void
sort_points_y(point * ps, int n, int ascending)
{
if (ascending >= 0)
qsort(ps, n, sizeof(point), _gty);
else
qsort(ps, n, sizeof(point), _lty);
}
#endif
static void
uniq_ints(int *is, int *n)
{
int i, j;
for (i = 0, j = 0; i < *n - 1; i++)
{
if (is[i] != is[i + 1])
{
is[j] = is[i];
j++;
}
}
is[j] = is[i];
j++;
*n = j;
}
static void
filter_points(point * p, int *np, int max_dx, int max_dy)
{
int i, j, n = *np;
/* this is step 6 from above */
for (i = 0; i < n; i++)
{
if (p[i].dx < 0)
p[i].dominated = 1;
if (p[i].dy < 0)
p[i].dominated = 1;
/* clip to screen */
if (p[i].dx > max_dx)
p[i].dx = max_dx;
if (p[i].dy > max_dy)
p[i].dy = max_dy;
}
/* this is step 7 from above */
for (i = 0; i < n; i++)
{
if (p[i].dominated)
continue;
for (j = 0; j < n; j++)
{
if (i == j)
continue; /* self dominance = no */
if ((p[i].dx <= p[j].dx) && (p[i].dy <= p[j].dy))
{
p[j].dominated = 1;
}
}
}
/* actual pareto frontier filtering */
for (i = 0, j = 0; i < n; i++)
{
if (!p[i].dominated)
{
p[j] = p[i];
j++;
}
}
*np = j;
}
#define _get_span_xy pareto_maximizer
static void
pareto_maximizer(EWin * ewin, int type, EWin * const *lst, int num,
int *avail_x1, int *avail_x2, int *avail_y1, int *avail_y2)
{
int x, y, w, h;
int cx, cy; /* center */
int i, j, k;
point *constraints_tr = NULL; /* top right */
point *constraints_tl = NULL; /* top left */
point *constraints_br = NULL; /* botom right */
point *constraints_bl = NULL; /* botom left */
int num_tr, num_tl, num_br, num_bl;
int *top_ds = NULL;
int *bottom_ds = NULL;
point *left_ds = NULL;
point *right_ds = NULL;
int *tc, *bc;
point *lc, *rc; /* temp cursors */
int num_t, num_b, num_l, num_r;
float aspect;
EWin **filtered_lst, **stacked_lst, *pe, *pe2;
char *stacked_flag;
int num_stacked;
long area, new_area;
int recenter, new_recenter;
int td, bd, ld, rd; /* displacement to maximized edges */
Dprintf("searching within %d-%d %d-%d\n",
*avail_x1, *avail_x2, *avail_y1, *avail_y2);
x = EoGetX(ewin);
y = EoGetY(ewin);
h = EoGetH(ewin);
w = EoGetW(ewin);
cx = x + w / 2;
cy = y + h / 2;
x1 = *px1;
x2 = *px2;
y1 = *py1;
y2 = *py2;
/* center must be within available region */
cx = (cx >= *avail_x2) ? *avail_x2 - 1 : cx;
cy = (cy >= *avail_y2) ? *avail_y2 - 1 : cy;
cx = (cx < *avail_x1) ? *avail_x1 : cx;
cy = (cy < *avail_y1) ? *avail_y1 : cy;
for (i = 0; i < num;)
filtered_lst = EMALLOC(EWin *, num);
stacked_lst = EMALLOC(EWin *, num + 1);
stacked_flag = ECALLOC(char, num);
if (!filtered_lst || !stacked_lst || !stacked_flag)
goto freedom;
stacked_lst[0] = ewin;
num_stacked = 1;
/* ignore windows already overlapping ours and any windows UNDER them */
/* start by detecting windows we overlap */
for (i = 0; i < num; i++)
{
int x1n, x2n, y1n, y2n;
int need_chop_y, need_chop_x;
int top, bottom, left, right;
pe = lst[i];
x1n = x1;
x2n = x2;
y1n = y1;
y2n = y2;
left = EoGetX(pe);
right = left + EoGetW(pe);
top = EoGetY(pe);
bottom = top + EoGetH(pe);
need_chop_x = need_chop_y = 0;
Dprintf("trying window #%d %s x:%d-%d y:%d-%d vs x:%d-%d y:%d-%d\n",
i, EoGetName(pe), left, right, top, bottom, x1, x2, y1, y2);
if (pe == ewin || _ignore(pe, type) ||
/* ignore windws that do not overlap with current search area */
!(SPANS_COMMON(x1, x2 - x1, EoGetX(pe), EoGetW(pe)) &&
SPANS_COMMON(y1, y2 - y1, EoGetY(pe), EoGetH(pe))) ||
/* ignore windows that already overlap with the orig window */
(SPANS_COMMON(x + 1, w - 2, EoGetX(pe), EoGetW(pe)) &&
SPANS_COMMON(y + 1, h - 2, EoGetY(pe), EoGetH(pe))))
if ((pe == ewin) || _ignore(pe, type))
{
i++;
stacked_flag[i] = 1;
Dprintf("ignoring #%d %s\n", i, EwinGetTitle(pe));
continue;
}
if (right <= x + w / 2)
if (SPANS_COMMON(x + 1, w - 2, EoGetX(pe), EoGetW(pe)) &&
SPANS_COMMON(y + 1, h - 2, EoGetY(pe), EoGetH(pe)))
{
need_chop_x = 1;
x1n = right;
}
if (left >= x + w / 2)
{
need_chop_x = 1;
x2n = left;
}
if (bottom <= y + h / 2)
{
need_chop_y = 1;
y1n = bottom;
}
if (top >= y + h / 2)
{
need_chop_y = 1;
y2n = top;
}
Dprintf("chop v: %d chop_x:%d\n",
(y2n - y1n) * (x2 - x1), (y2 - y1) * (x2n - x1n));
if (!(need_chop_y || need_chop_x))
{
Dprintf("no chop\n");
i++;
continue;
}
if (!need_chop_x)
{
Dprintf("chop_v\n");
y2 = y2n;
y1 = y1n;
}
else if (!need_chop_y)
{
Dprintf("chop_h\n");
x2 = x2n;
x1 = x1n;
}
/* greedily chop the minimum area either from the sides or top/bottom
* We may need to do a final cleanup pass below to escape from a
* local local minima of the area decision function */
else if ((y2 - y1) * (x2n - x1n) > (y2n - y1n) * (x2 - x1))
{
Dprintf("___chop_h\n");
x2 = x2n;
x1 = x1n;
stacked_lst[num_stacked] = pe;
num_stacked++;
stacked_flag[i] = 1;
Dprintf("overlap #%d %s\n", i, EwinGetTitle(pe));
}
else
{
Dprintf("___chop_v\n");
y2 = y2n;
y1 = y1n;
Dprintf("do not overlap #%d %s\n", i, EwinGetTitle(pe));
}
}
/* extend the stacked list to windows that are UNDER other items in the stacked list */
for (i = 1; i < num_stacked; i++)
{
int sx, sy, sw, sh;
pe2 = stacked_lst[i];
sx = EoGetX(pe2);
sy = EoGetY(pe2);
sh = EoGetH(pe2);
sw = EoGetW(pe2);
Dprintf("metaoverlap testing from stacked %s\n", EwinGetTitle(pe));
/* skip windows before this one in the list */
for (j = 0; j < num; j++)
{
pe = lst[j];
if (pe == pe2)
break;
}
for (; j < num; j++)
{
pe = lst[j];
if (stacked_flag[j])
continue;
if (pe->props.never_use_area)
{
Dprintf("not using area %s\n", EwinGetTitle(pe));
continue;
}
if (SPANS_COMMON(sx + 1, sw - 2, EoGetX(pe), EoGetW(pe)) &&
SPANS_COMMON(sy + 1, sh - 2, EoGetY(pe), EoGetH(pe)))
{
/* the list is already top down so if it overlaps it's also under */
/* we hope! */
stacked_lst[num_stacked] = pe;
num_stacked++;
stacked_flag[j] = 1;
Dprintf("metaoverlap #%d %s\n", j, EwinGetTitle(pe));
}
else
{
Dprintf("no metaoverlap #%d %s\n", j, EwinGetTitle(pe));
}
}
}
/* copy remaining windows to our working set */
for (i = 0, j = 0; i < num; i++)
{
pe = lst[i];
if (stacked_flag[i])
{
Dprintf("no stacked constraint from #%d %s\n", i,
EwinGetTitle(pe));
continue;
}
if ((pe == ewin) || _ignore(pe, type) ||
/* ignore windws that do not overlap with current search area */
!(SPANS_COMMON(*avail_x1, *avail_x2 - *avail_x1,
EoGetX(pe), EoGetW(pe)) &&
SPANS_COMMON(*avail_y1, *avail_y2 - *avail_y1,
EoGetY(pe), EoGetH(pe))))
{
Dprintf("no constraint from %s\n", EwinGetTitle(pe));
continue;
}
Dprintf("constraint from %s\n", EwinGetTitle(pe));
filtered_lst[j] = pe;
j++;
}
/* allocate memory to hold constraints */
num = j + 1;
num_tl = num_bl = num_br = num_tr = num * 5;
constraints_tl = EMALLOC(point, num * 5);
constraints_tr = EMALLOC(point, num * 5);
constraints_bl = EMALLOC(point, num * 5);
constraints_br = ECALLOC(point, num * 5);
if (!constraints_tl || !constraints_tr || !constraints_bl || !constraints_br)
goto freedom;
for (i = 1; i < num; i++)
{
pe = filtered_lst[i - 1];
/* store window corners as candidate constraints */
constraints_br[5 * i + 0].dx = EoGetX(pe);
constraints_br[5 * i + 0].dy = EoGetY(pe);
constraints_br[5 * i + 1].dx = EoGetX(pe) + EoGetW(pe);
constraints_br[5 * i + 1].dy = EoGetY(pe);
constraints_br[5 * i + 2].dx = EoGetX(pe) + EoGetW(pe);
constraints_br[5 * i + 2].dy = EoGetY(pe) + EoGetH(pe);
constraints_br[5 * i + 3].dx = EoGetX(pe);
constraints_br[5 * i + 3].dy = EoGetY(pe) + EoGetH(pe);
/* if window occupies 2 quadrants add a constraint for the
* intersection of the x or y axis and the window */
if (SPANS_COMMON(EoGetX(pe), EoGetW(pe), cx, 1))
{
Dprintf("got horiz edge contraint\n");
constraints_br[5 * i + 4].dx = cx;
if (EoGetY(pe) > cy)
{
constraints_br[5 * i + 4].dy = EoGetY(pe);
}
else
{
constraints_br[5 * i + 4].dy = EoGetY(pe) + EoGetH(pe);
}
}
else if (SPANS_COMMON(EoGetY(pe), EoGetH(pe), cy, 1))
{
Dprintf("got ver edge contraint\n");
constraints_br[5 * i + 4].dy = cy;
if (EoGetX(pe) > cx)
{
constraints_br[5 * i + 4].dx = EoGetX(pe);
}
else
{
constraints_br[5 * i + 4].dx = EoGetX(pe) + EoGetW(pe);
}
}
else
{
constraints_br[5 * i + 4].dominated = 1;
Dprintf("got no edge constraint from win #%d %s\n", i - 1,
EwinGetTitle(pe));
}
}
/* add constraints to keep the window on-screen */
constraints_br[0].dx = cx;
constraints_br[0].dy = *avail_y1 - 1;
constraints_br[1].dx = cx;
constraints_br[1].dy = *avail_y2 + 1;
constraints_br[2].dx = *avail_x1 - 1;
constraints_br[2].dy = cy;
constraints_br[3].dx = *avail_x2 + 1;
constraints_br[3].dy = cy;
constraints_br[4].dominated = 1;
/* subtract out center to get distance to constraints */
for (i = 0; i < num_tr; i++)
{
constraints_br[i].dx -= cx;
constraints_br[i].dy -= cy;
}
/* make 4 copies: one for each corner to optimize */
memcpy(constraints_tl, constraints_br, sizeof(point) * num_tl);
memcpy(constraints_bl, constraints_br, sizeof(point) * num_tl);
memcpy(constraints_tr, constraints_br, sizeof(point) * num_tl);
for (i = 0; i < num_tr; i++)
{
/* correct displacements to be positive in the direction of expansion */
constraints_tl[i].dx *= -1;
constraints_tl[i].dy *= -1;
constraints_bl[i].dx *= -1;
constraints_tr[i].dy *= -1;
}
/* bust out that pareto frontier */
filter_points(constraints_tl, &num_tl, cx - *avail_x1, cy - *avail_y1);
filter_points(constraints_tr, &num_tr, *avail_x2 - cx, cy - *avail_y1);
filter_points(constraints_bl, &num_bl, cx - *avail_x1, *avail_y2 - cy);
filter_points(constraints_br, &num_br, *avail_x2 - cx, *avail_y2 - cy);
/* now need to convert corner constraints to candidate edge constraints */
num_t = num_tl + num_tr;
num_b = num_bl + num_br;
num_l = num_tl + num_bl;
num_r = num_tr + num_br;
tc = top_ds = EMALLOC(int, num_t);
bc = bottom_ds = EMALLOC(int, num_b);
lc = left_ds = EMALLOC(point, num_l);
rc = right_ds = EMALLOC(point, num_r);
if (!tc || !bc || !lc || !rc)
goto freedom;
/* convert constraint distances back to to constraint displacements
* using cursor pointers to accumulate constraints for each edge */
for (i = 0; i < num_tr; i++)
{
rc->dx = constraints_tr[i].dx;
rc->dy = -constraints_tr[i].dy;
*tc = -constraints_tr[i].dy;
tc++;
rc++;
}
for (i = 0; i < num_tl; i++)
{
lc->dx = -constraints_tl[i].dx;
lc->dy = -constraints_tl[i].dy;
*tc = -constraints_tl[i].dy;
tc++;
lc++;
}
for (i = 0; i < num_br; i++)
{
rc->dx = constraints_br[i].dx;
rc->dy = constraints_br[i].dy;
*bc = constraints_br[i].dy;
bc++;
rc++;
}
for (i = 0; i < num_bl; i++)
{
lc->dx = -constraints_bl[i].dx;
lc->dy = constraints_bl[i].dy;
*bc = constraints_bl[i].dy;
bc++;
lc++;
}
/* sort the lists for easy searching */
sort_points_x(left_ds, num_l, +1);
sort_points_x(right_ds, num_r, -1);
sort_ints(top_ds, num_t, +1);
sort_ints(bottom_ds, num_b, -1);
uniq_ints(top_ds, &num_t);
uniq_ints(bottom_ds, &num_b);
/* brute force test the combinitorics:
* for each pair of possible top and bottom displacements,
* find the best possible pair of left and right displacements */
area = 0;
recenter = 0;
for (i = 0; i < num_t; i++) /* top indices */
{
for (j = 0; j < num_b; j++) /* bottom indices */
{
int new_w, new_h, trim;
td = top_ds[i]; /* displacement to top */
bd = bottom_ds[j]; /* displacement to bottom */
Dprintf("starting search in td-bd %d-%d\n", td, bd);
ld = left_ds[0].dx;
rd = right_ds[0].dx;
Dprintf("search in y from %d to %d\n", td, bd);
/* find furthest left given top, bottom */
for (k = 0; k < num_l; k++)
{
if (left_ds[k].dy <= td) /* constraint point is above top, skip it */
{
Dprintf("left ignoring above point %d,%d\n",
left_ds[k].dx, left_ds[k].dy);
continue;
}
if (left_ds[k].dy >= bd) /* constraint point is below bottom, skip it */
{
Dprintf("left ignoring below point %d,%d\n",
left_ds[k].dx, left_ds[k].dy);
continue;
}
Dprintf("left using point %d,%d\n", left_ds[k].dx,
left_ds[k].dy);
if (left_ds[k].dx > ld)
ld = left_ds[k].dx + 1;
}
/* find furthest right given top, bottom */
for (k = 0; k < num_r; k++)
{
if (right_ds[k].dy <= td) /* constraint point is above top, skip it */
{
Dprintf("right ignoring above point %d,%d\n",
right_ds[k].dx, right_ds[k].dy);
continue;
}
if (right_ds[k].dy >= bd) /* constraint point is below bottom, skip it */
{
Dprintf("right ignoring below point %d,%d\n",
right_ds[k].dx, right_ds[k].dy);
continue;
}
Dprintf("right using point %d,%d\n", right_ds[k].dx,
right_ds[k].dy);
if (right_ds[k].dx < rd)
rd = right_ds[k].dx - 1;
}
/* almost there..... need to correct for aspect ratio
* and keep center as close as possible to old center
* in case of duplicates...... */
new_w = 1 - ld + rd;
new_h = 1 - td + bd;
aspect = (float)new_w / new_h;
new_area = new_w * new_h;
if (new_area > area)
{
if (aspect > ewin->icccm.aspect_max)
{
do
{
if (abs(ld) < abs(rd))
{
Dprintf("trimming right for aspect\n");
rd--;
}
else
{
Dprintf("trimming left for aspect\n");
ld++;
}
new_w = 1 - ld + rd;
trim = new_w - new_h * ewin->icccm.aspect_min;
}
while (trim > 0);
Dprintf("ld now %d, rd now %d\n", ld, rd);
Dprintf("td now %d, bd now %d\n", td, bd);
Dprintf("\n");
}
else if (aspect < ewin->icccm.aspect_min)
{
do
{
if (abs(td) < abs(bd))
{
Dprintf("trimming bottom for aspect\n");
bd--;
}
else
{
Dprintf("trimming top for aspect\n");
td++;
}
new_h = 1 - td + bd;
trim = new_h - new_w / ewin->icccm.aspect_min;
}
while (trim > 0);
Dprintf("ld now %d, rd now %d\n", ld, rd);
Dprintf("td now %d, bd now %d\n", td, bd);
Dprintf("\n");
}
new_area = new_w * new_h;
new_recenter = abs(td) + abs(bd) + abs(ld) + abs(rd);
if ((new_area > area) ||
((new_area == area) && (new_recenter < recenter)))
{
Dprintf("new area %ld old area %ld\n", new_area, area);
Dprintf("%d-%d x %d-%d\n", x, x + w, y, y + h);
area = new_area;
recenter = new_recenter;
x = cx + ld;
y = cy + td;
w = (1 - ld + rd);
h = (1 - td + bd);
}
}
Dprintf("===========================\n");
}
}
*avail_x1 = x;
*avail_x2 = x + w;
*avail_y1 = y;
*avail_y2 = y + h;
/* rest a while */
freedom:
Efree(top_ds);
Efree(bottom_ds);
Efree(left_ds);
Efree(right_ds);
Efree(constraints_tl);
Efree(constraints_tr);
Efree(constraints_bl);
Efree(constraints_br);
Efree(filtered_lst);
Efree(stacked_lst);
Efree(stacked_flag);
}
#endif /* ENABLE_SMART_MAXIMISE */
void
MaxSizeHV(EWin * ewin, const char *resize_type, int hor, int ver)
@ -365,6 +929,7 @@ MaxSizeHV(EWin * ewin, const char *resize_type, int hor, int ver)
lst = EwinListGetForDesk(&num, EoGetDesk(ewin));
}
#if ENABLE_SMART_MAXIMISE
if (type == MAX_CONSERVATIVE && ver && hor &&
( /*(!old_hor && !old_ver) || */ Conf.movres.enable_smart_max_hv))
{
@ -375,6 +940,7 @@ MaxSizeHV(EWin * ewin, const char *resize_type, int hor, int ver)
h = y2 - y1;
break;
}
#endif
if (ver)
{