From 24c197ea93388c222b72c2192b968aa80b444a9a Mon Sep 17 00:00:00 2001
From: Micah N Gorrell <m@minego.net>
Date: Wed, 27 Mar 2024 12:32:49 -0600
Subject: [PATCH] varcol layout

---
 config.def.h |  25 ++-
 dwl.c        |  29 ++++
 varcol.c     | 429 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 480 insertions(+), 3 deletions(-)
 create mode 100644 varcol.c

diff --git a/config.def.h b/config.def.h
index 9009517..9345ff2 100644
--- a/config.def.h
+++ b/config.def.h
@@ -21,16 +21,18 @@ static const float fullscreen_bg[]         = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca
 static int log_level = WLR_ERROR;
 
 static const Rule rules[] = {
-	/* app_id     title       tags mask     isfloating   monitor */
+	/* app_id     title       tags mask     isfloating   monitor  isLeft */
 	/* examples:
-	{ "Gimp",     NULL,       0,            1,           -1 },
+	{ "Gimp",     NULL,       0,            1,           -1,      0 },
+	{ "slack",    NULL,       0,            1,           -1,      1 },
 	*/
-	{ "firefox",  NULL,       1 << 8,       0,           -1 },
+	{ "firefox",  NULL,       1 << 8,       0,           -1,      0 },
 };
 
 /* layout(s) */
 static const Layout layouts[] = {
 	/* symbol     arrange function */
+	{ "=O=",      varcol },  /* first entry is default */
 	{ "[]=",      tile },
 	{ "><>",      NULL },    /* no layout function means floating behavior */
 	{ "[M]",      monocle },
@@ -152,8 +154,25 @@ static const Key keys[] = {
 	TAGKEYS(          XKB_KEY_7, XKB_KEY_ampersand,                  6),
 	TAGKEYS(          XKB_KEY_8, XKB_KEY_asterisk,                   7),
 	TAGKEYS(          XKB_KEY_9, XKB_KEY_parenleft,                  8),
+
 	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q,          quit,           {0} },
 
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Tab,        pushleft,       {0}},
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_ISO_Left_Tab,pushleft,      {0}},
+ 
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_I,          incncols,       {.i = +1 }},
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_D,          incncols,       {.i = -1 }},
+	{ MODKEY,                    XKB_KEY_h,          setcolfact,     {.f = -0.05 }},
+	{ MODKEY,                    XKB_KEY_l,          setcolfact,     {.f = +0.05 }},
+
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Tab,        pushleft,       {0}},
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_ISO_Left_Tab,pushleft,      {0}},
+
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_I,          incncols,       {.i = +1}},
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_D,          incncols,       {.i = -1 }},
+	{ MODKEY,                    XKB_KEY_h,          setcolfact,     {.f = -0.05 }},
+	{ MODKEY,                    XKB_KEY_l,          setcolfact,     {.f = +0.05 }},
+
 	/* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */
 	{ WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} },
 	/* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is
diff --git a/dwl.c b/dwl.c
index 5867b0c..35343e7 100644
--- a/dwl.c
+++ b/dwl.c
@@ -139,6 +139,8 @@ typedef struct {
 	uint32_t tags;
 	int isfloating, isurgent, isfullscreen;
 	uint32_t resize; /* configure serial of a pending resize */
+	int isLeft;
+	float cfact;
 } Client;
 
 typedef struct {
@@ -205,6 +207,18 @@ struct Monitor {
 	int gamma_lut_changed;
 	int nmaster;
 	char ltsymbol[16];
+	float colfact[3];     /* Relative sizes of the different column types */
+	int nmastercols;      /* The number of master columns to use */
+	int nrightcols;       /* The number of right "stack" columns to use */
+
+	/*
+		NOTE: This patch does not set these values, but leaves these here as a
+		placeholder to make it easier to merge with patches that do set gaps.
+	*/
+	int gappih;           /* horizontal gap between windows */
+	int gappiv;           /* vertical gap between windows */
+	int gappoh;           /* horizontal outer gaps */
+	int gappov;           /* vertical outer gaps */
 };
 
 typedef struct {
@@ -228,6 +242,7 @@ typedef struct {
 	uint32_t tags;
 	int isfloating;
 	int monitor;
+	int isLeft;
 } Rule;
 
 typedef struct {
@@ -284,6 +299,7 @@ static void focusstack(const Arg *arg);
 static Client *focustop(Monitor *m);
 static void fullscreennotify(struct wl_listener *listener, void *data);
 static void handlesig(int signo);
+static void incncols(const Arg *arg);
 static void incnmaster(const Arg *arg);
 static void inputdevice(struct wl_listener *listener, void *data);
 static int keybinding(uint32_t mods, xkb_keysym_t sym);
@@ -307,6 +323,7 @@ static void outputmgrtest(struct wl_listener *listener, void *data);
 static void pointerfocus(Client *c, struct wlr_surface *surface,
 		double sx, double sy, uint32_t time);
 static void printstatus(void);
+static void pushleft(const Arg *arg);
 static void quit(const Arg *arg);
 static void rendermon(struct wl_listener *listener, void *data);
 static void requestdecorationmode(struct wl_listener *listener, void *data);
@@ -314,6 +331,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data);
 static void requestmonstate(struct wl_listener *listener, void *data);
 static void resize(Client *c, struct wlr_box geo, int interact);
 static void run(char *startup_cmd);
+static void setcolfact(const Arg *arg);
 static void setcursor(struct wl_listener *listener, void *data);
 static void setcursorshape(struct wl_listener *listener, void *data);
 static void setfloating(Client *c, int floating);
@@ -340,6 +358,7 @@ static void unmapnotify(struct wl_listener *listener, void *data);
 static void updatemons(struct wl_listener *listener, void *data);
 static void updatetitle(struct wl_listener *listener, void *data);
 static void urgent(struct wl_listener *listener, void *data);
+static void varcol(Monitor *m);
 static void view(const Arg *arg);
 static void virtualkeyboard(struct wl_listener *listener, void *data);
 static void virtualpointer(struct wl_listener *listener, void *data);
@@ -351,6 +370,7 @@ static void zoom(const Arg *arg);
 /* variables */
 static const char broken[] = "broken";
 static pid_t child_pid = -1;
+static const float colfact[3] = { 0.1f, 0.6f, 0.3f }; /* The relative factors for the size of each column */
 static int locked;
 static void *exclusive_focus;
 static struct wl_display *dpy;
@@ -457,12 +477,16 @@ applyrules(Client *c)
 		appid = broken;
 	if (!(title = client_get_title(c)))
 		title = broken;
+	c->isLeft = 0;
 
 	for (r = rules; r < END(rules); r++) {
 		if ((!r->title || strstr(title, r->title))
 				&& (!r->id || strstr(appid, r->id))) {
 			c->isfloating = r->isfloating;
 			newtags |= r->tags;
+			if (r->isLeft >= 0) {
+				c->isLeft = r->isLeft;
+			}
 			i = 0;
 			wl_list_for_each(m, &mons, link) {
 				if (r->monitor == i++)
@@ -882,6 +906,9 @@ createmon(struct wl_listener *listener, void *data)
 	wlr_output_state_init(&state);
 	/* Initialize monitor state using configured rules */
 	m->tagset[0] = m->tagset[1] = 1;
+	m->colfact[0] = colfact[0];
+	m->colfact[1] = colfact[1];
+	m->colfact[2] = colfact[2];
 	for (r = monrules; r < END(monrules); r++) {
 		if (!r->name || strstr(wlr_output->name, r->name)) {
 			m->m.x = r->x;
@@ -2929,6 +2956,8 @@ zoom(const Arg *arg)
 	arrange(selmon);
 }
 
+#include "varcol.c"
+
 #ifdef XWAYLAND
 void
 activatex11(struct wl_listener *listener, void *data)
diff --git a/varcol.c b/varcol.c
new file mode 100644
index 0000000..30fdb85
--- /dev/null
+++ b/varcol.c
@@ -0,0 +1,429 @@
+#include <math.h>
+
+static inline void
+client_get_size_hints(Client *c, struct wlr_box *max, struct wlr_box *min)
+{
+	struct wlr_xdg_toplevel *toplevel;
+	struct wlr_xdg_toplevel_state *state;
+#ifdef XWAYLAND
+	if (client_is_x11(c)) {
+		xcb_size_hints_t *size_hints = c->surface.xwayland->size_hints;
+		if (size_hints) {
+			max->width = size_hints->max_width;
+			max->height = size_hints->max_height;
+			min->width = size_hints->min_width;
+			min->height = size_hints->min_height;
+		}
+		return;
+	}
+#endif
+	toplevel = c->surface.xdg->toplevel;
+	state = &toplevel->current;
+	max->width = state->max_width;
+	max->height = state->max_height;
+	min->width = state->min_width;
+	min->height = state->min_height;
+}
+
+static int isleft(Client *c);
+
+/*
+	Variable Column Layout
+
+	- Special 'left' column, with helper to move clients in or out of the left
+	column. This is useful for things like chat, or status monitor windows that
+	you want to keep visible.
+
+	- Variable number of master columns
+	- Variable number of right columns
+	- Variable number of master windows
+
+	// TODO Calculate remainders
+*/
+
+/*
+	Move a client within a column
+
+	w		The width of the column
+	x		The left position of the column
+	offset	The offset of this client within the column
+	count	The number of clients in the column
+*/
+static void placeClientInColumn(Monitor *m, Client *c, int w, int x, int offset, int count, int totaln)
+{
+	struct wlr_box geom = {0};
+	int monheight = m->w.height;
+
+	if (totaln > 1) {
+		/* The total available space does not include the outer vertical gapps */
+		monheight -= (2 * m->gappov);
+
+		/*
+			Each client removes the inner vertical gapp from its own height,
+			but that leaves us off by one so account for that here.
+		*/
+		monheight += m->gappiv;
+	}
+
+	geom.width = w;
+	geom.height = (int) floor(monheight / count);
+
+	geom.x = x;
+	geom.y = m->w.y + (offset * geom.height);
+
+	/* Adjust the height to account for gapps AFTER adjusting the y position */
+	if (totaln > 1) {
+		geom.height -= m->gappiv;
+	}
+
+	resize(c, geom, 0);
+}
+
+/*
+	variable column layout
+
+	This layout has a variable number of columns, in 3 categories.
+		0-1 small left columns, containing clients that have been "pushed" left
+		1-n master columns
+		0-n right columns
+*/
+void varcol(Monitor *m)
+{
+	int				i, mastern, masterw, rightn, leftw, rightw, x, monwidth;
+	unsigned int	leftn, totaln, coln, offset;
+	float			colfacts;
+	Client			*c, *tmp;
+	int				nmastercols		= m->nmastercols;
+	int				nrightcols		= m->nrightcols;
+	struct wl_list	left_clients;
+
+	/*
+		Remove each of window that belongs in the left column, so they can be
+		reattached at the end of the list below.
+	*/
+	wl_list_init(&left_clients);
+
+	i = 0;
+	wl_list_for_each_safe(c, tmp, &clients, link) {
+		if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) {
+			continue;
+		}
+
+		if (i < m->nmaster) {
+			/* Master */
+			;
+		} else if (isleft(c)) {
+			/* Left; Detach and put in the left list */
+			wl_list_remove(&c->link);
+			wl_list_insert(&left_clients, &c->link);
+		}
+
+		i++;
+	}
+
+	/* Reattach the left clients to the main list */
+	wl_list_for_each_reverse_safe(c, tmp, &left_clients, link) {
+		wl_list_remove(&c->link);
+		wl_list_insert(clients.prev, &c->link);
+	}
+
+	/* Count the windows for each column type */
+	totaln = leftn = rightn = mastern = 0;
+	wl_list_for_each(c, &clients, link) {
+		if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) {
+			continue;
+		}
+
+		if (mastern < m->nmaster) {
+			mastern++;
+		} else if (isleft(c)) {
+			leftn++;
+		} else {
+			rightn++;
+		}
+
+		totaln++;
+	}
+
+	nmastercols	= MAX(MIN(mastern, nmastercols), 1);
+	nrightcols	= MAX(MIN(rightn, nrightcols), 1);
+
+	if (mastern == 0) {
+		return;
+	}
+
+	/* Calculate the total colfacts value */
+	colfacts = 0;
+
+	/* Left column */
+	if (leftn > 0) {
+		colfacts += m->colfact[0];
+	}
+
+	/* Center column(s) */
+	for (i = 0; i < nmastercols; i++) {
+		colfacts += m->colfact[1];
+	}
+
+	/* Right column(s) */
+	if (rightn > 0) {
+		for (i = 0; i < nrightcols; i++) {
+			colfacts += m->colfact[2];
+		}
+	}
+
+	/* Calculate the usable width, with gapps removed */
+	monwidth = m->w.width;
+
+	if (rightn > 0) {
+		/* Gap to the left of reach right column */
+		monwidth -= m->gappih * nrightcols;
+	}
+	if (leftn > 0) {
+		/* Gap on the right of the left column */
+		monwidth -= m->gappih;
+	}
+
+	if (totaln > 1) {
+		/* Outer gaps */
+		monwidth -= (2 * m->gappoh);
+	}
+
+	/* Calculate the width for each column type */
+	leftw	= (int) ((monwidth / colfacts) * m->colfact[0]);
+	masterw	= (int) ((monwidth / colfacts) * m->colfact[1]);
+	rightw	= (int) ((monwidth / colfacts) * m->colfact[2]);
+
+	/* Adjust right and left column to fit all clients */
+	wl_list_for_each(c, &clients, link) {
+		struct wlr_box min = {0}, max = {0};
+
+		if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) {
+			continue;
+		}
+
+		/* Get client size hints */
+		client_get_size_hints(c, &max, &min);
+
+		if (i < mastern) {
+			/* Master columns */
+
+		} else if (!isleft(c)) {
+			/* Right columns */
+			x = min.width - rightw;
+			if (x > 0) {
+				rightw += x;
+				masterw -= x;
+			}
+		} else if (leftn > 0) {
+			/* left column */
+			x = min.width - leftw;
+			if (x > 0) {
+				leftw += x;
+				masterw -= x;
+			}
+		}
+	}
+
+	/* Place each client */
+	x = m->w.x;
+	if (leftn > 0) {
+		x += leftw;
+		x += m->gappih;
+	}
+	if (totaln > 1) {
+		x += m->gappoh;
+	}
+
+	i = 0;
+	wl_list_for_each(c, &clients, link) {
+		struct wlr_box min = {0}, max = {0};
+
+		if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) {
+			continue;
+		}
+
+		/* Get client size hints */
+		client_get_size_hints(c, &max, &min);
+
+		if (i < mastern) {
+			/* Master columns */
+
+			/* Offset within the section */
+			offset = i;
+
+			/* Max number of items in each master column */
+			coln = (unsigned int) ceil((float) mastern / nmastercols);
+
+			placeClientInColumn(m, c, masterw, x, offset % coln, coln, totaln);
+
+			/* Only increment x if this is the last client in this column */
+			if ((++offset % coln) == 0) {
+				x += masterw;
+				x += m->gappih;
+			}
+		} else if (!isleft(c)) {
+			/* Right columns */
+
+			/* Offset within the section */
+			offset = (i - mastern);
+
+			/* Max number of items in each right column */
+			coln = (unsigned int) ceil((float) rightn / nrightcols);
+
+			placeClientInColumn(m, c, rightw, x, offset % coln, coln, totaln);
+
+			/* Only increment x if this is the last client in this column */
+			if ((++offset % coln) == 0) {
+				x += rightw;
+				x += m->gappih;
+			}
+		} else if (leftn > 0) {
+			/* left column */
+			x = m->w.x;
+			if (totaln > 1) {
+				x += m->gappoh;
+			}
+
+			/* Offset within the section */
+			offset = i - (mastern + rightn);
+
+			/* There is only one left column */
+			coln = leftn;
+
+			placeClientInColumn(m, c, leftw, x, offset, leftn, totaln);
+		}
+
+		i++;
+	}
+}
+
+static int isleft(Client *c)
+{
+	if (c == NULL) {
+		return 0;
+	}
+
+	if (c->mon != NULL && c->mon->m.width <= 2000) {
+		/* The left column is not worth using on a small monitor */
+		return 0;
+	}
+
+	return c->isLeft;
+}
+
+/* Return non-zero if the currently selected client is in a master column */
+static int ismaster(void)
+{
+	Client *c, *sel = focustop(selmon);
+	int i;
+
+	if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) {
+		return 0;
+	}
+
+	i = 0;
+	wl_list_for_each(c, &clients, link) {
+		if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen) {
+			continue;
+		}
+
+		if (sel == c) {
+			/* c is the selected client, and is index i */
+			if (i < selmon->nmaster) {
+				return 1;
+			} else {
+				return 0;
+			}
+		}
+		i++;
+	}
+
+	return 0;
+}
+
+/* A value >= 1.0 sets that colfact to that value - 1.0 */
+void setcolfact(const Arg *arg)
+{
+	Client	*sel = focustop(selmon);
+	int		index = 1;
+
+	if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) {
+		return;
+	}
+
+	if (ismaster()) {
+		index = 0;
+		/* master */
+		index = 0;
+	} else if (isleft(sel)) {
+		/* left */
+		index = -1;
+	} else {
+		/* right */
+		index = 1;
+	}
+	index++;
+
+	if (arg->f >= 1.0) {
+		selmon->colfact[index] = arg->f - 1.0f;
+	} else {
+		/* Adjust the argument based on the selected column */
+		selmon->colfact[index] += arg->f;
+	}
+
+	if (selmon->colfact[index] < 0.1) {
+		selmon->colfact[index] = 0.1f;
+	} else if (selmon->colfact[index] > 0.9) {
+		selmon->colfact[index] = 0.9f;
+	}
+	arrange(selmon);
+}
+
+static void pushleft(const Arg *arg)
+{
+	Client	*sel = focustop(selmon);
+
+	if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) {
+		return;
+	}
+
+	sel->isLeft = !sel->isLeft;
+	focusclient(sel, 1);
+	arrange(selmon);
+}
+
+/*
+	Modify either the right or master column count
+*/
+void incncols(const Arg *arg)
+{
+	Client	*sel = focustop(selmon);
+
+	if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) {
+		return;
+	}
+
+	if (selmon->nmastercols < 1) selmon->nmastercols = 1;
+	if (selmon->nrightcols < 1) selmon->nrightcols = 1;
+
+	if (ismaster()) {
+		/* master */
+		selmon->nmastercols += arg->i;
+
+		/* Auto adjust nmaster as well */
+		selmon->nmaster = selmon->nmastercols;
+	} else if (isleft(sel)) {
+		/* left */
+		;
+	} else {
+		/* right */
+		selmon->nrightcols += arg->i;
+	}
+
+	if (selmon->nmastercols < 1) selmon->nmastercols = 1;
+	if (selmon->nrightcols < 1) selmon->nrightcols = 1;
+
+	arrange(selmon);
+}
+
-- 
2.44.0

