opt: redo scroller and dwindle layout

This commit is contained in:
DreamMaoMao 2026-05-10 19:07:10 +08:00
parent bccdb5d4c5
commit d347649f33
8 changed files with 1352 additions and 1081 deletions

View file

@ -505,36 +505,51 @@ void resize_tile_dwindle(Client *grabc, bool isdrag, int32_t offsetx,
void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
int32_t offsety, uint32_t time, bool isvertical) {
Client *tc = NULL;
if (!grabc || grabc->isfullscreen || grabc->ismaximizescreen)
return;
if (grabc->mon->isoverview)
return;
Monitor *m = grabc->mon;
uint32_t tag = m->pertag->curtag;
struct TagScrollerState *st = m->pertag->scroller_state[tag];
if (!st)
return;
struct ScrollerStackNode *curnode = find_scroller_node(st, grabc);
if (!curnode)
return;
struct ScrollerStackNode *headnode = curnode;
while (headnode->prev_in_stack)
headnode = headnode->prev_in_stack;
Client *stack_head_client = headnode->client;
if (m->visible_tiling_clients == 1 &&
!config.scroller_ignore_proportion_single)
return;
float delta_x, delta_y;
float new_scroller_proportion;
float new_stack_proportion;
Client *stack_head = get_scroll_stack_head(grabc);
if (grabc && grabc->mon->visible_tiling_clients == 1 &&
!config.scroller_ignore_proportion_single)
return;
if (!start_drag_window && isdrag) {
drag_begin_cursorx = cursor->x;
drag_begin_cursory = cursor->y;
start_drag_window = true;
// 记录初始状态
stack_head->old_scroller_pproportion = stack_head->scroller_proportion;
grabc->old_stack_proportion = grabc->stack_proportion;
headnode->client->old_scroller_pproportion =
headnode->scroller_proportion;
grabc->old_stack_proportion = curnode->stack_proportion;
grabc->cursor_in_left_half =
cursor->x < grabc->geom.x + grabc->geom.width / 2;
grabc->cursor_in_upper_half =
cursor->y < grabc->geom.y + grabc->geom.height / 2;
// 记录初始几何信息
grabc->drag_begin_geom = grabc->geom;
} else {
// 计算相对于屏幕尺寸的比例变化
// 计算相对于屏幕尺寸的比例变化
if (isdrag) {
offsetx = cursor->x - drag_begin_cursorx;
offsety = cursor->y - drag_begin_cursory;
} else {
@ -542,37 +557,33 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
grabc->old_master_inner_per = grabc->master_inner_per;
grabc->old_stack_inner_per = grabc->stack_inner_per;
grabc->drag_begin_geom = grabc->geom;
stack_head->old_scroller_pproportion =
stack_head->scroller_proportion;
grabc->old_stack_proportion = grabc->stack_proportion;
stack_head_client->old_scroller_pproportion =
headnode->scroller_proportion;
grabc->old_stack_proportion = curnode->stack_proportion;
grabc->cursor_in_upper_half = false;
grabc->cursor_in_left_half = false;
}
if (isvertical) {
delta_y = (float)(offsety) *
(stack_head->old_scroller_pproportion) /
(headnode->client->old_scroller_pproportion) /
grabc->drag_begin_geom.height;
delta_x = (float)(offsetx) * (grabc->old_stack_proportion) /
grabc->drag_begin_geom.width;
} else {
delta_x = (float)(offsetx) *
(stack_head->old_scroller_pproportion) /
(headnode->client->old_scroller_pproportion) /
grabc->drag_begin_geom.width;
delta_y = (float)(offsety) * (grabc->old_stack_proportion) /
grabc->drag_begin_geom.height;
}
bool moving_up;
bool moving_down;
bool moving_left;
bool moving_right;
bool moving_up, moving_down, moving_left, moving_right;
if (!isdrag) {
moving_up = offsety < 0 ? true : false;
moving_down = offsety > 0 ? true : false;
moving_left = offsetx < 0 ? true : false;
moving_right = offsetx > 0 ? true : false;
moving_up = offsety < 0;
moving_down = offsety > 0;
moving_left = offsetx < 0;
moving_right = offsetx > 0;
} else {
moving_up = cursor->y < drag_begin_cursory;
moving_down = cursor->y > drag_begin_cursory;
@ -582,10 +593,8 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
if ((grabc->cursor_in_upper_half && moving_up) ||
(!grabc->cursor_in_upper_half && moving_down)) {
// 光标在窗口上方且向上移动,或在窗口下方且向下移动 → 增加高度
delta_y = fabsf(delta_y);
} else {
// 其他情况 → 减小高度
delta_y = -fabsf(delta_y);
}
@ -597,99 +606,93 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
}
if (isvertical) {
if (!grabc->next_in_stack && grabc->prev_in_stack && !isdrag) {
if (!curnode->next_in_stack && curnode->prev_in_stack && !isdrag) {
delta_x = delta_x * -1.0f;
}
if (!grabc->next_in_stack && grabc->prev_in_stack && isdrag) {
if (moving_right) {
if (!curnode->next_in_stack && curnode->prev_in_stack && isdrag) {
if (moving_right)
delta_x = -fabsf(delta_x);
} else {
else
delta_x = fabsf(delta_x);
}
}
if (!grabc->prev_in_stack && grabc->next_in_stack && isdrag) {
if (moving_left) {
if (!curnode->prev_in_stack && curnode->next_in_stack && isdrag) {
if (moving_left)
delta_x = -fabsf(delta_x);
} else {
else
delta_x = fabsf(delta_x);
}
}
if (isdrag) {
if (moving_up) {
if (moving_up)
delta_y = -fabsf(delta_y);
} else {
else
delta_y = fabsf(delta_y);
}
}
} else {
if (!grabc->next_in_stack && grabc->prev_in_stack && !isdrag) {
if (!curnode->next_in_stack && curnode->prev_in_stack && !isdrag) {
delta_y = delta_y * -1.0f;
}
if (!grabc->next_in_stack && grabc->prev_in_stack && isdrag) {
if (moving_down) {
if (!curnode->next_in_stack && curnode->prev_in_stack && isdrag) {
if (moving_down)
delta_y = -fabsf(delta_y);
} else {
else
delta_y = fabsf(delta_y);
}
}
if (!grabc->prev_in_stack && grabc->next_in_stack && isdrag) {
if (moving_up) {
if (!curnode->prev_in_stack && curnode->next_in_stack && isdrag) {
if (moving_up)
delta_y = -fabsf(delta_y);
} else {
else
delta_y = fabsf(delta_y);
}
}
if (isdrag) {
if (moving_left) {
if (moving_left)
delta_x = -fabsf(delta_x);
} else {
else
delta_x = fabsf(delta_x);
}
}
}
// 直接设置新的比例,基于初始值 + 变化量
if (isvertical) {
new_scroller_proportion =
stack_head->old_scroller_pproportion + delta_y;
headnode->client->old_scroller_pproportion + delta_y;
new_stack_proportion = grabc->old_stack_proportion + delta_x;
} else {
new_scroller_proportion =
stack_head->old_scroller_pproportion + delta_x;
headnode->client->old_scroller_pproportion + delta_x;
new_stack_proportion = grabc->old_stack_proportion + delta_y;
}
// 应用限制,确保比例在合理范围内
new_scroller_proportion =
fmaxf(0.1f, fminf(1.0f, new_scroller_proportion));
new_stack_proportion = fmaxf(0.1f, fminf(0.9f, new_stack_proportion));
grabc->stack_proportion = new_stack_proportion;
curnode->stack_proportion = new_stack_proportion;
headnode->scroller_proportion = new_scroller_proportion;
stack_head->scroller_proportion = new_scroller_proportion;
wl_list_for_each(tc, &clients, link) {
if (!isdrag && new_stack_proportion != 1.0f &&
grabc->old_stack_proportion != 1.0f && tc != grabc &&
ISTILED(tc) && get_scroll_stack_head(tc) == stack_head) {
tc->stack_proportion = (1.0f - new_stack_proportion) /
(1.0f - grabc->old_stack_proportion) *
tc->stack_proportion;
/* 调整同一堆叠内其他节点的 stack_proportion */
/* 调整整个堆叠内除当前窗口外的所有节点 */
if (!isdrag && grabc->old_stack_proportion != 1.0f) {
for (struct ScrollerStackNode *tc = headnode; tc;
tc = tc->next_in_stack) {
if (tc != curnode) {
tc->stack_proportion =
(1.0f - new_stack_proportion) /
(1.0f - grabc->old_stack_proportion) *
tc->stack_proportion;
}
}
}
/* 同步回全局字段*/
sync_scroller_state_to_clients(m, tag);
if (!isdrag) {
arrange(grabc->mon, false, false);
arrange(m, false, false);
return;
}
if (last_apply_drap_time == 0 ||
time - last_apply_drap_time > config.drag_tile_refresh_interval) {
arrange(grabc->mon, false, false);
arrange(m, false, false);
last_apply_drap_time = time;
}
}
@ -834,11 +837,15 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation,
m->visible_tiling_clients = 0;
m->visible_scroll_tiling_clients = 0;
wl_list_for_each(c, &clients, link) {
uint32_t tag = m->pertag->curtag;
struct TagScrollerState *st = m->pertag->scroller_state[tag];
if (!client_only_in_one_tag(c) || c->isglobal || c->isunglobal) {
exit_scroller_stack(c);
}
const Layout *cur_layout = m->pertag->ltidxs[m->pertag->curtag];
if (cur_layout->id == SCROLLER || cur_layout->id == VERTICAL_SCROLLER) {
update_scroller_state(m);
}
wl_list_for_each(c, &clients, link) {
if (from_view && (c->isglobal || c->isunglobal)) {
set_size_per(m, c);
@ -862,10 +869,15 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation,
if (ISTILED(c)) {
m->visible_tiling_clients++;
}
if (ISSCROLLTILED(c) && !c->prev_in_stack) {
m->visible_scroll_tiling_clients++;
/* 更新可见滚动客户端计数 */
if (st) {
struct ScrollerStackNode *n = find_scroller_node(st, c);
if (n && !n->prev_in_stack) /* 是堆叠头部 */
m->visible_scroll_tiling_clients++;
} else if (ISSCROLLTILED(c)) {
m->visible_scroll_tiling_clients++;
}
}
}
}
@ -879,7 +891,6 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation,
if (c->mon == m) {
if (VISIBLEON(c, m)) {
if (ISTILED(c)) {
if (i < nmasters) {
master_num++;
total_master_inner_percent += c->master_inner_per;
@ -898,7 +909,6 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation,
c->stack_inner_per;
}
}
i++;
}

View file

@ -184,325 +184,6 @@ void deck(Monitor *m) {
}
}
void horizontal_scroll_adjust_fullandmax(Client *c,
struct wlr_box *target_geom) {
Monitor *m = c->mon;
int32_t cur_gappih = enablegaps ? m->gappih : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappih;
cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappoh;
cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappov;
if (c->isfullscreen) {
target_geom->height = m->m.height;
target_geom->width = m->m.width;
target_geom->y = m->m.y;
return;
}
if (c->ismaximizescreen) {
target_geom->height = m->w.height - 2 * cur_gappov;
target_geom->width = m->w.width - 2 * cur_gappoh;
target_geom->y = m->w.y + cur_gappov;
return;
}
target_geom->height = m->w.height - 2 * cur_gappov;
target_geom->y = m->w.y + (m->w.height - target_geom->height) / 2;
}
void arrange_stack(Client *scroller_stack_head, struct wlr_box geometry,
int32_t gappiv) {
int32_t stack_size = 0;
Client *iter = scroller_stack_head;
while (iter) {
stack_size++;
iter = iter->next_in_stack;
}
if (stack_size == 0)
return;
float total_proportion = 0.0f;
iter = scroller_stack_head;
while (iter) {
if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f) {
iter->stack_proportion =
stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1);
}
total_proportion += iter->stack_proportion;
iter = iter->next_in_stack;
}
iter = scroller_stack_head;
while (iter) {
iter->stack_proportion = iter->stack_proportion / total_proportion;
iter = iter->next_in_stack;
}
int32_t client_height;
int32_t current_y = geometry.y;
int32_t remain_client_height = geometry.height - (stack_size - 1) * gappiv;
float remain_proportion = 1.0f;
iter = scroller_stack_head;
while (iter) {
client_height =
remain_client_height * (iter->stack_proportion / remain_proportion);
struct wlr_box client_geom = {.x = geometry.x,
.y = current_y,
.width = geometry.width,
.height = client_height};
resize(iter, client_geom, 0);
remain_proportion -= iter->stack_proportion;
remain_client_height -= client_height;
current_y += client_height + gappiv;
iter = iter->next_in_stack;
}
}
void horizontal_check_scroller_root_inside_mon(Client *c,
struct wlr_box *geometry) {
if (!GEOMINSIDEMON(geometry, c->mon)) {
geometry->x = c->mon->w.x + (c->mon->w.width - geometry->width) / 2;
}
}
// 滚动布局
void scroller(Monitor *m) {
int32_t i, n, j;
float single_proportion = 1.0;
Client *c = NULL, *root_client = NULL;
Client **tempClients = NULL; // 初始化为 NULL
struct wlr_box target_geom;
int32_t focus_client_index = 0;
bool need_scroller = false;
bool over_overspread_to_left = false;
int32_t cur_gappih = enablegaps ? m->gappih : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappih;
cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappoh;
cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappov;
int32_t max_client_width =
m->w.width - 2 * config.scroller_structs - cur_gappih;
n = m->visible_scroll_tiling_clients;
if (n == 0) {
return; // 没有需要处理的客户端,直接返回
}
// 动态分配内存
tempClients = malloc(n * sizeof(Client *));
if (!tempClients) {
// 处理内存分配失败的情况
return;
}
// 第二次遍历,填充 tempClients
j = 0;
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, m) && ISSCROLLTILED(c) && !c->prev_in_stack) {
tempClients[j] = c;
j++;
}
}
if (n == 1 && !config.scroller_ignore_proportion_single &&
!tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) {
c = tempClients[0];
single_proportion = c->scroller_proportion_single > 0.0f
? c->scroller_proportion_single
: config.scroller_default_proportion_single;
target_geom.height = m->w.height - 2 * cur_gappov;
target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion;
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
horizontal_check_scroller_root_inside_mon(c, &target_geom);
arrange_stack(c, target_geom, cur_gappiv);
free(tempClients); // 释放内存
return;
}
if (m->sel && !client_is_unmanaged(m->sel) && ISSCROLLTILED(m->sel)) {
root_client = m->sel;
} else if (m->prevsel && ISSCROLLTILED(m->prevsel) &&
VISIBLEON(m->prevsel, m) && !client_is_unmanaged(m->prevsel)) {
root_client = m->prevsel;
} else {
root_client = center_tiled_select(m);
}
// root_client might be in a stack, find the stack head
if (root_client) {
root_client = get_scroll_stack_head(root_client);
}
if (!root_client) {
free(tempClients); // 释放内存
return;
}
for (i = 0; i < n; i++) {
c = tempClients[i];
if (root_client == c) {
if (c->geom.x >= m->w.x + config.scroller_structs &&
c->geom.x + c->geom.width <=
m->w.x + m->w.width - config.scroller_structs) {
need_scroller = false;
} else {
need_scroller = true;
}
focus_client_index = i;
break;
}
}
bool need_apply_overspread =
config.scroller_prefer_overspread &&
m->visible_scroll_tiling_clients > 1 &&
(focus_client_index == 0 || focus_client_index == n - 1) &&
tempClients[focus_client_index]->scroller_proportion < 1.0f;
if (need_apply_overspread) {
if (focus_client_index == 0) {
over_overspread_to_left = true;
} else {
over_overspread_to_left = false;
}
if (over_overspread_to_left &&
(!INSIDEMON(tempClients[1]) ||
(tempClients[1]->scroller_proportion +
tempClients[focus_client_index]->scroller_proportion >=
1.0f))) {
need_scroller = true;
} else if (!over_overspread_to_left &&
(!INSIDEMON(tempClients[focus_client_index - 1]) ||
(tempClients[focus_client_index - 1]->scroller_proportion +
tempClients[focus_client_index]->scroller_proportion >=
1.0f))) {
need_scroller = true;
} else {
need_apply_overspread = false;
}
}
bool need_apply_center =
config.scroller_focus_center || m->visible_scroll_tiling_clients == 1 ||
(config.scroller_prefer_center && !need_apply_overspread &&
(!m->prevsel ||
(ISSCROLLTILED(m->prevsel) &&
(m->prevsel->scroller_proportion * max_client_width) +
(tempClients[focus_client_index]->scroller_proportion *
max_client_width) >
m->w.width - 2 * config.scroller_structs - cur_gappih)));
if (n == 1 && config.scroller_ignore_proportion_single) {
need_scroller = true;
}
if (start_drag_window)
need_scroller = false;
target_geom.height = m->w.height - 2 * cur_gappov;
target_geom.width = max_client_width * c->scroller_proportion;
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
horizontal_scroll_adjust_fullandmax(tempClients[focus_client_index],
&target_geom);
if (tempClients[focus_client_index]->isfullscreen) {
target_geom.x = m->m.x;
horizontal_check_scroller_root_inside_mon(
tempClients[focus_client_index], &target_geom);
arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv);
} else if (tempClients[focus_client_index]->ismaximizescreen) {
target_geom.x = m->w.x + cur_gappoh;
horizontal_check_scroller_root_inside_mon(
tempClients[focus_client_index], &target_geom);
arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv);
} else if (need_scroller) {
if (need_apply_center) {
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
} else if (need_apply_overspread) {
if (over_overspread_to_left) {
target_geom.x = m->w.x + config.scroller_structs;
} else {
target_geom.x =
m->w.x +
(m->w.width -
tempClients[focus_client_index]->scroller_proportion *
max_client_width -
config.scroller_structs);
}
} else {
target_geom.x = tempClients[focus_client_index]->geom.x >
m->w.x + (m->w.width) / 2
? m->w.x + (m->w.width -
tempClients[focus_client_index]
->scroller_proportion *
max_client_width -
config.scroller_structs)
: m->w.x + config.scroller_structs;
}
horizontal_check_scroller_root_inside_mon(
tempClients[focus_client_index], &target_geom);
arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv);
} else {
target_geom.x = c->geom.x;
horizontal_check_scroller_root_inside_mon(
tempClients[focus_client_index], &target_geom);
arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv);
}
for (i = 1; i <= focus_client_index; i++) {
c = tempClients[focus_client_index - i];
target_geom.width = max_client_width * c->scroller_proportion;
horizontal_scroll_adjust_fullandmax(c, &target_geom);
target_geom.x = tempClients[focus_client_index - i + 1]->geom.x -
cur_gappih - target_geom.width;
arrange_stack(c, target_geom, cur_gappiv);
}
for (i = 1; i < n - focus_client_index; i++) {
c = tempClients[focus_client_index + i];
target_geom.width = max_client_width * c->scroller_proportion;
horizontal_scroll_adjust_fullandmax(c, &target_geom);
target_geom.x = tempClients[focus_client_index + i - 1]->geom.x +
cur_gappih +
tempClients[focus_client_index + i - 1]->geom.width;
arrange_stack(c, target_geom, cur_gappiv);
}
free(tempClients); // 最后释放内存
}
void center_tile(Monitor *m) {
int32_t i, n = 0, h, r, ie = enablegaps, mw, mx, my, oty, ety, tw;
Client *c = NULL;

888
src/layout/scroll.h Normal file
View file

@ -0,0 +1,888 @@
/* 获取或创建指定 monitor 某个 tag 的 scroller 状态 */
static struct TagScrollerState *ensure_scroller_state(Monitor *m,
uint32_t tag) {
if (!m->pertag->scroller_state[tag]) {
struct TagScrollerState *st =
calloc(1, sizeof(struct TagScrollerState));
m->pertag->scroller_state[tag] = st;
}
return m->pertag->scroller_state[tag];
}
/* 在 tag 状态中查找客户端对应的节点(无则返回 NULL */
static struct ScrollerStackNode *find_scroller_node(struct TagScrollerState *st,
Client *c) {
if (!st)
return NULL;
for (struct ScrollerStackNode *n = st->all_first; n; n = n->all_next)
if (n->client == c)
return n;
return NULL;
}
/* 创建一个新节点并插入到 tag 状态的 all 链表中 */
static struct ScrollerStackNode *
scroller_node_create(struct TagScrollerState *st, Client *c) {
struct ScrollerStackNode *n = calloc(1, sizeof(*n));
n->client = c;
n->scroller_proportion = c->scroller_proportion;
n->stack_proportion = c->stack_proportion;
n->scroller_proportion_single = c->scroller_proportion_single;
n->next_in_stack = NULL;
n->prev_in_stack = NULL;
n->all_next = st->all_first;
st->all_first = n;
st->count++;
return n;
}
/* 从 tag 状态中移除一个节点并释放 */
static void scroller_node_remove(struct TagScrollerState *st,
struct ScrollerStackNode *target) {
if (!st || !target)
return;
/* 保存邻居 */
struct ScrollerStackNode *prev = target->prev_in_stack;
struct ScrollerStackNode *next = target->next_in_stack;
/* 从堆叠链表中摘除 */
if (prev)
prev->next_in_stack = next;
if (next)
next->prev_in_stack = prev;
/* 从 all 链表摘除 */
struct ScrollerStackNode **indirect = &st->all_first;
while (*indirect && *indirect != target)
indirect = &(*indirect)->all_next;
if (*indirect == target) {
*indirect = target->all_next;
st->count--;
}
free(target);
}
/* 清空一个 tag 的全部 scroller 状态 */
static void clear_scroller_state(struct TagScrollerState *st) {
if (!st)
return;
struct ScrollerStackNode *n = st->all_first;
while (n) {
struct ScrollerStackNode *next = n->all_next;
free(n);
n = next;
}
free(st);
}
/* 在 Monitor 销毁时清理所有 tag 的 scroller 状态 */
static void cleanup_monitor_scroller(Monitor *m) {
for (int t = 0; t < LENGTH(tags) + 1; t++) {
if (m->pertag->scroller_state[t]) {
clear_scroller_state(m->pertag->scroller_state[t]);
m->pertag->scroller_state[t] = NULL;
}
}
}
/* 将某个 tag 的状态同步回所有客户端的全局字段 */
static void sync_scroller_state_to_clients(Monitor *m, uint32_t tag) {
struct TagScrollerState *st = m->pertag->scroller_state[tag];
if (!st)
return;
for (struct ScrollerStackNode *n = st->all_first; n; n = n->all_next) {
Client *c = n->client;
c->scroller_proportion = n->scroller_proportion;
c->stack_proportion = n->stack_proportion;
c->scroller_proportion_single = n->scroller_proportion_single;
}
}
void vertical_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) {
Monitor *m = c->mon;
int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
cur_gappiv = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappiv;
cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappov;
cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappoh;
if (c->isfullscreen) {
target_geom->width = m->m.width;
target_geom->height = m->m.height;
target_geom->x = m->m.x;
return;
}
if (c->ismaximizescreen) {
target_geom->width = m->w.width - 2 * cur_gappoh;
target_geom->height = m->w.height - 2 * cur_gappov;
target_geom->x = m->w.x + cur_gappoh;
return;
}
target_geom->width = m->w.width - 2 * cur_gappoh;
target_geom->x = m->w.x + (m->w.width - target_geom->width) / 2;
}
void vertical_check_scroller_root_inside_mon(Client *c,
struct wlr_box *geometry) {
if (!GEOMINSIDEMON(geometry, c->mon)) {
geometry->y = c->mon->w.y + (c->mon->w.height - geometry->height) / 2;
}
}
void horizontal_scroll_adjust_fullandmax(Client *c,
struct wlr_box *target_geom) {
Monitor *m = c->mon;
int32_t cur_gappih = enablegaps ? m->gappih : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappih;
cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappoh;
cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappov;
if (c->isfullscreen) {
target_geom->height = m->m.height;
target_geom->width = m->m.width;
target_geom->y = m->m.y;
return;
}
if (c->ismaximizescreen) {
target_geom->height = m->w.height - 2 * cur_gappov;
target_geom->width = m->w.width - 2 * cur_gappoh;
target_geom->y = m->w.y + cur_gappov;
return;
}
target_geom->height = m->w.height - 2 * cur_gappov;
target_geom->y = m->w.y + (m->w.height - target_geom->height) / 2;
}
void horizontal_check_scroller_root_inside_mon(Client *c,
struct wlr_box *geometry) {
if (!GEOMINSIDEMON(geometry, c->mon)) {
geometry->x = c->mon->w.x + (c->mon->w.width - geometry->width) / 2;
}
}
void arrange_stack_node(struct ScrollerStackNode *head, struct wlr_box geometry,
int32_t gappiv) {
int32_t stack_size = 0;
struct ScrollerStackNode *iter = head;
while (iter) {
stack_size++;
iter = iter->next_in_stack;
}
if (stack_size == 0)
return;
/* 归一化比例 */
float total_proportion = 0.0f;
iter = head;
while (iter) {
if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f)
iter->stack_proportion =
stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1);
total_proportion += iter->stack_proportion;
iter = iter->next_in_stack;
}
iter = head;
while (iter) {
iter->stack_proportion /= total_proportion;
iter = iter->next_in_stack;
}
/* 竖向排列(水平堆叠) */
int32_t client_height;
int32_t current_y = geometry.y;
int32_t remain_client_height = geometry.height - (stack_size - 1) * gappiv;
float remain_proportion = 1.0f;
iter = head;
while (iter) {
client_height =
remain_client_height * (iter->stack_proportion / remain_proportion);
struct wlr_box client_geom = {.x = geometry.x,
.y = current_y,
.width = geometry.width,
.height = client_height};
resize(iter->client, client_geom, 0);
remain_proportion -= iter->stack_proportion;
remain_client_height -= client_height;
current_y += client_height + gappiv;
iter = iter->next_in_stack;
}
}
void arrange_stack_vertical_node(struct ScrollerStackNode *head,
struct wlr_box geometry, int32_t gappih) {
int32_t stack_size = 0;
struct ScrollerStackNode *iter = head;
while (iter) {
stack_size++;
iter = iter->next_in_stack;
}
if (stack_size == 0)
return;
/* 归一化比例 */
float total_proportion = 0.0f;
iter = head;
while (iter) {
if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f)
iter->stack_proportion =
stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1);
total_proportion += iter->stack_proportion;
iter = iter->next_in_stack;
}
iter = head;
while (iter) {
iter->stack_proportion /= total_proportion;
iter = iter->next_in_stack;
}
/* 横向排列(垂直堆叠) */
int32_t client_width;
int32_t current_x = geometry.x;
int32_t remain_client_width = geometry.width - (stack_size - 1) * gappih;
float remain_proportion = 1.0f;
iter = head;
while (iter) {
client_width =
remain_client_width * (iter->stack_proportion / remain_proportion);
struct wlr_box client_geom = {.y = geometry.y,
.x = current_x,
.height = geometry.height,
.width = client_width};
resize(iter->client, client_geom, 0);
remain_proportion -= iter->stack_proportion;
remain_client_width -= client_width;
current_x += client_width + gappih;
iter = iter->next_in_stack;
}
}
void scroller(Monitor *m) {
uint32_t tag = m->pertag->curtag;
struct TagScrollerState *st = ensure_scroller_state(m, tag);
Client *c = NULL;
/* 按全局客户端链表顺序收集所有堆叠头,确保视觉顺序正确 */
struct ScrollerStackNode *heads[64];
int32_t n_heads = 0;
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, m) && ISSCROLLTILED(c)) {
struct ScrollerStackNode *node = find_scroller_node(st, c);
if (node && !node->prev_in_stack) {
bool already = false;
for (int k = 0; k < n_heads; k++) {
if (heads[k] == node) {
already = true;
break;
}
}
if (!already)
heads[n_heads++] = node;
}
}
}
if (n_heads == 0) {
sync_scroller_state_to_clients(m, tag);
return;
}
m->visible_scroll_tiling_clients = n_heads;
int32_t cur_gappih = enablegaps ? m->gappih : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
if (config.smartgaps && n_heads == 1) {
cur_gappih = cur_gappoh = cur_gappov = 0;
}
int32_t max_client_width =
m->w.width - 2 * config.scroller_structs - cur_gappih;
/* 单客户端特例 */
if (n_heads == 1 && !config.scroller_ignore_proportion_single &&
!heads[0]->client->isfullscreen &&
!heads[0]->client->ismaximizescreen) {
struct ScrollerStackNode *head = heads[0];
float single_proportion =
head->scroller_proportion_single > 0.0f
? head->scroller_proportion_single
: config.scroller_default_proportion_single;
struct wlr_box target_geom;
target_geom.height = m->w.height - 2 * cur_gappov;
target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion;
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
horizontal_check_scroller_root_inside_mon(head->client, &target_geom);
arrange_stack_node(head, target_geom, cur_gappiv);
sync_scroller_state_to_clients(m, tag);
return;
}
struct ScrollerStackNode *root_node = NULL;
if (m->sel && ISSCROLLTILED(m->sel)) {
root_node = find_scroller_node(st, m->sel);
if (root_node) {
while (root_node->prev_in_stack)
root_node = root_node->prev_in_stack;
}
}
if (!root_node && m->prevsel && ISSCROLLTILED(m->prevsel)) {
root_node = find_scroller_node(st, m->prevsel);
if (root_node) {
while (root_node->prev_in_stack)
root_node = root_node->prev_in_stack;
}
}
if (!root_node)
root_node = heads[n_heads / 2]; /* 简单回退 */
int32_t focus_index = -1;
for (int i = 0; i < n_heads; i++) {
if (heads[i] == root_node) {
focus_index = i;
break;
}
}
if (focus_index < 0)
focus_index = n_heads / 2;
/* 判断是否需要滚动、overspread、center */
bool need_scroller = false;
bool over_overspread_to_left = false;
Client *root_client = root_node->client;
if (root_client->geom.x >= m->w.x + config.scroller_structs &&
root_client->geom.x + root_client->geom.width <=
m->w.x + m->w.width - config.scroller_structs) {
need_scroller = false;
} else {
need_scroller = true;
}
bool need_apply_overspread =
config.scroller_prefer_overspread && n_heads > 1 &&
(focus_index == 0 || focus_index == n_heads - 1) &&
heads[focus_index]->scroller_proportion < 1.0f;
if (need_apply_overspread) {
if (focus_index == 0) {
over_overspread_to_left = true;
} else {
over_overspread_to_left = false;
}
if (over_overspread_to_left &&
(!INSIDEMON(heads[1]->client) ||
(heads[1]->scroller_proportion + heads[0]->scroller_proportion >=
1.0f))) {
need_scroller = true;
} else if (!over_overspread_to_left &&
(!INSIDEMON(heads[focus_index - 1]->client) ||
(heads[focus_index - 1]->scroller_proportion +
heads[focus_index]->scroller_proportion >=
1.0f))) {
need_scroller = true;
} else {
need_apply_overspread = false;
}
}
bool need_apply_center =
config.scroller_focus_center || n_heads == 1 ||
(config.scroller_prefer_center && !need_apply_overspread &&
(!m->prevsel ||
(ISSCROLLTILED(m->prevsel) &&
(m->prevsel->scroller_proportion * max_client_width) +
(heads[focus_index]->scroller_proportion *
max_client_width) >
m->w.width - 2 * config.scroller_structs - cur_gappih)));
if (n_heads == 1 && config.scroller_ignore_proportion_single) {
need_scroller = true;
}
if (start_drag_window)
need_scroller = false;
struct wlr_box target_geom;
target_geom.height = m->w.height - 2 * cur_gappov;
target_geom.width =
max_client_width * heads[focus_index]->scroller_proportion;
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
horizontal_scroll_adjust_fullandmax(heads[focus_index]->client,
&target_geom);
if (heads[focus_index]->client->isfullscreen) {
target_geom.x = m->m.x;
horizontal_check_scroller_root_inside_mon(heads[focus_index]->client,
&target_geom);
arrange_stack_node(heads[focus_index], target_geom, cur_gappiv);
} else if (heads[focus_index]->client->ismaximizescreen) {
target_geom.x = m->w.x + cur_gappoh;
horizontal_check_scroller_root_inside_mon(heads[focus_index]->client,
&target_geom);
arrange_stack_node(heads[focus_index], target_geom, cur_gappiv);
} else if (need_scroller) {
if (need_apply_center) {
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
} else if (need_apply_overspread) {
if (over_overspread_to_left) {
target_geom.x = m->w.x + config.scroller_structs;
} else {
target_geom.x =
m->w.x + (m->w.width -
heads[focus_index]->scroller_proportion *
max_client_width -
config.scroller_structs);
}
} else {
target_geom.x =
root_client->geom.x > m->w.x + (m->w.width) / 2
? m->w.x + (m->w.width -
heads[focus_index]->scroller_proportion *
max_client_width -
config.scroller_structs)
: m->w.x + config.scroller_structs;
}
horizontal_check_scroller_root_inside_mon(heads[focus_index]->client,
&target_geom);
arrange_stack_node(heads[focus_index], target_geom, cur_gappiv);
} else {
target_geom.x = root_client->geom.x;
horizontal_check_scroller_root_inside_mon(heads[focus_index]->client,
&target_geom);
arrange_stack_node(heads[focus_index], target_geom, cur_gappiv);
}
/* 排列左侧的堆叠 */
for (int i = 1; i <= focus_index; i++) {
struct ScrollerStackNode *cur = heads[focus_index - i];
struct wlr_box left_geom;
left_geom.height = m->w.height - 2 * cur_gappov;
left_geom.width = max_client_width * cur->scroller_proportion;
horizontal_scroll_adjust_fullandmax(cur->client, &left_geom);
left_geom.x = heads[focus_index - i + 1]->client->geom.x - cur_gappih -
left_geom.width;
arrange_stack_node(cur, left_geom, cur_gappiv);
}
/* 排列右侧的堆叠 */
for (int i = 1; i < n_heads - focus_index; i++) {
struct ScrollerStackNode *cur = heads[focus_index + i];
struct wlr_box right_geom;
right_geom.height = m->w.height - 2 * cur_gappov;
right_geom.width = max_client_width * cur->scroller_proportion;
horizontal_scroll_adjust_fullandmax(cur->client, &right_geom);
right_geom.x = heads[focus_index + i - 1]->client->geom.x + cur_gappih +
heads[focus_index + i - 1]->client->geom.width;
arrange_stack_node(cur, right_geom, cur_gappiv);
}
sync_scroller_state_to_clients(m, tag);
}
void vertical_scroller(Monitor *m) {
uint32_t tag = m->pertag->curtag;
struct TagScrollerState *st = ensure_scroller_state(m, tag);
Client *c = NULL;
/* 按全局顺序收集堆叠头 */
struct ScrollerStackNode *heads[64];
int32_t n_heads = 0;
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, m) && ISSCROLLTILED(c)) {
struct ScrollerStackNode *node = find_scroller_node(st, c);
if (node && !node->prev_in_stack) {
bool already = false;
for (int k = 0; k < n_heads; k++)
if (heads[k] == node)
already = true;
if (!already)
heads[n_heads++] = node;
}
}
}
if (n_heads == 0) {
sync_scroller_state_to_clients(m, tag);
return;
}
m->visible_scroll_tiling_clients = n_heads;
int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
int32_t cur_gappih = enablegaps ? m->gappih : 0;
if (config.smartgaps && n_heads == 1) {
cur_gappiv = cur_gappov = cur_gappoh = 0;
}
int32_t max_client_height =
m->w.height - 2 * config.scroller_structs - cur_gappiv;
if (n_heads == 1 && !config.scroller_ignore_proportion_single &&
!heads[0]->client->isfullscreen &&
!heads[0]->client->ismaximizescreen) {
struct ScrollerStackNode *head = heads[0];
float single_proportion =
head->scroller_proportion_single > 0.0f
? head->scroller_proportion_single
: config.scroller_default_proportion_single;
struct wlr_box target_geom;
target_geom.width = m->w.width - 2 * cur_gappoh;
target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion;
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
vertical_check_scroller_root_inside_mon(head->client, &target_geom);
arrange_stack_vertical_node(head, target_geom, cur_gappih);
sync_scroller_state_to_clients(m, tag);
return;
}
struct ScrollerStackNode *root_node = NULL;
if (m->sel && ISSCROLLTILED(m->sel)) {
root_node = find_scroller_node(st, m->sel);
if (root_node) {
while (root_node->prev_in_stack)
root_node = root_node->prev_in_stack;
}
}
if (!root_node && m->prevsel && ISSCROLLTILED(m->prevsel)) {
root_node = find_scroller_node(st, m->prevsel);
if (root_node) {
while (root_node->prev_in_stack)
root_node = root_node->prev_in_stack;
}
}
if (!root_node)
root_node = heads[n_heads / 2];
int32_t focus_index = -1;
for (int i = 0; i < n_heads; i++) {
if (heads[i] == root_node) {
focus_index = i;
break;
}
}
if (focus_index < 0)
focus_index = n_heads / 2;
bool need_scroller = false;
bool over_overspread_to_up = false;
Client *root_client = root_node->client;
if (root_client->geom.y >= m->w.y + config.scroller_structs &&
root_client->geom.y + root_client->geom.height <=
m->w.y + m->w.height - config.scroller_structs) {
need_scroller = false;
} else {
need_scroller = true;
}
bool need_apply_overspread =
config.scroller_prefer_overspread && n_heads > 1 &&
(focus_index == 0 || focus_index == n_heads - 1) &&
heads[focus_index]->scroller_proportion < 1.0f;
if (need_apply_overspread) {
if (focus_index == 0) {
over_overspread_to_up = true;
} else {
over_overspread_to_up = false;
}
if (over_overspread_to_up &&
(!INSIDEMON(heads[1]->client) ||
(heads[1]->scroller_proportion + heads[0]->scroller_proportion >=
1.0f))) {
need_scroller = true;
} else if (!over_overspread_to_up &&
(!INSIDEMON(heads[focus_index - 1]->client) ||
(heads[focus_index - 1]->scroller_proportion +
heads[focus_index]->scroller_proportion >=
1.0f))) {
need_scroller = true;
} else {
need_apply_overspread = false;
}
}
bool need_apply_center =
config.scroller_focus_center || n_heads == 1 ||
(config.scroller_prefer_center && !need_apply_overspread &&
(!m->prevsel ||
(ISSCROLLTILED(m->prevsel) &&
(m->prevsel->scroller_proportion * max_client_height) +
(heads[focus_index]->scroller_proportion *
max_client_height) >
m->w.height - 2 * config.scroller_structs - cur_gappiv)));
if (n_heads == 1 && config.scroller_ignore_proportion_single) {
need_scroller = true;
}
if (start_drag_window)
need_scroller = false;
struct wlr_box target_geom;
target_geom.width = m->w.width - 2 * cur_gappoh;
target_geom.height =
max_client_height * heads[focus_index]->scroller_proportion;
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
vertical_scroll_adjust_fullandmax(heads[focus_index]->client, &target_geom);
if (heads[focus_index]->client->isfullscreen) {
target_geom.y = m->m.y;
vertical_check_scroller_root_inside_mon(heads[focus_index]->client,
&target_geom);
arrange_stack_vertical_node(heads[focus_index], target_geom,
cur_gappih);
} else if (heads[focus_index]->client->ismaximizescreen) {
target_geom.y = m->w.y + cur_gappov;
vertical_check_scroller_root_inside_mon(heads[focus_index]->client,
&target_geom);
arrange_stack_vertical_node(heads[focus_index], target_geom,
cur_gappih);
} else if (need_scroller) {
if (need_apply_center) {
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
} else if (need_apply_overspread) {
if (over_overspread_to_up) {
target_geom.y = m->w.y + config.scroller_structs;
} else {
target_geom.y =
m->w.y + (m->w.height -
heads[focus_index]->scroller_proportion *
max_client_height -
config.scroller_structs);
}
} else {
target_geom.y =
root_client->geom.y > m->w.y + (m->w.height) / 2
? m->w.y + (m->w.height -
heads[focus_index]->scroller_proportion *
max_client_height -
config.scroller_structs)
: m->w.y + config.scroller_structs;
}
vertical_check_scroller_root_inside_mon(heads[focus_index]->client,
&target_geom);
arrange_stack_vertical_node(heads[focus_index], target_geom,
cur_gappih);
} else {
target_geom.y = root_client->geom.y;
vertical_check_scroller_root_inside_mon(heads[focus_index]->client,
&target_geom);
arrange_stack_vertical_node(heads[focus_index], target_geom,
cur_gappih);
}
for (int i = 1; i <= focus_index; i++) {
struct ScrollerStackNode *cur = heads[focus_index - i];
struct wlr_box up_geom;
up_geom.width = m->w.width - 2 * cur_gappoh;
up_geom.height = max_client_height * cur->scroller_proportion;
vertical_scroll_adjust_fullandmax(cur->client, &up_geom);
up_geom.y = heads[focus_index - i + 1]->client->geom.y - cur_gappiv -
up_geom.height;
arrange_stack_vertical_node(cur, up_geom, cur_gappih);
}
for (int i = 1; i < n_heads - focus_index; i++) {
struct ScrollerStackNode *cur = heads[focus_index + i];
struct wlr_box down_geom;
down_geom.width = m->w.width - 2 * cur_gappoh;
down_geom.height = max_client_height * cur->scroller_proportion;
vertical_scroll_adjust_fullandmax(cur->client, &down_geom);
down_geom.y = heads[focus_index + i - 1]->client->geom.y + cur_gappiv +
heads[focus_index + i - 1]->client->geom.height;
arrange_stack_vertical_node(cur, down_geom, cur_gappih);
}
sync_scroller_state_to_clients(m, tag);
}
void scroller_remove_client(Client *c) {
Monitor *m;
wl_list_for_each(m, &mons, link) {
for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) {
struct TagScrollerState *st = m->pertag->scroller_state[t];
if (!st)
continue;
struct ScrollerStackNode *node = find_scroller_node(st, c);
if (node) {
scroller_node_remove(st, node);
}
}
}
}
void scroller_insert_stack(Client *c, Client *target_client,
bool insert_before) {
if (!target_client || target_client->mon != c->mon)
return;
if (c->isfullscreen)
setfullscreen(c, 0);
if (c->ismaximizescreen)
setmaximizescreen(c, 0);
Monitor *m = c->mon;
uint32_t tag = m->pertag->curtag;
struct TagScrollerState *st = ensure_scroller_state(m, tag);
struct ScrollerStackNode *cnode = find_scroller_node(st, c);
if (cnode)
scroller_node_remove(st, cnode);
struct ScrollerStackNode *tnode = find_scroller_node(st, target_client);
if (!tnode)
tnode = scroller_node_create(st, target_client);
struct ScrollerStackNode *newnode = scroller_node_create(st, c);
/* 将新节点插入到 tnode 的前面或后面 */
if (insert_before) {
newnode->next_in_stack = tnode;
newnode->prev_in_stack = tnode->prev_in_stack;
if (tnode->prev_in_stack)
tnode->prev_in_stack->next_in_stack = newnode;
tnode->prev_in_stack = newnode;
} else {
newnode->prev_in_stack = tnode;
newnode->next_in_stack = tnode->next_in_stack;
if (tnode->next_in_stack)
tnode->next_in_stack->prev_in_stack = newnode;
tnode->next_in_stack = newnode;
}
/* 处理堆叠头部的全屏/最大化状态*/
struct ScrollerStackNode *head = tnode;
while (head->prev_in_stack)
head = head->prev_in_stack;
Client *stack_head = head->client;
if (stack_head->ismaximizescreen)
setmaximizescreen(stack_head, 0);
if (stack_head->isfullscreen)
setfullscreen(stack_head, 0);
/* 同步到 Client 字段 */
sync_scroller_state_to_clients(m, tag);
arrange(m, false, false);
}
void scroller_drop_tile(Client *c, Client *closest, int vertical) {
Client *stack_head = scroll_get_stack_head_client(closest);
if (vertical) {
if (closest->drop_direction == LEFT) {
setfloating(c, 0);
scroller_insert_stack(c, closest, true);
return;
} else if (closest->drop_direction == RIGHT) {
setfloating(c, 0);
scroller_insert_stack(c, closest, false);
return;
} else if (closest->drop_direction == UP) {
wl_list_remove(&c->link);
wl_list_insert(stack_head->link.prev, &c->link);
} else if (closest->drop_direction == DOWN) {
wl_list_remove(&c->link);
wl_list_insert(&stack_head->link, &c->link);
}
} else {
if (closest->drop_direction == UP) {
setfloating(c, 0);
scroller_insert_stack(c, closest, true);
return;
} else if (closest->drop_direction == DOWN) {
setfloating(c, 0);
scroller_insert_stack(c, closest, false);
return;
} else if (closest->drop_direction == LEFT) {
wl_list_remove(&c->link);
wl_list_insert(stack_head->link.prev, &c->link);
} else if (closest->drop_direction == RIGHT) {
wl_list_remove(&c->link);
wl_list_insert(&stack_head->link, &c->link);
}
}
setfloating(c, 0);
}
Client *scroll_get_stack_head_client(Client *c) {
if (!c || !c->mon)
return c;
uint32_t tag = c->mon->pertag->curtag;
struct TagScrollerState *st = c->mon->pertag->scroller_state[tag];
if (st) {
struct ScrollerStackNode *n = find_scroller_node(st, c);
if (n) {
while (n->prev_in_stack)
n = n->prev_in_stack;
return n->client;
}
}
return c;
}
static void update_scroller_state(Monitor *m) {
uint32_t tag = m->pertag->curtag;
struct TagScrollerState *st = ensure_scroller_state(m, tag);
/* 收集当前可见的所有 scroller 平铺窗口 */
Client *vis[512];
int32_t count = 0;
Client *c;
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, m) && ISSCROLLTILED(c))
vis[count++] = c;
if (count == 512)
break;
}
/* 移除不再可见的节点 */
struct ScrollerStackNode *n = st->all_first;
while (n) {
bool found = false;
for (int i = 0; i < count; i++) {
if (vis[i] == n->client) {
found = true;
break;
}
}
struct ScrollerStackNode *next = n->all_next;
if (!found)
scroller_node_remove(st, n);
n = next;
}
/* 为新的可见窗口创建节点 */
for (int i = 0; i < count; i++) {
if (!find_scroller_node(st, vis[i])) {
scroller_node_create(st, vis[i]);
}
}
}

View file

@ -176,324 +176,6 @@ void vertical_deck(Monitor *m) {
}
}
void vertical_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) {
Monitor *m = c->mon;
int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
cur_gappiv = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappiv;
cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappov;
cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappoh;
if (c->isfullscreen) {
target_geom->width = m->m.width;
target_geom->height = m->m.height;
target_geom->x = m->m.x;
return;
}
if (c->ismaximizescreen) {
target_geom->width = m->w.width - 2 * cur_gappoh;
target_geom->height = m->w.height - 2 * cur_gappov;
target_geom->x = m->w.x + cur_gappoh;
return;
}
target_geom->width = m->w.width - 2 * cur_gappoh;
target_geom->x = m->w.x + (m->w.width - target_geom->width) / 2;
}
void arrange_stack_vertical(Client *scroller_stack_head,
struct wlr_box geometry, int32_t gappih) {
int32_t stack_size = 0;
Client *iter = scroller_stack_head;
while (iter) {
stack_size++;
iter = iter->next_in_stack;
}
if (stack_size == 0)
return;
float total_proportion = 0.0f;
iter = scroller_stack_head;
while (iter) {
if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f) {
iter->stack_proportion =
stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1);
}
total_proportion += iter->stack_proportion;
iter = iter->next_in_stack;
}
iter = scroller_stack_head;
while (iter) {
iter->stack_proportion = iter->stack_proportion / total_proportion;
iter = iter->next_in_stack;
}
int32_t client_width;
int32_t current_x = geometry.x;
int32_t remain_client_width = geometry.width - (stack_size - 1) * gappih;
float remain_proportion = 1.0f;
iter = scroller_stack_head;
while (iter) {
client_width =
remain_client_width * (iter->stack_proportion / remain_proportion);
struct wlr_box client_geom = {.y = geometry.y,
.x = current_x,
.height = geometry.height,
.width = client_width};
resize(iter, client_geom, 0);
remain_proportion -= iter->stack_proportion;
remain_client_width -= client_width;
current_x += client_width + gappih;
iter = iter->next_in_stack;
}
}
void vertical_check_scroller_root_inside_mon(Client *c,
struct wlr_box *geometry) {
if (!GEOMINSIDEMON(geometry, c->mon)) {
geometry->y = c->mon->w.y + (c->mon->w.height - geometry->height) / 2;
}
}
// 竖屏滚动布局
void vertical_scroller(Monitor *m) {
int32_t i, n, j;
float single_proportion = 1.0;
Client *c = NULL, *root_client = NULL;
Client **tempClients = NULL;
struct wlr_box target_geom;
int32_t focus_client_index = 0;
bool need_scroller = false;
bool over_overspread_to_up = false;
int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
int32_t cur_gappih = enablegaps ? m->gappih : 0;
cur_gappiv = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappiv;
cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappov;
cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1
? 0
: cur_gappoh;
int32_t max_client_height =
m->w.height - 2 * config.scroller_structs - cur_gappiv;
n = m->visible_scroll_tiling_clients;
if (n == 0) {
return;
}
tempClients = malloc(n * sizeof(Client *));
if (!tempClients) {
return;
}
j = 0;
wl_list_for_each(c, &clients, link) {
if (VISIBLEON(c, m) && ISSCROLLTILED(c) && !c->prev_in_stack) {
tempClients[j] = c;
j++;
}
}
if (n == 1 && !config.scroller_ignore_proportion_single &&
!tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) {
c = tempClients[0];
single_proportion = c->scroller_proportion_single > 0.0f
? c->scroller_proportion_single
: config.scroller_default_proportion_single;
target_geom.width = m->w.width - 2 * cur_gappoh;
target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion;
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
vertical_check_scroller_root_inside_mon(c, &target_geom);
arrange_stack_vertical(c, target_geom, cur_gappih);
free(tempClients);
return;
}
if (m->sel && !client_is_unmanaged(m->sel) && ISSCROLLTILED(m->sel)) {
root_client = m->sel;
} else if (m->prevsel && ISSCROLLTILED(m->prevsel) &&
VISIBLEON(m->prevsel, m) && !client_is_unmanaged(m->prevsel)) {
root_client = m->prevsel;
} else {
root_client = center_tiled_select(m);
}
// root_client might be in a stack, find the stack head
if (root_client) {
root_client = get_scroll_stack_head(root_client);
}
if (!root_client) {
free(tempClients);
return;
}
for (i = 0; i < n; i++) {
c = tempClients[i];
if (root_client == c) {
if (c->geom.y >= m->w.y + config.scroller_structs &&
c->geom.y + c->geom.height <=
m->w.y + m->w.height - config.scroller_structs) {
need_scroller = false;
} else {
need_scroller = true;
}
focus_client_index = i;
break;
}
}
bool need_apply_overspread =
config.scroller_prefer_overspread &&
m->visible_scroll_tiling_clients > 1 &&
(focus_client_index == 0 || focus_client_index == n - 1) &&
tempClients[focus_client_index]->scroller_proportion < 1.0f;
if (need_apply_overspread) {
if (focus_client_index == 0) {
over_overspread_to_up = true;
} else {
over_overspread_to_up = false;
}
if (over_overspread_to_up &&
(!INSIDEMON(tempClients[1]) ||
(tempClients[1]->scroller_proportion +
tempClients[focus_client_index]->scroller_proportion >=
1.0f))) {
need_scroller = true;
} else if (!over_overspread_to_up &&
(!INSIDEMON(tempClients[focus_client_index - 1]) ||
(tempClients[focus_client_index - 1]->scroller_proportion +
tempClients[focus_client_index]->scroller_proportion >=
1.0f))) {
need_scroller = true;
} else {
need_apply_overspread = false;
}
}
bool need_apply_center =
config.scroller_focus_center || m->visible_scroll_tiling_clients == 1 ||
(config.scroller_prefer_center && !need_apply_overspread &&
(!m->prevsel ||
(ISSCROLLTILED(m->prevsel) &&
(m->prevsel->scroller_proportion * max_client_height) +
(tempClients[focus_client_index]->scroller_proportion *
max_client_height) >
m->w.height - 2 * config.scroller_structs - cur_gappiv)));
if (n == 1 && config.scroller_ignore_proportion_single) {
need_scroller = true;
}
if (start_drag_window)
need_scroller = false;
target_geom.width = m->w.width - 2 * cur_gappoh;
target_geom.height = max_client_height * c->scroller_proportion;
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
vertical_scroll_adjust_fullandmax(tempClients[focus_client_index],
&target_geom);
if (tempClients[focus_client_index]->isfullscreen) {
target_geom.y = m->m.y;
vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
&target_geom);
arrange_stack_vertical(tempClients[focus_client_index], target_geom,
cur_gappih);
} else if (tempClients[focus_client_index]->ismaximizescreen) {
target_geom.y = m->w.y + cur_gappov;
vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
&target_geom);
arrange_stack_vertical(tempClients[focus_client_index], target_geom,
cur_gappih);
} else if (need_scroller) {
if (need_apply_center) {
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
} else if (need_apply_overspread) {
if (over_overspread_to_up) {
target_geom.y = m->w.y + config.scroller_structs;
} else {
target_geom.y =
m->w.y +
(m->w.height -
tempClients[focus_client_index]->scroller_proportion *
max_client_height -
config.scroller_structs);
}
} else {
target_geom.y = root_client->geom.y > m->w.y + (m->w.height) / 2
? m->w.y + (m->w.height -
tempClients[focus_client_index]
->scroller_proportion *
max_client_height -
config.scroller_structs)
: m->w.y + config.scroller_structs;
}
vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
&target_geom);
arrange_stack_vertical(tempClients[focus_client_index], target_geom,
cur_gappih);
} else {
target_geom.y = c->geom.y;
vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
&target_geom);
arrange_stack_vertical(tempClients[focus_client_index], target_geom,
cur_gappih);
}
for (i = 1; i <= focus_client_index; i++) {
c = tempClients[focus_client_index - i];
target_geom.height = max_client_height * c->scroller_proportion;
vertical_scroll_adjust_fullandmax(c, &target_geom);
target_geom.y = tempClients[focus_client_index - i + 1]->geom.y -
cur_gappiv - target_geom.height;
arrange_stack_vertical(c, target_geom, cur_gappih);
}
for (i = 1; i < n - focus_client_index; i++) {
c = tempClients[focus_client_index + i];
target_geom.height = max_client_height * c->scroller_proportion;
vertical_scroll_adjust_fullandmax(c, &target_geom);
target_geom.y = tempClients[focus_client_index + i - 1]->geom.y +
cur_gappiv +
tempClients[focus_client_index + i - 1]->geom.height;
arrange_stack_vertical(c, target_geom, cur_gappih);
}
free(tempClients);
}
void vertical_grid(Monitor *m) {
int32_t i, n;
int32_t cx, cy, cw, ch;