From 2b7e152bd39432cd82738a29a8815cf9264f02d8 Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
Date: Wed, 1 Jan 2025 19:06:03 +0200
Subject: [PATCH] btrtile init

---
 btrtile.c    | 659 +++++++++++++++++++++++++++++++++++++++++++++++++++
 config.def.h |  12 +
 dwl.c        | 177 +++++++++++---
 3 files changed, 821 insertions(+), 27 deletions(-)
 create mode 100644 btrtile.c

diff --git a/btrtile.c b/btrtile.c
new file mode 100644
index 0000000..7fb0716
--- /dev/null
+++ b/btrtile.c
@@ -0,0 +1,659 @@
+/* ************************************************************************** */
+/*                                                                            */
+/*                                                        :::      ::::::::   */
+/*   btrtile.c                                          :+:      :+:    :+:   */
+/*                                                    +:+ +:+         +:+     */
+/*   By: jmakkone <jmakkone@student.hive.fi>        +#+  +:+       +#+        */
+/*                                                +#+#+#+#+#+   +#+           */
+/*   Created: 2024/12/15 00:26:07 by jmakkone          #+#    #+#             */
+/*   Updated: 2025/01/01 18:40:14 by jmakkone         ###   ########.fr       */
+/*                                                                            */
+/* ************************************************************************** */
+
+typedef enum {
+	COORD_X,
+	COORD_Y
+} CoordType;
+
+typedef struct LayoutNode {
+	unsigned int is_client_node;
+	unsigned int split_vertically;
+	float split_ratio;
+	struct LayoutNode *left;
+	struct LayoutNode *right;
+	struct LayoutNode *split_node;
+	Client *client;
+} LayoutNode;
+
+struct TreeLayout {
+	LayoutNode *root[TAGCOUNT + 1];
+	struct wl_list tiled_clients[TAGCOUNT + 1];
+};
+
+static void add_client_to_tiled_list(Client *c, struct wl_list *tiled_clients);
+static void apply_layout(Monitor *m, LayoutNode *node,
+						struct wlr_box area, unsigned int is_root);
+static void btrtile(Monitor *m);
+static LayoutNode *create_client_node(Client *c);
+static LayoutNode *create_split_node(unsigned int split_vertically,
+									LayoutNode *left, LayoutNode *right);
+static void destroy_node_tree(LayoutNode *node);
+static void destroy_tree_layout(Monitor *m);
+static LayoutNode *find_client_node(LayoutNode *node, Client *c);
+static LayoutNode *find_split_node(LayoutNode *root, LayoutNode *child);
+static LayoutNode *find_suitable_split_node(LayoutNode *client_node,
+											unsigned int need_vertical);
+static LayoutNode *find_closest_client_node(LayoutNode *split_node,
+											enum Direction dir, int current_x,
+											int current_y, LayoutNode **closest,
+											int *closest_dist);
+static unsigned int get_client_center(LayoutNode *node, CoordType type);
+static unsigned int get_current_tag(Monitor *m);
+static void init_tree_layout(Monitor *m);
+static void insert_client(Monitor *m, Client *focused, Client *new_client,
+						LayoutNode **root, struct wl_list *tiled_clients);
+static unsigned int is_client_tiled(Client *c, struct wl_list *tiled_clients);
+static LayoutNode *remove_client_node(LayoutNode *node, Client *c);
+static void remove_client(Monitor *m, Client *c,
+						LayoutNode **root, struct wl_list *tiled_clients);
+static void setratio_h(const Arg *arg);
+static void setratio_v(const Arg *arg);
+static void swapclients(const Arg *arg);
+
+static int resizing_from_mouse = 0;
+static double resize_last_update_x, resize_last_update_y;
+static uint32_t last_resize_time = 0;
+
+void
+add_client_to_tiled_list(Client *c, struct wl_list *tiled_clients)
+{
+	if (!is_client_tiled(c, tiled_clients))
+		wl_list_insert(tiled_clients, &c->link_tiled);
+}
+
+void
+apply_layout(Monitor *m, LayoutNode *node,
+			struct wlr_box area, unsigned int is_root)
+{
+	float ratio;
+	int mid;
+	struct wlr_box left_area, right_area;
+
+	if (!node)
+		return;
+
+	if (node->is_client_node) {
+		resize(node->client, area, 0);
+		node->client->old_geom = area;
+		return;
+	}
+
+	ratio = node->split_ratio;
+	if (ratio == 0.0f)
+		ratio = 0.5f;
+	if (ratio < 0.05f)
+		ratio = 0.05f;
+	if (ratio > 0.95f)
+		ratio = 0.95f;
+
+	if (node->split_vertically) {
+		mid = (int)(area.width * ratio);
+		left_area = (struct wlr_box){ area.x, area.y, mid, area.height};
+		right_area = (struct wlr_box){ area.x + mid, area.y,
+			area.width - mid, area.height};
+	} else {
+		mid = (int)(area.height * ratio);
+		left_area = (struct wlr_box){ area.x, area.y, area.width, mid };
+		right_area = (struct wlr_box){ area.x, area.y + mid,
+			area.width, area.height - mid };
+	}
+
+	apply_layout(m, node->left, left_area, 0);
+	apply_layout(m, node->right, right_area, 0);
+}
+
+void btrtile(Monitor *m)
+{
+	uint32_t active_tags = m->tagset[m->seltags];
+	unsigned int n = 0, found = 0, curtag;
+	Client *c, *cc, *tmp, *cur, *focused_client = NULL;
+	LayoutNode **root_ptr;
+	struct wl_list current_clients, *tiled_clients;
+	struct wlr_box full_area = m->w;
+
+	/* We skip handling clients in btrtile if multiple tags are selected */
+	if (!m->tree_layout || (active_tags && (active_tags & (active_tags - 1))))
+		return;
+	curtag = get_current_tag(m);
+	root_ptr = &m->tree_layout->root[curtag];
+	tiled_clients = &m->tree_layout->tiled_clients[curtag];
+	wl_list_for_each_reverse(c, &clients, link) {
+		if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) {
+			n++;
+			if (!focused_client &&
+				cursor->x >= c->old_geom.x
+				&& cursor->x < c->old_geom.x + c->old_geom.width
+				&& cursor->y >= c->old_geom.y
+				&& cursor->y < c->old_geom.y + c->old_geom.height) {
+				focused_client = c;
+			}
+		}
+	}
+
+	/* If no visible clients, clear the node tree and tiled_clients list. */
+	if (n == 0) {
+		destroy_node_tree(*root_ptr);
+		*root_ptr = NULL;
+		wl_list_init(tiled_clients);
+		return;
+	}
+
+	/* If no focused client found, pick the first visible */
+	if (!focused_client) {
+		wl_list_for_each_reverse(c, &clients, link) {
+			if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) {
+				focused_client = c;
+				break;
+			}
+		}
+	}
+
+	/* Build a temporary list of currently visible tiled clients.
+	 * If new clients are found add them to tree.*/
+	wl_list_init(&current_clients);
+	wl_list_for_each_reverse(c, &clients, link) {
+		if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) {
+			found = 0;
+			/* If client has multiple tags set, we might create infinite
+			 * point to self loops by adding the same client to multiple
+			 * tiled_clients lists, so hacky way to prevent that is to revert
+			 * clients active tags to current active tag if multiple
+			 * tags are selected. */
+			if (c->tags != 1u << curtag)
+				c->tags = 1u << curtag;
+			wl_list_for_each(cc, tiled_clients, link_tiled) {
+				if (cc == c) {
+					found = 1;
+					break;
+				}
+			}
+			if (!found) {
+				insert_client(m, focused_client, c, root_ptr, tiled_clients);
+			}
+			wl_list_insert(&current_clients, &c->link_temp);
+		}
+	}
+
+	/* Compare the current list of clients to the previous one and remove
+	 * clients that no longer exist on the current tag.
+	 * This handles cases where user moves clients to other tags.
+	 * When client is closed or killed we manage the list and tree clean up in
+	 * destroynotify */
+	wl_list_for_each_safe(cc, tmp, tiled_clients, link_tiled) {
+		found = 0;
+		wl_list_for_each(cur, &current_clients, link_temp) {
+			if (cur == cc) {
+				found = 1;
+				break;
+			}
+		}
+		if (!found) {
+			remove_client(m, cc, root_ptr, tiled_clients);
+		}
+	}
+
+	/* Rebuild the updated client list */
+	wl_list_init(tiled_clients);
+	wl_list_for_each(cur, &current_clients, link_temp) {
+		wl_list_insert(tiled_clients, &cur->link_tiled);
+	}
+
+	/* Tile visible clients. */
+	apply_layout(m, *root_ptr, full_area, 1);
+}
+
+LayoutNode *
+create_client_node(Client *c)
+{
+	LayoutNode *node = calloc(1, sizeof(LayoutNode));
+
+	if (!node)
+		return NULL;
+	node->is_client_node = 1;
+	node->split_ratio = 0.5f;
+	node->client = c;
+	return node;
+}
+
+LayoutNode *
+create_split_node(unsigned int split_vertically,
+				LayoutNode *left, LayoutNode *right)
+{
+	LayoutNode *node = calloc(1, sizeof(LayoutNode));
+
+	if (!node)
+		return NULL;
+	node->is_client_node = 0;
+	node->split_vertically = split_vertically;
+	node->left = left;
+	node->right = right;
+	if (left)
+		left->split_node = node;
+	if (right)
+		right->split_node = node;
+	return node;
+}
+
+unsigned int
+is_client_tiled(Client *c, struct wl_list *tiled_clients)
+{
+	Client *cc;
+	wl_list_for_each(cc, tiled_clients, link_tiled) {
+		if (cc == c)
+			return 1;
+	}
+	return 0;
+}
+
+void
+destroy_node_tree(LayoutNode *node)
+{
+	if (!node)
+		return;
+	if (!node->is_client_node) {
+		destroy_node_tree(node->left);
+		destroy_node_tree(node->right);
+	}
+	free(node);
+}
+
+void
+destroy_tree_layout(Monitor *m)
+{
+	if (!m)
+		return;
+	for (int i = 0; i <= TAGCOUNT; i++) {
+		destroy_node_tree(m->tree_layout->root[i]);
+	}
+}
+
+LayoutNode *
+find_client_node(LayoutNode *node, Client *c)
+{
+	LayoutNode *res;
+
+	if (!node)
+		return NULL;
+	if (node->is_client_node)
+		return (node->client == c) ? node : NULL;
+	res = find_client_node(node->left, c);
+	return res ? res : find_client_node(node->right, c);
+}
+
+LayoutNode *
+find_split_node(LayoutNode *root, LayoutNode *child)
+{
+	LayoutNode *res;
+
+	if (!root || root->is_client_node)
+		return NULL;
+	if (root->left == child || root->right == child)
+		return root;
+	res = find_split_node(root->left, child);
+	return res ? res : find_split_node(root->right, child);
+}
+
+LayoutNode *
+find_suitable_split_node(LayoutNode *client_node, unsigned int need_vertical)
+{
+	LayoutNode *node = client_node;
+	unsigned int curtag;
+
+	curtag = get_current_tag(selmon);
+	/* If we're starting from a client_node, go up one level first */
+	if (node->is_client_node) {
+		node = node->split_node ? node->split_node :
+			find_split_node(selmon->tree_layout->root[curtag], node);
+	}
+
+	/* Climb the tree until we find a node that is not client_node and
+	 * match needed orientation. */
+	while (node && (node->is_client_node ||
+			node->split_vertically != need_vertical)) {
+		node = node->split_node ? node->split_node :
+			find_split_node(selmon->tree_layout->root[curtag], node);
+	}
+
+	return node;
+}
+
+LayoutNode *
+find_closest_client_node(LayoutNode *split_node, enum Direction dir,
+						 int current_x, int current_y, LayoutNode **closest,
+						 int *closest_dist)
+{
+	int client_center_x, client_center_y, dist, is_candidate;
+	if (!split_node)
+		return NULL;
+
+	if (split_node->is_client_node && split_node->client) {
+		client_center_x = get_client_center(split_node, COORD_X);
+		client_center_y = get_client_center(split_node, COORD_Y);
+		dist = 0;
+		is_candidate = 0;
+
+		switch (dir) {
+			case DIR_LEFT:
+				if (client_center_x < current_x) {
+					dist = current_x - client_center_x;
+					is_candidate = 1;
+				}
+				break;
+			case DIR_RIGHT:
+				if (client_center_x > current_x) {
+					dist = client_center_x - current_x;
+					is_candidate = 1;
+				}
+				break;
+			case DIR_UP:
+				if (client_center_y < current_y) {
+					dist = current_y - client_center_y;
+					is_candidate = 1;
+				}
+				break;
+			case DIR_DOWN:
+				if (client_center_y > current_y) {
+					dist = client_center_y - current_y;
+					is_candidate = 1;
+				}
+				break;
+			default:
+				break;
+		}
+
+		if (is_candidate && dist < *closest_dist) {
+			*closest_dist = dist;
+			*closest = split_node;
+		}
+	}
+
+	/* Recursively search in left and right split_nodes */
+	find_closest_client_node(split_node->left, dir, current_x, current_y,
+							closest, closest_dist);
+	find_closest_client_node(split_node->right, dir, current_x, current_y,
+							closest, closest_dist);
+
+	return *closest;
+}
+
+unsigned int
+get_client_center(LayoutNode *node, CoordType type)
+{
+	if (!node || !node->is_client_node || !node->client)
+		return 0;
+
+	switch (type) {
+		case COORD_X:
+			return node->client->old_geom.x + node->client->old_geom.width / 2;
+		case COORD_Y:
+			return node->client->old_geom.y + node->client->old_geom.height / 2;
+		default:
+			return 0;
+	}
+}
+
+unsigned int
+get_current_tag(Monitor *m)
+{
+	uint32_t active;
+
+	if (!m)
+		return 0;
+
+	active = m->tagset[m->seltags];
+	for (int i = 0; i < TAGCOUNT; i++) {
+		if (active & (1u << i))
+			return i;
+	}
+	return 0;
+}
+
+void
+init_tree_layout(Monitor *m)
+{
+	if (!m)
+		return;
+	m->tree_layout = calloc(1, sizeof(TreeLayout));
+	for (int i = 0; i <= TAGCOUNT; i++) {
+		m->tree_layout->root[i] = NULL;
+		wl_list_init(&m->tree_layout->tiled_clients[i]);
+	}
+}
+
+void
+insert_client(Monitor *m, Client *focused, Client *new_client,
+			  LayoutNode **root, struct wl_list *tiled_clients)
+{
+	int mid_x, mid_y;
+	LayoutNode *old_root, *client_node, *old_client_node, *new_client_node;
+	unsigned int wider;
+
+	/* If there is no root node, inserted client must be the first one.
+	 * If there's no focused client or client node cannot be found for the
+	 * focused client we treat them as root node.*/
+	if (!*root) {
+		*root = create_client_node(new_client);
+		add_client_to_tiled_list(new_client, tiled_clients);
+	} else if (!focused || !(client_node = find_client_node(*root, focused))) {
+		old_root = *root;
+		*root = create_split_node(1, old_root, create_client_node(new_client));
+		add_client_to_tiled_list(new_client, tiled_clients);
+	} else {
+		/* We check the cursor location on splittable area and choose
+		 * the new client's position. On horizontal splits left node represent
+		 * the upper node and vice versa.*/
+		mid_x = focused->old_geom.x + focused->old_geom.width / 2;
+		mid_y = focused->old_geom.y + focused->old_geom.height / 2;
+		old_client_node = create_client_node(client_node->client);
+		new_client_node = create_client_node(new_client);
+
+		wider = focused->old_geom.width >= focused->old_geom.height;
+		if (wider) {
+			/* Vertical split */
+			client_node->split_vertically = 1;
+			if (cursor->x < mid_x) {
+				client_node->left = new_client_node;
+				client_node->right = old_client_node;
+			} else {
+				client_node->left = old_client_node;
+				client_node->right = new_client_node;
+			}
+		} else {
+			/* Horizontal split */
+			client_node->split_vertically = 0;
+			if (cursor->y < mid_y) {
+				client_node->left = new_client_node;
+				client_node->right = old_client_node;
+			} else {
+				client_node->left = old_client_node;
+				client_node->right = new_client_node;
+			}
+		}
+		/* The old client node becomes the splitnode for the old and new client
+		 * nodes.*/
+		client_node->is_client_node = 0;
+		client_node->client = NULL;
+		add_client_to_tiled_list(new_client, tiled_clients);
+	}
+}
+
+LayoutNode *
+remove_client_node(LayoutNode *node, Client *c)
+{
+	LayoutNode *tmp;
+	if (!node)
+		return NULL;
+	if (node->is_client_node) {
+		/* If this client_node is the client we're removing,
+		 * return NULL to remove it */
+		if (node->client == c) {
+			free(node);
+			return NULL;
+		}
+		return node;
+	}
+
+	node->left = remove_client_node(node->left, c);
+	node->right = remove_client_node(node->right, c);
+
+	/* If one of the client node is NULL after removal and the other is not,
+	 * we "lift" the other client node up to replace this split node. */
+	if (!node->left && node->right) {
+		tmp = node->right;
+
+		/* Save pointer to split node */
+		if (tmp)
+			tmp->split_node = node->split_node;
+
+		free(node);
+		return tmp;
+	}
+
+	if (!node->right && node->left) {
+		tmp = node->left;
+
+		/* Save pointer to split node */
+		if (tmp)
+			tmp->split_node = node->split_node;
+
+		free(node);
+		return tmp;
+	}
+
+	/* If both children exist or both are NULL (empty tree),
+	 * return node as is. */
+	return node;
+}
+
+void
+remove_client(Monitor *m, Client *c, LayoutNode **root,
+			  struct wl_list *tiled_clients)
+{
+	Client *cc, *tmp;
+
+	*root = remove_client_node(*root, c);
+	wl_list_for_each_safe(cc, tmp, tiled_clients, link_tiled) {
+		if (cc == c) {
+			wl_list_remove(&cc->link_tiled);
+			break;
+		}
+	}
+}
+
+void
+setratio_h(const Arg *arg)
+{
+	Client *sel = focustop(selmon);
+	LayoutNode *client_node, *node;
+	float new_ratio;
+
+	if (!arg || !sel || !selmon->lt[selmon->sellt]->arrange)
+		return;
+
+	client_node = find_client_node(selmon->tree_layout->root[get_current_tag(selmon)], sel);
+	if (!client_node)
+		return;
+
+	/* Find a suitable vertical node */
+	node = find_suitable_split_node(client_node, 1);
+	if (!node)
+		return;
+
+	new_ratio = arg->f ? (node->split_ratio + arg->f) : 0.5f;
+	if (new_ratio < 0.05f)
+		new_ratio = 0.05f;
+	if (new_ratio > 0.95f)
+		new_ratio = 0.95f;
+
+	node->split_ratio = new_ratio;
+	/* Skip the arrange if done resizing by mouse,
+	 * we call arrange from motionotify */
+	if (!resizing_from_mouse) {
+		arrange(selmon);
+	}
+}
+
+void
+setratio_v(const Arg *arg)
+{
+	float new_ratio;
+	Client *sel = focustop(selmon);
+	LayoutNode *client_node, *node;
+
+	if (!arg || !sel || !selmon->lt[selmon->sellt]->arrange)
+		return;
+
+	client_node = find_client_node(selmon->tree_layout->root[get_current_tag(selmon)], sel);
+	if (!client_node)
+		return;
+
+	/* Find a suitable horizontal node */
+	node = find_suitable_split_node(client_node, 0);
+	if (!node)
+		return;
+
+	new_ratio = arg->f ? (node->split_ratio + arg->f) : 0.5f;
+	if (new_ratio < 0.05f) new_ratio = 0.5f;
+	if (new_ratio > 0.95f) new_ratio = 0.95f;
+
+	node->split_ratio = new_ratio;
+	/* Skip the arrange if done resizing by mouse,
+	 * we call arrange from motionotify */
+	if (!resizing_from_mouse) {
+		arrange(selmon);
+	}
+}
+
+void
+swapclients(const Arg *arg)
+{
+	Client *tmp, *sel = focustop(selmon);
+	enum Direction dir = (enum Direction)arg->i;
+	LayoutNode *client_node, *target = NULL, *split_node = NULL;
+	unsigned int current_x, current_y, curtag = get_current_tag(selmon);
+	int closest_dist = INT_MAX;
+
+	if (!arg || !sel || !selmon->lt[selmon->sellt]->arrange)
+		return;
+
+	client_node = find_client_node(selmon->tree_layout->root[curtag], sel);
+	if (!client_node)
+		return;
+
+	current_x = get_client_center(client_node, COORD_X);
+	current_y = get_client_center(client_node, COORD_Y);
+
+	/* For up/down swaps, restrict search within the current horizontal split
+	 * node if no suitable horizontal split node is found, default to vertical */
+	if (dir == DIR_UP || dir == DIR_DOWN) {
+		split_node = find_suitable_split_node(client_node, 0);
+		if (!split_node) {
+			return;
+		}
+	} else {
+		split_node = selmon->tree_layout->root[curtag];
+	}
+
+	/* Find the closest client node in the specified direction and swap
+	 * the clients */
+	find_closest_client_node(split_node, dir, current_x, current_y,
+							&target, &closest_dist);
+
+	if (target && target->is_client_node && target->client) {
+		tmp = client_node->client;
+		client_node->client = target->client;
+		target->client = tmp;
+
+		arrange(selmon);
+	}
+}
diff --git a/config.def.h b/config.def.h
index 22d2171..92f3ad6 100644
--- a/config.def.h
+++ b/config.def.h
@@ -13,7 +13,10 @@ static const float focuscolor[]            = COLOR(0x005577ff);
 static const float urgentcolor[]           = COLOR(0xff0000ff);
 /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */
 static const float fullscreen_bg[]         = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */
+static const float resize_factor           = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */
+static const uint32_t resize_interval_ms   = 16; /* Resize interval depends on framerate and screen refresh rate. */
 
+enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN };
 /* tagging - TAGCOUNT must be no greater than 31 */
 #define TAGCOUNT (9)
 
@@ -31,6 +34,7 @@ static const Rule rules[] = {
 /* layout(s) */
 static const Layout layouts[] = {
 	/* symbol     arrange function */
+	{ "|w|",      btrtile },
 	{ "[]=",      tile },
 	{ "><>",      NULL },    /* no layout function means floating behavior */
 	{ "[M]",      monocle },
@@ -148,6 +152,14 @@ static const Key keys[] = {
 	{ MODKEY,                    XKB_KEY_period,     focusmon,       {.i = WLR_DIRECTION_RIGHT} },
 	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less,       tagmon,         {.i = WLR_DIRECTION_LEFT} },
 	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater,    tagmon,         {.i = WLR_DIRECTION_RIGHT} },
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up,         swapclients,    {.i = DIR_UP} },
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down,       swapclients,    {.i = DIR_DOWN} },
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right,      swapclients,    {.i = DIR_RIGHT} },
+	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left,       swapclients,    {.i = DIR_LEFT} },
+	{ MODKEY|WLR_MODIFIER_CTRL,  XKB_KEY_Right,      setratio_h,     {.f = +0.025f} },
+	{ MODKEY|WLR_MODIFIER_CTRL,  XKB_KEY_Left,       setratio_h,     {.f = -0.025f} },
+	{ MODKEY|WLR_MODIFIER_CTRL,  XKB_KEY_Up,         setratio_v,     {.f = -0.025f} },
+	{ MODKEY|WLR_MODIFIER_CTRL,  XKB_KEY_Down,       setratio_v,     {.f = +0.025f} },
 	TAGKEYS(          XKB_KEY_1, XKB_KEY_exclam,                     0),
 	TAGKEYS(          XKB_KEY_2, XKB_KEY_at,                         1),
 	TAGKEYS(          XKB_KEY_3, XKB_KEY_numbersign,                 2),
diff --git a/dwl.c b/dwl.c
index def2562..604a9a4 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,7 @@
 /*
  * See LICENSE file for copyright and license details.
  */
+#include <limits.h>
 #include <getopt.h>
 #include <libinput.h>
 #include <linux/input-event-codes.h>
@@ -103,6 +104,7 @@ typedef struct {
 	const Arg arg;
 } Button;
 
+typedef struct TreeLayout TreeLayout;
 typedef struct Monitor Monitor;
 typedef struct {
 	/* Must keep these three elements in this order */
@@ -139,8 +141,11 @@ typedef struct {
 #endif
 	unsigned int bw;
 	uint32_t tags;
-	int isfloating, isurgent, isfullscreen;
+	int isfloating, isurgent, isfullscreen, was_tiled;
 	uint32_t resize; /* configure serial of a pending resize */
+	struct wlr_box old_geom;
+	struct wl_list link_tiled;
+	struct wl_list link_temp;
 } Client;
 
 typedef struct {
@@ -208,6 +213,7 @@ struct Monitor {
 	int nmaster;
 	char ltsymbol[16];
 	int asleep;
+	TreeLayout *tree_layout;
 };
 
 typedef struct {
@@ -250,6 +256,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
 		struct wlr_box *usable_area, int exclusive);
 static void arrangelayers(Monitor *m);
 static void axisnotify(struct wl_listener *listener, void *data);
+static void btrtile(Monitor *m);
 static void buttonpress(struct wl_listener *listener, void *data);
 static void chvt(const Arg *arg);
 static void checkidleinhibitor(struct wlr_surface *exclude);
@@ -333,6 +340,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags);
 static void setpsel(struct wl_listener *listener, void *data);
 static void setsel(struct wl_listener *listener, void *data);
 static void setup(void);
+static void setratio_h(const Arg *arg);
+static void setratio_v(const Arg *arg);
+static void swapclients(const Arg *arg);
 static void spawn(const Arg *arg);
 static void startdrag(struct wl_listener *listener, void *data);
 static void tag(const Arg *arg);
@@ -431,6 +441,7 @@ static xcb_atom_t netatom[NetLast];
 
 /* attempt to encapsulate suck into one file */
 #include "client.h"
+#include "btrtile.c"
 
 /* function implementations */
 void
@@ -600,10 +611,17 @@ buttonpress(struct wl_listener *listener, void *data)
 {
 	struct wlr_pointer_button_event *event = data;
 	struct wlr_keyboard *keyboard;
-	uint32_t mods;
-	Client *c;
+	struct wl_list *tiled_clients;
+	struct wlr_surface *surface;
+	double sx, sy;
+	LayoutNode **root, *old_root;
+	uint32_t mods, curtag, active_tags = selmon->tagset[selmon->seltags];
+	Client *c, *target;
 	const Button *b;
 
+	curtag = get_current_tag(selmon);
+	root = &selmon->tree_layout->root[curtag];
+	tiled_clients = &selmon->tree_layout->tiled_clients[curtag];
 	wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
 
 	switch (event->state) {
@@ -632,15 +650,49 @@ buttonpress(struct wl_listener *listener, void *data)
 		/* If you released any buttons, we exit interactive move/resize mode. */
 		/* TODO should reset to the pointer focus's current setcursor */
 		if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) {
+			c = grabc;
+			if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) {
+				/* Check if more than one tag is active, if so we escape */
+				if (active_tags && (active_tags & (active_tags - 1)))
+					break;
+				if (cursor_mode == CurMove && c->isfloating) {
+					target = NULL;
+					surface = NULL;
+					xytonode(cursor->x, cursor->y, &surface, &target, NULL, &sx, &sy);
+
+					if (target && !target->isfloating && !target->isfullscreen) {
+						insert_client(selmon, target, c, root, tiled_clients);
+					} else {
+						if (!root) {
+							*root = create_client_node(c);
+							add_client_to_tiled_list(c, tiled_clients);
+						} else {
+							old_root = *root;
+							*root = create_split_node(1, old_root, create_client_node(c));
+							add_client_to_tiled_list(c, tiled_clients);
+						}
+					}
+
+					setfloating(c, 0);
+					arrange(selmon);
+
+				} else if (cursor_mode == CurResize && !c->isfloating) {
+					resizing_from_mouse = 0;
+				}
+			} else {
+				if (cursor_mode == CurResize && resizing_from_mouse)
+					resizing_from_mouse = 0;
+			}
+			/* Default behaviour */
 			wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
 			cursor_mode = CurNormal;
 			/* Drop the window off on its new monitor */
 			selmon = xytomon(cursor->x, cursor->y);
 			setmon(grabc, selmon, 0);
+			grabc = NULL;
 			return;
-		} else {
-			cursor_mode = CurNormal;
 		}
+		cursor_mode = CurNormal;
 		break;
 	}
 	/* If the event wasn't handled by the compositor, notify the client with
@@ -720,6 +772,9 @@ cleanupmon(struct wl_listener *listener, void *data)
 	wlr_output_layout_remove(output_layout, m->wlr_output);
 	wlr_scene_output_destroy(m->scene_output);
 
+	destroy_tree_layout(m);
+	free(m->tree_layout);
+	m->tree_layout = NULL;
 	closemon(m);
 	wlr_scene_node_destroy(&m->fullscreen_bg->node);
 	free(m);
@@ -1026,6 +1081,7 @@ createmon(struct wl_listener *listener, void *data)
 
 	wl_list_insert(&mons, &m->link);
 	printstatus();
+	init_tree_layout(m);
 
 	/* The xdg-protocol specifies:
 	 *
@@ -1265,6 +1321,15 @@ destroynotify(struct wl_listener *listener, void *data)
 	wl_list_remove(&c->destroy.link);
 	wl_list_remove(&c->set_title.link);
 	wl_list_remove(&c->fullscreen.link);
+	/* We check if the destroyed client was part of any tiled_list, to catch
+	 * client removals even if they would not be currently managed by btrtile */
+	if (selmon && selmon->tree_layout) {
+		for (int i = 0; i < TAGCOUNT; i++) {
+			remove_client(selmon, c,
+		 &selmon->tree_layout->root[i],
+		 &selmon->tree_layout->tiled_clients[i]);
+		}
+	}
 #ifdef XWAYLAND
 	if (c->type != XDGShell) {
 		wl_list_remove(&c->activate.link);
@@ -1811,7 +1876,8 @@ void
 motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
 		double dx_unaccel, double dy_unaccel)
 {
-	double sx = 0, sy = 0, sx_confined, sy_confined;
+	int tiled = 0;
+	double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total;
 	Client *c = NULL, *w = NULL;
 	LayerSurface *l = NULL;
 	struct wlr_surface *surface = NULL;
@@ -1865,18 +1931,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
 	/* Update drag icon's position */
 	wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y));
 
-	/* If we are currently grabbing the mouse, handle and return */
+	/* Skip if internal call or already resizing */
+	if (time == 0 && resizing_from_mouse)
+		goto focus;
+
+	tiled = grabc && !grabc->isfloating && !grabc->isfullscreen;
 	if (cursor_mode == CurMove) {
 		/* Move the grabbed client to the new position. */
-		resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy,
-			.width = grabc->geom.width, .height = grabc->geom.height}, 1);
-		return;
+		if (grabc && grabc->isfloating) {
+			resize(grabc, (struct wlr_box){
+				.x = (int)round(cursor->x) - grabcx,
+				.y = (int)round(cursor->y) - grabcy,
+				.width = grabc->geom.width,
+				.height = grabc->geom.height
+			}, 1);
+			return;
+		}
 	} else if (cursor_mode == CurResize) {
-		resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y,
-			.width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1);
-		return;
+		if (tiled && resizing_from_mouse) {
+			dx_total = cursor->x - resize_last_update_x;
+			dy_total = cursor->y - resize_last_update_y;
+
+			if (time - last_resize_time >= resize_interval_ms) {
+				Arg a = {0};
+				if (fabs(dx_total) > fabs(dy_total)) {
+					a.f = (float)(dx_total * resize_factor);
+					setratio_h(&a);
+				} else {
+					a.f = (float)(dy_total * resize_factor);
+					setratio_v(&a);
+				}
+				arrange(selmon);
+
+				last_resize_time = time;
+				resize_last_update_x = cursor->x;
+				resize_last_update_y = cursor->y;
+			}
+
+		} else if (grabc && grabc->isfloating) {
+			/* Floating resize as original */
+			resize(grabc, (struct wlr_box){
+				.x = grabc->geom.x,
+				.y = grabc->geom.y,
+				.width = (int)round(cursor->x) - grabc->geom.x,
+				.height = (int)round(cursor->y) - grabc->geom.y
+			}, 1);
+			return;
+		}
 	}
 
+focus:
 	/* If there's no client surface under the cursor, set the cursor image to a
 	 * default. This is what makes the cursor image appear when you move it
 	 * off of a client or over its border. */
@@ -1910,22 +2014,41 @@ moveresize(const Arg *arg)
 	if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
 		return;
 
-	/* Float the window and tell motionnotify to grab it */
-	setfloating(grabc, 1);
-	switch (cursor_mode = arg->ui) {
-	case CurMove:
-		grabcx = (int)round(cursor->x) - grabc->geom.x;
-		grabcy = (int)round(cursor->y) - grabc->geom.y;
-		wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
-		break;
-	case CurResize:
-		/* Doesn't work for X11 output - the next absolute motion event
-		 * returns the cursor to where it started */
-		wlr_cursor_warp_closest(cursor, NULL,
+	cursor_mode = arg->ui;
+	grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen);
+
+	if (grabc->was_tiled) {
+		switch (cursor_mode) {
+			case CurMove:
+				setfloating(grabc, 1);
+				grabcx = (int)round(cursor->x) - grabc->geom.x;
+				grabcy = (int)round(cursor->y) - grabc->geom.y;
+				wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
+				break;
+			case CurResize:
+				wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
+				resize_last_update_x = cursor->x;
+				resize_last_update_y = cursor->y;
+				resizing_from_mouse = 1;
+				break;
+		}
+	} else {
+		/* Default floating logic */
+		/* Float the window and tell motionnotify to grab it */
+		setfloating(grabc, 1);
+		switch (cursor_mode) {
+			case CurMove:
+				grabcx = (int)round(cursor->x) - grabc->geom.x;
+				grabcy = (int)round(cursor->y) - grabc->geom.y;
+				wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
+				break;
+			case CurResize:
+				wlr_cursor_warp_closest(cursor, NULL,
 				grabc->geom.x + grabc->geom.width,
 				grabc->geom.y + grabc->geom.height);
-		wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
-		break;
+				wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
+				break;
+		}
 	}
 }
 
-- 
2.45.2

