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..2f9993be 100644 --- a/docs/visuals/theming.md +++ b/docs/visuals/theming.md @@ -52,6 +52,39 @@ 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_label_decorate_fg_color` | `0xc4939dff` | text color. | +| `jump_label_decorate_bg_color` | `0x201b14ff` | background color.| +| `jump_label_decorate_focus_fg_color` | `0x201b14ff` | text color for focus. | +| `jump_label_decorate_focus_bg_color` | `0xc4939dff` | background color for focus.| +| `jump_label_decorate_border_color` | `0x8BAA9Bff` | border color.| +| `jump_label_decorate_border_width` | `4` | border width.| +| `jump_label_decorate_corner_radius` | `5` | corner radius.| +| `jump_label_decorate_padding_x` | `10` | horizontal padding.| +| `jump_label_decorate_padding_y` | `10` | vertical padding.| +| `jump_label_decorate_font_desc` | `monospace Bold 16` | font set.| + +### Tab Bar For Monocle Layout +| Setting | Default | Description | +| :--- | :--- | :--- | +| `tab_bar_height` | `50` | Height of the tab bar for monocle layout. | +| `tab_bar_decorate_fg_color` | `0xc4939dff` | text color. +| `tab_bar_decorate_bg_color` | `0x201b14ff` | background color.| +| `tab_bar_decorate_focus_fg_color` | `0x201b14ff` | text color for focus. | +| `tab_bar_decorate_focus_bg_color` | `0xc4939dff` | background color for focus.| +| `tab_bar_decorate_border_color` | `0x8BAA9Bff` | border color.| +| `tab_bar_decorate_border_width` | `4` | border width.| +| `tab_bar_decorate_corner_radius` | `5` | corner radius.| +| `tab_bar_decorate_padding_x` | `0` | horizontal padding.| +| `tab_bar_decorate_padding_y` | `0` | vertical padding.| +| `tab_bar_decorate_font_desc` | `monospace Bold 16` | font set.| + +## Borders + +Control the appearance of window borders. + ## 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 fa5409b1..81ec9473 100644 --- a/meson.build +++ b/meson.build @@ -26,6 +26,7 @@ endif cc = meson.get_compiler('c') libm = cc.find_library('m') +libdrm = dependency('libdrm') xcb = dependency('xcb', required : get_option('xwayland')) xlibs = dependency('xcb-icccm', required : get_option('xwayland')) wlroots_dep = dependency('wlroots-0.20',version: '>=0.20.0') @@ -36,6 +37,7 @@ libwayland_client_dep = dependency('wayland-client') pcre2_dep = dependency('libpcre2-8') pixman_dep = dependency('pixman-1') cjson_dep = dependency('libcjson') +pangocairo_dep = dependency('pangocairo') # version info git = find_program('git', required : false) @@ -93,9 +95,11 @@ endif executable('mango-wl', 'src/mango.c', 'src/common/util.c', + 'src/draw/text-node.c', wayland_sources, dependencies : [ libm, + libdrm, xcb, xlibs, wayland_server_dep, @@ -106,6 +110,7 @@ executable('mango-wl', 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..8c35eb83 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -3,8 +3,10 @@ libX11, libinput, libxcb, + libdrm, libxkbcommon, pcre2, + pango, cjson, pixman, pkg-config, @@ -49,6 +51,7 @@ stdenv.mkDerivation { libxcb libxkbcommon pcre2 + pango cjson pixman wayland @@ -56,6 +59,7 @@ stdenv.mkDerivation { wlroots_0_19 scenefx libGL + libdrm ] ++ lib.optionals enableXWayland [ libX11 diff --git a/src/action/client.h b/src/action/client.h index 11602e70..876284cd 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -95,4 +95,28 @@ void client_pending_force_kill(Client *c) { if (!c) return; kill(c->pid, SIGKILL); -} \ No newline at end of file +} + +void client_add_jump_label_node(Client *c) { + c->jump_label_node = + mango_jump_label_node_create(c->scene, config.jumplabeldata); + wlr_scene_node_lower_to_bottom(&c->jump_label_node->scene_buffer->node); + wlr_scene_node_set_enabled(&c->jump_label_node->scene_buffer->node, false); +} + +void client_add_tab_bar_node(Client *c) { + + if (config.tab_bar_height <= 0) { + return; + } + + MangoNodeData *mangonodedata = ecalloc(1, sizeof(MangoNodeData)); + mangonodedata->node_data = c; + mangonodedata->type = MANGO_TITLE_NODE; + + c->tab_bar_node = mango_tab_bar_node_create( + mangonodedata, layers[LyrDecorate], config.tabdata, 0, 0); + wlr_scene_node_lower_to_bottom(&c->tab_bar_node->scene_buffer->node); + wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, false); + mango_tab_bar_node_update(c->tab_bar_node, client_get_title(c), 1.0); +} diff --git a/src/animation/client.h b/src/animation/client.h index 91920c4b..832caa0b 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -289,6 +289,20 @@ void apply_shield(Client *c, struct wlr_box clip_box) { } } +void global_draw_tab_bar(Client *c, int32_t x, int32_t y, int32_t width, + int32_t height) { + if (!c->tab_bar_node) + return; + + if (height <= 0) { + wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, false); + } + + wlr_scene_node_set_position(&c->tab_bar_node->scene_buffer->node, x, y); + wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, true); + mango_tab_bar_node_set_size(c->tab_bar_node, width, height); +} + void apply_split_border(Client *c, bool hit_no_border) { if (c->iskilling || !c->mon || !client_surface(c)->mapped) @@ -544,8 +558,10 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { (ISSCROLLTILED(c) || c->animation.tagouting || c->animation.tagining)) { c->is_clip_to_hide = true; wlr_scene_node_set_enabled(&c->scene->node, false); - } else if (c->is_clip_to_hide && VISIBLEON(c, c->mon)) { + } else if (c->is_clip_to_hide && VISIBLEON(c, c->mon) && + (!c->is_monocle_hide || !is_monocle_layout(c->mon))) { c->is_clip_to_hide = false; + c->is_monocle_hide = false; wlr_scene_node_set_enabled(&c->scene->node, true); } @@ -898,7 +914,7 @@ void fadeout_client_animation_next_tick(Client *c) { double percent = config.fadeout_begin_opacity - (opacity_eased_progress * config.fadeout_begin_opacity); - double opacity = MAX(percent, 0); + double opacity = MANGO_MAX(percent, 0); if (config.animation_fade_out && !c->nofadeout) wlr_scene_node_for_each_buffer(&c->scene->node, @@ -1177,8 +1193,8 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { if (is_scroller_layout(c->mon) && (!c->isfloating || c == grabc)) { c->geom = geo; - c->geom.width = MAX(1 + 2 * (int32_t)c->bw, c->geom.width); - c->geom.height = MAX(1 + 2 * (int32_t)c->bw, c->geom.height); + c->geom.width = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.width); + c->geom.height = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.height); } else { // 这里会限制不允许窗口划出屏幕 c->geom = geo; applybounds( diff --git a/src/animation/layer.h b/src/animation/layer.h index 414cf2bd..476cba63 100644 --- a/src/animation/layer.h +++ b/src/animation/layer.h @@ -233,7 +233,7 @@ void fadeout_layer_animation_next_tick(LayerSurface *l) { double percent = config.fadeout_begin_opacity - (opacity_eased_progress * config.fadeout_begin_opacity); - double opacity = MAX(percent, 0.0f); + double opacity = MANGO_MAX(percent, 0.0f); if (config.animation_fade_out) wlr_scene_node_for_each_buffer(&l->scene->node, @@ -277,10 +277,10 @@ void layer_animation_next_tick(LayerSurface *l) { double opacity_eased_progress = find_animation_curve_at(animation_passed, OPAFADEIN); - double opacity = - MIN(config.fadein_begin_opacity + - opacity_eased_progress * (1.0 - config.fadein_begin_opacity), - 1.0f); + double opacity = MANGO_MIN(config.fadein_begin_opacity + + opacity_eased_progress * + (1.0 - config.fadein_begin_opacity), + 1.0f); if (config.animation_fade_in) wlr_scene_node_for_each_buffer(&l->scene->node, diff --git a/src/animation/tag.h b/src/animation/tag.h index 893d4b0b..859ee1b8 100644 --- a/src/animation/tag.h +++ b/src/animation/tag.h @@ -23,30 +23,32 @@ void set_tagin_animation(Monitor *m, Client *c) { c->animainit_geom.x = config.tag_animation_direction == VERTICAL ? c->animation.current.x - : MAX(c->mon->m.x + c->mon->m.width, - c->geom.x + c->mon->m.width); + : MANGO_MAX(c->mon->m.x + c->mon->m.width, + c->geom.x + c->mon->m.width); c->animainit_geom.y = config.tag_animation_direction == VERTICAL - ? MAX(c->mon->m.y + c->mon->m.height, - c->geom.y + c->mon->m.height) + ? MANGO_MAX(c->mon->m.y + c->mon->m.height, + c->geom.y + c->mon->m.height) : c->animation.current.y; } else { - c->animainit_geom.x = - config.tag_animation_direction == VERTICAL - ? c->animation.current.x - : MIN(m->m.x - c->geom.width, c->geom.x - c->mon->m.width); - c->animainit_geom.y = - config.tag_animation_direction == VERTICAL - ? MIN(m->m.y - c->geom.height, c->geom.y - c->mon->m.height) - : c->animation.current.y; + c->animainit_geom.x = config.tag_animation_direction == VERTICAL + ? c->animation.current.x + : MANGO_MIN(m->m.x - c->geom.width, + c->geom.x - c->mon->m.width); + c->animainit_geom.y = config.tag_animation_direction == VERTICAL + ? MANGO_MIN(m->m.y - c->geom.height, + c->geom.y - c->mon->m.height) + : c->animation.current.y; } } void set_arrange_visible(Monitor *m, Client *c, bool want_animation) { - if (!c->is_clip_to_hide || !ISTILED(c) || !is_scroller_layout(c->mon)) { + if (!ISTILED(c) || ((!c->is_clip_to_hide || !is_scroller_layout(c->mon)) && + (!c->is_monocle_hide || !is_monocle_layout(c->mon)))) { c->is_clip_to_hide = false; + c->is_monocle_hide = false; wlr_scene_node_set_enabled(&c->scene->node, true); wlr_scene_node_set_enabled(&c->scene_surface->node, true); } @@ -84,13 +86,13 @@ void set_tagout_animation(Monitor *m, Client *c) { : m->pertag->curtag > m->pertag->prevtag; if (going_forward) { c->pending = c->geom; - c->pending.x = - config.tag_animation_direction == VERTICAL - ? c->animation.current.x - : MIN(c->mon->m.x - c->geom.width, c->geom.x - c->mon->m.width); + c->pending.x = config.tag_animation_direction == VERTICAL + ? c->animation.current.x + : MANGO_MIN(c->mon->m.x - c->geom.width, + c->geom.x - c->mon->m.width); c->pending.y = config.tag_animation_direction == VERTICAL - ? MIN(c->mon->m.y - c->geom.height, - c->geom.y - c->mon->m.height) + ? MANGO_MIN(c->mon->m.y - c->geom.height, + c->geom.y - c->mon->m.height) : c->animation.current.y; resize(c, c->geom, 0); @@ -98,11 +100,11 @@ void set_tagout_animation(Monitor *m, Client *c) { c->pending = c->geom; c->pending.x = config.tag_animation_direction == VERTICAL ? c->animation.current.x - : MAX(c->mon->m.x + c->mon->m.width, - c->geom.x + c->mon->m.width); + : MANGO_MAX(c->mon->m.x + c->mon->m.width, + c->geom.x + c->mon->m.width); c->pending.y = config.tag_animation_direction == VERTICAL - ? MAX(c->mon->m.y + c->mon->m.height, - c->geom.y + c->mon->m.height) + ? MANGO_MAX(c->mon->m.y + c->mon->m.height, + c->geom.y + c->mon->m.height) : c->animation.current.y; resize(c, c->geom, 0); } diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 12243d76..8903d645 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -256,6 +256,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; @@ -310,6 +311,7 @@ typedef struct { uint32_t gappoh; uint32_t gappov; uint32_t borderpx; + uint32_t tab_bar_height; float scratchpad_width_ratio; float scratchpad_height_ratio; float rootcolor[4]; @@ -386,6 +388,8 @@ typedef struct { struct xkb_context *ctx; struct xkb_keymap *keymap; + DecorateDrawData jumplabeldata; + DecorateDrawData tabdata; } Config; typedef int32_t (*FuncType)(const Arg *); @@ -1009,6 +1013,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); @@ -1701,6 +1708,146 @@ 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, "tab_bar_decorate_font_desc") == 0) { + config->tabdata.font_desc = strdup(value); + } else if (strcmp(key, "tab_bar_decorate_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "tab_bar_decorate_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->tabdata.fg_color, color); + } + } else if (strcmp(key, "tab_bar_decorate_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "tab_bar_decorate_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->tabdata.bg_color, color); + } + } else if (strcmp(key, "tab_bar_decorate_focus_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "tab_bar_decorate_focus_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->tabdata.focus_fg_color, color); + } + } else if (strcmp(key, "tab_bar_decorate_focus_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "tab_bar_decorate_focus_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->tabdata.focus_bg_color, color); + } + } else if (strcmp(key, "tab_bar_decorate_border_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "tab_bar_decorate_border_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->tabdata.border_color, color); + } + } else if (strcmp(key, "tab_bar_decorate_border_width") == 0) { + config->tabdata.border_width = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "tab_bar_decorate_corner_radius") == 0) { + config->tabdata.corner_radius = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "tab_bar_decorate_padding_x") == 0) { + config->tabdata.padding_x = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "tab_bar_decorate_padding_y") == 0) { + config->tabdata.padding_y = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_font_desc") == 0) { + config->jumplabeldata.font_desc = strdup(value); + } else if (strcmp(key, "jump_label_decorate_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.fg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.bg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_focus_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_focus_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.focus_fg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_focus_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_focus_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.focus_bg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_border_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_border_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.border_color, color); + } + } else if (strcmp(key, "jump_label_decorate_border_width") == 0) { + config->jumplabeldata.border_width = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_corner_radius") == 0) { + config->jumplabeldata.corner_radius = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_padding_x") == 0) { + config->jumplabeldata.padding_x = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_padding_y") == 0) { + config->jumplabeldata.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) { @@ -1747,6 +1894,8 @@ bool parse_option(Config *config, char *key, char *value) { config->scratchpad_height_ratio = atof(value); } else if (strcmp(key, "borderpx") == 0) { config->borderpx = atoi(value); + } else if (strcmp(key, "tab_bar_height") == 0) { + config->tab_bar_height = atoi(value); } else if (strcmp(key, "rootcolor") == 0) { int64_t color = parse_color(value); if (color == -1) { @@ -3160,6 +3309,16 @@ void free_config(void) { config.cursor_theme = NULL; } + if (config.jumplabeldata.font_desc) { + free((void *)config.jumplabeldata.font_desc); + config.jumplabeldata.font_desc = NULL; + } + + if (config.tabdata.font_desc) { + free((void *)config.tabdata.font_desc); + config.tabdata.font_desc = NULL; + } + if (config.tablet_map_to_mon) { free(config.tablet_map_to_mon); config.tablet_map_to_mon = NULL; @@ -3321,10 +3480,27 @@ void override_config(void) { config.scratchpad_height_ratio = CLAMP_FLOAT(config.scratchpad_height_ratio, 0.1f, 1.0f); config.borderpx = CLAMP_INT(config.borderpx, 0, 200); + config.tab_bar_height = CLAMP_INT(config.tab_bar_height, 0, 500); config.smartgaps = CLAMP_INT(config.smartgaps, 0, 1); 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.tabdata.border_width = + CLAMP_INT(config.tabdata.border_width, 0, 100); + config.tabdata.corner_radius = + CLAMP_INT(config.tabdata.corner_radius, 0, 100); + config.tabdata.padding_x = CLAMP_INT(config.tabdata.padding_x, 0, 100); + config.tabdata.padding_y = CLAMP_INT(config.tabdata.padding_y, 0, 100); + + config.jumplabeldata.border_width = + CLAMP_INT(config.jumplabeldata.border_width, 0, 100); + config.jumplabeldata.corner_radius = + CLAMP_INT(config.jumplabeldata.corner_radius, 0, 100); + config.jumplabeldata.padding_x = + CLAMP_INT(config.jumplabeldata.padding_x, 0, 100); + config.jumplabeldata.padding_y = + CLAMP_INT(config.jumplabeldata.padding_y, 0, 100); } void set_value_default() { @@ -3363,7 +3539,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; @@ -3413,6 +3588,7 @@ void set_value_default() { config.idleinhibit_ignore_visible = 0; config.borderpx = 4; + config.tab_bar_height = 50; config.overviewgappi = 5; config.overviewgappo = 30; config.cursor_hide_timeout = 0; @@ -3477,6 +3653,56 @@ void set_value_default() { config.animation_curve_opafadeout[2] = 0.5; config.animation_curve_opafadeout[3] = 0.5; + config.tabdata.fg_color[0] = 0xc4 / 255.0f; + config.tabdata.fg_color[1] = 0x93 / 255.0f; + config.tabdata.fg_color[2] = 0x9d / 255.0f; + config.tabdata.fg_color[3] = 1.0f; + config.tabdata.bg_color[0] = 0x32 / 255.0f; + config.tabdata.bg_color[1] = 0x32 / 255.0f; + config.tabdata.bg_color[2] = 0x32 / 255.0f; + config.tabdata.bg_color[3] = 1.0f; + config.tabdata.focus_fg_color[0] = 0xed / 255.0f; + config.tabdata.focus_fg_color[1] = 0xa6 / 255.0f; + config.tabdata.focus_fg_color[2] = 0xb4 / 255.0f; + config.tabdata.focus_fg_color[3] = 1.0f; + config.tabdata.focus_bg_color[0] = 0x4e / 255.0f; + config.tabdata.focus_bg_color[1] = 0x45 / 255.0f; + config.tabdata.focus_bg_color[2] = 0x3c / 255.0f; + config.tabdata.focus_bg_color[3] = 1.0f; + config.tabdata.border_color[0] = 0x8b / 255.0f; + config.tabdata.border_color[1] = 0xaa / 255.0f; + config.tabdata.border_color[2] = 0x9b / 255.0f; + config.tabdata.border_color[3] = 1.0f; + config.tabdata.border_width = 4; + config.tabdata.corner_radius = 5; + config.tabdata.padding_x = 0; + config.tabdata.padding_y = 0; + + config.jumplabeldata.fg_color[0] = 0xc4 / 255.0f; + config.jumplabeldata.fg_color[1] = 0x93 / 255.0f; + config.jumplabeldata.fg_color[2] = 0x9d / 255.0f; + config.jumplabeldata.fg_color[3] = 1.0f; + config.jumplabeldata.bg_color[0] = 0x32 / 255.0f; + config.jumplabeldata.bg_color[1] = 0x32 / 255.0f; + config.jumplabeldata.bg_color[2] = 0x32 / 255.0f; + config.jumplabeldata.bg_color[3] = 1.0f; + config.jumplabeldata.focus_fg_color[0] = 0xed / 255.0f; + config.jumplabeldata.focus_fg_color[1] = 0xa6 / 255.0f; + config.jumplabeldata.focus_fg_color[2] = 0xb4 / 255.0f; + config.jumplabeldata.focus_fg_color[3] = 1.0f; + config.jumplabeldata.focus_bg_color[0] = 0x4e / 255.0f; + config.jumplabeldata.focus_bg_color[1] = 0x45 / 255.0f; + config.jumplabeldata.focus_bg_color[2] = 0x3c / 255.0f; + config.jumplabeldata.focus_bg_color[3] = 1.0f; + config.jumplabeldata.border_color[0] = 0x8b / 255.0f; + config.jumplabeldata.border_color[1] = 0xaa / 255.0f; + config.jumplabeldata.border_color[2] = 0x9b / 255.0f; + config.jumplabeldata.border_color[3] = 1.0f; + config.jumplabeldata.border_width = 4; + config.jumplabeldata.corner_radius = 5; + config.jumplabeldata.padding_x = 10; + config.jumplabeldata.padding_y = 10; + config.rootcolor[0] = 0x32 / 255.0f; config.rootcolor[1] = 0x32 / 255.0f; config.rootcolor[2] = 0x32 / 255.0f; @@ -3589,6 +3815,8 @@ bool parse_config(void) { config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; + config.jumplabeldata.font_desc = NULL; + config.tabdata.font_desc = NULL; config.tablet_map_to_mon = NULL; strcpy(config.keymode, "default"); @@ -3726,6 +3954,10 @@ void reapply_property(void) { c->bw = config.borderpx; } + mango_jump_label_node_apply_config(c->jump_label_node, + &config.jumplabeldata); + mango_tab_bar_node_apply_config(c->tab_bar_node, &config.tabdata); + wlr_scene_rect_set_color(c->droparea, config.dropcolor); wlr_scene_rect_set_color(c->splitindicator[0], config.splitcolor); wlr_scene_rect_set_color(c->splitindicator[1], config.splitcolor); 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 de51c0a0..61762271 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -137,6 +137,7 @@ int32_t focusdir(const Arg *arg) { Client *c = NULL; c = direction_select(arg); + if (!selmon->isoverview) c = get_focused_stack_client(c, arg->tc); if (c) { @@ -265,7 +266,7 @@ int32_t incnmaster(const Arg *arg) { if (!arg || !selmon) return 0; selmon->pertag->nmasters[selmon->pertag->curtag] = - MAX(selmon->pertag->nmasters[selmon->pertag->curtag] + arg->i, 0); + MANGO_MAX(selmon->pertag->nmasters[selmon->pertag->curtag] + arg->i, 0); arrange(selmon, false, false); return 0; } @@ -737,12 +738,12 @@ int32_t smartmovewin(const Arg *arg) { continue; buttom = tc->geom.y + tc->geom.height + config.gappiv; if (top > buttom && ny < buttom) { - tar = MAX(tar, buttom); + tar = MANGO_MAX(tar, buttom); }; } ny = tar == -99999 ? ny : tar; - ny = MAX(ny, c->mon->w.y + c->mon->gappov); + ny = MANGO_MAX(ny, c->mon->w.y + c->mon->gappov); break; case DOWN: tar = 99999; @@ -757,12 +758,12 @@ int32_t smartmovewin(const Arg *arg) { continue; top = tc->geom.y - config.gappiv; if (buttom < top && (ny + c->geom.height) > top) { - tar = MIN(tar, top - c->geom.height); + tar = MANGO_MIN(tar, top - c->geom.height); }; } ny = tar == 99999 ? ny : tar; - ny = MIN(ny, c->mon->w.y + c->mon->w.height - c->geom.height - - c->mon->gappov); + ny = MANGO_MIN(ny, c->mon->w.y + c->mon->w.height - c->geom.height - + c->mon->gappov); break; case LEFT: tar = -99999; @@ -777,12 +778,12 @@ int32_t smartmovewin(const Arg *arg) { continue; right = tc->geom.x + tc->geom.width + config.gappih; if (left > right && nx < right) { - tar = MAX(tar, right); + tar = MANGO_MAX(tar, right); }; } nx = tar == -99999 ? nx : tar; - nx = MAX(nx, c->mon->w.x + c->mon->gappoh); + nx = MANGO_MAX(nx, c->mon->w.x + c->mon->gappoh); break; case RIGHT: tar = 99999; @@ -796,12 +797,12 @@ int32_t smartmovewin(const Arg *arg) { continue; left = tc->geom.x - config.gappih; if (right < left && (nx + c->geom.width) > left) { - tar = MIN(tar, left - c->geom.width); + tar = MANGO_MIN(tar, left - c->geom.width); }; } nx = tar == 99999 ? nx : tar; - nx = MIN(nx, c->mon->w.x + c->mon->w.width - c->geom.width - - c->mon->gappoh); + nx = MANGO_MIN(nx, c->mon->w.x + c->mon->w.width - c->geom.width - + c->mon->gappoh); break; } @@ -829,7 +830,7 @@ int32_t smartresizewin(const Arg *arg) { switch (arg->i) { case UP: nh -= selmon->w.height / 8; - nh = MAX(nh, selmon->w.height / 10); + nh = MANGO_MAX(nh, selmon->w.height / 10); break; case DOWN: tar = -99999; @@ -844,7 +845,7 @@ int32_t smartresizewin(const Arg *arg) { continue; top = tc->geom.y - config.gappiv; if (buttom < top && (nh + c->geom.y) > top) { - tar = MAX(tar, top - c->geom.y); + tar = MANGO_MAX(tar, top - c->geom.y); }; } nh = tar == -99999 ? nh : tar; @@ -853,7 +854,7 @@ int32_t smartresizewin(const Arg *arg) { break; case LEFT: nw -= selmon->w.width / 16; - nw = MAX(nw, selmon->w.width / 10); + nw = MANGO_MAX(nw, selmon->w.width / 10); break; case RIGHT: tar = 99999; @@ -867,7 +868,7 @@ int32_t smartresizewin(const Arg *arg) { continue; left = tc->geom.x - config.gappih; if (right < left && (nw + c->geom.x) > left) { - tar = MIN(tar, left - c->geom.x); + tar = MANGO_MIN(tar, left - c->geom.x); }; } @@ -1064,7 +1065,7 @@ int32_t switch_layout(const Arg *arg) { if (config.circle_layout_count != 0) { for (jk = 0; jk < config.circle_layout_count; jk++) { - len = MAX( + len = MANGO_MAX( strlen(config.circle_layout[jk]), strlen(selmon->pertag->ltidxs[selmon->pertag->curtag]->name)); @@ -1083,7 +1084,8 @@ int32_t switch_layout(const Arg *arg) { } for (ji = 0; ji < LENGTH(layouts); ji++) { - len = MAX(strlen(layouts[ji].name), strlen(target_layout_name)); + len = + MANGO_MAX(strlen(layouts[ji].name), strlen(target_layout_name)); if (strncmp(layouts[ji].name, target_layout_name, len) == 0) { selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[ji]; @@ -1738,6 +1740,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) && @@ -1782,6 +1788,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 && @@ -1795,6 +1802,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..c85b25dc --- /dev/null +++ b/src/draw/text-node.c @@ -0,0 +1,845 @@ +#include "text-node.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +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; + } +} + +static void text_buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base); + 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_jump_label_node * +mango_jump_label_node_create(struct wlr_scene_tree *parent, + DecorateDrawData data) { + struct mango_jump_label_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; + } + + memcpy(node->fg_color, data.fg_color, sizeof(node->fg_color)); + memcpy(node->bg_color, data.bg_color, sizeof(node->bg_color)); + memcpy(node->focus_fg_color, data.focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->focus_bg_color, data.focus_bg_color, + sizeof(node->focus_bg_color)); + memcpy(node->border_color, data.border_color, sizeof(node->border_color)); + 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 = + g_strdup(data.font_desc ? data.font_desc : "monospace Bold 16"); + + node->cached_text = NULL; + node->cached_scale = -1.0f; + node->cached_font_desc = NULL; + node->focused = false; + node->cached_focused = false; + + node->measure_surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + node->measure_cr = cairo_create(node->measure_surface); + node->measure_context = pango_cairo_create_context(node->measure_cr); + node->measure_layout = pango_layout_new(node->measure_context); + node->measure_scale = 1.0f; + + node->scene_buffer->node.data = NULL; + + return node; +} + +void mango_jump_label_node_destroy(struct mango_jump_label_node *node) { + if (!node) + return; + + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + + if (node->measure_layout) + g_object_unref(node->measure_layout); + if (node->measure_context) + g_object_unref(node->measure_context); + if (node->measure_cr) + cairo_destroy(node->measure_cr); + if (node->measure_surface) + cairo_surface_destroy(node->measure_surface); + + wlr_scene_node_destroy(&node->scene_buffer->node); + + g_free(node->font_desc); + g_free(node->cached_text); + g_free(node->cached_font_desc); + + free(node); +} + +void mango_jump_label_node_set_background(struct mango_jump_label_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_jump_label_node_set_border(struct mango_jump_label_node *node, + float r, float g, float b, float a, + int32_t width, int32_t 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 ? width : 0; + node->corner_radius = radius; +} + +void mango_jump_label_node_set_padding(struct mango_jump_label_node *node, + int32_t pad_x, int32_t pad_y) { + if (!node) + return; + node->padding_x = pad_x >= 0 ? pad_x : 0; + node->padding_y = pad_y >= 0 ? pad_y : 0; +} + +static void get_text_pixel_size(struct mango_jump_label_node *node, + const char *text, float scale, int32_t *out_w, + int32_t *out_h) { + if (node->measure_scale != scale) { + pango_cairo_context_set_resolution(node->measure_context, 96.0 * scale); + node->measure_scale = scale; + } + + PangoFontDescription *desc = get_cached_font_desc(node->font_desc); + pango_layout_set_font_description(node->measure_layout, desc); + pango_layout_set_text(node->measure_layout, text, -1); + + pango_layout_get_pixel_size(node->measure_layout, out_w, out_h); +} + +static void draw_rounded_rect(cairo_t *cr, double x, double y, double w, + double h, double r) { + 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_jump_label_node_update(struct mango_jump_label_node *node, + const char *text, float scale) { + if (!node || !text) + return; + if (scale <= 0.0f) + scale = 1.0f; + + /* 脏检查,加入 focused 状态 */ + if (node->cached_scale == scale && node->cached_font_desc && + strcmp(node->cached_font_desc, node->font_desc) == 0 && + node->cached_text && strcmp(node->cached_text, text) == 0 && + memcmp(node->cached_fg_color, node->fg_color, sizeof(node->fg_color)) == + 0 && + memcmp(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)) == + 0 && + memcmp(node->cached_focus_fg_color, node->focus_fg_color, + sizeof(node->focus_fg_color)) == 0 && + memcmp(node->cached_focus_bg_color, node->focus_bg_color, + sizeof(node->focus_bg_color)) == 0 && + memcmp(node->cached_border_color, node->border_color, + sizeof(node->border_color)) == 0 && + node->cached_border_width == node->border_width && + node->cached_corner_radius == node->corner_radius && + node->cached_padding_x == node->padding_x && + node->cached_padding_y == node->padding_y && + node->cached_focused == node->focused) { + return; + } + + /* 更新缓存 */ + g_free(node->cached_text); + node->cached_text = g_strdup(text); + g_free(node->cached_font_desc); + node->cached_font_desc = g_strdup(node->font_desc); + node->cached_scale = scale; + memcpy(node->cached_fg_color, node->fg_color, sizeof(node->fg_color)); + memcpy(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)); + memcpy(node->cached_focus_fg_color, node->focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->cached_focus_bg_color, node->focus_bg_color, + sizeof(node->focus_bg_color)); + memcpy(node->cached_border_color, node->border_color, + sizeof(node->border_color)); + node->cached_border_width = node->border_width; + node->cached_corner_radius = node->corner_radius; + node->cached_padding_x = node->padding_x; + node->cached_padding_y = node->padding_y; + node->cached_focused = node->focused; + + int32_t text_pixel_w, text_pixel_h; + get_text_pixel_size(node, text, scale, &text_pixel_w, &text_pixel_h); + + if (text_pixel_w <= 0 || text_pixel_h <= 0) { + wlr_scene_buffer_set_buffer(node->scene_buffer, NULL); + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + node->logical_width = 0; + node->logical_height = 0; + wlr_scene_buffer_set_dest_size(node->scene_buffer, 0, 0); + return; + } + + int32_t logical_text_w = (int32_t)(text_pixel_w / scale + 0.5f); + int32_t logical_text_h = (int32_t)(text_pixel_h / scale + 0.5f); + int32_t box_logical_w = logical_text_w + 2 * node->padding_x; + int32_t box_logical_h = logical_text_h + 2 * node->padding_y; + + int32_t required_pixel_w = + (int32_t)((box_logical_w + 2 * node->border_width) * scale + 0.5f); + int32_t required_pixel_h = + (int32_t)((box_logical_h + 2 * node->border_width) * scale + 0.5f); + if (required_pixel_w < 1) + required_pixel_w = 1; + if (required_pixel_h < 1) + required_pixel_h = 1; + + bool surface_size_changed = (!node->surface) || + (node->surface_pixel_w != required_pixel_w) || + (node->surface_pixel_h != required_pixel_h); + + if (surface_size_changed) { + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + + node->surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, required_pixel_w, required_pixel_h); + node->surface_pixel_w = required_pixel_w; + node->surface_pixel_h = required_pixel_h; + } + + cairo_t *cr = cairo_create(node->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 border = node->border_width * scale; + double bg_x = border; + double bg_y = border; + double bg_w = box_logical_w * scale; + double bg_h = box_logical_h * scale; + + double radius; + if (node->corner_radius < 0) { + radius = (bg_w < bg_h ? bg_w : bg_h) / 2.0; + } else { + radius = node->corner_radius * scale; + } + if (radius > bg_w / 2.0) + radius = bg_w / 2.0; + if (radius > bg_h / 2.0) + radius = bg_h / 2.0; + + const float *active_bg = + node->focused ? node->focus_bg_color : node->bg_color; + const float *active_fg = + node->focused ? node->focus_fg_color : node->fg_color; + + bool draw_bg = (active_bg[3] > 0.0f); // 使用 active_bg + bool draw_border = + (node->border_width > 0) && (node->border_color[3] > 0.0f); + + if (draw_bg) { + cairo_set_source_rgba(cr, active_bg[0], active_bg[1], active_bg[2], + active_bg[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); + double text_x = (node->border_width + node->padding_x) * scale; + double text_y = (node->border_width + node->padding_y) * scale; + cairo_translate(cr, text_x, text_y); + + PangoContext *ctx = pango_cairo_create_context(cr); + pango_cairo_context_set_resolution(ctx, 96.0 * scale); + PangoLayout *layout = pango_layout_new(ctx); + PangoFontDescription *desc = get_cached_font_desc(node->font_desc); + pango_layout_set_font_description(layout, desc); + pango_layout_set_text(layout, text, -1); + + cairo_set_source_rgba(cr, active_fg[0], active_fg[1], active_fg[2], + active_fg[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); + + double half_lw = border * 0.5; + double bx = bg_x - half_lw; + double by = bg_y - half_lw; + double bw = bg_w + border; + double bh = bg_h + border; + + 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); + } else { + cairo_rectangle(cr, bx, by, bw, bh); + } + cairo_stroke(cr); + } + + cairo_surface_flush(node->surface); + cairo_destroy(cr); + + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + + struct mango_text_buffer *buf = calloc(1, sizeof(*buf)); + if (!buf) { + return; + } + wlr_buffer_init(&buf->base, &text_buffer_impl, node->surface_pixel_w, + node->surface_pixel_h); + buf->surface = node->surface; + node->buffer = buf; + + wlr_scene_buffer_set_buffer(node->scene_buffer, &buf->base); + + node->logical_width = box_logical_w + 2 * node->border_width; + node->logical_height = box_logical_h + 2 * node->border_width; + wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width, + node->logical_height); +} + +void mango_jump_label_node_set_focus(struct mango_jump_label_node *node, + bool focused) { + if (!node || node->focused == focused) + return; + node->focused = focused; + // 使用缓存的文本和缩放触发重绘(如果无文本则不重绘) + if (node->cached_text && node->cached_scale > 0.0f) { + mango_jump_label_node_update(node, node->cached_text, + node->cached_scale); + } +} + +struct mango_tab_bar_node * +mango_tab_bar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, + DecorateDrawData data, int32_t width, + int32_t height) { + struct mango_tab_bar_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; + } + + memcpy(node->fg_color, data.fg_color, sizeof(node->fg_color)); + memcpy(node->bg_color, data.bg_color, sizeof(node->bg_color)); + memcpy(node->focus_fg_color, data.focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->focus_bg_color, data.focus_bg_color, + sizeof(node->focus_bg_color)); + memcpy(node->border_color, data.border_color, sizeof(node->border_color)); + 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 = + g_strdup(data.font_desc ? data.font_desc : "monospace Bold 16"); + + node->target_width = width; + node->target_height = height; + node->focused = false; + node->cached_focused = false; + + node->measure_surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + node->measure_cr = cairo_create(node->measure_surface); + node->measure_context = pango_cairo_create_context(node->measure_cr); + node->measure_layout = pango_layout_new(node->measure_context); + node->measure_scale = 1.0f; + + node->cached_scale = -1.0f; + node->last_text = NULL; + node->last_scale = 0.0f; + node->scene_buffer->node.data = mango_node_data; + + return node; +} + +void mango_tab_bar_node_destroy(struct mango_tab_bar_node *node) { + if (!node) + return; + + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + if (node->measure_surface) { + cairo_surface_destroy(node->measure_surface); + node->measure_surface = NULL; + } + + if (node->measure_layout) + g_object_unref(node->measure_layout); + if (node->measure_context) + g_object_unref(node->measure_context); + if (node->measure_cr) + cairo_destroy(node->measure_cr); + + void *data = node->scene_buffer->node.data; + wlr_scene_node_destroy(&node->scene_buffer->node); + + g_free(node->font_desc); + g_free(node->cached_text); + g_free(node->cached_font_desc); + g_free(node->last_text); + free(data); + free(node); +} + +void mango_tab_bar_node_set_size(struct mango_tab_bar_node *node, int32_t width, + int32_t height) { + if (!node) + return; + + if (width < 0) + width = 0; + if (height < 0) + height = 0; + + if (node->target_width == width && node->target_height == height) + return; + + node->target_width = width; + node->target_height = height; + + const char *redraw_text = node->last_text ? node->last_text : ""; + float redraw_scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; + + mango_tab_bar_node_update(node, redraw_text, redraw_scale); +} + +void mango_tab_bar_node_update(struct mango_tab_bar_node *node, + const char *text, float scale) { + if (!node || !text) + return; + if (scale <= 0.0f) + scale = 1.0f; + + char *safe_text = g_strdup(text); + + g_free(node->last_text); + node->last_text = safe_text; // 所有权转移 + node->last_scale = scale; + + // 脏检查加入 focused + if (node->cached_scale == scale && node->cached_font_desc && + strcmp(node->cached_font_desc, node->font_desc) == 0 && + node->cached_text && strcmp(node->cached_text, safe_text) == 0 && + memcmp(node->cached_fg_color, node->fg_color, sizeof(node->fg_color)) == + 0 && + memcmp(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)) == + 0 && + memcmp(node->cached_focus_fg_color, node->focus_fg_color, + sizeof(node->focus_fg_color)) == 0 && + memcmp(node->cached_focus_bg_color, node->focus_bg_color, + sizeof(node->focus_bg_color)) == 0 && + memcmp(node->cached_border_color, node->border_color, + sizeof(node->border_color)) == 0 && + node->cached_border_width == node->border_width && + node->cached_corner_radius == node->corner_radius && + node->cached_padding_x == node->padding_x && + node->cached_padding_y == node->padding_y && + node->cached_target_width == node->target_width && + node->cached_target_height == node->target_height && + node->cached_focused == node->focused) { + return; + } + + // 更新缓存 + g_free(node->cached_text); + node->cached_text = g_strdup(safe_text); + + g_free(node->cached_font_desc); + node->cached_font_desc = g_strdup(node->font_desc); + node->cached_scale = scale; + memcpy(node->cached_fg_color, node->fg_color, sizeof(node->fg_color)); + memcpy(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)); + memcpy(node->cached_focus_fg_color, node->focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->cached_focus_bg_color, node->focus_bg_color, + sizeof(node->focus_bg_color)); + memcpy(node->cached_border_color, node->border_color, + sizeof(node->border_color)); + node->cached_border_width = node->border_width; + node->cached_corner_radius = node->corner_radius; + node->cached_padding_x = node->padding_x; + node->cached_padding_y = node->padding_y; + node->cached_target_width = node->target_width; + node->cached_target_height = node->target_height; + node->cached_focused = node->focused; + + if (node->target_width <= 0 || node->target_height <= 0) { + wlr_scene_buffer_set_buffer(node->scene_buffer, NULL); + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + node->logical_width = 0; + node->logical_height = 0; + wlr_scene_buffer_set_dest_size(node->scene_buffer, 0, 0); + return; + } + + int32_t box_logical_w = node->target_width - 2 * node->border_width; + int32_t box_logical_h = node->target_height - 2 * node->border_width; + if (box_logical_w < 0) + box_logical_w = 0; + if (box_logical_h < 0) + box_logical_h = 0; + + int32_t required_pixel_w = (int32_t)(node->target_width * scale + 0.5f); + int32_t required_pixel_h = (int32_t)(node->target_height * scale + 0.5f); + if (required_pixel_w < 1) + required_pixel_w = 1; + if (required_pixel_h < 1) + required_pixel_h = 1; + + bool surface_size_changed = (!node->surface) || + (node->surface_pixel_w != required_pixel_w) || + (node->surface_pixel_h != required_pixel_h); + + if (surface_size_changed) { + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + node->surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, required_pixel_w, required_pixel_h); + node->surface_pixel_w = required_pixel_w; + node->surface_pixel_h = required_pixel_h; + } + + cairo_t *cr = cairo_create(node->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 border_phys = node->border_width * scale; + double bg_x = border_phys; + double bg_y = border_phys; + double bg_w = box_logical_w * scale; + double bg_h = box_logical_h * scale; + + double radius; + if (node->corner_radius < 0) { + radius = (bg_w < bg_h ? bg_w : bg_h) / 2.0; + } else { + radius = node->corner_radius * scale; + } + if (radius > bg_w / 2.0) + radius = bg_w / 2.0; + if (radius > bg_h / 2.0) + radius = bg_h / 2.0; + + const float *active_bg = + node->focused ? node->focus_bg_color : node->bg_color; + const float *active_fg = + node->focused ? node->focus_fg_color : node->fg_color; + + bool draw_bg = (active_bg[3] > 0.0f); + bool draw_border = + (node->border_width > 0) && (node->border_color[3] > 0.0f); + + if (draw_bg) { + cairo_set_source_rgba(cr, active_bg[0], active_bg[1], active_bg[2], + active_bg[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); + } + } + + int32_t text_area_logical_w = box_logical_w - 2 * node->padding_x; + int32_t text_area_logical_h = box_logical_h - 2 * node->padding_y; + if (text_area_logical_w > 0 && text_area_logical_h > 0) { + cairo_save(cr); + + double text_x = (node->border_width + node->padding_x) * scale; + double text_y = (node->border_width + node->padding_y) * scale; + double text_area_w = text_area_logical_w * scale; + double text_area_h = text_area_logical_h * scale; + + PangoContext *ctx = pango_cairo_create_context(cr); + pango_cairo_context_set_resolution(ctx, 96.0 * scale); + PangoLayout *layout = pango_layout_new(ctx); + PangoFontDescription *desc = get_cached_font_desc(node->font_desc); + pango_layout_set_font_description(layout, desc); + pango_layout_set_text(layout, safe_text, -1); + + pango_layout_set_wrap(layout, PANGO_WRAP_NONE); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); + pango_layout_set_width(layout, (int)(text_area_w * PANGO_SCALE)); + + int text_pixel_w, text_pixel_h; + pango_layout_get_pixel_size(layout, &text_pixel_w, &text_pixel_h); + double y_offset = (text_area_h - text_pixel_h) / 2.0; + if (y_offset < 0) + y_offset = 0; + + cairo_translate(cr, text_x, text_y + y_offset); + + cairo_set_source_rgba(cr, active_fg[0], active_fg[1], active_fg[2], + active_fg[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_phys); + + double half_lw = border_phys * 0.5; + double bx = bg_x - half_lw; + double by = bg_y - half_lw; + double bw = bg_w + border_phys; + double bh = bg_h + border_phys; + + 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); + } else { + cairo_rectangle(cr, bx, by, bw, bh); + } + cairo_stroke(cr); + } + + cairo_surface_flush(node->surface); + cairo_destroy(cr); + + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + + struct mango_text_buffer *buf = calloc(1, sizeof(*buf)); + if (!buf) + return; + + wlr_buffer_init(&buf->base, &text_buffer_impl, node->surface_pixel_w, + node->surface_pixel_h); + buf->surface = node->surface; + node->buffer = buf; + + wlr_scene_buffer_set_buffer(node->scene_buffer, &buf->base); + + node->logical_width = node->target_width; + node->logical_height = node->target_height; + wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width, + node->logical_height); +} + +void mango_tab_bar_node_set_focus(struct mango_tab_bar_node *node, + bool focused) { + if (!node || node->focused == focused) + return; + node->focused = focused; + if (node->last_text) { + float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; + mango_tab_bar_node_update(node, node->last_text, scale); + } +} + +void mango_tab_bar_node_set_colors(struct mango_tab_bar_node *node, + const float fg[4], const float bg[4]) { + if (!node) + return; + + memcpy(node->fg_color, fg, sizeof(node->fg_color)); + memcpy(node->bg_color, bg, sizeof(node->bg_color)); + + if (!node->focused && node->last_text) { + float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; + mango_tab_bar_node_update(node, node->last_text, scale); + } +} + +void mango_jump_label_node_apply_config(struct mango_jump_label_node *node, + const DecorateDrawData *data) { + if (!node || !data) + return; + + memcpy(node->fg_color, data->fg_color, sizeof(node->fg_color)); + memcpy(node->bg_color, data->bg_color, sizeof(node->bg_color)); + memcpy(node->focus_fg_color, data->focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->focus_bg_color, data->focus_bg_color, + sizeof(node->focus_bg_color)); + memcpy(node->border_color, data->border_color, sizeof(node->border_color)); + node->border_width = data->border_width; + node->corner_radius = data->corner_radius; + node->padding_x = data->padding_x; + node->padding_y = data->padding_y; + + g_free(node->font_desc); + node->font_desc = + g_strdup(data->font_desc ? data->font_desc : "monospace Bold 16"); + + if (node->cached_text && node->cached_scale > 0.0f) { + mango_jump_label_node_update(node, node->cached_text, + node->cached_scale); + } +} + +void mango_tab_bar_node_apply_config(struct mango_tab_bar_node *node, + const DecorateDrawData *data) { + if (!node || !data) + return; + + memcpy(node->fg_color, data->fg_color, sizeof(node->fg_color)); + memcpy(node->bg_color, data->bg_color, sizeof(node->bg_color)); + memcpy(node->focus_fg_color, data->focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->focus_bg_color, data->focus_bg_color, + sizeof(node->focus_bg_color)); + memcpy(node->border_color, data->border_color, sizeof(node->border_color)); + node->border_width = data->border_width; + node->corner_radius = data->corner_radius; + node->padding_x = data->padding_x; + node->padding_y = data->padding_y; + + g_free(node->font_desc); + node->font_desc = + g_strdup(data->font_desc ? data->font_desc : "monospace Bold 16"); + + if (node->last_text) { + float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; + mango_tab_bar_node_update(node, node->last_text, scale); + } +} \ 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..079af908 --- /dev/null +++ b/src/draw/text-node.h @@ -0,0 +1,166 @@ +#ifndef jump_label_node_H +#define jump_label_node_H + +#include +#include +#include +#include +#include +#include + +// 原有结构体,假设已存在 +typedef struct { + float fg_color[4]; + float bg_color[4]; + float focus_fg_color[4]; + float focus_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; +} DecorateDrawData; + +struct mango_text_buffer { + struct wlr_buffer base; + cairo_surface_t *surface; +}; + +struct mango_jump_label_node { + struct wlr_scene_buffer *scene_buffer; + struct mango_text_buffer *buffer; + cairo_surface_t *surface; + int surface_pixel_w, surface_pixel_h; + + float fg_color[4]; + float bg_color[4]; + float focus_fg_color[4]; + float focus_bg_color[4]; + float border_color[4]; + int32_t border_width; + int32_t corner_radius; + int32_t padding_x; + int32_t padding_y; + char *font_desc; + + // 缓存 + char *cached_text; + char *cached_font_desc; + float cached_scale; + float cached_fg_color[4]; + float cached_bg_color[4]; + float cached_focus_fg_color[4]; + float cached_focus_bg_color[4]; + float cached_border_color[4]; + int32_t cached_border_width; + int32_t cached_corner_radius; + int32_t cached_padding_x; + int32_t cached_padding_y; + bool cached_focused; + + bool focused; + + // 测量 + cairo_surface_t *measure_surface; + cairo_t *measure_cr; + PangoContext *measure_context; + PangoLayout *measure_layout; + float measure_scale; + + int32_t logical_width; + int32_t logical_height; +}; + +struct mango_tab_bar_node { + struct wlr_scene_buffer *scene_buffer; + struct mango_text_buffer *buffer; + cairo_surface_t *surface; + int surface_pixel_w, surface_pixel_h; + + // 初始配置 + float fg_color[4]; + float bg_color[4]; + float focus_fg_color[4]; + float focus_bg_color[4]; + float border_color[4]; + int32_t border_width; + int32_t corner_radius; + int32_t padding_x; + int32_t padding_y; + char *font_desc; + + // 尺寸 + int32_t target_width; + int32_t target_height; + + // 缓存 + char *cached_text; + char *cached_font_desc; + float cached_scale; + float cached_fg_color[4]; + float cached_bg_color[4]; + float cached_focus_fg_color[4]; + float cached_focus_bg_color[4]; + float cached_border_color[4]; + int32_t cached_border_width; + int32_t cached_corner_radius; + int32_t cached_padding_x; + int32_t cached_padding_y; + int32_t cached_target_width; + int32_t cached_target_height; + bool cached_focused; + + bool focused; + + // 上次绘制参数(用于尺寸变化重绘) + char *last_text; + float last_scale; + + // 测量 + cairo_surface_t *measure_surface; + cairo_t *measure_cr; + PangoContext *measure_context; + PangoLayout *measure_layout; + float measure_scale; + + int32_t logical_width; + int32_t logical_height; +}; + +void mango_text_global_finish(void); +struct mango_jump_label_node * +mango_jump_label_node_create(struct wlr_scene_tree *parent, + DecorateDrawData data); +void mango_jump_label_node_destroy(struct mango_jump_label_node *node); +void mango_jump_label_node_set_background(struct mango_jump_label_node *node, + float r, float g, float b, float a); +void mango_jump_label_node_set_border(struct mango_jump_label_node *node, + float r, float g, float b, float a, + int32_t width, int32_t radius); +void mango_jump_label_node_set_padding(struct mango_jump_label_node *node, + int32_t pad_x, int32_t pad_y); +void mango_jump_label_node_update(struct mango_jump_label_node *node, + const char *text, float scale); + +struct mango_tab_bar_node * +mango_tab_bar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, + DecorateDrawData data, int32_t width, int32_t height); +void mango_tab_bar_node_destroy(struct mango_tab_bar_node *node); +void mango_tab_bar_node_set_size(struct mango_tab_bar_node *node, int32_t width, + int32_t height); +void mango_tab_bar_node_update(struct mango_tab_bar_node *node, + const char *text, float scale); + +void mango_jump_label_node_set_focus(struct mango_jump_label_node *node, + bool focused); +void mango_tab_bar_node_set_focus(struct mango_tab_bar_node *node, + bool focused); + +void mango_tab_bar_node_set_colors(struct mango_tab_bar_node *node, + const float fg[4], const float bg[4]); +void mango_jump_label_node_apply_config(struct mango_jump_label_node *node, + const DecorateDrawData *data); +void mango_tab_bar_node_apply_config(struct mango_tab_bar_node *node, + const DecorateDrawData *data); +#endif // jump_label_node_H \ No newline at end of file diff --git a/src/fetch/client.h b/src/fetch/client.h index fb50c798..62bc14fe 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -188,8 +188,12 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, continue; if (!findfloating && c->isfloating) continue; + if (c->is_monocle_hide) + continue; if (c->isunglobal) continue; + if (c->is_monocle_hide) + continue; if (!config.focus_cross_monitor && c->mon != tc->mon) continue; if (!(c->tags & c->mon->tagset[c->mon->seltags])) @@ -440,7 +444,7 @@ Client *get_focused_stack_client(Client *sc, Client *custom_focus_client) { return sc; wl_list_for_each(tc, &fstack, flink) { - if (tc->iskilling || tc->isunglobal) + if (tc->iskilling || tc->isunglobal || tc->is_monocle_hide) continue; if (!VISIBLEON(tc, sc->mon)) continue; diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h index 6144ee40..c5a5869a 100644 --- a/src/fetch/monitor.h +++ b/src/fetch/monitor.h @@ -26,6 +26,14 @@ bool is_scroller_layout(Monitor *m) { return false; } +bool is_monocle_layout(Monitor *m) { + + if (m->pertag->ltidxs[m->pertag->curtag]->id == MONOCLE) + return true; + + return false; +} + bool is_centertile_layout(Monitor *m) { if (m->pertag->ltidxs[m->pertag->curtag]->id == CENTER_TILE) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index f1c97f3a..4782b3ea 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -37,6 +37,21 @@ void set_size_per(Monitor *m, Client *c) { } } +void monocle_set_focus(Client *c, bool focused) { + + if (!c || !c->mon) + return; + + c->is_monocle_hide = !focused; + mango_tab_bar_node_set_focus(c->tab_bar_node, focused); + wlr_scene_node_set_enabled(&c->scene->node, focused); + + if (!focused) { + c->animation.current = c->animainit_geom = c->animation.initial = + c->pending = c->current = c->geom; + } +} + void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, int32_t offsety, uint32_t time, int32_t type) { @@ -1128,6 +1143,20 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, set_size_per(m, c); } + if (m->is_jump_mode && !c->jump_label_node) { + client_add_jump_label_node(c); + } + + if (m->pertag->ltidxs[m->pertag->curtag]->id == MONOCLE && + !c->tab_bar_node) { + client_add_tab_bar_node(c); + } + + if (c->tab_bar_node && c->mon == m) { + wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, + false); + } + if (c->mon == m && (c->isglobal || c->isunglobal)) { c->tags = m->tagset[m->seltags]; } diff --git a/src/layout/dwindle.h b/src/layout/dwindle.h index 9562b5d8..5e56a6bf 100644 --- a/src/layout/dwindle.h +++ b/src/layout/dwindle.h @@ -259,7 +259,8 @@ static void dwindle_assign(DwindleNode *node, int32_t ax, int32_t ay, if (node->client) { if (!node->client->isfullscreen && !node->client->ismaximizescreen) { - struct wlr_box box = {ax, ay, MAX(1, aw), MAX(1, ah)}; + struct wlr_box box = {ax, ay, MANGO_MAX(1, aw), + MANGO_MAX(1, ah)}; resize(node->client, box, 0); } } @@ -273,12 +274,12 @@ static void dwindle_assign(DwindleNode *node, int32_t ax, int32_t ay, node->container_w = aw; node->container_h = ah; if (node->split_h) { - int32_t w1 = MAX(1, (int32_t)(aw * node->ratio) - gap_h / 2); + int32_t w1 = MANGO_MAX(1, (int32_t)(aw * node->ratio) - gap_h / 2); dwindle_assign(node->first, ax, ay, w1, ah, gap_h, gap_v); dwindle_assign(node->second, ax + w1 + gap_h, ay, aw - w1 - gap_h, ah, gap_h, gap_v); } else { - int32_t h1 = MAX(1, (int32_t)(ah * node->ratio) - gap_v / 2); + int32_t h1 = MANGO_MAX(1, (int32_t)(ah * node->ratio) - gap_v / 2); dwindle_assign(node->first, ax, ay, aw, h1, gap_h, gap_v); dwindle_assign(node->second, ax, ay + h1 + gap_v, aw, ah - h1 - gap_v, gap_h, gap_v); @@ -357,7 +358,7 @@ static void dwindle_resize_client(Monitor *m, Client *c) { return; if (dwindle_locked_h_node) { - float cw = (float)MAX(1, dwindle_locked_h_node->container_w); + float cw = (float)MANGO_MAX(1, dwindle_locked_h_node->container_w); float ox = (float)(cursor->x - drag_begin_cursorx); if (config.dwindle_smart_resize) { /* Move the boundary toward the cursor: invert direction when @@ -374,7 +375,7 @@ static void dwindle_resize_client(Monitor *m, Client *c) { } if (dwindle_locked_v_node) { - float ch = (float)MAX(1, dwindle_locked_v_node->container_h); + float ch = (float)MANGO_MAX(1, dwindle_locked_v_node->container_h); float oy = (float)(cursor->y - drag_begin_cursory); if (config.dwindle_smart_resize) { /* Same logic for the vertical split line. */ @@ -427,13 +428,13 @@ static void dwindle_resize_client_step(Monitor *m, Client *c, int32_t dx, return; if (h_node && dx) { - float cw = (float)MAX(1, h_node->container_w); + float cw = (float)MANGO_MAX(1, h_node->container_w); float delta = (float)dx / cw; h_node->ratio = CLAMP_FLOAT(h_node->ratio + delta, 0.05f, 0.95f); } if (v_node && dy) { - float ch = (float)MAX(1, v_node->container_h); + float ch = (float)MANGO_MAX(1, v_node->container_h); float delta = (float)dy / ch; v_node->ratio = CLAMP_FLOAT(v_node->ratio + delta, 0.05f, 0.95f); } diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 419e218a..96c0ed54 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -62,7 +62,7 @@ void tile(Monitor *m) { if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < m->pertag->nmasters[m->pertag->curtag]) { - r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; + r = MANGO_MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; if (c->master_inner_per > 0.0f) { h = master_surplus_height * c->master_inner_per / master_surplus_ratio; @@ -179,7 +179,7 @@ void right_tile(Monitor *m) { if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < m->pertag->nmasters[m->pertag->curtag]) { - r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; + r = MANGO_MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; if (c->master_inner_per > 0.0f) { h = master_surplus_height * c->master_inner_per / master_surplus_ratio; @@ -345,7 +345,7 @@ void center_tile(Monitor *m) { if (i < nmasters) { // 主区域窗口 - r = MIN(n, nmasters) - i; + r = MANGO_MIN(n, nmasters) - i; if (c->master_inner_per > 0.0f) { h = master_surplus_height * c->master_inner_per / master_surplus_ratio; @@ -518,8 +518,8 @@ void deck(Monitor *m) { continue; if (i < nmasters) { c->master_mfact_per = mfact; - int32_t h = - (m->w.height - 2 * cur_gappov - my) / (MIN(n, nmasters) - i); + int32_t h = (m->w.height - 2 * cur_gappov - my) / + (MANGO_MIN(n, nmasters) - i); client_tile_resize(c, (struct wlr_box){.x = m->w.x + cur_gappoh, .y = m->w.y + cur_gappov + my, @@ -545,32 +545,83 @@ void deck(Monitor *m) { } } -void // 17 -monocle(Monitor *m) { - Client *c = NULL; +void monocle(Monitor *m) { + Client *c = NULL, *fc = NULL; struct wlr_box geom; - int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + int32_t cur_gapiv = enablegaps ? m->gappiv : 0; + int32_t cur_gapih = enablegaps ? m->gappih : 0; - cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappoh; - cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappov; + if (config.smartgaps && m->visible_fake_tiling_clients == 1) { + cur_gappov = cur_gappoh = cur_gapiv = cur_gapih = 0; + } - wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISFAKETILED(c)) + int n = m->visible_fake_tiling_clients; + if (n == 0) + return; + + wl_list_for_each(c, &fstack, flink) { + if (c->iskilling || c->isunglobal || !ISFAKETILED(c)) continue; + if (VISIBLEON(c, m)) { + fc = c; + break; + } + } + + if (n == 1) { geom.x = m->w.x + cur_gappoh; geom.y = m->w.y + cur_gappov; geom.width = m->w.width - 2 * cur_gappoh; geom.height = m->w.height - 2 * cur_gappov; - client_tile_resize(c, geom, 0); + client_tile_resize(fc, geom, 0); + monocle_set_focus(fc, true); + return; + } + + int tab_bar_height = config.tab_bar_height; + + int tab_bar_inner_gap_height = + config.tab_bar_height > 0 ? 2 * cur_gapiv : 0; + int tab_bar_y_offset = config.tab_bar_height > 0 ? cur_gapiv : 0; + + int tab_y = m->w.y + cur_gappov; + int main_y = tab_y + tab_bar_height + tab_bar_y_offset; + int main_height = m->w.height - 2 * cur_gappov - tab_bar_inner_gap_height - + tab_bar_height; + + int tab_area_width = m->w.width - 2 * cur_gappoh; + + int total_gaps = (n - 1) * cur_gapih; + int base_width = (tab_area_width - total_gaps) / n; + int remainder = (tab_area_width - total_gaps) % n; + + int tab_x = m->w.x + cur_gappoh; + int idx = 0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) + continue; + + if (c == fc) { + monocle_set_focus(c, true); + } else { + monocle_set_focus(c, false); + } + + geom.x = m->w.x + cur_gappoh; + geom.y = main_y; + geom.width = m->w.width - 2 * cur_gappoh; + geom.height = main_height; + client_tile_resize(c, geom, 0); + + int tw = base_width + (idx < remainder ? 1 : 0); + global_draw_tab_bar(c, tab_x, tab_y, tw, tab_bar_height); + + tab_x += tw + cur_gapih; + idx++; } - if ((c = focustop(m))) - wlr_scene_node_raise_to_top(&c->scene->node); } // 网格布局窗口大小和位置计算 diff --git a/src/layout/overview.h b/src/layout/overview.h index 88fc4258..1ae9e7a0 100644 --- a/src/layout/overview.h +++ b/src/layout/overview.h @@ -353,10 +353,66 @@ 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) && !c->isunglobal && !client_is_x11_popup(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_jump_label_node_update(c->jump_label_node, label_text, 1.0f); + wlr_scene_node_set_enabled(&c->jump_label_node->scene_buffer->node, + true); + wlr_scene_node_raise_to_top( + &c->jump_label_node->scene_buffer->node); + wlr_scene_node_set_position( + &c->jump_label_node->scene_buffer->node, + c->geom.width / 2 - c->jump_label_node->logical_width / 2, + c->geom.height / 2 - c->jump_label_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->jump_label_node->scene_buffer->node.enabled) { + c->jump_char = '\0'; + wlr_scene_node_set_enabled( + &c->jump_label_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/layout/vertical.h b/src/layout/vertical.h index b1de212e..6c018399 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -58,7 +58,7 @@ void vertical_tile(Monitor *m) { if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < m->pertag->nmasters[m->pertag->curtag]) { - r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; + r = MANGO_MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; if (c->master_inner_per > 0.0f) { w = master_surplus_width * c->master_inner_per / master_surplus_ratio; @@ -155,8 +155,8 @@ void vertical_deck(Monitor *m) { continue; if (i < nmasters) { c->master_mfact_per = mfact; - int32_t w = - (m->w.width - 2 * cur_gappoh - mx) / (MIN(n, nmasters) - i); + int32_t w = (m->w.width - 2 * cur_gappoh - mx) / + (MANGO_MIN(n, nmasters) - i); client_tile_resize(c, (struct wlr_box){.x = m->w.x + cur_gappoh + mx, .y = m->w.y + cur_gappov, diff --git a/src/mango.c b/src/mango.c index 295c6465..7b099bc5 100644 --- a/src/mango.c +++ b/src/mango.c @@ -94,10 +94,11 @@ #include #endif #include "common/util.h" +#include "draw/text-node.h" /* macros */ -#define MAX(A, B) ((A) > (B) ? (A) : (B)) -#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MANGO_MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MANGO_MIN(A, B) ((A) < (B) ? (A) : (B)) #define GEZERO(A) ((A) >= 0 ? (A) : 0) #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) #define INSIDEMON(A) \ @@ -165,6 +166,7 @@ enum { AxisUp, AxisDown, AxisLeft, AxisRight }; // 滚轮滚动的方向 enum { LyrBg, LyrBottom, + LyrDecorate, LyrTile, LyrTop, LyrFadeOut, @@ -173,6 +175,9 @@ enum { LyrBlock, NUM_LAYERS }; /* scene layers */ + +enum mango_node_type { MANGO_TITLE_NODE, MANGO_jump_label_node }; + #ifdef XWAYLAND enum { NetWMWindowTypeDialog, @@ -239,6 +244,11 @@ typedef struct { Client *tc; } Arg; +typedef struct { + enum mango_node_type type; + void *node_data; +} MangoNodeData; + typedef struct { uint32_t mod; uint32_t button; @@ -262,8 +272,8 @@ typedef struct { struct wl_list link; struct wlr_input_device *wlr_device; struct libinput_device *libinput_device; - struct wl_listener destroy_listener; // 用于监听设备销毁事件 - void *device_data; // 新增:指向设备特定数据(如 Switch) + struct wl_listener destroy_listener; + void *device_data; } InputDevice; typedef struct { @@ -329,6 +339,8 @@ struct Client { struct wlr_ext_image_capture_source_v1 *image_capture_source; struct wlr_scene_tree *overview_scene_surface; + struct mango_jump_label_node *jump_label_node; + struct mango_tab_bar_node *tab_bar_node; struct wl_list link; struct wl_list flink; struct wl_list fadeout_link; @@ -395,6 +407,7 @@ struct Client { int32_t istagswitching; int32_t isnamedscratchpad; int32_t shield_when_capture; + bool is_monocle_hide; bool is_pending_open_animation; bool is_restoring_from_ov; float scroller_proportion; @@ -434,6 +447,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; @@ -552,6 +566,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; @@ -847,6 +862,7 @@ static struct wlr_scene_tree * wlr_scene_tree_snapshot(struct wlr_scene_node *node, struct wlr_scene_tree *parent); static bool is_scroller_layout(Monitor *m); +static bool is_monocle_layout(Monitor *m); static bool is_centertile_layout(Monitor *m); static void create_output(struct wlr_backend *backend, void *data); static void get_layout_abbr(char *abbr, const char *full_name); @@ -911,6 +927,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" @@ -1143,8 +1163,8 @@ void client_change_mon(Client *c, Monitor *m) { void applybounds(Client *c, struct wlr_box *bbox) { /* set minimum possible */ - c->geom.width = MAX(1 + 2 * (int32_t)c->bw, c->geom.width); - c->geom.height = MAX(1 + 2 * (int32_t)c->bw, c->geom.height); + c->geom.width = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.width); + c->geom.height = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.height); if (c->geom.x >= bbox->x + bbox->width) c->geom.x = bbox->x + bbox->width - c->geom.width; @@ -1275,6 +1295,10 @@ void swallow(Client *c, Client *w) { overview_backup_surface(c); } + if (w->tab_bar_node) { + wlr_scene_node_set_enabled(&w->tab_bar_node->scene_buffer->node, false); + } + /* 全局链表替换 */ wl_list_insert(&w->link, &c->link); wl_list_insert(&w->flink, &c->flink); @@ -1567,7 +1591,7 @@ void set_float_malposition(Client *tc) { y = tc->geom.y; xreverse = 1; yreverse = 1; - offset = MIN(tc->mon->w.width / 20, tc->mon->w.height / 20); + offset = MANGO_MIN(tc->mon->w.width / 20, tc->mon->w.height / 20); wl_list_for_each(c, &clients, link) { if (c->isfloating && c != tc && VISIBLEON(c, tc->mon) && @@ -2382,6 +2406,17 @@ bool handle_buttonpress(struct wlr_pointer_button_event *event) { return true; } + // handle click on tile node + struct wlr_scene_node *node = wlr_scene_node_at( + &layers[LyrDecorate]->node, cursor->x, cursor->y, NULL, NULL); + if (node && node->data) { + MangoNodeData *mangonodedata = (MangoNodeData *)node->data; + if (mangonodedata->type == MANGO_TITLE_NODE) { + Client *c = mangonodedata->node_data; + focusclient(c, 1); + } + } + // 当鼠标焦点在layer上的时候,不检测虚拟键盘的mod状态, // 避免layer虚拟键盘锁死mod按键状态 hard_keyboard = &kb_group->wlr_group->keyboard; @@ -2581,6 +2616,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) { @@ -3800,17 +3837,17 @@ void focusclient(Client *c, int32_t lift) { // decide whether need to re-arrange - if (c && selmon->prevsel && - (selmon->prevsel->tags & selmon->tagset[selmon->seltags]) && - (c->tags & selmon->tagset[selmon->seltags]) && !c->isfloating && - is_scroller_layout(selmon)) { - arrange(selmon, false, false); - } - // change focus link position wl_list_remove(&c->flink); wl_list_insert(&fstack, &c->flink); + if (c && selmon->prevsel && + (selmon->prevsel->tags & selmon->tagset[selmon->seltags]) && + (c->tags & selmon->tagset[selmon->seltags]) && !c->isfloating && + (is_scroller_layout(selmon) || is_monocle_layout(selmon))) { + arrange(selmon, false, false); + } + // change border color c->isurgent = 0; } @@ -4207,6 +4244,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 */ @@ -4288,6 +4346,9 @@ void locksession(struct wl_listener *listener, void *data) { void init_client_properties(Client *c) { c->grid_col_per = 1.0f; c->grid_row_per = 1.0f; + c->is_monocle_hide = false; + c->jump_label_node = NULL; + c->tab_bar_node = NULL; c->overview_scene_surface = NULL; c->drop_direction = UNDIR; c->enable_drop_area_draw = false; @@ -5579,10 +5640,10 @@ void setfullscreen(Client *c, int32_t fullscreen, } void setgaps(int32_t oh, int32_t ov, int32_t ih, int32_t iv) { - selmon->gappoh = MAX(oh, 0); - selmon->gappov = MAX(ov, 0); - selmon->gappih = MAX(ih, 0); - selmon->gappiv = MAX(iv, 0); + selmon->gappoh = MANGO_MAX(oh, 0); + selmon->gappov = MANGO_MAX(ov, 0); + selmon->gappih = MANGO_MAX(ih, 0); + selmon->gappiv = MANGO_MAX(iv, 0); arrange(selmon, false, false); } @@ -6534,6 +6595,15 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->stack_proportion = 0.0f; + if (c->jump_label_node) { + mango_jump_label_node_destroy(c->jump_label_node); + c->jump_label_node = NULL; + } + if (c->tab_bar_node) { + mango_tab_bar_node_destroy(c->tab_bar_node); + c->tab_bar_node = NULL; + } + wlr_scene_node_destroy(&c->scene->node); printstatus(IPC_WATCH_ARRANGGE); motionnotify(0, NULL, 0, 0, 0, 0); @@ -6681,6 +6751,7 @@ void updatetitle(struct wl_listener *listener, void *data) { const char *title; title = client_get_title(c); + mango_tab_bar_node_update(c->tab_bar_node, title, 1.0); if (title && c->foreign_toplevel) wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); if (title && c->ext_foreign_toplevel) {