mirror of
https://github.com/DreamMaoMao/maomaowm.git
synced 2026-05-09 23:50:21 -04:00
Merge pull request #907 from mangowm/dwindle
feat: dwindle layout support
This commit is contained in:
commit
ed03dacd22
12 changed files with 682 additions and 49 deletions
|
|
@ -48,7 +48,7 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f
|
|||
- vertical_tile
|
||||
- vertical_grid
|
||||
- vertical_scroller
|
||||
- tgmix
|
||||
- dwindle
|
||||
|
||||
# Installation
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ mmsg -s -t 2^
|
|||
|
||||
### Layouts
|
||||
|
||||
Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `TG` (TGMix).
|
||||
Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `DW` (Dwindle).
|
||||
|
||||
```bash
|
||||
# Switch to Scroller
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ mangowm supports a variety of layouts that can be assigned per tag.
|
|||
- `vertical_scroller`
|
||||
- `vertical_grid`
|
||||
- `vertical_deck`
|
||||
- `tgmix`
|
||||
- `dwindle`
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -83,6 +83,35 @@ default_nmaster=1
|
|||
|
||||
---
|
||||
|
||||
## Dwindle Layout
|
||||
|
||||
The Dwindle layout arranges windows as a binary tree of recursive splits. Each new window splits the focused window's container, producing a spiral-like tiling.
|
||||
|
||||
### Configuration
|
||||
|
||||
| Setting | Default | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `dwindle_split_ratio` | `0.5` | Ratio used for new splits (`0.05`–`0.95`). |
|
||||
| `dwindle_smart_split` | `0` | Pick the split axis from the cursor's position inside the focused window. The new window appears on the cursor's side. |
|
||||
| `dwindle_hsplit` | `0` | Side-by-side splits: where the new window goes. `0` = follow cursor, `1` = right, `2` = left. |
|
||||
| `dwindle_vsplit` | `0` | Top/bottom splits: where the new window goes. `0` = follow cursor, `1` = below, `2` = above. |
|
||||
| `dwindle_preserve_split` | `0` | Keep the sibling's split orientation when a window is closed. |
|
||||
| `dwindle_smart_resize` | `0` | When dragging to resize, move the split toward the cursor regardless of which side was grabbed. |
|
||||
| `dwindle_drop_simple_split` | `1` | Drag-to-tile drop preview. `1` = 2-zone preview matching `dwindle_split_ratio`, `0` = 4-quadrant preview. |
|
||||
|
||||
```ini
|
||||
# Example dwindle configuration
|
||||
dwindle_split_ratio=0.5
|
||||
dwindle_smart_split=0
|
||||
dwindle_hsplit=0
|
||||
dwindle_vsplit=0
|
||||
dwindle_preserve_split=0
|
||||
dwindle_smart_resize=0
|
||||
dwindle_drop_simple_split=1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Switching Layouts
|
||||
|
||||
You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules).
|
||||
|
|
|
|||
|
|
@ -514,6 +514,9 @@ void client_set_drop_area(Client *c) {
|
|||
bool first_draw = false;
|
||||
int32_t drop_direction = UNDIR;
|
||||
|
||||
if (!c || !c->mon)
|
||||
return;
|
||||
|
||||
if (!c->enable_drop_area_draw && !c->droparea->node.enabled) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -538,51 +541,123 @@ void client_set_drop_area(Client *c) {
|
|||
|
||||
struct wlr_box drop_box;
|
||||
|
||||
// 中心区域:x和y都在30%~70%之间 → 无方向
|
||||
if (rel_x > client_width * 0.3 && rel_x < client_width * 0.7 &&
|
||||
rel_y > client_height * 0.3 && rel_y < client_height * 0.7) {
|
||||
drop_box.x = bw + client_width * 0.3;
|
||||
drop_box.y = bw + client_height * 0.3;
|
||||
drop_box.width = client_width * 0.4;
|
||||
drop_box.height = client_height * 0.4;
|
||||
const Layout *cur_layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag];
|
||||
bool dwindle_familiar =
|
||||
cur_layout->id == DWINDLE && config.dwindle_drop_simple_split;
|
||||
|
||||
uint32_t nmaster = c->mon->pertag->nmasters[c->mon->pertag->curtag];
|
||||
|
||||
bool should_swap =
|
||||
(cur_layout->id == DECK || cur_layout->id == VERTICAL_DECK ||
|
||||
cur_layout->id == MONOCLE || cur_layout->id == GRID ||
|
||||
cur_layout->id == VERTICAL_GRID) ||
|
||||
((cur_layout->id == TILE || cur_layout->id == VERTICAL_TILE ||
|
||||
cur_layout->id == CENTER_TILE || cur_layout->id == RIGHT_TILE) &&
|
||||
nmaster == 1 && c->ismaster);
|
||||
|
||||
if (dwindle_familiar) {
|
||||
bool split_h = c->geom.width >= c->geom.height;
|
||||
float ratio = config.dwindle_split_ratio;
|
||||
if (split_h) {
|
||||
if (rel_x < client_width * 0.5) {
|
||||
drop_direction = LEFT;
|
||||
drop_box.x = bw;
|
||||
drop_box.y = bw;
|
||||
drop_box.width = (int32_t)(client_width * ratio);
|
||||
drop_box.height = client_height;
|
||||
} else {
|
||||
drop_direction = RIGHT;
|
||||
drop_box.x = bw + (int32_t)(client_width * ratio);
|
||||
drop_box.y = bw;
|
||||
drop_box.width = client_width - (int32_t)(client_width * ratio);
|
||||
drop_box.height = client_height;
|
||||
}
|
||||
} else {
|
||||
if (rel_y < client_height * 0.5) {
|
||||
drop_direction = UP;
|
||||
drop_box.x = bw;
|
||||
drop_box.y = bw;
|
||||
drop_box.width = client_width;
|
||||
drop_box.height = (int32_t)(client_height * ratio);
|
||||
} else {
|
||||
drop_direction = DOWN;
|
||||
drop_box.x = bw;
|
||||
drop_box.y = bw + (int32_t)(client_height * ratio);
|
||||
drop_box.width = client_width;
|
||||
drop_box.height =
|
||||
client_height - (int32_t)(client_height * ratio);
|
||||
}
|
||||
}
|
||||
} else if (should_swap) {
|
||||
drop_box.x = bw;
|
||||
drop_box.y = bw;
|
||||
drop_box.width = client_width;
|
||||
drop_box.height = client_height;
|
||||
drop_direction = UNDIR;
|
||||
} else if (cur_layout->id == TILE || cur_layout->id == DECK ||
|
||||
cur_layout->id == CENTER_TILE || cur_layout->id == RIGHT_TILE) {
|
||||
if (rel_y < client_height * 0.5) {
|
||||
drop_direction = UP;
|
||||
drop_box.x = bw;
|
||||
drop_box.y = bw;
|
||||
drop_box.width = client_width;
|
||||
drop_box.height = client_height / 2;
|
||||
} else {
|
||||
drop_direction = DOWN;
|
||||
drop_box.x = bw;
|
||||
drop_box.y = bw + client_height / 2;
|
||||
drop_box.width = client_width;
|
||||
drop_box.height = client_height / 2;
|
||||
}
|
||||
} else if (cur_layout->id == VERTICAL_TILE ||
|
||||
cur_layout->id == VERTICAL_DECK) {
|
||||
if (rel_x < client_width * 0.5) {
|
||||
drop_direction = LEFT;
|
||||
drop_box.x = bw;
|
||||
drop_box.y = bw;
|
||||
drop_box.width = client_width / 2;
|
||||
drop_box.height = client_height;
|
||||
} else {
|
||||
drop_direction = RIGHT;
|
||||
drop_box.x = bw + client_width / 2;
|
||||
drop_box.y = bw;
|
||||
drop_box.width = client_width / 2;
|
||||
drop_box.height = client_height;
|
||||
}
|
||||
} else {
|
||||
// 否则根据到各边的距离决定方向
|
||||
double dist_left = rel_x;
|
||||
double dist_right = client_width - rel_x;
|
||||
double dist_top = rel_y;
|
||||
double dist_bottom = client_height - rel_y;
|
||||
|
||||
// 找出最小距离的方向(相等时按左、右、上、下的优先级顺序)
|
||||
if (dist_left <= dist_right && dist_left <= dist_top &&
|
||||
dist_left <= dist_bottom) {
|
||||
drop_direction = LEFT;
|
||||
drop_box.x = bw;
|
||||
drop_box.y = bw;
|
||||
drop_box.width = client_width * 0.3;
|
||||
drop_box.width = client_width / 2;
|
||||
drop_box.height = client_height;
|
||||
} else if (dist_right <= dist_top && dist_right <= dist_bottom) {
|
||||
drop_direction = RIGHT;
|
||||
drop_box.x = bw + client_width * 0.7;
|
||||
drop_box.x = bw + client_width / 2;
|
||||
drop_box.y = bw;
|
||||
drop_box.width = client_width * 0.3;
|
||||
drop_box.width = client_width / 2;
|
||||
drop_box.height = client_height;
|
||||
} else if (dist_top <= dist_bottom) {
|
||||
drop_direction = UP;
|
||||
drop_box.x = bw;
|
||||
drop_box.y = bw;
|
||||
drop_box.width = client_width;
|
||||
drop_box.height = client_height * 0.3;
|
||||
drop_box.height = client_height / 2;
|
||||
} else {
|
||||
drop_direction = DOWN;
|
||||
drop_box.x = bw;
|
||||
drop_box.y = bw + client_height * 0.7;
|
||||
drop_box.y = bw + client_height / 2;
|
||||
drop_box.width = client_width;
|
||||
drop_box.height = client_height * 0.3;
|
||||
drop_box.height = client_height / 2;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果方向和上次相同且不是第一次绘制,则跳过更新
|
||||
if (!first_draw && c->drop_direction == drop_direction) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,6 +242,15 @@ 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;
|
||||
int32_t dwindle_drop_simple_split;
|
||||
float dwindle_split_ratio;
|
||||
|
||||
uint32_t hotarea_size;
|
||||
uint32_t hotarea_corner;
|
||||
uint32_t enable_hotarea;
|
||||
|
|
@ -1615,6 +1624,20 @@ 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_drop_simple_split") == 0) {
|
||||
config->dwindle_drop_simple_split = 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 +3202,16 @@ 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_drop_simple_split =
|
||||
CLAMP_INT(config.dwindle_drop_simple_split, 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 +3351,14 @@ 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_drop_simple_split = 1;
|
||||
config.dwindle_split_ratio = 0.5f;
|
||||
|
||||
config.log_level = WLR_ERROR;
|
||||
config.numlockon = 0;
|
||||
config.capslock = 0;
|
||||
|
|
@ -3716,7 +3757,7 @@ void reapply_rootbg(void) {
|
|||
wlr_scene_rect_set_color(root_bg, config.rootcolor);
|
||||
}
|
||||
|
||||
void reapply_border(void) {
|
||||
void reapply_property(void) {
|
||||
Client *c = NULL;
|
||||
|
||||
// reset border width when config change
|
||||
|
|
@ -3725,6 +3766,8 @@ void reapply_border(void) {
|
|||
if (!c->isnoborder && !c->isfullscreen) {
|
||||
c->bw = config.borderpx;
|
||||
}
|
||||
|
||||
wlr_scene_rect_set_color(c->droparea, config.dropcolor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3870,7 +3913,7 @@ void reset_option(void) {
|
|||
run_exec();
|
||||
|
||||
reapply_cursor_style();
|
||||
reapply_border();
|
||||
reapply_property();
|
||||
reapply_rootbg();
|
||||
reapply_keyboard();
|
||||
reapply_pointer();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -558,7 +558,7 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) {
|
|||
|
||||
if (id != SCROLLER && id != VERTICAL_SCROLLER && id != TILE &&
|
||||
id != VERTICAL_TILE && id != DECK && id != VERTICAL_DECK &&
|
||||
id != CENTER_TILE && id != RIGHT_TILE && id != TGMIX)
|
||||
id != CENTER_TILE && id != RIGHT_TILE)
|
||||
return false;
|
||||
|
||||
if (id == SCROLLER || id == VERTICAL_SCROLLER) {
|
||||
|
|
@ -583,15 +583,6 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (id == TGMIX) {
|
||||
if (tc->ismaster ^ sc->ismaster)
|
||||
return false;
|
||||
if (fc && !(fc->ismaster ^ sc->ismaster))
|
||||
return false;
|
||||
if (!sc->ismaster && sc->mon->visible_tiling_clients <= 3)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (id == CENTER_TILE) {
|
||||
if (tc->ismaster ^ sc->ismaster)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -488,6 +488,21 @@ 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 (!isdrag) {
|
||||
dwindle_resize_client_step(grabc->mon, grabc, offsetx, offsety);
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_apply_drap_time == 0 ||
|
||||
time - last_apply_drap_time > config.drag_tile_refresh_interval) {
|
||||
dwindle_resize_client(grabc->mon, grabc);
|
||||
last_apply_drap_time = time;
|
||||
}
|
||||
}
|
||||
|
||||
void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
|
||||
int32_t offsety, uint32_t time, bool isvertical) {
|
||||
Client *tc = NULL;
|
||||
|
|
@ -692,8 +707,7 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx,
|
|||
const Layout *current_layout =
|
||||
grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag];
|
||||
if (current_layout->id == TILE || current_layout->id == DECK ||
|
||||
current_layout->id == CENTER_TILE || current_layout->id == RIGHT_TILE ||
|
||||
(current_layout->id == TGMIX && grabc->mon->visible_tiling_clients <= 3)
|
||||
current_layout->id == CENTER_TILE || current_layout->id == RIGHT_TILE
|
||||
|
||||
) {
|
||||
resize_tile_master_horizontal(grabc, isdrag, offsetx, offsety, time,
|
||||
|
|
@ -706,6 +720,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
433
src/layout/dwindle.h
Normal file
433
src/layout/dwindle.h
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
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, bool split_h,
|
||||
bool lock) {
|
||||
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;
|
||||
split->split_h = split_h;
|
||||
split->split_locked = lock;
|
||||
|
||||
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);
|
||||
bool split_h = (dir == LEFT || dir == RIGHT);
|
||||
dwindle_insert(root, c, target, ratio, as_first, split_h, true);
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
drag_begin_cursorx = cursor->x;
|
||||
drag_begin_cursory = cursor->y;
|
||||
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;
|
||||
|
||||
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;
|
||||
bool split_h = false;
|
||||
bool lock = 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) {
|
||||
double nx = (cursor->x - fcx) / (fg->width * 0.5);
|
||||
double ny = (cursor->y - fcy) / (fg->height * 0.5);
|
||||
|
||||
if (fabs(ny) > fabs(nx)) {
|
||||
split_h = false; // vertical split
|
||||
as_first = (ny < 0); // top → new window on top
|
||||
} else {
|
||||
split_h = true; // horizontal split
|
||||
as_first = (nx < 0); // left → new window on left
|
||||
}
|
||||
lock = true; // lock split direction
|
||||
} else {
|
||||
// normal mode, auto split
|
||||
bool likely_h = (fg->width >= fg->height);
|
||||
if (likely_h) {
|
||||
if (config.dwindle_hsplit == 0)
|
||||
as_first = (cursor->x < fcx);
|
||||
else
|
||||
as_first = (config.dwindle_hsplit == 2);
|
||||
} else {
|
||||
if (config.dwindle_vsplit == 0)
|
||||
as_first = (cursor->y < fcy);
|
||||
else
|
||||
as_first = (config.dwindle_vsplit == 2);
|
||||
}
|
||||
// split_h and lock are false, decided by width/height ratio
|
||||
}
|
||||
}
|
||||
|
||||
dwindle_insert(root, new_c, focused, ratio, as_first, split_h, lock);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -993,15 +993,4 @@ monocle(Monitor *m) {
|
|||
}
|
||||
if ((c = focustop(m)))
|
||||
wlr_scene_node_raise_to_top(&c->scene->node);
|
||||
}
|
||||
|
||||
void tgmix(Monitor *m) {
|
||||
int32_t n = m->visible_tiling_clients;
|
||||
if (n <= 3) {
|
||||
tile(m);
|
||||
return;
|
||||
} else {
|
||||
grid(m);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ static void vertical_overview(Monitor *m);
|
|||
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"};
|
||||
|
|
@ -28,7 +28,7 @@ enum {
|
|||
VERTICAL_GRID,
|
||||
VERTICAL_DECK,
|
||||
RIGHT_TILE,
|
||||
TGMIX,
|
||||
DWINDLE,
|
||||
};
|
||||
|
||||
Layout layouts[] = {
|
||||
|
|
@ -46,5 +46,5 @@ Layout layouts[] = {
|
|||
{"VT", vertical_tile, "vertical_tile", VERTICAL_TILE}, // 垂直平铺布局
|
||||
{"VG", vertical_grid, "vertical_grid", VERTICAL_GRID}, // 垂直格子布局
|
||||
{"VK", vertical_deck, "vertical_deck", VERTICAL_DECK}, // 垂直卡片布局
|
||||
{"TG", tgmix, "tgmix", TGMIX}, // 混合布局
|
||||
{"DW", dwindle, "dwindle", DWINDLE},
|
||||
};
|
||||
55
src/mango.c
55
src/mango.c
|
|
@ -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,11 @@ 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);
|
||||
|
||||
#include "data/static_keymap.h"
|
||||
#include "dispatch/bind_declare.h"
|
||||
|
|
@ -946,6 +953,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 +1026,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 +1188,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) {
|
||||
|
|
@ -2066,6 +2086,13 @@ Client *find_closest_tiled_client(Client *c) {
|
|||
if (tc == c || !ISTILED(tc) || !VISIBLEON(tc, c->mon))
|
||||
continue;
|
||||
|
||||
if (cursor->x >= tc->geom.x &&
|
||||
cursor->x < tc->geom.x + tc->geom.width &&
|
||||
cursor->y >= tc->geom.y &&
|
||||
cursor->y < tc->geom.y + tc->geom.height) {
|
||||
return tc;
|
||||
}
|
||||
|
||||
int32_t dx = tc->geom.x + (int32_t)(tc->geom.width / 2) - cursor->x;
|
||||
int32_t dy = tc->geom.y + (int32_t)(tc->geom.height / 2) - cursor->y;
|
||||
long dist = (long)dx * dx + (long)dy * dy;
|
||||
|
|
@ -2181,8 +2208,8 @@ void place_drag_tile_client(Client *c) {
|
|||
closest->mon->pertag->ltidxs[closest->mon->pertag->curtag];
|
||||
|
||||
if (closest->drop_direction == UNDIR) {
|
||||
exchange_two_client(c, closest);
|
||||
setfloating(c, 0);
|
||||
exchange_two_client(c, closest);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2194,6 +2221,18 @@ 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);
|
||||
bool split_h = (closest->drop_direction == LEFT ||
|
||||
closest->drop_direction == RIGHT);
|
||||
dwindle_insert(&c->mon->pertag->dwindle_root[tag], c, closest,
|
||||
config.dwindle_split_ratio, insert_before, split_h,
|
||||
!config.dwindle_drop_simple_split);
|
||||
setfloating(c, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (closest->drop_direction == LEFT || closest->drop_direction == UP) {
|
||||
wl_list_remove(&c->link);
|
||||
|
|
@ -2507,6 +2546,9 @@ void cleanupmon(struct wl_listener *listener, void *data) {
|
|||
m->skip_frame_timeout = NULL;
|
||||
}
|
||||
m->wlr_output->data = NULL;
|
||||
|
||||
for (uint32_t t = 0; t < LENGTH(tags) + 1; t++)
|
||||
dwindle_free_tree(m->pertag->dwindle_root[t]);
|
||||
free(m->pertag);
|
||||
free(m);
|
||||
}
|
||||
|
|
@ -5101,10 +5143,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 +6395,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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue