The idea of having more than one master windows is brilliant.  The `tile'
layout in current hg tip basically splits the master and slave areas
vertically, and tiles windows in each of the two areas using a vertical
stacking algorithm.  The `ncol' layout does it slightly differently by
tilling the master windows using a horizontal stacking algorithm.  The
`n?bstack' layout also does it slightly differently, by splitting the
master and slave areas horizontally, and tiles windows in each of them
using a horizontal stacking algorithm.

All of these layouts fit in a generalized model.  Basically, there're two
things that differentiate one master-slave layout from another:

  1. How the master and slave areas are separated.  Practically, the most
  useful way to do this is simply split the screen in two parts, either
  vertically, or horizontally.

  2. What layout is used in each of the master and slave areas.  We can
  use different layouts for the masters and the slaves.  We should reuse
  the layout algorithms when we can.

With this model in mind, I came up with the function apply_mslts(), i.e.
"apply master-slave layouts", which takes care of the two things stated
above.  The actual layout algorithms are defined in the lt_* functions:

  lt_hstack tiles windows in a horizontal stack, i.e. columns.
  lt_vstack tiles windows in a vertical stack, i.e. rows.
  lt_grid tiles windows in a (gapless) grid.
  lt_monocle stacks windows, each maximized to fill the "booth".

The "booth" is the box in which the layout is performed.  Such functions
are simpler and easier to read.  And they are reusable.

To define a master-slave layout is as simple as:

  static void
  grid(Monitor *m) {
    apply_mslts(m, False, lt_vstack, lt_grid);
  }

Here `False` means split master and slave areas vertically; lt_vstack is
applied to the master area, and lt_grid to the slaves.  I've never seen
such a layout before, you may find it interesting too.

To test this out, simply grab the hg tip, #include mslts.c in config.h,
add some example layouts such as `grid', bind a key combo, `make`, and
run.  Adjust the nmaster value with Mod + d/i, and mfact with Mod + h/j
to see the effects.

Also, this approach accepts nmaster being 0, in which case, all windows
are slaves, and are thus tiled using the slaves' layout algorithm.  This
should be the case for the current hg tip, too, IMO.

Finally, I'm not really good at C yet, so the code could probably use a
whole lot of improvement.  I'd really appreciate it if you could review
the code and help me improve it, because I really like this idea.

The code is in the attachment.
typedef struct {
	int x, y, w, h;
} Booth;

static void apply_lt(Monitor *m, void (*ltf)(Client **, Booth *, unsigned int));
static void apply_mslts(Monitor *m, Bool hsplit,
		void (*mltf)(Client **, Booth *, unsigned int),  /* master layout func */
		void (*sltf)(Client **, Booth *, unsigned int)); /* slave layout func */
static void lt_hstack(Client **c, Booth *b, unsigned int n);
static void lt_vstack(Client **c, Booth *b, unsigned int n);
static void lt_grid(Client **c, Booth *b, unsigned int n);
static void lt_monocle(Client **c, Booth *b, unsigned int n);

/* Example master-slave layouts */

static void ntile(Monitor *m);
static void ncol(Monitor *m);
static void bstack(Monitor *m);
static void grid(Monitor *m);

static void
ntile(Monitor *m) {
	apply_mslts(m, False, lt_vstack, lt_vstack);
}

static void
ncol(Monitor *m) {
	apply_mslts(m, False, lt_hstack, lt_vstack);
}

static void
bstack(Monitor *m) {
	apply_mslts(m, True, lt_hstack, lt_hstack);
}

static void
grid(Monitor *m) {
	apply_mslts(m, False, lt_vstack, lt_grid);
}

/* Functions that apply layouts */

static void
apply_lt(Monitor *m, void (*ltf)(Client **, Booth *, unsigned int)) {
  unsigned int n;
  Client *c;

	for(n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++);
	if(n == 0)
		return;

	Booth b = { .x = m->wx, .y = m->wy, .w = m->ww, .h = m->wh };
	(*ltf)(&c, &b, n);
}

static void
apply_mslts(Monitor *m, Bool hsplit,
		void (*mltf)(Client **, Booth *, unsigned int),
		void (*sltf)(Client **, Booth *, unsigned int)) {
	unsigned int nm, n;
	Client *c;

	for(n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++);
	if(n == 0)
		return;

	nm = MIN(n, m->nmaster);  /* number of masters */

	if (nm == 0) {
		/* all slaves */
		c = m->clients;
		Booth b = { .x = m->wx, .y = m->wy, .w = m->ww, .h = m->wh };
		(*sltf)(&c, &b, n);
	}
	else if(n > nm) {
		/* masters and slaves */
		c = m->clients;
		if(hsplit) {
			/* masters above slaves */
			Booth b = { .x = m->wx, .y = m->wy, .w = m->ww, .h = m->wh * m->mfact };
			(*mltf)(&c, &b, nm);
			b.y += b.h;
			b.h = m->wh - b.h;
			(*sltf)(&c, &b, n - nm);
		}
		else {
			/* masters at the left of slaves */
			Booth b = { .x = m->wx, .y = m->wy, .w = m->ww * m->mfact, .h = m->wh };
			(*mltf)(&c, &b, nm);
			b.x += b.w;
			b.w = m->ww - b.w;
			(*sltf)(&c, &b, n - nm);
		}
	}
	else {
		/* all masters */
		c = m->clients;
		Booth b = { .x = m->wx, .y = m->wy, .w = m->ww, .h = m->wh };
		(*mltf)(&c, &b, n);
	}
}

/* Layout functions: the actual algorithms */

static void
lt_hstack(Client **c, Booth *b, unsigned int n) {
	unsigned int i;
	int x, y, w, h;

	x = b->x;  /* x offset of the next cell */
	y = b->y;
	h = b->h;
	for(i = 0, *c = nexttiled(*c); *c && i < n; *c = nexttiled((*c)->next), i++) {
		w = (b->x + b->w - x) / (n - i);
		resize(*c, x, y, w - 2 * (*c)->bw, h - 2 * (*c)->bw, False);
		x += WIDTH(*c);
	}
}

static void
lt_vstack(Client **c, Booth *b, unsigned int n) {
	unsigned int i;
	int x, y, w, h;

	x = b->x;
	y = b->y;  /* y offset of the next cell */
	w = b->w;
	for(i = 0, *c = nexttiled(*c); *c && i < n; *c = nexttiled((*c)->next), i++) {
		h = (b->y + b->h - y) / (n - i);
		resize(*c, x, y, w - 2 * (*c)->bw, h - 2 * (*c)->bw, False);
		y += HEIGHT(*c);
	}
}

static void
lt_monocle(Client **c, Booth *b, unsigned int n) {
  unsigned int i;

	for(i = 0, *c = nexttiled(*c); *c && i < n; *c = nexttiled((*c)->next), i++)
		resize(*c, b->x, b->y, b->w - 2 * (*c)->bw, b->h - 2 * (*c)->bw, False);
}

static void
lt_grid(Client **c, Booth *b, unsigned int n) {
	unsigned int cols, rows, cn, rn, i, cx, cy, cw, ch;

	/* grid dimensions */
	for(cols = 0; cols <= n / 2; cols++)
		if(cols * cols >= n)
			break;
	if(n == 5)  /* set layout against the general rule: not 1:2:2, but 2:3 */
		cols = 2;
	rows = n / cols;

	/* window geometries */
	cw = cols ? b->w / cols : b->w;
	cn = 0;  /* current column number */
	rn = 0;  /* current row number */
	for(i = 0, *c = nexttiled(*c); *c && i < n; *c = nexttiled((*c)->next), i++) {
		if(i / rows + 1 > cols - n % cols)
			rows = n / cols + 1;
		ch = rows ? b->h / rows : b->h;
		cx = b->x + cn * cw;
		cy = b->y + rn * ch;
		resize(*c, cx, cy, cw - 2 * (*c)->bw, ch - 2 * (*c)->bw, False);
		rn++;
		if(rn >= rows) {
			rn = 0;
			cn++;
		}
	}
}

Reply via email to