wmfs/src/layout.c

576 lines
14 KiB
C

/*
* wmfs2 by Martin Duquesnoy <xorg62@gmail.com> { for(i = 2011; i < 2111; ++i) ©(i); }
* For license, see COPYING.
*/
#include <X11/Xutil.h>
#include "layout.h"
#include "config.h"
#include "client.h"
#include "draw.h"
#include "event.h"
#include "util.h"
void
layout_save_set(struct tag *t)
{
struct client *c;
struct layout_set *l;
struct geo_list *g, *gp;
int n = 1;
l = xcalloc(1, sizeof(struct layout_set));
SLIST_INIT(&l->geos);
FOREACH_NFCLIENT(c, &t->clients, tnext)
{
g = xcalloc(1, sizeof(struct geo_list));
g->geo = c->geo;
if(!SLIST_FIRST(&l->geos))
SLIST_INSERT_HEAD(&l->geos, g, next);
else
SLIST_INSERT_AFTER(gp, g, next);
++n;
gp = g;
}
l->n = n;
TAILQ_INSERT_TAIL(&t->sets, l, next);
}
static void
layout_apply_set(struct tag *t, struct layout_set *l)
{
struct geo_list *g;
struct client *c;
int nc = 1;
FOREACH_NFCLIENT(c, &t->clients, tnext)
++nc;
/* TODO: Adapt different client number case */
if(l->n != nc)
return;
for(g = SLIST_FIRST(&l->geos), c = SLIST_FIRST(&t->clients);
c; c = SLIST_NEXT(c, tnext))
{
if(g)
{
client_moveresize(c, &g->geo);
g = SLIST_NEXT(g, next);
}
/* TODO
* Not enough geos in the set;
* then integrate remains of client
*
else
layout_split_integrate(c, SLIST_FIRST(&t->clients));
*/
}
/* TODO
* Not enough clients for geos in set;
* arrange clients with not set geo.
*
if((g = SLIST_NEXT(g, next)))
for(cc.tag = t; g; g = SLIST_NEXT(g, next))
{
cc.geo = g->geo;
layout_split_arrange_closed(&cc);
}
*/
/* Re-insert set in historic */
layout_save_set(t);
}
void
layout_free_set(struct tag *t)
{
struct layout_set *l;
TAILQ_FOREACH(l, &t->sets, next)
{
TAILQ_REMOVE(&t->sets, l, next);
FREE_LIST(geo_list, l->geos);
free(l);
}
}
#define _REV_BORDER() \
SLIST_FOREACH(g, &l->geos, next) { \
cd.geo = g->geo; \
draw_reversed_rect(W->root, &cd, false); \
}
static void
_historic_set(struct tag *t, bool prev)
{
struct keybind *k;
struct layout_set *l;
struct geo_list *g;
struct client cd = { .screen = t->screen, .theme = THEME_DEFAULT };
bool b = true;
XEvent ev;
KeySym keysym;
if(TAILQ_EMPTY(&t->sets))
return;
l = TAILQ_LAST(&t->sets, ssub);
if(prev)
l = TAILQ_PREV(l, ssub, next);
if(!l)
return;
/* TODO
if(option_simple_manual_resize)
{
layout_set_apply(l);
return;
*/
XGrabKeyboard(W->dpy, W->root, True, GrabModeAsync, GrabModeAsync, CurrentTime);
_REV_BORDER();
do
{
XMaskEvent(W->dpy, KeyPressMask, &ev);
if(ev.type == KeyPress)
{
XKeyPressedEvent *ke = &ev.xkey;
keysym = XKeycodeToKeysym(W->dpy, (KeyCode)ke->keycode, 0);
_REV_BORDER();
SLIST_FOREACH(k, &W->h.keybind, next)
if(k->keysym == keysym && KEYPRESS_MASK(k->mod) == KEYPRESS_MASK(ke->state)
&& k->func)
{
if(k->func == uicb_layout_prev_set)
{
if(!(l = TAILQ_PREV(l, ssub, next)))
l = TAILQ_LAST(&t->sets, ssub);
}
else if(k->func == uicb_layout_next_set)
{
if(!(l = TAILQ_NEXT(l, next)))
l = TAILQ_FIRST(&t->sets);
}
else
{
k->func(k->cmd);
keysym = XK_Escape;
}
}
if(!l)
l = TAILQ_LAST(&t->sets, ssub);
_REV_BORDER();
/* Gtfo of this loop */
if(keysym == XK_Return)
break;
else if(keysym == XK_Escape)
{
b = false;
break;
}
XSync(W->dpy, False);
}
XNextEvent(W->dpy, &ev);
} while(ev.type != KeyPress);
_REV_BORDER();
if(b)
layout_apply_set(t, l);
XUngrabServer(W->dpy);
XUngrabKeyboard(W->dpy, CurrentTime);
}
void
uicb_layout_prev_set(Uicb cmd)
{
(void)cmd;
_historic_set(W->screen->seltag, true);
}
void
uicb_layout_next_set(Uicb cmd)
{
(void)cmd;
_historic_set(W->screen->seltag, false);
}
static struct geo
layout_split(struct client *c, bool vertical)
{
struct geo og, geo;
geo = og = c->geo;
if(vertical)
{
c->geo.w >>= 1;
geo.x = c->geo.x + c->geo.w;
geo.w >>= 1;
/* Remainder */
geo.w += (og.x + og.w) - (geo.x + geo.w);
}
else
{
c->geo.h >>= 1;
geo.y = c->geo.y + c->geo.h;
geo.h >>= 1;
/* Remainder */
geo.h += (og.y + og.h) - (geo.y + geo.h);
}
return geo;
}
static inline void
layout_split_arrange_size(struct geo *g, struct client *c, enum position p)
{
if(LDIR(p))
{
c->geo.w += g->w;
if(p == Right)
c->geo.x = g->x;
}
else
{
c->geo.h += g->h;
if(p == Bottom)
c->geo.y = g->y;
}
client_moveresize(c, &c->geo);
}
static inline bool
layout_split_check_row_dir(struct client *c, struct client *g, enum position p)
{
struct geo cgeo = c->geo;
struct client *cc;
int s = 0, cs = (LDIR(p) ? g->geo.h : g->geo.w);
FOREACH_NFCLIENT(cc, &c->tag->clients, tnext)
if(GEO_PARENTROW(cgeo, cc->geo, RPOS(p))
&& GEO_CHECK_ROW(cc->geo, g->geo, p))
{
s += (LDIR(p) ? cc->geo.h : cc->geo.w);
if(s == cs)
return true;
if(s > cs)
return false;
}
return false;
}
/* Use ghost client properties to fix holes in tile
*
* ~ .--. ~ ~
*_____ ~ /xx \ ~ ~
* |>>| ~\O _ (____ ~
* | |__.| .--'-==~ ~
* |>>'---\ '. ~ , ~
*__|__| '. '-.___.-'/ ~
* '-.__ _.' ~
* ````` ~
*/
#define _ARRANGE_SINGLE_PARENT(p) \
do { \
if((c = client_next_with_pos(ghost, p))) \
if(GEO_CHECK2(ghost->geo, c->geo, p)) \
{ \
layout_split_arrange_size(&ghost->geo, c, p); \
layout_save_set(ghost->tag); \
return; \
} \
} while(/* CONSTCOND */ 0);
void
layout_split_arrange_closed(struct client *ghost)
{
struct client *c, *cc;
struct geo g;
bool b = false;
enum position p;
/* Search for single parent for easy resize
* Example case:
* ___________ ___________
* | | B | -> -> | | |
* | A |_____| -> Close -> | A | B |
* | | C | -> C -> | |v v v|
* |_____|_____| -> -> |_____|_____|
*/
_ARRANGE_SINGLE_PARENT(Right);
_ARRANGE_SINGLE_PARENT(Left);
_ARRANGE_SINGLE_PARENT(Top);
_ARRANGE_SINGLE_PARENT(Bottom);
/* Check row parents for full resize
* Example case:
* ___________ ___________
* | | B | -> -> | << B |
* | A |_____| -> Close -> |___________|
* | | C | -> A -> | << C |
* |_____|_____| -> -> |___________|
*/
for(p = Right; p < Center && !b; ++p)
{
if((c = client_next_with_pos(ghost, p))
&& layout_split_check_row_dir(c, ghost, p))
{
g = c->geo;
FOREACH_NFCLIENT(cc, &c->tag->clients, tnext)
if(GEO_PARENTROW(g, cc->geo, RPOS(p))
&& GEO_CHECK_ROW(cc->geo, ghost->geo, p))
{
layout_split_arrange_size(&ghost->geo, cc, p);
b = true;
}
}
}
layout_save_set(ghost->tag);
}
/* Integrate a client in split layout: split sc and fill c in new geo */
void
layout_split_integrate(struct client *c, struct client *sc)
{
struct geo g;
/* No sc or not compatible sc */
if(!sc || sc == c || sc->tag != c->tag
|| sc->flags & CLIENT_FREE)
{
/*
* check for the first tiled client or
* maximize the lonely client
*/
FOREACH_NFCLIENT(sc, &c->tag->clients, tnext)
{
if(!(sc->flags & CLIENT_TILED) || sc == c)
continue;
break;
}
/* Ok there is no client to integrate in */
if(!sc)
{
client_maximize(c);
c->flags |= CLIENT_TILED;
return;
}
}
c->flags |= CLIENT_TILED;
g = layout_split(sc, (sc->geo.h < sc->geo.w));
client_moveresize(c, &g);
client_moveresize(sc, &sc->geo);
client_fac_hint(c);
client_fac_hint(sc);
layout_save_set(c->tag);
}
/* Arrange inter-clients holes:
* ___________ ___________
* | || | -> | | |
* | A || B | -> | A >| B |
* | || | -> | >| |
* |_____||____| -> |______|____|
* ^ void
*
* and client-screen edge holes
* ___________ ___________
* | | || -> | | |
* | A | B || -> | A | B >|
* | | || -> | | >|
* |_____|----'| -> |_____|__v__|
* ^^^ void
*/
inline void
layout_fix_hole(struct client *c)
{
struct client *cr = client_next_with_pos(c, Right);
struct client *cb = client_next_with_pos(c, Bottom);
c->geo.w += (cr ? cr->geo.x : c->screen->ugeo.w) - (c->geo.x + c->geo.w);
c->geo.h += (cb ? cb->geo.y : c->screen->ugeo.h) - (c->geo.y + c->geo.h);
client_moveresize(c, &c->geo);
}
/* Layout rotation: Rotate 90° all client to right or left.
* Avoid if(left) condition in layout_rotate loop; use func ptr
*
* Right rotation
* ____________ ____________
* | | B | -> | | A |
* | A |_______| -> |__|_________|
* |____| C | D | -> |_____| B |
* |____|___|___| -> |_____|______|
*
* Left rotation
* ____________ ____________
* | | B | -> | B |_____|
* | A |_______| -> |______|_____|
* |____| C | D | -> | A | |
* |____|___|___| -> |_________|__|
*
*/
static inline void
_pos_rotate_left(struct geo *g, struct geo *ug, struct geo *og)
{
g->x = (ug->h - (og->y + og->h));
g->y = og->x;
}
static inline void
_pos_rotate_right(struct geo *g, struct geo *ug, struct geo *og)
{
g->x = og->y;
g->y = (ug->w - (og->x + og->w));
}
static void
layout_rotate(struct tag *t, void (*pfunc)(struct geo*, struct geo*, struct geo*))
{
struct client *c;
struct geo g, *ug = &t->screen->ugeo;
float f1 = (float)t->screen->ugeo.w / (float)t->screen->ugeo.h;
float f2 = 1 / f1;
FOREACH_NFCLIENT(c, &t->clients, tnext)
{
pfunc(&g, ug, &c->geo);
g.x *= f1;
g.y *= f2;
g.w = c->geo.h * f1;
g.h = c->geo.w * f2;
client_moveresize(c, &g);
}
/* Rotate sometimes do not set back perfect size.. */
FOREACH_NFCLIENT(c, &t->clients, tnext)
layout_fix_hole(c);
layout_save_set(t);
}
void
uicb_layout_rotate_left(Uicb cmd)
{
(void)cmd;
layout_rotate(W->screen->seltag, _pos_rotate_left);
}
void
uicb_layout_rotate_right(Uicb cmd)
{
(void)cmd;
layout_rotate(W->screen->seltag, _pos_rotate_right);
}
/*
* Really simple functions, don't need static no-uicb backend
* so we avoid the use of if(vertical) .. else
*
* Vertical mirror
* ____________ ____________
* | | B | -> | B | |
* | A |_______| -> |_______| A |
* | | C | D | -> | D | C | |
* |____|___|___| -> |___|___|____|
*
* Horizontal mirror
* ____________ ____________
* | | B | -> | | C | D |
* | A |_______| -> | A |___|___|
* | | C | D | -> | | B |
* |____|___|___| -> |____|_______|
*/
void
uicb_layout_vmirror(Uicb cmd)
{
(void)cmd;
struct client *c;
FOREACH_NFCLIENT(c, &W->screen->seltag->clients, tnext)
{
c->geo.x = W->screen->ugeo.w - (c->geo.x + c->geo.w);
client_moveresize(c, &c->geo);
}
layout_save_set(W->screen->seltag);
}
void
uicb_layout_hmirror(Uicb cmd)
{
(void)cmd;
struct client *c;
FOREACH_NFCLIENT(c, &W->screen->seltag->clients, tnext)
{
c->geo.y = W->screen->ugeo.h - (c->geo.y + c->geo.h);
client_moveresize(c, &c->geo);
}
layout_save_set(W->screen->seltag);
}
void
layout_client(struct client *c)
{
if(c->flags & CLIENT_IGNORE_LAYOUT)
{
c->flags ^= CLIENT_IGNORE_LAYOUT;
return;
}
if(c->flags & CLIENT_FREE)
{
c->flags &= ~CLIENT_TILED;
layout_split_arrange_closed(c);
client_moveresize(c, &c->fgeo);
XRaiseWindow(W->dpy, c->frame);
}
else if(!(c->flags & CLIENT_TABBED))
layout_split_integrate(c, c->tag->sel);
}