From bb909cc2ed26f9b10669cb62b3844984cdadb046 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 16 Jun 2026 20:18:51 +0800 Subject: [PATCH] feat: add dispatch togglejump --- docs/bindings/keys.md | 1 + docs/visuals/theming.md | 12 ++ meson.build | 4 - src/config/parse_config.h | 82 ++++++- src/dispatch/bind_declare.h | 1 + src/dispatch/bind_define.h | 23 ++ src/draw/text-node.c | 412 ++++++++++++++++++++++++++---------- src/draw/text-node.h | 68 +++--- src/layout/overview.h | 55 +++++ src/mango.c | 33 ++- 10 files changed, 544 insertions(+), 147 deletions(-) diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index be8757c2..2ae0e555 100644 --- a/docs/bindings/keys.md +++ b/docs/bindings/keys.md @@ -161,6 +161,7 @@ bindr=Super,Super_L,spawn,rofi -show run | `reload_config` | - | Hot-reload configuration. | | `quit` | - | Exit mangowm. | | `toggleoverview` | - | Toggle overview mode. | +| `toggleojump` | - | Toggle overview with jump mode. | | `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). | | `destroy_all_virtual_output` | - | Destroy all virtual monitors. | | `toggleoverlay` | - | Toggle overlay state for the focused window. | diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md index 676c575b..71d3a818 100644 --- a/docs/visuals/theming.md +++ b/docs/visuals/theming.md @@ -52,6 +52,18 @@ You can also color-code windows based on their state: > **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration. +### Overview Jump Mode +| Setting | Default | Description | +| :--- | :--- | :--- | +| `jump_hit_fg_color` | `0xc4939dff` | label text color. | +| `jump_hit_bg_color` | `0x201b14ff` | label background color. +| `jump_hit_border_color` | `0x8BAA9Bff` | label border color. +| `jump_hit_border_width` | `4` | label border width. +| `jump_hit_corner_radius` | `5` | label corner radius. +| `jump_hit_padding_x` | `10` | label horizontal padding. +| `jump_hit_padding_y` | `10` | label vertical padding. +| `jump_hit_font_desc` | `monospace Bold 24` | label font set.| + ## Cursor Theme Set the size and theme of your mouse cursor. diff --git a/meson.build b/meson.build index 47b78428..f26f87e1 100644 --- a/meson.build +++ b/meson.build @@ -37,8 +37,6 @@ pcre2_dep = dependency('libpcre2-8') libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1') pixman_dep = dependency('pixman-1') cjson_dep = dependency('libcjson') -cairo_dep = dependency('cairo') -pango_dep = dependency('pango') pangocairo_dep = dependency('pangocairo') # version info @@ -113,8 +111,6 @@ executable('mango', pcre2_dep, pixman_dep, cjson_dep, - cairo_dep, - pango_dep, pangocairo_dep, ], install : true, diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 849da3c1..0640150c 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -261,6 +261,7 @@ typedef struct { int32_t enable_hotarea; int32_t ov_tab_mode; int32_t ov_no_resize; + int32_t overviewgappi; int32_t overviewgappo; uint32_t cursor_hide_timeout; @@ -407,6 +408,7 @@ typedef struct { struct xkb_context *ctx; struct xkb_keymap *keymap; + JumphitData jumhitdata; } Config; typedef int32_t (*FuncType)(const Arg *); @@ -1030,6 +1032,9 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "toggleoverview") == 0) { func = toggleoverview; (*arg).i = atoi(arg_value); + } else if (strcmp(func_name, "togglejump") == 0) { + func = togglejump; + (*arg).i = atoi(arg_value); } else if (strcmp(func_name, "set_proportion") == 0) { func = set_proportion; (*arg).f = atof(arg_value); @@ -1758,6 +1763,50 @@ bool parse_option(Config *config, char *key, char *value) { config->cursor_size = atoi(value); } else if (strcmp(key, "cursor_theme") == 0) { config->cursor_theme = strdup(value); + } else if (strcmp(key, "jump_hit_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumhitdata.fg_color, color); + } + } else if (strcmp(key, "jump_hit_font_desc") == 0) { + config->jumhitdata.font_desc = strdup(value); + } else if (strcmp(key, "jump_hit_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumhitdata.bg_color, color); + } + } else if (strcmp(key, "jump_hit_border_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_border_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumhitdata.border_color, color); + } + } else if (strcmp(key, "jump_hit_border_width") == 0) { + config->jumhitdata.border_width = CLAMP_FLOAT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_hit_corner_radius") == 0) { + config->jumhitdata.corner_radius = CLAMP_FLOAT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_hit_padding_x") == 0) { + config->jumhitdata.padding_x = CLAMP_FLOAT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_hit_padding_y") == 0) { + config->jumhitdata.padding_y = CLAMP_FLOAT(atoi(value), 0, 100); } else if (strcmp(key, "disable_while_typing") == 0) { config->disable_while_typing = atoi(value); } else if (strcmp(key, "left_handed") == 0) { @@ -3247,6 +3296,11 @@ void free_config(void) { config.cursor_theme = NULL; } + if (config.jumhitdata.font_desc) { + free((void *)config.jumhitdata.font_desc); + config.jumhitdata.font_desc = NULL; + } + if (config.tablet_map_to_mon) { free(config.tablet_map_to_mon); config.tablet_map_to_mon = NULL; @@ -3437,6 +3491,15 @@ void override_config(void) { config.focused_opacity = CLAMP_FLOAT(config.focused_opacity, 0.0f, 1.0f); config.unfocused_opacity = CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); + + config.jumhitdata.border_width = + CLAMP_INT(config.jumhitdata.border_width, 0, 100); + config.jumhitdata.corner_radius = + CLAMP_INT(config.jumhitdata.corner_radius, 0, 100); + config.jumhitdata.padding_x = + CLAMP_INT(config.jumhitdata.padding_x, 0, 100); + config.jumhitdata.padding_y = + CLAMP_INT(config.jumhitdata.padding_y, 0, 100); } void set_value_default() { @@ -3475,7 +3538,6 @@ void set_value_default() { config.log_level = WLR_ERROR; config.numlockon = 0; config.capslock = 0; - config.ov_tab_mode = 1; config.ov_no_resize = 1; config.hotarea_size = 10; @@ -3613,6 +3675,23 @@ void set_value_default() { config.animation_curve_opafadeout[2] = 0.5; config.animation_curve_opafadeout[3] = 0.5; + config.jumhitdata.fg_color[0] = 0xc4 / 255.0f; + config.jumhitdata.fg_color[1] = 0x93 / 255.0f; + config.jumhitdata.fg_color[2] = 0x9d / 255.0f; + config.jumhitdata.fg_color[3] = 1.0f; + config.jumhitdata.bg_color[0] = 0x32 / 255.0f; + config.jumhitdata.bg_color[1] = 0x32 / 255.0f; + config.jumhitdata.bg_color[2] = 0x32 / 255.0f; + config.jumhitdata.bg_color[3] = 1.0f; + config.jumhitdata.border_color[0] = 0x8b / 255.0f; + config.jumhitdata.border_color[1] = 0xaa / 255.0f; + config.jumhitdata.border_color[2] = 0x9b / 255.0f; + config.jumhitdata.border_color[3] = 1.0f; + config.jumhitdata.border_width = 4; + config.jumhitdata.corner_radius = 5; + config.jumhitdata.padding_x = 10; + config.jumhitdata.padding_y = 10; + config.rootcolor[0] = 0x32 / 255.0f; config.rootcolor[1] = 0x32 / 255.0f; config.rootcolor[2] = 0x32 / 255.0f; @@ -3725,6 +3804,7 @@ bool parse_config(void) { config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; + config.jumhitdata.font_desc = NULL; config.tablet_map_to_mon = NULL; strcpy(config.keymode, "default"); diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index a04ba0c1..6960b001 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -3,6 +3,7 @@ int32_t restore_minimized(const Arg *arg); int32_t toggle_scratchpad(const Arg *arg); int32_t focusdir(const Arg *arg); int32_t toggleoverview(const Arg *arg); +int32_t togglejump(const Arg *arg); int32_t set_proportion(const Arg *arg); int32_t switch_proportion_preset(const Arg *arg); int32_t zoom(const Arg *arg); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index db082b68..c3a35676 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1737,6 +1737,10 @@ int32_t toggleoverview(const Arg *arg) { uint32_t target; uint32_t visible_client_number = 0; + if (!selmon->isoverview && selmon->is_jump_mode) { + finish_jump_mode(selmon); + } + if (selmon->isoverview) { wl_list_for_each(c, &clients, link) if (c && c->mon == selmon && !client_is_unmanaged(c) && @@ -1781,6 +1785,7 @@ int32_t toggleoverview(const Arg *arg) { } } } else { + selmon->tagset[selmon->seltags] = target; wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !c->iskilling && @@ -1794,6 +1799,24 @@ int32_t toggleoverview(const Arg *arg) { view(&(Arg){.ui = target}, false); fix_mon_tagset_from_overview(selmon); refresh_monitors_workspaces_status(selmon); + + return 0; +} + +int32_t togglejump(const Arg *arg) { + if (!selmon) + return 0; + + if (!selmon->isoverview) { + begin_jump_mode(selmon); + toggleoverview(arg); + return 0; + } + + if (selmon->isoverview) { + toggleoverview(arg); + } + return 0; } diff --git a/src/draw/text-node.c b/src/draw/text-node.c index 638ab1fe..c0b01bfa 100644 --- a/src/draw/text-node.c +++ b/src/draw/text-node.c @@ -1,140 +1,322 @@ #include "text-node.h" -#include #include -#include -#include -#include #include +#include +#include +#include +#include +#include // 自定义 wlr_buffer,用于将 Cairo Surface 包装并注入 wlr_scene struct mango_text_buffer { - struct wlr_buffer base; - cairo_surface_t *surface; + struct wlr_buffer base; + cairo_surface_t *surface; }; +// 全局字体描述缓存 +static GHashTable *font_desc_cache = NULL; + +static PangoFontDescription *get_cached_font_desc(const char *font_desc) { + if (!font_desc_cache) { + font_desc_cache = + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)pango_font_description_free); + } + + PangoFontDescription *desc = + g_hash_table_lookup(font_desc_cache, font_desc); + if (!desc) { + desc = pango_font_description_from_string(font_desc); + g_hash_table_insert(font_desc_cache, g_strdup(font_desc), desc); + } + return desc; +} + +void mango_text_global_finish(void) { + if (font_desc_cache) { + g_hash_table_destroy(font_desc_cache); + font_desc_cache = NULL; + } +} + +// wlr_buffer 实现 static void text_buffer_destroy(struct wlr_buffer *wlr_buffer) { - struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base); - cairo_surface_destroy(buf->surface); - free(buf); + struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base); + cairo_surface_destroy(buf->surface); + free(buf); } static bool text_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, - uint32_t flags, - void **data, - uint32_t *format, - size_t *stride) { - (void)flags; // 该参数当前无需使用 - struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base); - *data = cairo_image_surface_get_data(buf->surface); - *format = DRM_FORMAT_ARGB8888; - *stride = cairo_image_surface_get_stride(buf->surface); - return true; + uint32_t flags, void **data, + uint32_t *format, + size_t *stride) { + (void)flags; + struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base); + *data = cairo_image_surface_get_data(buf->surface); + *format = DRM_FORMAT_ARGB8888; + *stride = cairo_image_surface_get_stride(buf->surface); + return true; } -static void text_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { - // Cairo 表面数据在 flush 后稳定,此处无需额外操作 -} +static void text_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) {} static const struct wlr_buffer_impl text_buffer_impl = { - .destroy = text_buffer_destroy, - .begin_data_ptr_access = text_buffer_begin_data_ptr_access, - .end_data_ptr_access = text_buffer_end_data_ptr_access, + .destroy = text_buffer_destroy, + .begin_data_ptr_access = text_buffer_begin_data_ptr_access, + .end_data_ptr_access = text_buffer_end_data_ptr_access, }; -struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent) { - struct mango_text_node *node = calloc(1, sizeof(*node)); - if (!node) return NULL; +struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, + JumphitData data) { + struct mango_text_node *node = calloc(1, sizeof(*node)); + if (!node) + return NULL; - node->scene_buffer = wlr_scene_buffer_create(parent, NULL); - if (!node->scene_buffer) { - free(node); - return NULL; - } - return node; -} + node->scene_buffer = wlr_scene_buffer_create(parent, NULL); + if (!node->scene_buffer) { + free(node); + return NULL; + } -void mango_text_node_update(struct mango_text_node *node, const char *text, - const char *font_desc, float color[4], float scale) { - if (!node || !text || !font_desc) return; - if (scale <= 0.0f) scale = 1.0f; + // 默认外观:无边框、无背景、无内边距(保持原有行为) + node->fg_color[0] = data.fg_color[0]; + node->fg_color[1] = data.fg_color[1]; + node->fg_color[2] = data.fg_color[2]; + node->fg_color[3] = data.fg_color[3]; + node->bg_color[0] = data.bg_color[0]; + node->bg_color[1] = data.bg_color[1]; + node->bg_color[2] = data.bg_color[2]; + node->bg_color[3] = data.bg_color[3]; + node->border_color[0] = data.border_color[0]; + node->border_color[1] = data.border_color[1]; + node->border_color[2] = data.border_color[2]; + node->border_color[3] = data.border_color[3]; + node->border_width = data.border_width; + node->corner_radius = data.corner_radius; + node->padding_x = data.padding_x; + node->padding_y = data.padding_y; + node->font_desc = data.font_desc ? data.font_desc : "monospace Bold 24"; - // 测量文字像素尺寸 - cairo_surface_t *dummy_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); - cairo_t *dummy_cr = cairo_create(dummy_surface); - PangoContext *dummy_ctx = pango_cairo_create_context(dummy_cr); - pango_cairo_context_set_resolution(dummy_ctx, 96.0 * scale); - PangoLayout *dummy_layout = pango_layout_new(dummy_ctx); - PangoFontDescription *desc = pango_font_description_from_string(font_desc); - pango_layout_set_font_description(dummy_layout, desc); - pango_layout_set_text(dummy_layout, text, -1); - - int pixel_width, pixel_height; - pango_layout_get_pixel_size(dummy_layout, &pixel_width, &pixel_height); - - g_object_unref(dummy_layout); - pango_font_description_free(desc); - g_object_unref(dummy_ctx); - cairo_destroy(dummy_cr); - cairo_surface_destroy(dummy_surface); - - if (pixel_width <= 0 || pixel_height <= 0) { - wlr_scene_buffer_set_buffer(node->scene_buffer, NULL); - node->logical_width = 0; - node->logical_height = 0; - return; - } - - // 创建真实大小的 Surface 并绘制文字 - cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pixel_width, pixel_height); - cairo_t *cr = cairo_create(surface); - - cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0); - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - cairo_paint(cr); - cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - - PangoContext *ctx = pango_cairo_create_context(cr); - pango_cairo_context_set_resolution(ctx, 96.0 * scale); - PangoLayout *layout = pango_layout_new(ctx); - desc = pango_font_description_from_string(font_desc); - pango_layout_set_font_description(layout, desc); - pango_layout_set_text(layout, text, -1); - - cairo_set_source_rgba(cr, color[0], color[1], color[2], color[3]); - pango_cairo_show_layout(cr, layout); - cairo_surface_flush(surface); - - // 包装成 wlr_buffer - struct mango_text_buffer *buf = calloc(1, sizeof(*buf)); - if (!buf) { - cairo_surface_destroy(surface); - cairo_destroy(cr); - g_object_unref(layout); - pango_font_description_free(desc); - g_object_unref(ctx); - return; - } - wlr_buffer_init(&buf->base, &text_buffer_impl, pixel_width, pixel_height); - buf->surface = surface; - - // 提交给场景图 - wlr_scene_buffer_set_buffer(node->scene_buffer, &buf->base); - wlr_buffer_drop(&buf->base); // 函数内引用计数 -1,交给场景图管理 - - // 设置逻辑大小,实现 HiDPI 缩放 - node->logical_width = (int)(pixel_width / scale); - node->logical_height = (int)(pixel_height / scale); - wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width, node->logical_height); - - // 清理绘制资源 - g_object_unref(layout); - pango_font_description_free(desc); - g_object_unref(ctx); - cairo_destroy(cr); + return node; } void mango_text_node_destroy(struct mango_text_node *node) { - if (!node) return; - wlr_scene_node_destroy(&node->scene_buffer->node); - free(node); + if (!node) + return; + + wlr_scene_node_destroy(&node->scene_buffer->node); + free(node); +} + +void mango_text_node_set_background(struct mango_text_node *node, float r, + float g, float b, float a) { + if (!node) + return; + node->bg_color[0] = r; + node->bg_color[1] = g; + node->bg_color[2] = b; + node->bg_color[3] = a; +} + +void mango_text_node_set_border(struct mango_text_node *node, float r, float g, + float b, float a, float width, float radius) { + if (!node) + return; + node->border_color[0] = r; + node->border_color[1] = g; + node->border_color[2] = b; + node->border_color[3] = a; + node->border_width = width > 0.0f ? width : 0.0f; + node->corner_radius = radius >= 0.0f ? radius : 0.0f; +} + +void mango_text_node_set_padding(struct mango_text_node *node, float pad_x, + float pad_y) { + if (!node) + return; + node->padding_x = pad_x >= 0.0f ? pad_x : 0.0f; + node->padding_y = pad_y >= 0.0f ? pad_y : 0.0f; +} + +static void draw_rounded_rect(cairo_t *cr, double x, double y, double w, + double h, double r) { + // 使用 Cairo 的标准圆角矩形路径 + double degrees = G_PI / 180.0; + cairo_new_sub_path(cr); + cairo_arc(cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); + cairo_arc(cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); + cairo_arc(cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); + cairo_arc(cr, x + r, y + r, r, 180 * degrees, 270 * degrees); + cairo_close_path(cr); +} + +void mango_text_node_update(struct mango_text_node *node, const char *text, + + float scale) { + if (!node || !text) + return; + if (scale <= 0.0f) + scale = 1.0f; + + const char *font_desc = node->font_desc; + PangoFontDescription *desc = get_cached_font_desc(font_desc); + + // 测量文字像素尺寸(无缩放的实际像素) + cairo_surface_t *dummy_surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + cairo_t *dummy_cr = cairo_create(dummy_surface); + PangoContext *dummy_ctx = pango_cairo_create_context(dummy_cr); + pango_cairo_context_set_resolution(dummy_ctx, 96.0 * scale); + PangoLayout *dummy_layout = pango_layout_new(dummy_ctx); + pango_layout_set_font_description(dummy_layout, desc); + pango_layout_set_text(dummy_layout, text, -1); + + int text_pixel_w, text_pixel_h; + pango_layout_get_pixel_size(dummy_layout, &text_pixel_w, &text_pixel_h); + + g_object_unref(dummy_layout); + g_object_unref(dummy_ctx); + cairo_destroy(dummy_cr); + cairo_surface_destroy(dummy_surface); + + if (text_pixel_w <= 0 || text_pixel_h <= 0) { + wlr_scene_buffer_set_buffer(node->scene_buffer, NULL); + node->logical_width = 0; + node->logical_height = 0; + return; + } + + // 逻辑尺寸:文本的逻辑宽高 + float logical_text_w = text_pixel_w / scale; + float logical_text_h = text_pixel_h / scale; + + // 背景框的逻辑尺寸(包含内边距) + float pad_x = node->padding_x; + float pad_y = node->padding_y; + float box_logical_w = logical_text_w + 2.0f * pad_x; + float box_logical_h = logical_text_h + 2.0f * pad_y; + + // 边框相关尺寸(全部以逻辑像素为单位) + float border = node->border_width; + bool draw_border = (border > 0.0f) && (node->border_color[3] > 0.0f); + bool draw_bg = (node->bg_color[3] > 0.0f); + + // 最终 surface 的像素尺寸:背景框 + 两倍边框宽度(边框外扩) + int surface_pixel_w = (int)((box_logical_w + 2.0f * border) * scale + 0.5f); + int surface_pixel_h = (int)((box_logical_h + 2.0f * border) * scale + 0.5f); + + // 限制最小尺寸以避免 Cairo 错误 + if (surface_pixel_w < 1) + surface_pixel_w = 1; + if (surface_pixel_h < 1) + surface_pixel_h = 1; + + // 创建绘制表面 + cairo_surface_t *surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, surface_pixel_w, surface_pixel_h); + cairo_t *cr = cairo_create(surface); + + // 清空为全透明 + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + // --- 计算关键坐标(像素) --- + double bg_x = border * scale; + double bg_y = border * scale; + double bg_w = box_logical_w * scale; + double bg_h = box_logical_h * scale; + + double radius = node->corner_radius * scale; + // 如果 radius < + // 0,表示自动使用半宽半高形成胶囊形状(备选,这里保持简单,默认0不圆角) + if (radius < 0) { + radius = (bg_w < bg_h ? bg_w : bg_h) / 2.0; + } + if (radius > bg_w / 2.0) + radius = bg_w / 2.0; + if (radius > bg_h / 2.0) + radius = bg_h / 2.0; + + // 1. 绘制背景 + if (draw_bg) { + cairo_set_source_rgba(cr, node->bg_color[0], node->bg_color[1], + node->bg_color[2], node->bg_color[3]); + if (radius > 0.0) { + draw_rounded_rect(cr, bg_x, bg_y, bg_w, bg_h, radius); + cairo_fill(cr); + } else { + cairo_rectangle(cr, bg_x, bg_y, bg_w, bg_h); + cairo_fill(cr); + } + } + + // 2. 绘制文本(偏移 = 边框 + 内边距) + cairo_save(cr); + cairo_translate(cr, (border + pad_x) * scale, (border + pad_y) * scale); + + PangoContext *ctx = pango_cairo_create_context(cr); + pango_cairo_context_set_resolution(ctx, 96.0 * scale); + PangoLayout *layout = pango_layout_new(ctx); + pango_layout_set_font_description(layout, desc); + pango_layout_set_text(layout, text, -1); + + cairo_set_source_rgba(cr, node->fg_color[0], node->fg_color[1], + node->fg_color[2], node->fg_color[3]); + pango_cairo_show_layout(cr, layout); + + g_object_unref(layout); + g_object_unref(ctx); + cairo_restore(cr); // 回到原坐标,方便画边框 + + // 3. 绘制边框 + if (draw_border) { + cairo_set_source_rgba(cr, node->border_color[0], node->border_color[1], + node->border_color[2], node->border_color[3]); + cairo_set_line_width(cr, border * scale); + + // 边框路径:比背景框外扩半个线宽,使得边框外缘恰好贴合 surface 边缘 + double half_lw = border * scale * 0.5; + double bx = bg_x - half_lw; + double by = bg_y - half_lw; + double bw = bg_w + border * scale; + double bh = bg_h + border * scale; + + if (radius > 0.0) { + // 圆角边框同样外扩半径(半径也需增加 half_lw) + double outer_radius = radius + half_lw; + if (outer_radius < 0.0) + outer_radius = 0.0; + draw_rounded_rect(cr, bx, by, bw, bh, outer_radius); + cairo_stroke(cr); + } else { + cairo_rectangle(cr, bx, by, bw, bh); + cairo_stroke(cr); + } + } + + cairo_surface_flush(surface); + + // 包装成 wlr_buffer + struct mango_text_buffer *buf = calloc(1, sizeof(*buf)); + if (!buf) { + cairo_surface_destroy(surface); + cairo_destroy(cr); + return; + } + wlr_buffer_init(&buf->base, &text_buffer_impl, surface_pixel_w, + surface_pixel_h); + buf->surface = surface; + + wlr_scene_buffer_set_buffer(node->scene_buffer, &buf->base); + wlr_buffer_drop(&buf->base); + + // 最终逻辑大小 = 背景框逻辑尺寸 + 边框逻辑尺寸 + node->logical_width = (int)(box_logical_w + 2.0f * border); + node->logical_height = (int)(box_logical_h + 2.0f * border); + wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width, + node->logical_height); + + cairo_destroy(cr); } \ No newline at end of file diff --git a/src/draw/text-node.h b/src/draw/text-node.h index 36fb8026..b8c4900d 100644 --- a/src/draw/text-node.h +++ b/src/draw/text-node.h @@ -1,34 +1,52 @@ -#ifndef MANGO_TEXT_H -#define MANGO_TEXT_H +#ifndef MANGO_TEXT_NODE_H +#define MANGO_TEXT_NODE_H +#include #include struct mango_text_node { - struct wlr_scene_buffer *scene_buffer; - int logical_width; - int logical_height; + struct wlr_scene_buffer *scene_buffer; + int logical_width; + int logical_height; + const char *font_desc; + + // 外观属性 + float fg_color[4]; // 文本颜色 RGBA,默认白色 + float bg_color[4]; // 背景色 RGBA,默认透明 + float border_color[4]; // 边框色 RGBA,默认透明 + int32_t border_width; // 边框宽度(逻辑像素),<=0 则不绘制 + int32_t corner_radius; // 圆角半径(逻辑像素),<0 时自动取 + // min(width,height)/2 形成胶囊 + int32_t padding_x; // 文本左右内边距(逻辑像素) + int32_t padding_y; // 文本上下内边距(逻辑像素) }; -/** - * 创建一个独立的文字场景节点 - * @param parent 父级场景树节点(例如窗口装饰树、OSD层树或状态栏树) - */ -struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent); +typedef struct { + float fg_color[4]; + float bg_color[4]; + float border_color[4]; + int32_t border_width; + int32_t corner_radius; + int32_t padding_x; + int32_t padding_y; + const char *font_desc; +} JumphitData; -/** - * 更新节点中的文字内容、字体、颜色和缩放 - * @param node 文字节点指针 - * @param text 要显示的文本内容 - * @param font_desc Pango 字体描述字符串 (例如 "Sans 12" 或 "JetBrains Mono Bold 14") - * @param color RGBA 颜色数组,范围 0.0 ~ 1.0 - * @param scale 当前输出设备的缩放比例 (HiDPI 支持) - */ -void mango_text_node_update(struct mango_text_node *node, const char *text, - const char *font_desc, float color[4], float scale); - -/** - * 销毁文字节点并释放相关场景图资源 - */ +struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, + JumphitData data); void mango_text_node_destroy(struct mango_text_node *node); +void mango_text_node_update(struct mango_text_node *node, const char *text, + float scale); -#endif /* MANGO_TEXT_H */ \ No newline at end of file +// 外观设置接口 +void mango_text_node_set_background(struct mango_text_node *node, float r, + float g, float b, float a); +void mango_text_node_set_border(struct mango_text_node *node, float r, float g, + float b, float a, float width, float radius); +void mango_text_node_set_padding(struct mango_text_node *node, float pad_x, + float pad_y); + +void mango_text_node_destroy(struct mango_text_node *node); +void mango_text_global_finish(void); + +#endif \ No newline at end of file diff --git a/src/layout/overview.h b/src/layout/overview.h index 88fc4258..06e2213f 100644 --- a/src/layout/overview.h +++ b/src/layout/overview.h @@ -353,10 +353,65 @@ void overview_resize(Monitor *m) { free(c_arr); } +void create_jump_hints(Monitor *m) { + const char jump_labels[] = "HJKLASDFGQWERTYUIOPZXCVBNM"; + int label_idx = 0; + Client *c; + + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && + ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + if (label_idx >= 26) + break; + char c_char = jump_labels[label_idx]; + c->jump_char = c_char; + + // 把字符变成字符串 + char label_text[2] = {c_char, '\0'}; + + mango_text_node_update(c->text_node, label_text, 1.0f); + wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, true); + wlr_scene_node_raise_to_top(&c->text_node->scene_buffer->node); + wlr_scene_node_set_position( + &c->text_node->scene_buffer->node, + c->geom.width / 2 - c->text_node->logical_width / 2, + c->geom.height / 2 - c->text_node->logical_height / 2); + label_idx++; + } + } +} + +void begin_jump_mode(Monitor *m) { m->is_jump_mode = 1; } + +void finish_jump_mode(Monitor *m) { + Client *c = NULL; + + if (!m->is_jump_mode) { + return; + } + + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m)) { + if (c->text_node->scene_buffer->node.enabled) { + c->jump_char = '\0'; + wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, + false); + } + } + } + + m->is_jump_mode = 0; +} + void overview(Monitor *m) { + if (config.ov_no_resize) { overview_scale(m); } else { overview_resize(m); } + + if (m->is_jump_mode) { + create_jump_hints(m); + } } \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 85cf5840..0c430f47 100644 --- a/src/mango.c +++ b/src/mango.c @@ -435,6 +435,7 @@ struct Client { int32_t allow_shortcuts_inhibit; float scroller_proportion_single; bool isfocusing; + char jump_char; bool enable_drop_area_draw; int32_t drop_direction; struct wlr_box drag_tile_float_backup_geom; @@ -556,6 +557,7 @@ struct Monitor { uint32_t ovbk_prev_tagset; Client *sel, *prevsel; int32_t isoverview; + int32_t is_jump_mode; int32_t is_in_hotarea; int32_t asleep; uint32_t visible_clients; @@ -901,6 +903,10 @@ Client *scroll_get_stack_tail_client(Client *c); static DwindleNode *dwindle_find_leaf(DwindleNode *node, Client *c); static void overview_backup_surface(Client *c); +static void create_jump_hints(Monitor *m); +static void finish_jump_mode(Monitor *m); +static void begin_jump_mode(Monitor *m); + #include "data/static_keymap.h" #include "dispatch/bind_declare.h" #include "layout/layout.h" @@ -2563,6 +2569,8 @@ void cleanup(void) { /* Destroy after the wayland display (when the monitors are already destroyed) to avoid destroying them with an invalid scene output. */ wlr_scene_node_destroy(&scene->tree.node); + + mango_text_global_finish(); } void cleanupmon(struct wl_listener *listener, void *data) { @@ -4256,6 +4264,27 @@ void keypress(struct wl_listener *listener, void *data) { if (handled) return; + if (selmon && selmon->is_jump_mode && + event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) { + xkb_keysym_t sym = xkb_keysym_to_lower(syms[i]); + if (sym >= XKB_KEY_a && sym <= XKB_KEY_z) { + char c_char = 'A' + (sym - XKB_KEY_a); + Client *c; + wl_list_for_each(c, &clients, link) { + if (c->mon == selmon && c->jump_char == c_char) { + focusclient(c, 1); + toggleoverview(&(Arg){.i = 1}); + return; + } + } + } else if (sym == XKB_KEY_Escape) { + togglejump(&(Arg){.i = 0}); + return; + } + } + } + /* don't pass when popup is focused * this is better than having popups (like fuzzel or wmenu) closing * while typing in a passed keybind */ @@ -4515,8 +4544,7 @@ mapnotify(struct wl_listener *listener, void *data) { #endif // extra node - c->text_node = mango_text_node_create(c->scene); - mango_text_node_update(c->text_node, "hello world", "Sans 50", config.focuscolor, 1.0f); + c->text_node = mango_text_node_create(c->scene, config.jumhitdata); wlr_scene_node_lower_to_bottom(&c->text_node->scene_buffer->node); wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, false); @@ -6517,6 +6545,7 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->stack_proportion = 0.0f; + mango_text_node_destroy(c->text_node); wlr_scene_node_destroy(&c->scene->node); printstatus(IPC_WATCH_ARRANGGE); motionnotify(0, NULL, 0, 0, 0, 0);