diff --git a/CMakeLists.txt b/CMakeLists.txt index 47b0871..d2dd388 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(${APP_NAME} ${SOURCE_DIR}/xres.c ${SOURCE_DIR}/status.c ${SOURCE_DIR}/audio.c + ${SOURCE_DIR}/rect.c ) find_package(PkgConfig REQUIRED) diff --git a/src/action.c b/src/action.c index 58f5305..26139cd 100644 --- a/src/action.c +++ b/src/action.c @@ -118,3 +118,23 @@ void kill_client(const user_action_arg_t *arg) { 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); +} diff --git a/src/action.h b/src/action.h index e0ac62c..9dbaefe 100644 --- a/src/action.h +++ b/src/action.h @@ -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_minimize(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); diff --git a/src/client.c b/src/client.c index d51e662..c83daea 100644 --- a/src/client.c +++ b/src/client.c @@ -10,6 +10,7 @@ #include "atoms-extern.h" #include "base.h" #include "monitor.h" +#include "rect.h" #include "types.h" #include "utils.h" #include "wm.h" @@ -646,3 +647,50 @@ void client_stack_raise(client_t *client) { bool client_is_visible(client_t *client) { 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; +} diff --git a/src/client.h b/src/client.h index 19d7c27..b2c8acf 100644 --- a/src/client.h +++ b/src/client.h @@ -61,6 +61,13 @@ void client_stack_raise(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) { return c->geometry.width + c->border_width * 2; } diff --git a/src/default_config.h b/src/default_config.h index 8bf1170..644a3c6 100644 --- a/src/default_config.h +++ b/src/default_config.h @@ -29,7 +29,13 @@ static const char *const active_border_color = "#005577"; static const button_t button_list[] = { {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) \ diff --git a/src/rect.c b/src/rect.c new file mode 100644 index 0000000..9baf8dd --- /dev/null +++ b/src/rect.c @@ -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(¤t); + current = next; + current_length = next_length; + current_capacity = next_capacity; + } + } + + if (remaining) { + *remaining = current; + } else { + p_delete(¤t); + } + + return current_length; +} diff --git a/src/rect.h b/src/rect.h new file mode 100644 index 0000000..2fb9f19 --- /dev/null +++ b/src/rect.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +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);