feat: dwindwl first test

This commit is contained in:
DreamMaoMao 2026-05-09 11:55:59 +08:00
parent cfc7773dfa
commit 89e5de3976
6 changed files with 541 additions and 0 deletions

View file

@ -242,6 +242,14 @@ typedef struct {
int32_t center_master_overspread;
int32_t center_when_single_stack;
/* dwindle layout */
int32_t dwindle_vsplit;
int32_t dwindle_hsplit;
int32_t dwindle_preserve_split;
int32_t dwindle_smart_split;
int32_t dwindle_smart_resize;
float dwindle_split_ratio;
uint32_t hotarea_size;
uint32_t hotarea_corner;
uint32_t enable_hotarea;
@ -1615,6 +1623,18 @@ bool parse_option(Config *config, char *key, char *value) {
config->center_master_overspread = atoi(value);
} else if (strcmp(key, "center_when_single_stack") == 0) {
config->center_when_single_stack = atoi(value);
} else if (strcmp(key, "dwindle_vsplit") == 0) {
config->dwindle_vsplit = atoi(value);
} else if (strcmp(key, "dwindle_hsplit") == 0) {
config->dwindle_hsplit = atoi(value);
} else if (strcmp(key, "dwindle_preserve_split") == 0) {
config->dwindle_preserve_split = atoi(value);
} else if (strcmp(key, "dwindle_smart_split") == 0) {
config->dwindle_smart_split = atoi(value);
} else if (strcmp(key, "dwindle_smart_resize") == 0) {
config->dwindle_smart_resize = atoi(value);
} else if (strcmp(key, "dwindle_split_ratio") == 0) {
config->dwindle_split_ratio = atof(value);
} else if (strcmp(key, "hotarea_size") == 0) {
config->hotarea_size = atoi(value);
} else if (strcmp(key, "hotarea_corner") == 0) {
@ -3179,6 +3199,14 @@ void override_config(void) {
config.center_when_single_stack =
CLAMP_INT(config.center_when_single_stack, 0, 1);
config.new_is_master = CLAMP_INT(config.new_is_master, 0, 1);
config.dwindle_vsplit = CLAMP_INT(config.dwindle_vsplit, 0, 2);
config.dwindle_hsplit = CLAMP_INT(config.dwindle_hsplit, 0, 2);
config.dwindle_preserve_split =
CLAMP_INT(config.dwindle_preserve_split, 0, 1);
config.dwindle_smart_split = CLAMP_INT(config.dwindle_smart_split, 0, 1);
config.dwindle_smart_resize = CLAMP_INT(config.dwindle_smart_resize, 0, 1);
config.dwindle_split_ratio =
CLAMP_FLOAT(config.dwindle_split_ratio, 0.05f, 0.95f);
config.hotarea_size = CLAMP_INT(config.hotarea_size, 1, 1000);
config.hotarea_corner = CLAMP_INT(config.hotarea_corner, 0, 3);
config.enable_hotarea = CLAMP_INT(config.enable_hotarea, 0, 1);
@ -3318,6 +3346,13 @@ void set_value_default() {
config.center_master_overspread = 0;
config.center_when_single_stack = 1;
config.dwindle_vsplit = 0;
config.dwindle_hsplit = 0;
config.dwindle_preserve_split = 0;
config.dwindle_smart_split = 0;
config.dwindle_smart_resize = 0;
config.dwindle_split_ratio = 0.5f;
config.log_level = WLR_ERROR;
config.numlockon = 0;
config.capslock = 0;

View file

@ -112,6 +112,10 @@ int32_t exchange_client(const Arg *arg) {
Client *tc = direction_select(arg);
tc = get_focused_stack_client(tc);
if (!tc)
return 0;
exchange_two_client(c, tc);
return 0;
}

View file

@ -488,6 +488,29 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx,
}
}
void resize_tile_dwindle(Client *grabc, bool isdrag, int32_t offsetx,
int32_t offsety, uint32_t time, bool isvertical) {
if (!start_drag_window) {
dwindle_resize_client_step(grabc->mon, grabc, offsetx, offsety);
}
if (isdrag) {
int32_t dx = (int32_t)round(cursor->x) - drag_begin_cursorx;
int32_t dy = (int32_t)round(cursor->y) - drag_begin_cursory;
dwindle_resize_client(grabc->mon, grabc, dx, dy);
} else if (last_apply_drap_time == 0 ||
time - last_apply_drap_time >
config.drag_tile_refresh_interval) {
dwindle_resize_client(grabc->mon, grabc, offsetx, offsety);
last_apply_drap_time = time;
}
if (!isdrag) {
start_drag_window = false;
}
}
void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
int32_t offsety, uint32_t time, bool isvertical) {
Client *tc = NULL;
@ -706,6 +729,8 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx,
resize_tile_scroller(grabc, isdrag, offsetx, offsety, time, false);
} else if (current_layout->id == VERTICAL_SCROLLER) {
resize_tile_scroller(grabc, isdrag, offsetx, offsety, time, true);
} else if (current_layout->id == DWINDLE) {
resize_tile_dwindle(grabc, isdrag, offsetx, offsety, time, true);
}
}

435
src/layout/dwindle.h Normal file
View file

@ -0,0 +1,435 @@
typedef struct DwindleNode DwindleNode;
struct DwindleNode {
bool is_split;
bool split_h;
bool split_locked;
float ratio;
float drag_init_ratio;
int32_t container_x;
int32_t container_y;
int32_t container_w;
int32_t container_h;
DwindleNode *parent;
DwindleNode *first;
DwindleNode *second;
Client *client;
};
static DwindleNode *dwindle_locked_h_node = NULL;
static DwindleNode *dwindle_locked_v_node = NULL;
static DwindleNode *dwindle_new_leaf(Client *c) {
DwindleNode *n = calloc(1, sizeof(DwindleNode));
n->client = c;
return n;
}
static DwindleNode *dwindle_find_leaf(DwindleNode *node, Client *c) {
if (!node)
return NULL;
if (!node->is_split)
return node->client == c ? node : NULL;
DwindleNode *r = dwindle_find_leaf(node->first, c);
return r ? r : dwindle_find_leaf(node->second, c);
}
static DwindleNode *dwindle_first_leaf(DwindleNode *node) {
if (!node)
return NULL;
while (node->is_split)
node = node->first;
return node;
}
static void dwindle_free_tree(DwindleNode *node) {
if (!node)
return;
dwindle_free_tree(node->first);
dwindle_free_tree(node->second);
free(node);
}
static void dwindle_insert(DwindleNode **root, Client *new_c, Client *focused,
float ratio, bool as_first) {
DwindleNode *new_leaf = dwindle_new_leaf(new_c);
if (!*root) {
*root = new_leaf;
return;
}
DwindleNode *target = focused ? dwindle_find_leaf(*root, focused) : NULL;
if (!target)
target = dwindle_first_leaf(*root);
DwindleNode *split = calloc(1, sizeof(DwindleNode));
split->is_split = true;
split->ratio = ratio;
if (as_first) {
split->first = new_leaf;
split->second = target;
} else {
split->first = target;
split->second = new_leaf;
}
split->parent = target->parent;
target->parent = split;
new_leaf->parent = split;
if (!split->parent) {
*root = split;
} else {
if (split->parent->first == target)
split->parent->first = split;
else
split->parent->second = split;
}
}
static void dwindle_remove(DwindleNode **root, Client *c) {
DwindleNode *leaf = dwindle_find_leaf(*root, c);
if (!leaf)
return;
DwindleNode *parent = leaf->parent;
if (!parent) {
free(leaf);
*root = NULL;
return;
}
DwindleNode *sibling =
(parent->first == leaf) ? parent->second : parent->first;
DwindleNode *grandparent = parent->parent;
sibling->parent = grandparent;
/* Preserve split direction on sibling split-nodes when requested. */
if (!sibling->is_split ||
(!config.dwindle_preserve_split && !config.dwindle_smart_split)) {
sibling->container_w = 0;
sibling->container_h = 0;
}
if (!grandparent) {
*root = sibling;
} else {
if (grandparent->first == parent)
grandparent->first = sibling;
else
grandparent->second = sibling;
}
free(leaf);
free(parent);
}
static void dwindle_assign(DwindleNode *node, int32_t ax, int32_t ay,
int32_t aw, int32_t ah, int32_t gap_h,
int32_t gap_v) {
if (!node)
return;
if (!node->is_split) {
if (node->client) {
struct wlr_box box = {ax, ay, MAX(1, aw), MAX(1, ah)};
resize(node->client, box, 0);
}
return;
}
if (!node->split_locked && node->container_w == 0 && node->container_h == 0)
node->split_h = (aw >= ah);
node->container_x = ax;
node->container_y = ay;
node->container_w = aw;
node->container_h = ah;
if (node->split_h) {
int32_t w1 = MAX(1, (int32_t)(aw * node->ratio) - gap_h / 2);
dwindle_assign(node->first, ax, ay, w1, ah, gap_h, gap_v);
dwindle_assign(node->second, ax + w1 + gap_h, ay, aw - w1 - gap_h, ah,
gap_h, gap_v);
} else {
int32_t h1 = MAX(1, (int32_t)(ah * node->ratio) - gap_v / 2);
dwindle_assign(node->first, ax, ay, aw, h1, gap_h, gap_v);
dwindle_assign(node->second, ax, ay + h1 + gap_v, aw, ah - h1 - gap_v,
gap_h, gap_v);
}
}
static void dwindle_move_client(DwindleNode **root, Client *c, Client *target,
float ratio, int32_t dir) {
if (!c || !target || c == target)
return;
if (!dwindle_find_leaf(*root, c) || !dwindle_find_leaf(*root, target))
return;
dwindle_remove(root, c);
bool as_first = (dir == UP || dir == LEFT);
dwindle_insert(root, c, target, ratio, as_first);
}
static void dwindle_swap_clients(DwindleNode **root, Client *a, Client *b) {
DwindleNode *la = dwindle_find_leaf(*root, a);
DwindleNode *lb = dwindle_find_leaf(*root, b);
if (!la || !lb || la == lb)
return;
la->client = b;
lb->client = a;
}
static void dwindle_resize_client(Monitor *m, Client *c, int32_t dx,
int32_t dy) {
uint32_t tag = m->pertag->curtag;
DwindleNode *leaf = dwindle_find_leaf(m->pertag->dwindle_root[tag], c);
if (!leaf)
return;
if (!start_drag_window) {
start_drag_window = true;
dwindle_locked_h_node = NULL;
dwindle_locked_v_node = NULL;
DwindleNode *node = leaf->parent;
while (node) {
if (node->split_h && !dwindle_locked_h_node) {
dwindle_locked_h_node = node;
node->drag_init_ratio = node->ratio;
}
if (!node->split_h && !dwindle_locked_v_node) {
dwindle_locked_v_node = node;
node->drag_init_ratio = node->ratio;
}
if (dwindle_locked_h_node && dwindle_locked_v_node)
break;
node = node->parent;
}
}
if (!dwindle_locked_h_node && !dwindle_locked_v_node)
return;
if (dwindle_locked_h_node) {
float cw = (float)MAX(1, dwindle_locked_h_node->container_w);
float ox = (float)(cursor->x - drag_begin_cursorx);
if (config.dwindle_smart_resize) {
/* Move the boundary toward the cursor: invert direction when
* the drag started on the right side of the split line. */
float split_x = dwindle_locked_h_node->container_x +
cw * dwindle_locked_h_node->drag_init_ratio;
if (drag_begin_cursorx >= split_x)
ox = -ox;
}
dwindle_locked_h_node->ratio =
dwindle_locked_h_node->drag_init_ratio + ox / cw;
dwindle_locked_h_node->ratio =
CLAMP_FLOAT(dwindle_locked_h_node->ratio, 0.05f, 0.95f);
}
if (dwindle_locked_v_node) {
float ch = (float)MAX(1, dwindle_locked_v_node->container_h);
float oy = (float)(cursor->y - drag_begin_cursory);
if (config.dwindle_smart_resize) {
/* Same logic for the vertical split line. */
float split_y = dwindle_locked_v_node->container_y +
ch * dwindle_locked_v_node->drag_init_ratio;
if (drag_begin_cursory >= split_y)
oy = -oy;
}
dwindle_locked_v_node->ratio =
dwindle_locked_v_node->drag_init_ratio + oy / ch;
dwindle_locked_v_node->ratio =
CLAMP_FLOAT(dwindle_locked_v_node->ratio, 0.05f, 0.95f);
}
int32_t n = m->visible_tiling_clients;
int32_t gap_ih = enablegaps ? m->gappih : 0;
int32_t gap_iv = enablegaps ? m->gappiv : 0;
int32_t gap_oh = enablegaps ? m->gappoh : 0;
int32_t gap_ov = enablegaps ? m->gappov : 0;
if (config.smartgaps && n == 1)
gap_ih = gap_iv = gap_oh = gap_ov = 0;
dwindle_assign(m->pertag->dwindle_root[tag], m->w.x + gap_oh,
m->w.y + gap_ov, m->w.width - 2 * gap_oh,
m->w.height - 2 * gap_ov, gap_ih, gap_iv);
}
static void dwindle_resize_client_step(Monitor *m, Client *c, int32_t dx,
int32_t dy) {
uint32_t tag = m->pertag->curtag;
DwindleNode *leaf = dwindle_find_leaf(m->pertag->dwindle_root[tag], c);
if (!leaf)
return;
DwindleNode *h_node = NULL;
DwindleNode *v_node = NULL;
DwindleNode *node = leaf->parent;
drag_begin_cursorx = cursor->x;
drag_begin_cursory = cursor->y;
while (node) {
if (node->split_h && !h_node)
h_node = node;
if (!node->split_h && !v_node)
v_node = node;
if (h_node && v_node)
break;
node = node->parent;
}
if (!h_node && !v_node)
return;
if (h_node && dx) {
float cw = (float)MAX(1, h_node->container_w);
float delta = (float)dx / cw;
h_node->ratio = CLAMP_FLOAT(h_node->ratio + delta, 0.05f, 0.95f);
}
if (v_node && dy) {
float ch = (float)MAX(1, v_node->container_h);
float delta = (float)dy / ch;
v_node->ratio = CLAMP_FLOAT(v_node->ratio + delta, 0.05f, 0.95f);
}
int32_t n_clients = m->visible_tiling_clients;
int32_t gap_ih = enablegaps ? m->gappih : 0;
int32_t gap_iv = enablegaps ? m->gappiv : 0;
int32_t gap_oh = enablegaps ? m->gappoh : 0;
int32_t gap_ov = enablegaps ? m->gappov : 0;
if (config.smartgaps && n_clients == 1)
gap_ih = gap_iv = gap_oh = gap_ov = 0;
dwindle_assign(m->pertag->dwindle_root[tag], m->w.x + gap_oh,
m->w.y + gap_ov, m->w.width - 2 * gap_oh,
m->w.height - 2 * gap_ov, gap_ih, gap_iv);
}
static void dwindle_remove_client(Client *c) {
Monitor *m;
wl_list_for_each(m, &mons, link) {
for (uint32_t t = 0; t < LENGTH(tags) + 1; t++)
dwindle_remove(&m->pertag->dwindle_root[t], c);
}
}
/* Insert a new client respecting dwindle_vsplit, dwindle_hsplit, and
* dwindle_smart_split config options. */
static void dwindle_insert_with_config(DwindleNode **root, Client *new_c,
Client *focused, float ratio) {
bool as_first = false;
if (focused) {
struct wlr_box *fg = &focused->geom;
double fcx = fg->x + fg->width * 0.5;
double fcy = fg->y + fg->height * 0.5;
if (config.dwindle_smart_split) {
/* Divide the focused window into 4 triangles using its diagonals.
* The triangle the cursor falls in determines both the split axis
* and which side the new window appears on. */
double nx = (cursor->x - fcx) / (fg->width * 0.5);
double ny = (cursor->y - fcy) / (fg->height * 0.5);
bool do_split_h;
if (fabs(ny) > fabs(nx)) {
do_split_h = false;
as_first = (ny < 0); /* top triangle → new window on top */
} else {
do_split_h = true;
as_first = (nx < 0); /* left triangle → new window on left */
}
dwindle_insert(root, new_c, focused, ratio, as_first);
DwindleNode *leaf = dwindle_find_leaf(*root, new_c);
if (leaf && leaf->parent) {
leaf->parent->split_h = do_split_h;
leaf->parent->split_locked = true;
}
return;
}
/* Predict likely split direction from the focused window's shape. */
bool likely_h = (fg->width >= fg->height);
if (likely_h) {
if (config.dwindle_hsplit == 0)
as_first = (cursor->x < fcx); /* follow mouse */
else
as_first = (config.dwindle_hsplit == 2); /* 2=left, 1=right */
} else {
if (config.dwindle_vsplit == 0)
as_first = (cursor->y < fcy); /* follow mouse */
else
as_first = (config.dwindle_vsplit == 2); /* 2=top, 1=bottom */
}
}
dwindle_insert(root, new_c, focused, ratio, as_first);
}
void dwindle(Monitor *m) {
int32_t n = m->visible_tiling_clients;
if (n == 0)
return;
uint32_t tag = m->pertag->curtag;
DwindleNode **root = &m->pertag->dwindle_root[tag];
float ratio = config.dwindle_split_ratio;
Client *vis[512];
int32_t count = 0;
Client *c;
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, m) && ISTILED(c))
vis[count++] = c;
if (count >= 512)
break;
}
{
DwindleNode *leaves[512];
int32_t lc = 0;
DwindleNode *stack[1024];
int32_t sp = 0;
if (*root)
stack[sp++] = *root;
while (sp > 0) {
DwindleNode *nd = stack[--sp];
if (!nd->is_split) {
leaves[lc++] = nd;
} else {
if (nd->second)
stack[sp++] = nd->second;
if (nd->first)
stack[sp++] = nd->first;
}
}
for (int32_t i = 0; i < lc; i++) {
bool found = false;
for (int32_t j = 0; j < count; j++)
if (vis[j] == leaves[i]->client) {
found = true;
break;
}
if (!found)
dwindle_remove(root, leaves[i]->client);
}
}
Client *focused = focustop(m);
if (focused && !dwindle_find_leaf(*root, focused))
focused = m->sel;
for (int32_t i = 0; i < count; i++) {
if (!dwindle_find_leaf(*root, vis[i]))
dwindle_insert_with_config(root, vis[i], focused, ratio);
}
int32_t gap_ih = enablegaps ? m->gappih : 0;
int32_t gap_iv = enablegaps ? m->gappiv : 0;
int32_t gap_oh = enablegaps ? m->gappoh : 0;
int32_t gap_ov = enablegaps ? m->gappov : 0;
if (config.smartgaps && n == 1)
gap_ih = gap_iv = gap_oh = gap_ov = 0;
dwindle_assign(*root, m->w.x + gap_oh, m->w.y + gap_ov,
m->w.width - 2 * gap_oh, m->w.height - 2 * gap_ov, gap_ih,
gap_iv);
}

View file

@ -12,6 +12,7 @@ static void vertical_grid(Monitor *m);
static void vertical_scroller(Monitor *m);
static void vertical_deck(Monitor *mon);
static void tgmix(Monitor *m);
static void dwindle(Monitor *m);
/* layout(s) */
Layout overviewlayout = {"󰃇", overview, "overview"};
@ -29,6 +30,7 @@ enum {
VERTICAL_DECK,
RIGHT_TILE,
TGMIX,
DWINDLE,
};
Layout layouts[] = {
@ -47,4 +49,5 @@ Layout layouts[] = {
{"VG", vertical_grid, "vertical_grid", VERTICAL_GRID}, // 垂直格子布局
{"VK", vertical_deck, "vertical_deck", VERTICAL_DECK}, // 垂直卡片布局
{"TG", tgmix, "tgmix", TGMIX}, // 混合布局
{"DW", dwindle, "dwindle", DWINDLE},
};

View file

@ -560,6 +560,8 @@ typedef struct {
struct wl_listener destroy;
} SessionLock;
typedef struct DwindleNode DwindleNode;
/* function declarations */
static void applybounds(
Client *c,
@ -815,6 +817,12 @@ static void client_pending_maximized_state(Client *c, int32_t ismaximized);
static void client_pending_minimized_state(Client *c, int32_t isminimized);
static void scroller_insert_stack(Client *c, Client *target_client,
bool insert_before);
static void dwindle_move_client(DwindleNode **root, Client *c, Client *target,
float ratio, int32_t dir);
static void dwindle_resize_client_step(Monitor *m, Client *c, int32_t dx,
int32_t dy);
static void dwindle_resize_client(Monitor *m, Client *c, int32_t dx,
int32_t dy);
#include "data/static_keymap.h"
#include "dispatch/bind_declare.h"
@ -946,6 +954,7 @@ struct Pertag {
int32_t no_hide[LENGTH(tags) + 1]; /* no_hide per tag */
int32_t no_render_border[LENGTH(tags) + 1]; /* no_render_border per tag */
int32_t open_as_floating[LENGTH(tags) + 1]; /* open_as_floating per tag */
struct DwindleNode *dwindle_root[LENGTH(tags) + 1];
const Layout
*ltidxs[LENGTH(tags) + 1]; /* matrix of tags and layouts indexes */
};
@ -1018,6 +1027,7 @@ static struct wl_event_source *sync_keymap;
#include "ext-protocol/all.h"
#include "fetch/fetch.h"
#include "layout/arrange.h"
#include "layout/dwindle.h"
#include "layout/horizontal.h"
#include "layout/vertical.h"
@ -1179,6 +1189,17 @@ void swallow(Client *c, Client *w) {
client_pending_fullscreen_state(c, w->isfullscreen);
client_pending_maximized_state(c, w->ismaximizescreen);
client_pending_minimized_state(c, w->isminimized);
Monitor *m;
wl_list_for_each(m, &mons, link) {
for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) {
DwindleNode **root = &m->pertag->dwindle_root[t];
dwindle_remove(root, c);
DwindleNode *wn = dwindle_find_leaf(*root, w);
if (wn)
wn->client = c;
}
}
}
bool switch_scratchpad_client_state(Client *c) {
@ -2194,6 +2215,13 @@ void place_drag_tile_client(Client *c) {
try_scroller_drop(c, closest, 1);
return;
}
if (layout->id == DWINDLE) {
uint32_t tag = c->mon->pertag->curtag;
bool insert_before = closest->drop_direction == LEFT ||
closest->drop_direction == UP;
dwindle_insert(&c->mon->pertag->dwindle_root[tag], c, closest,
config.dwindle_split_ratio, insert_before);
}
if (closest->drop_direction == LEFT || closest->drop_direction == UP) {
wl_list_remove(&c->link);
@ -5101,10 +5129,20 @@ void exchange_two_client(Client *c1, Client *c2) {
tmp_tags = c2->tags;
setmon(c2, c1->mon, c1->tags, false);
setmon(c1, tmp_mon, tmp_tags, false);
if (c1->mon &&
c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]->id == DWINDLE)
dwindle_swap_clients(
&c1->mon->pertag->dwindle_root[c1->mon->pertag->curtag], c1,
c2);
arrange(c1->mon, false, false);
arrange(c2->mon, false, false);
focusclient(c1, 0);
} else {
if (c1->mon &&
c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]->id == DWINDLE)
dwindle_swap_clients(
&c1->mon->pertag->dwindle_root[c1->mon->pertag->curtag], c1,
c2);
arrange(c1->mon, false, false);
}
@ -6343,6 +6381,7 @@ void unmapnotify(struct wl_listener *listener, void *data) {
c->next_in_stack = NULL;
c->prev_in_stack = NULL;
dwindle_remove_client(c);
wlr_scene_node_destroy(&c->scene->node);
printstatus();
motionnotify(0, NULL, 0, 0, 0, 0);