feat: client 的任务栏标题点击行为根据 client 窗口的遮挡状态进行

- 当点击 client 在任务中的标题时,如果 client 被其他窗口遮挡,则将其显
示出来,否则切换其 minimize 属性
- 新增 rect 模块用于辅助判断 client 的遮挡状态,添加
rect_intersection/rect_subtract/rect_subtract_many 函数
- client 模块新增 client_get_obscured_state 计算 client 窗口被遮挡状态
- action 模块新增 change_client_state_dwim,实现上述的 client 状态切换
This commit is contained in:
2026-03-03 00:35:11 +08:00
parent 9b7c3a67ef
commit 41a2289c96
8 changed files with 280 additions and 1 deletions

View File

@@ -43,6 +43,7 @@ add_executable(${APP_NAME}
${SOURCE_DIR}/xres.c ${SOURCE_DIR}/xres.c
${SOURCE_DIR}/status.c ${SOURCE_DIR}/status.c
${SOURCE_DIR}/audio.c ${SOURCE_DIR}/audio.c
${SOURCE_DIR}/rect.c
) )
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)

View File

@@ -118,3 +118,23 @@ void kill_client(const user_action_arg_t *arg) {
client_kill(wm.client_focused); client_kill(wm.client_focused);
} }
} }
/**
* @brief 根据 client 的当前状态切换 client 下一状态
* @description 如果当前 client 被遮挡了或者最小化了,则显示出来,否则最小化
*/
void change_client_state_dwim(const user_action_arg_t *arg) {
client_t *client = (client_t *)arg->ptr;
if (!client) return;
if (client != wm.client_stack_list &&
client_get_obscured_state(client, wm.client_stack_list) !=
obscured_none) {
client_stack_raise(client);
client_focus(client);
return;
}
client_set_minimize(client, !client->minimize);
if (!client->minimize) client_stack_raise(client);
}

View File

@@ -63,3 +63,4 @@ void toggle_client_fullscreen(const user_action_arg_t *arg);
void toggle_client_maximize(const user_action_arg_t *arg); void toggle_client_maximize(const user_action_arg_t *arg);
void toggle_client_minimize(const user_action_arg_t *arg); void toggle_client_minimize(const user_action_arg_t *arg);
void kill_client(const user_action_arg_t *arg); void kill_client(const user_action_arg_t *arg);
void change_client_state_dwim(const user_action_arg_t *arg);

View File

@@ -10,6 +10,7 @@
#include "atoms-extern.h" #include "atoms-extern.h"
#include "base.h" #include "base.h"
#include "monitor.h" #include "monitor.h"
#include "rect.h"
#include "types.h" #include "types.h"
#include "utils.h" #include "utils.h"
#include "wm.h" #include "wm.h"
@@ -646,3 +647,50 @@ void client_stack_raise(client_t *client) {
bool client_is_visible(client_t *client) { bool client_is_visible(client_t *client) {
return client->tags & client->monitor->selected_tag->mask; return client->tags & client->monitor->selected_tag->mask;
} }
static inline rect_t client_to_rect(client_t *client) {
return (rect_t){
.x1 = client->geometry.x,
.y1 = client->geometry.y,
.x2 = client->geometry.x + (int32_t)client_width(client),
.y2 = client->geometry.y + (int32_t)client_height(client),
};
}
obscured_t client_get_obscured_state(client_t *client, client_t *client_stack) {
if (!client || !client_stack) return obscured_none;
rect_t source = client_to_rect(client);
size_t clip_count = 0;
size_t clip_capacity = 0;
rect_t *clips = nullptr;
bool has_overlap = false;
for (client_t *c = client_stack; c; c = c->stack_next) {
if (c == client) break;
if (c->minimize || !client_is_visible(c)) continue;
rect_t clip = client_to_rect(c);
if (!rect_intersection(source, clip, nullptr)) continue;
has_overlap = true;
if (clip_count == clip_capacity) {
clip_capacity = clip_capacity ? clip_capacity * 2 : 4;
p_realloc(&clips, clip_capacity);
}
clips[clip_count++] = clip;
}
if (!has_overlap) {
p_delete(&clips);
return obscured_none;
}
rect_t *remaining = nullptr;
size_t remaining_count =
rect_subtract_many(source, clips, clip_count, &remaining);
p_delete(&clips);
p_delete(&remaining);
return remaining_count == 0 ? obscured_fully : obscured_partially;
}

View File

@@ -61,6 +61,13 @@ void client_stack_raise(client_t *client);
bool client_is_visible(client_t *client); bool client_is_visible(client_t *client);
typedef enum obscured_t {
obscured_none,
obscured_partially,
obscured_fully,
} obscured_t;
obscured_t client_get_obscured_state(client_t *client, client_t *client_stack);
static inline uint16_t client_width(client_t *c) { static inline uint16_t client_width(client_t *c) {
return c->geometry.width + c->border_width * 2; return c->geometry.width + c->border_width * 2;
} }

View File

@@ -29,7 +29,13 @@ static const char *const active_border_color = "#005577";
static const button_t button_list[] = { static const button_t button_list[] = {
{click_tag, modifier_none, button_left, select_tag_of_current_monitor, {0}}, {click_tag, modifier_none, button_left, select_tag_of_current_monitor, {0}},
{click_client_name, modifier_none, button_left, toggle_client_minimize, {0}}, {
click_client_name,
modifier_none,
button_left,
change_client_state_dwim,
{0},
},
}; };
#define TAGKEYS(KEY, TAG) \ #define TAGKEYS(KEY, TAG) \

155
src/rect.c Normal file
View File

@@ -0,0 +1,155 @@
#include "rect.h"
#include "utils.h"
static inline bool rect_valid(rect_t rect) {
return rect.x1 < rect.x2 && rect.y1 < rect.y2;
}
/* 将一个合法矩形追加到动态矩形数组中。 */
static inline void rect_list_append(rect_t **list, size_t *length,
size_t *capacity, rect_t rect) {
if (!rect_valid(rect)) return;
if (*length == *capacity) {
*capacity = *capacity ? *capacity * 2 : 4;
p_realloc(list, *capacity);
}
(*list)[(*length)++] = rect;
}
bool rect_intersection(rect_t a, rect_t b, rect_t *intersection) {
if (!rect_valid(a) || !rect_valid(b)) return false;
rect_t overlap = {
.x1 = MAX(a.x1, b.x1),
.y1 = MAX(a.y1, b.y1),
.x2 = MIN(a.x2, b.x2),
.y2 = MIN(a.y2, b.y2),
};
if (!rect_valid(overlap)) return false;
if (intersection) *intersection = overlap;
return true;
}
size_t rect_subtract(rect_t source, rect_t clip, rect_t remaining[4]) {
if (!rect_valid(source)) return 0;
rect_t overlap;
if (!rect_intersection(source, clip, &overlap)) {
if (remaining) remaining[0] = source;
return 1;
}
size_t count = 0;
/*
* source: [sx1,sx2) x [sy1,sy2)
* overlap: [ox1,ox2) x [oy1,oy2) = source 与 clip 的交集
*
* y=sy1 ┌───────────────────────────────────────────┐
* │ TOP │
* │ UL UR │
* y=oy1 ├───────────────┬───────────────┬───────────┤
* │ LEFT │ OVERLAP │ RIGHT │
* y=oy2 ├───────────────┴───────────────┴───────────┤
* │ LL LR │
* │ BOTTOM │
* y=sy2 └───────────────────────────────────────────┘
* x=sx1 x=ox1 x=ox2 x=sx2
*
* 角块归属:
* UL、UR 属于 TOP
* LL、LR 属于 BOTTOM
* LEFT/RIGHT 只覆盖 y ∈ [oy1, oy2),因此不会包含四个角块。
*/
rect_t parts[] = {
/* top */
{
.x1 = source.x1,
.y1 = source.y1,
.x2 = source.x2,
.y2 = overlap.y1,
},
/* bottom */
{
.x1 = source.x1,
.y1 = overlap.y2,
.x2 = source.x2,
.y2 = source.y2,
},
/* left */
{
.x1 = source.x1,
.y1 = overlap.y1,
.x2 = overlap.x1,
.y2 = overlap.y2,
},
/* right */
{
.x1 = overlap.x2,
.y1 = overlap.y1,
.x2 = source.x2,
.y2 = overlap.y2,
},
};
for (size_t i = 0; i < countof(parts); i++) {
if (!rect_valid(parts[i])) continue;
if (remaining) remaining[count] = parts[i];
count++;
}
return count;
}
size_t rect_subtract_many(rect_t source, const rect_t clips[],
size_t clip_count, rect_t **remaining) {
/*
* 实现说明:
* 初始剩余集合为 {source},然后依次处理每个 clip
* 1) 用 clip 去减当前集合中的每个碎片
* 2) 将产生的新碎片合并到 next 集合
* 3) 用 next 替换 current进入下一轮
* 最终 current 即 source - clips[0] - clips[1] - ... 的结果。
*/
if (remaining) *remaining = nullptr;
if (!rect_valid(source)) return 0;
size_t current_length = 1;
size_t current_capacity = 1;
rect_t *current = p_new(rect_t, current_capacity);
current[0] = source;
if (clips) {
for (size_t i = 0; i < clip_count && current_length > 0; i++) {
rect_t clip = clips[i];
if (!rect_valid(clip)) continue;
size_t next_length = 0;
size_t next_capacity = current_length ? current_length : 1;
rect_t *next = p_new(rect_t, next_capacity);
for (size_t j = 0; j < current_length; j++) {
rect_t fragments[4];
size_t fragment_count = rect_subtract(current[j], clip, fragments);
for (size_t k = 0; k < fragment_count; k++) {
rect_list_append(&next, &next_length, &next_capacity, fragments[k]);
}
}
p_delete(&current);
current = next;
current_length = next_length;
current_capacity = next_capacity;
}
}
if (remaining) {
*remaining = current;
} else {
p_delete(&current);
}
return current_length;
}

41
src/rect.h Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef struct rect_t {
int32_t x1;
int32_t y1;
int32_t x2;
int32_t y2;
} rect_t;
bool rect_intersection(rect_t a, rect_t b, rect_t *intersection);
size_t rect_subtract(rect_t source, rect_t clip, rect_t remaining[4]);
/**
* @brief 从 source 中按顺序减去 clips 中的多个矩形
* @param source 源矩形
* @param clips 要减去的矩形数组,可为 nullptr
* @param clip_count clips 的长度
* @param remaining 输出剩余矩形数组;若不为 nullptr则由函数分配内存并由调用方释放
* @return 剩余矩形数量
*
* @code{.c}
* rect_t source = {.x1 = 0, .y1 = 0, .x2 = 100, .y2 = 80};
* rect_t clips[] = {
* {.x1 = 10, .y1 = 10, .x2 = 40, .y2 = 40},
* {.x1 = 30, .y1 = 20, .x2 = 70, .y2 = 60},
* };
*
* rect_t *remaining = nullptr;
* size_t count = rect_subtract_many(source, clips, 2, &remaining);
*
* for (size_t i = 0; i < count; i++) {
* // use remaining[i]
* }
*
* free(remaining);
* @endcode
*/
size_t rect_subtract_many(rect_t source, const rect_t clips[],
size_t clip_count, rect_t **remaining);