From b9789420f166c20579f29ecd171a8956c681848d Mon Sep 17 00:00:00 2001 From: julmajustus Date: Thu, 13 Feb 2025 23:23:40 +0200 Subject: [PATCH] btrtile with multi-tag support --- btrtile.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++++++ config.def.h | 12 ++ dwl.c | 152 +++++++++++--- 3 files changed, 698 insertions(+), 29 deletions(-) create mode 100644 btrtile.c diff --git a/btrtile.c b/btrtile.c new file mode 100644 index 0000000..03f4680 --- /dev/null +++ b/btrtile.c @@ -0,0 +1,563 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* btrtile.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: jmakkone +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ +/* Updated: 2025/02/13 23:22:33 by jmakkone ### ########.fr */ +/* */ +/* ************************************************************************** */ + +typedef struct LayoutNode { + unsigned int is_client_node; + unsigned int is_split_vertically; + float split_ratio; + struct LayoutNode *left; + struct LayoutNode *right; + struct LayoutNode *split_node; + Client *client; +} LayoutNode; + +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 is_split_vertically, + LayoutNode *left, LayoutNode *right); +static void destroy_node(LayoutNode *node); +static void destroy_tree(Monitor *m); +static LayoutNode *find_client_node(LayoutNode *node, Client *c); +static LayoutNode *find_suitable_split(LayoutNode *start, unsigned int need_vert); +static void init_tree(Monitor *m); +static void insert_client(Monitor *m, Client *focused_client, Client *new_client); +static LayoutNode *remove_client_node(LayoutNode *node, Client *c); +static void remove_client(Monitor *m, Client *c); +static void setratio_h(const Arg *arg); +static void setratio_v(const Arg *arg); +static void swapclients(const Arg *arg); +static unsigned int visible_count(LayoutNode *node, Monitor *m); +static Client *xytoclient(double x, double y); + +static int resizing_from_mouse = 0; +static double resize_last_update_x, resize_last_update_y; +static uint32_t last_resize_time = 0; + +void +apply_layout(Monitor *m, LayoutNode *node, + struct wlr_box area, unsigned int is_root) +{ + Client *c; + float ratio; + unsigned int left_count, right_count, mid; + struct wlr_box left_area, right_area; + + if (!node) + return; + + /* If this node is a client node, check if it is visible. */ + if (node->is_client_node) { + c = node->client; + if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + return; + resize(c, area, 0); + c->old_geom = area; + return; + } + + /* For a split node, we see how many visible children are on each side: */ + left_count = visible_count(node->left, m); + right_count = visible_count(node->right, m); + + if (left_count == 0 && right_count == 0) { + return; + } else if (left_count > 0 && right_count == 0) { + apply_layout(m, node->left, area, 0); + return; + } else if (left_count == 0 && right_count > 0) { + apply_layout(m, node->right, area, 0); + return; + } + + /* If we’re here, we have visible clients in both subtrees. */ + ratio = node->split_ratio; + if (ratio < 0.05f) + ratio = 0.05f; + if (ratio > 0.95f) + ratio = 0.95f; + + memset(&left_area, 0, sizeof(left_area)); + memset(&right_area, 0, sizeof(right_area)); + + if (node->is_split_vertically) { + mid = (unsigned int)(area.width * ratio); + left_area.x = area.x; + left_area.y = area.y; + left_area.width = mid; + left_area.height = area.height; + + right_area.x = area.x + mid; + right_area.y = area.y; + right_area.width = area.width - mid; + right_area.height = area.height; + } else { + /* horizontal split */ + mid = (unsigned int)(area.height * ratio); + left_area.x = area.x; + left_area.y = area.y; + left_area.width = area.width; + left_area.height = mid; + + right_area.x = area.x; + right_area.y = area.y + mid; + right_area.width = area.width; + right_area.height= area.height - mid; + } + + apply_layout(m, node->left, left_area, 0); + apply_layout(m, node->right, right_area, 0); +} + +void +btrtile(Monitor *m) +{ + Client *c, *focused = NULL; + int n = 0; + LayoutNode *found; + struct wlr_box full_area; + + if (!m || !m->root) + return; + + /* Remove non tiled clients from tree. */ + wl_list_for_each(c, &clients, link) { + if (c->mon == m && !c->isfloating && !c->isfullscreen) { + } else { + remove_client(m, c); + } + } + + /* If no client is found under cursor, fallback to focustop(m) */ + if (!(focused = xytoclient(cursor->x, cursor->y))) + focused = focustop(m); + + /* Insert visible clients that are not part of the tree. */ + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) { + found = find_client_node(m->root, c); + if (!found) { + insert_client(m, focused, c); + } + n++; + } + } + + if (n == 0) + return; + + full_area = m->w; + apply_layout(m, m->root, 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 is_split_vertically, + LayoutNode *left, LayoutNode *right) +{ + LayoutNode *node = calloc(1, sizeof(LayoutNode)); + + if (!node) + return NULL; + node->is_client_node = 0; + node->split_ratio = 0.5f; + node->is_split_vertically = is_split_vertically; + node->left = left; + node->right = right; + if (left) + left->split_node = node; + if (right) + right->split_node = node; + return node; +} + +void +destroy_node(LayoutNode *node) +{ + if (!node) + return; + if (!node->is_client_node) { + destroy_node(node->left); + destroy_node(node->right); + } + free(node); +} + +void +destroy_tree(Monitor *m) +{ + if (!m || !m->root) + return; + destroy_node(m->root); + m->root = NULL; +} + +LayoutNode * +find_client_node(LayoutNode *node, Client *c) +{ + LayoutNode *res; + + if (!node || !c) + 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_suitable_split(LayoutNode *start_node, unsigned int need_vertical) +{ + LayoutNode *n = start_node; + /* if we started from a client node, jump to its parent: */ + if (n && n->is_client_node) + n = n->split_node; + + while (n) { + if (!n->is_client_node && n->is_split_vertically == need_vertical && + visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0) + return n; + n = n->split_node; + } + return NULL; +} + +void +init_tree(Monitor *m) +{ + if (!m) + return; + m->root = calloc(1, sizeof(LayoutNode)); + if (!m->root) + m->root = NULL; +} + +void +insert_client(Monitor *m, Client *focused_client, Client *new_client) +{ + Client *old_client; + LayoutNode **root = &m->root, *old_root, + *focused_node, *new_client_node, *old_client_node; + unsigned int wider, mid_x, mid_y; + + /* If no root , new client becomes the root. */ + if (!*root) { + *root = create_client_node(new_client); + return; + } + + /* Find the focused_client node, + * if not found split the root. */ + focused_node = focused_client ? + find_client_node(*root, focused_client) : NULL; + if (!focused_node) { + old_root = *root; + new_client_node = create_client_node(new_client); + *root = create_split_node(1, old_root, new_client_node); + return; + } + + /* Turn focused node from a client node into a split node, + * and attach old_client + new_client. */ + old_client = focused_node->client; + old_client_node = create_client_node(old_client); + new_client_node = create_client_node(new_client); + + /* Decide split direction. */ + wider = (focused_client->geom.width >= focused_client->geom.height); + focused_node->is_client_node = 0; + focused_node->client = NULL; + focused_node->is_split_vertically = (wider ? 1 : 0); + + /* Pick new_client side depending on the cursor position. */ + mid_x = focused_client->geom.x + focused_client->geom.width / 2; + mid_y = focused_client->geom.y + focused_client->geom.height / 2; + + if (wider) { + /* vertical split => left vs right */ + if (cursor->x <= mid_x) { + focused_node->left = new_client_node; + focused_node->right = old_client_node; + } else { + focused_node->left = old_client_node; + focused_node->right = new_client_node; + } + } else { + /* horizontal split => top vs bottom */ + if (cursor->y <= mid_y) { + focused_node->left = new_client_node; + focused_node->right = old_client_node; + } else { + focused_node->left = old_client_node; + focused_node->right = new_client_node; + } + } + old_client_node->split_node = focused_node; + new_client_node->split_node = focused_node; + focused_node->split_ratio = 0.5f; +} + +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) +{ + if (!m->root || !c) + return; + m->root = remove_client_node(m->root, c); +} + +void +setratio_h(const Arg *arg) +{ + Client *sel = focustop(selmon); + LayoutNode *client_node, *split_node; + float new_ratio; + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) + return; + + client_node = find_client_node(selmon->root, sel); + if (!client_node) + return; + + split_node = find_suitable_split(client_node, 1); + if (!split_node) + return; + + new_ratio = (arg->f != 0.0f) ? (split_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; + split_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) +{ + Client *sel = focustop(selmon); + LayoutNode *client_node, *split_node; + float new_ratio; + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) + return; + + client_node = find_client_node(selmon->root, sel); + if (!client_node) + return; + + split_node = find_suitable_split(client_node, 0); + if (!split_node) + return; + + new_ratio = (arg->f != 0.0f) ? (split_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; + split_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 *c, *tmp, *target = NULL, *sel = focustop(selmon); + LayoutNode *sel_node, *target_node; + int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y, + cand_center_x, cand_center_y; + + if (!sel || sel->isfullscreen || + !selmon->root || !selmon->lt[selmon->sellt]->arrange) + return; + + + /* Get the center coordinates of the selected client */ + sel_center_x = sel->geom.x + sel->geom.width / 2; + sel_center_y = sel->geom.y + sel->geom.height / 2; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel) + continue; + + /* Get the center of candidate client */ + cand_center_x = c->geom.x + c->geom.width / 2; + cand_center_y = c->geom.y + c->geom.height / 2; + + /* Check that the candidate lies in the requested direction. */ + switch (arg->ui) { + case 0: + if (cand_center_x >= sel_center_x) + continue; + break; + case 1: + if (cand_center_x <= sel_center_x) + continue; + break; + case 2: + if (cand_center_y >= sel_center_y) + continue; + break; + case 3: + if (cand_center_y <= sel_center_y) + continue; + break; + default: + continue; + } + + /* Get distance between the centers */ + dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y); + if (dist < closest_dist) { + closest_dist = dist; + target = c; + } + } + + /* If target is found, swap the two clients’ positions in the layout tree */ + if (target) { + sel_node = find_client_node(selmon->root, sel); + target_node = find_client_node(selmon->root, target); + if (sel_node && target_node) { + tmp = sel_node->client; + sel_node->client = target_node->client; + target_node->client = tmp; + arrange(selmon); + } + } +} + +unsigned int +visible_count(LayoutNode *node, Monitor *m) +{ + Client *c; + + if (!node) + return 0; + /* Check if this client is visible. */ + if (node->is_client_node) { + c = node->client; + if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) + return 1; + return 0; + } + /* Else it’s a split node. */ + return visible_count(node->left, m) + visible_count(node->right, m); +} + +Client * +xytoclient(double x, double y) { + Client *c, *closest = NULL; + double dist, mindist = INT_MAX, dx, dy; + + wl_list_for_each_reverse(c, &clients, link) { + if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen && + x >= c->geom.x && x <= (c->geom.x + c->geom.width) && + y >= c->geom.y && y <= (c->geom.y + c->geom.height)){ + return c; + } + } + + /* If no client was found at cursor position fallback to closest. */ + wl_list_for_each_reverse(c, &clients, link) { + if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) { + dx = 0, dy = 0; + + if (x < c->geom.x) + dx = c->geom.x - x; + else if (x > (c->geom.x + c->geom.width)) + dx = x - (c->geom.x + c->geom.width); + + if (y < c->geom.y) + dy = c->geom.y - y; + else if (y > (c->geom.y + c->geom.height)) + dy = y - (c->geom.y + c->geom.height); + + dist = sqrt(dx * dx + dy * dy); + if (dist < mindist) { + mindist = dist; + closest = c; + } + } + } + return closest; +} 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 a2711f6..e49a061 100644 --- a/dwl.c +++ b/dwl.c @@ -1,6 +1,7 @@ /* * See LICENSE file for copyright and license details. */ +#include #include #include #include @@ -103,6 +104,7 @@ typedef struct { const Arg arg; } Button; +typedef struct LayoutNode LayoutNode; typedef struct Monitor Monitor; typedef struct { /* Must keep these three elements in this order */ @@ -139,8 +141,9 @@ 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; } Client; typedef struct { @@ -208,6 +211,7 @@ struct Monitor { int nmaster; char ltsymbol[16]; int asleep; + LayoutNode *root; }; typedef struct { @@ -250,6 +254,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 +338,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 +439,7 @@ static xcb_atom_t netatom[NetLast]; /* attempt to encapsulate suck into one file */ #include "client.h" +#include "btrtile.c" /* function implementations */ void @@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data) struct wlr_pointer_button_event *event = data; struct wlr_keyboard *keyboard; uint32_t mods; - Client *c; + Client *c, *target = NULL; const Button *b; wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); @@ -622,7 +631,7 @@ buttonpress(struct wl_listener *listener, void *data) mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; for (b = buttons; b < END(buttons); b++) { if (CLEANMASK(mods) == CLEANMASK(b->mod) && - event->button == b->button && b->func) { + event->button == b->button && b->func) { b->func(&b->arg); return; } @@ -632,15 +641,36 @@ 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|")) { + if (cursor_mode == CurMove && c->isfloating) { + target = xytoclient(cursor->x, cursor->y); + + if (target && !target->isfloating && !target->isfullscreen) + insert_client(selmon, target, c); + else + selmon->root = create_client_node(c); + + 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 +750,7 @@ 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(m); closemon(m); wlr_scene_node_destroy(&m->fullscreen_bg->node); free(m); @@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data) wl_list_insert(&mons, &m->link); printstatus(); + init_tree(m); /* The xdg-protocol specifies: * @@ -1263,6 +1295,10 @@ 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->root) + remove_client(selmon, c); #ifdef XWAYLAND if (c->type != XDGShell) { wl_list_remove(&c->activate.link); @@ -1809,7 +1845,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; @@ -1863,18 +1900,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. */ @@ -1908,22 +1983,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, - grabc->geom.x + grabc->geom.width, - grabc->geom.y + grabc->geom.height); - wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); - break; + 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; + } } } -- 2.45.3