diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index be8757c2..a7694d74 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. | +| `togglejump` | - | 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/installation.md b/docs/installation.md index 1fed83d8..8cb7eafe 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -274,6 +274,7 @@ If your distribution isn't listed above, or you want the latest unreleased chang > - `hwdata` > - `seatd` > - `pcre2` +> - `pango` > - `cjson` > - `pixman` > - `xorg-xwayland` 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/mangowm.scm b/mangowm.scm index fcd7f583..e2b0287a 100644 --- a/mangowm.scm +++ b/mangowm.scm @@ -54,6 +54,7 @@ hwdata seatd pcre2 + pango cjson libxcb pixman diff --git a/meson.build b/meson.build index c4e27fd5..f26f87e1 100644 --- a/meson.build +++ b/meson.build @@ -37,6 +37,7 @@ pcre2_dep = dependency('libpcre2-8') libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1') pixman_dep = dependency('pixman-1') cjson_dep = dependency('libcjson') +pangocairo_dep = dependency('pangocairo') # version info git = find_program('git', required : false) @@ -94,6 +95,7 @@ endif executable('mango', 'src/mango.c', 'src/common/util.c', + 'src/draw/text-node.c', 'src/ext-protocol/wlr_ext_workspace_v1.c', wayland_sources, dependencies : [ @@ -109,6 +111,7 @@ executable('mango', pcre2_dep, pixman_dep, cjson_dep, + pangocairo_dep, ], install : true, c_args : c_args, diff --git a/nix/default.nix b/nix/default.nix index d7bcab16..cbfc917a 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,6 +5,7 @@ libxcb, libxkbcommon, pcre2, + pango, cjson, pixman, pkg-config, @@ -49,6 +50,7 @@ stdenv.mkDerivation { libxcb libxkbcommon pcre2 + pango cjson pixman wayland diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 849da3c1..e41bcf90 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_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_hit_corner_radius") == 0) { + config->jumhitdata.corner_radius = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_hit_padding_x") == 0) { + config->jumhitdata.padding_x = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_hit_padding_y") == 0) { + config->jumhitdata.padding_y = CLAMP_INT(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 new file mode 100644 index 00000000..b96cd4ac --- /dev/null +++ b/src/draw/text-node.c @@ -0,0 +1,308 @@ +#include "text-node.h" +#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; +}; + +// 全局字体描述缓存 +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); +} + +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; +} + +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, +}; + +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; + } + + 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"; + + 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); +} + +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); + + 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); + + 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; + + 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; + + 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); + } + } + + 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); + + 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); + + 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) { + 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 new file mode 100644 index 00000000..b8c4900d --- /dev/null +++ b/src/draw/text-node.h @@ -0,0 +1,52 @@ +#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; + 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; // 文本上下内边距(逻辑像素) +}; + +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; + +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); + +// 外观设置接口 +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 3f8362f4..0c430f47 100644 --- a/src/mango.c +++ b/src/mango.c @@ -96,6 +96,7 @@ #include #endif #include "common/util.h" +#include "draw/text-node.h" /* macros */ #define MAX(A, B) ((A) > (B) ? (A) : (B)) @@ -327,6 +328,7 @@ struct Client { struct wlr_scene_shadow *shadow; struct wlr_scene_tree *scene_surface; struct wlr_scene_tree *overview_scene_surface; + struct mango_text_node *text_node; struct wl_list link; struct wl_list flink; struct wl_list fadeout_link; @@ -433,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; @@ -554,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; @@ -899,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" @@ -2561,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) { @@ -4254,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 */ @@ -4513,6 +4544,10 @@ mapnotify(struct wl_listener *listener, void *data) { #endif // extra node + 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); + for (i = 0; i < 2; i++) { c->splitindicator[i] = wlr_scene_rect_create( c->scene, 0, 0, @@ -6510,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);