diff --git a/src/animation/client.h b/src/animation/client.h index 3e5004b4..af6764ab 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -58,6 +58,14 @@ int32_t is_special_animation_rule(Client *c) { } } +void set_overview_enter_animation(Client *c) { + struct wlr_box geo = c->geom; + c->animainit_geom.width = geo.width * 1.2; + c->animainit_geom.height = geo.height * 1.2; + c->animainit_geom.x = geo.x + (geo.width - c->animainit_geom.width) / 2; + c->animainit_geom.y = geo.y + (geo.height - c->animainit_geom.height) / 2; +} + void set_client_open_animation(Client *c, struct wlr_box geo) { int32_t slide_direction; int32_t horizontal, horizontal_value; @@ -213,6 +221,27 @@ void scene_buffer_apply_effect(struct wlr_scene_buffer *buffer, int32_t sx, return; } +void scene_buffer_apply_overview_effect(struct wlr_scene_buffer *buffer, + int32_t sx, int32_t sy, void *data) { + BufferData *buffer_data = (BufferData *)data; + + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(buffer); + + if (scene_surface == NULL) + return; + + struct wlr_surface *surface = scene_surface->surface; + + if (buffer_data->width > 0 && buffer_data->height > 0) { + wlr_scene_buffer_set_dest_size(buffer, buffer_data->width, + buffer_data->height); + } + + if (wlr_xdg_popup_try_from_wlr_surface(surface) != NULL) + return; +} + void buffer_set_effect(Client *c, BufferData data) { if (!c || c->iskilling) @@ -226,8 +255,13 @@ void buffer_set_effect(Client *c, BufferData data) { if (c == grabc) data.should_scale = false; - wlr_scene_node_for_each_buffer(&c->scene_surface->node, - scene_buffer_apply_effect, &data); + if (c->mon->isoverview) { + wlr_scene_node_for_each_buffer( + &c->scene_surface->node, scene_buffer_apply_overview_effect, &data); + } else { + wlr_scene_node_for_each_buffer(&c->scene_surface->node, + scene_buffer_apply_effect, &data); + } } void apply_shield(Client *c, struct wlr_box clip_box) { @@ -727,7 +761,18 @@ void client_apply_clip(Client *c, float factor) { // 应用窗口表面剪切 apply_shield(c, clip_box); - wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); + if (!c->mon->isoverview) { + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); + } else { + struct wlr_box ov_surface_box = c->geom; + ov_surface_box.x = 0; + ov_surface_box.y = 0; + ov_surface_box.width = c->mon->m.width - 2 * c->bw; + ov_surface_box.height = c->mon->m.height - 2 * c->bw; + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, + &ov_surface_box); + wlr_scene_node_set_enabled(&c->scene_surface->node, true); + } // 获取剪切后的表面的实际大小用于计算缩放 int32_t acutal_surface_width = geometry.width - offset.x - offset.width; @@ -837,6 +882,10 @@ void client_animation_next_tick(Client *c) { ? (double)passed_time / (double)c->animation.duration : 1.0; + if (c->mon->isoverview && animation_passed >= 1.0) { + animation_passed = 1.0; + } + int32_t type = c->animation.action == NONE ? MOVE : c->animation.action; double factor = find_animation_curve_at(animation_passed, type); @@ -866,7 +915,10 @@ void client_animation_next_tick(Client *c) { client_apply_clip(c, factor); - if (animation_passed >= 1.0) { + if (c->mon->isoverview && animation_passed >= 1.0) { + c->animation.overining = false; + return; + } else if (animation_passed >= 1.0) { // clear the open action state // To prevent him from being mistaken that @@ -1093,8 +1145,11 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { c->animation.begin_fade_in = false; } - if (c->animation.action == OPEN && !c->animation.tagining && - !c->animation.tagouting && wlr_box_equal(&c->geom, &c->current)) { + if (c->animation.overining) { + c->animation.action = OVERVIEW; + } else if (c->animation.action == OPEN && !c->animation.tagining && + !c->animation.tagouting && + wlr_box_equal(&c->geom, &c->current)) { c->animation.action = c->animation.action; } else if (c->animation.tagouting) { c->animation.duration = config.animation_duration_tag; @@ -1133,8 +1188,9 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { } // c->geom 是真实的窗口大小和位置,跟过度的动画无关,用于计算布局 - c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw, - c->geom.height - 2 * c->bw); + if (!c->mon->isoverview) + c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw, + c->geom.height - 2 * c->bw); if (c->configure_serial != 0) { c->mon->resizing_count_pending++; @@ -1177,6 +1233,11 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { c->animainit_geom = c->geom; } + if (c->mon->isoverview && c != c->mon->sel && + c->animation.action == OVERVIEW) { + set_overview_enter_animation(c); + } + // 开始应用动画设置 client_set_pending_state(c); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index e5034435..7eea2a39 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1744,8 +1744,10 @@ int32_t toggleoverview(const Arg *arg) { wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !client_is_unmanaged(c) && - !client_is_x11_popup(c) && !c->isunglobal) + !client_is_x11_popup(c) && !c->isunglobal) { + c->animation.overining = true; overview_backup(c); + } } } else { wl_list_for_each(c, &clients, link) { diff --git a/src/fetch/common.h b/src/fetch/common.h index 57a1a8e6..7704c6e1 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -77,6 +77,19 @@ void get_layout_abbr(char *abbr, const char *full_name) { } } +Client *xytoclient(double x, double y) { + Client *c = NULL, *tmp = NULL; + wl_list_for_each_safe(c, tmp, &clients, link) { + if (VISIBLEON(c, c->mon) && c->animation.current.x <= x && + c->animation.current.y <= y && + c->animation.current.x + c->animation.current.width >= x && + c->animation.current.y + c->animation.current.height >= y) { + return c; + } + } + return NULL; +} + void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, LayerSurface **pl, double *nx, double *ny) { struct wlr_scene_node *node, *pnode; @@ -84,6 +97,7 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, Client *c = NULL; LayerSurface *l = NULL; int32_t layer; + Client *ovc = NULL; for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { @@ -130,4 +144,12 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, *pc = c; if (pl) *pl = l; + + if (selmon && selmon->isoverview && !l) { + ovc = xytoclient(x, y); + if (pc) + *pc = ovc; + if (psurface && ovc) + *psurface = client_surface(ovc); + } } \ No newline at end of file diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 00bea0e0..87fe2a96 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -1,117 +1,3 @@ - -// 网格布局窗口大小和位置计算 -void overview(Monitor *m) { - int32_t i, n; - int32_t cx, cy, cw, ch; - int32_t dx; - int32_t cols, rows, overcols; - Client *c = NULL; - n = 0; - int32_t target_gappo = - enablegaps ? m->isoverview ? config.overviewgappo : config.gappoh : 0; - int32_t target_gappi = - enablegaps ? m->isoverview ? config.overviewgappi : config.gappih : 0; - float single_width_ratio = m->isoverview ? 0.7 : 0.9; - float single_height_ratio = m->isoverview ? 0.8 : 0.9; - - n = m->isoverview ? m->visible_clients : m->visible_tiling_clients; - - if (n == 0) { - return; // 没有需要处理的客户端,直接返回 - } - - if (n == 1) { - wl_list_for_each(c, &clients, link) { - - if (c->mon != m) - continue; - - if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { - cw = (m->w.width - 2 * target_gappo) * single_width_ratio; - ch = (m->w.height - 2 * target_gappo) * single_height_ratio; - c->geom.x = m->w.x + (m->w.width - cw) / 2; - c->geom.y = m->w.y + (m->w.height - ch) / 2; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - return; - } - } - } - - if (n == 2) { - cw = (m->w.width - 2 * target_gappo - target_gappi) / 2; - ch = (m->w.height - 2 * target_gappo) * 0.65; - i = 0; - wl_list_for_each(c, &clients, link) { - if (c->mon != m) - continue; - - if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { - if (i == 0) { - c->geom.x = m->w.x + target_gappo; - c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - } else if (i == 1) { - c->geom.x = m->w.x + cw + target_gappo + target_gappi; - c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - } - i++; - } - } - return; - } - - // 计算列数和行数 - for (cols = 0; cols <= n / 2; cols++) { - if (cols * cols >= n) { - break; - } - } - rows = (cols && (cols - 1) * cols >= n) ? cols - 1 : cols; - - // 计算每个客户端的高度和宽度 - ch = (m->w.height - 2 * target_gappo - (rows - 1) * target_gappi) / rows; - cw = (m->w.width - 2 * target_gappo - (cols - 1) * target_gappi) / cols; - - // 处理多余的列 - overcols = n % cols; - if (overcols) { - dx = (m->w.width - overcols * cw - (overcols - 1) * target_gappi) / 2 - - target_gappo; - } - - // 调整每个客户端的位置和大小 - i = 0; - wl_list_for_each(c, &clients, link) { - - if (c->mon != m) - continue; - - if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { - cx = m->w.x + (i % cols) * (cw + target_gappi); - cy = m->w.y + (i / cols) * (ch + target_gappi); - if (overcols && i >= n - overcols) { - cx += dx; - } - c->geom.x = cx + target_gappo; - c->geom.y = cy + target_gappo; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - i++; - } - } -} - void deck(Monitor *m) { int32_t mw, my; int32_t i, n = 0; diff --git a/src/layout/overview.h b/src/layout/overview.h new file mode 100644 index 00000000..1e414f73 --- /dev/null +++ b/src/layout/overview.h @@ -0,0 +1,138 @@ +// 紧凑型自适应行高概览布局 (Mission Control / GNOME 风格) +void overview(Monitor *m) { + int32_t target_gappo = + enablegaps ? (m->isoverview ? config.overviewgappo : config.gappoh) : 0; + int32_t target_gappi = + enablegaps ? (m->isoverview ? config.overviewgappi : config.gappih) : 0; + + int n = m->isoverview ? m->visible_clients : m->visible_tiling_clients; + if (n == 0) + return; + + // 收集有效客户端,并提取它们原始的宽高比 + Client *c_arr[n]; + float aspects[n]; + int actual_n = 0; + Client *c = NULL; + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && + ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + + c_arr[actual_n] = c; + float aspect = 1.0f; + if (c->overview_backup_geom.height > 0 && + c->overview_backup_geom.width > 0) { + aspect = (float)c->overview_backup_geom.width / + c->overview_backup_geom.height; + } + // 限制极端宽高比,防止某个 1px 宽度的异常窗口毁掉整个布局的数学计算 + if (aspect < 0.2f) + aspect = 0.2f; + if (aspect > 5.0f) + aspect = 5.0f; + + aspects[actual_n] = aspect; + actual_n++; + } + } + n = actual_n; + if (n == 0) + return; + + // 动态决定行数与列数分配 (例如 7 个窗口分发为 3, 2, 2) + int cols = 1; + while (cols * cols < n) + cols++; + int rows = (n + cols - 1) / cols; + + int items_per_row[rows]; + int remaining = n; + for (int r = 0; r < rows; r++) { + // 使用向上取整的除法,让首行尽可能多排布,视觉重心更稳 + int count = (remaining + rows - r - 1) / (rows - r); + items_per_row[r] = count; + remaining -= count; + } + + // 计算整个布局允许的最大可用区域 (留出四周安全边距) + float max_avail_w = m->w.width - 2 * target_gappo; + float max_avail_h = m->w.height - 2 * target_gappo; + if (max_avail_w < 10) + max_avail_w = 10; + if (max_avail_h < 10) + max_avail_h = 10; + + // 计算能够满足所有限制条件的 "最大统一行高" + float A_sum[rows]; // 每行窗口宽高比之和 + float h_max_w = 999999.0f; // 受宽度限制推导出的行高上限 + + for (int r = 0; r < rows; r++) { + A_sum[r] = 0; + int start_idx = 0; + for (int i = 0; i < r; i++) + start_idx += items_per_row[i]; + + for (int i = 0; i < items_per_row[r]; i++) { + A_sum[r] += aspects[start_idx + i]; + } + + // 这行所有的内部间隙总和 + float gap_x_total = (items_per_row[r] - 1) * target_gappi; + // 保证最宽的一行也不会超出 max_avail_w + float h_limit = (max_avail_w - gap_x_total) / A_sum[r]; + if (h_limit < h_max_w) + h_max_w = h_limit; + } + + float gap_y_total = (rows - 1) * target_gappi; + // 保证总行高不会超出 max_avail_h + float h_max_h = (max_avail_h - gap_y_total) / rows; + + // 最终采用的行高是水平和垂直双向限制中最严苛的一个 + float row_height = h_max_w < h_max_h ? h_max_w : h_max_h; + + // 应用坐标并进行防撕裂渲染 + float total_layout_height = rows * row_height + gap_y_total; + // 计算全局起点 Y,确保整个概览在屏幕垂直居中 + float start_y = m->w.y + (m->w.height - total_layout_height) / 2.0f; + + int current_idx = 0; + float current_y = start_y; + + for (int r = 0; r < rows; r++) { + // 根据当前确定的行高,反推这行真实的像素宽度 + float row_width = + row_height * A_sum[r] + (items_per_row[r] - 1) * target_gappi; + // 让当前这一排窗口在屏幕水平居中 (不满列数的行会自动居中对齐) + float current_x = m->w.x + (m->w.width - row_width) / 2.0f; + + for (int i = 0; i < items_per_row[r]; i++) { + Client *client = c_arr[current_idx]; + float aspect = aspects[current_idx]; + float client_width = row_height * aspect; + + // 【关键防错位】累加 float 坐标,最后写入 client + // 时再强转+0.5四舍五入。 + // 避免每次计算内部小间隙都丢弃浮点精度,导致最后多出或少出 1px + // 缝隙。 + struct wlr_box client_geom; + client_geom.x = (int)(current_x + 0.5f); + client_geom.y = (int)(current_y + 0.5f); + + float next_x = current_x + client_width; + client_geom.width = (int)(next_x + 0.5f) - client_geom.x; + + float next_y = current_y + row_height; + client_geom.height = (int)(next_y + 0.5f) - client_geom.y; + + resize(client, client_geom, 0); + + current_x = next_x + target_gappi; + current_idx++; + } + current_y += row_height + target_gappi; + } +} \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 682e0a20..86ec20e3 100644 --- a/src/mango.c +++ b/src/mango.c @@ -172,7 +172,7 @@ enum { }; /* EWMH atoms */ #endif enum { UP, DOWN, LEFT, RIGHT, UNDIR }; /* smartmovewin */ -enum { NONE, OPEN, MOVE, CLOSE, TAG, FOCUS, OPAFADEIN, OPAFADEOUT }; +enum { NONE, OPEN, MOVE, CLOSE, TAG, FOCUS, OPAFADEIN, OPAFADEOUT, OVERVIEW }; enum { UNFOLD, FOLD, INVALIDFOLD }; enum { PREV, NEXT }; enum { STATE_UNSPECIFIED = 0, STATE_ENABLED, STATE_DISABLED }; @@ -276,6 +276,7 @@ struct dwl_animation { bool tagouting; bool begin_fade_in; bool tag_from_rule; + bool overining; uint32_t time_started; uint32_t duration; struct wlr_box initial; @@ -1099,6 +1100,7 @@ static struct wl_event_source *sync_keymap; #include "layout/arrange.h" #include "layout/dwindle.h" #include "layout/horizontal.h" +#include "layout/overview.h" #include "layout/scroll.h" #include "layout/vertical.h" @@ -4322,6 +4324,7 @@ mapnotify(struct wl_listener *listener, void *data) { // init client geom c->geom.width += 2 * c->bw; c->geom.height += 2 * c->bw; + c->overview_backup_geom = c->geom; struct wlr_ext_foreign_toplevel_handle_v1_state foreign_toplevel_state = { .app_id = client_get_appid(c), @@ -4807,7 +4810,8 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, if (config.sloppyfocus && !start_drag_window && c && time && c->scene && c->scene->node.enabled && !c->animation.tagining && - (surface != seat->pointer_state.focused_surface) && + (surface != seat->pointer_state.focused_surface || + (selmon && selmon->isoverview && selmon->sel != c)) && !client_is_unmanaged(c) && VISIBLEON(c, c->mon)) focusclient(c, 0);