From 604372cabff11e6f35646924c3312f7b3b0edbdd Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 17 Jun 2026 10:22:37 +0800 Subject: [PATCH] feat: monocle layout support title tab --- docs/visuals/theming.md | 19 +- src/action/client.h | 20 +- src/animation/client.h | 14 +- src/animation/layer.h | 8 +- src/animation/tag.h | 38 +-- src/config/parse_config.h | 145 +++++++---- src/dispatch/bind_define.h | 8 +- src/draw/text-node.c | 485 +++++++++++++++++++++++++++++++++++-- src/draw/text-node.h | 130 ++++++++-- src/fetch/monitor.h | 8 + src/layout/arrange.h | 29 +++ src/layout/horizontal.h | 68 ++++-- src/mango.c | 52 +++- 13 files changed, 868 insertions(+), 156 deletions(-) diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md index 71d3a818..928b1d24 100644 --- a/docs/visuals/theming.md +++ b/docs/visuals/theming.md @@ -14,6 +14,7 @@ Control the sizing of window borders and gaps. | `gappiv` | `5` | Vertical inner gap. | | `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). | | `gappov` | `10` | Vertical outer gap. | +| `tab_bar_height` | `50` | Height of the tab bar for monocle layout. | ## Colors @@ -55,14 +56,16 @@ You can also color-code windows based on their state: ### 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.| +| `text_decorate_fg_color` | `0xc4939dff` | label text color. | +| `text_decorate_bg_color` | `0x201b14ff` | label background color.| +| `text_decorate_focus_fg_color` | `0x201b14ff` | label text color for focus. | +| `text_decorate_focus_bg_color` | `0xc4939dff` | label background color for focus.| +| `text_decorate_border_color` | `0x8BAA9Bff` | label border color.| +| `text_decorate_border_width` | `4` | label border width.| +| `text_decorate_corner_radius` | `5` | label corner radius.| +| `text_decorate_padding_x` | `10` | label horizontal padding.| +| `text_decorate_padding_y` | `10` | label vertical padding.| +| `text_decorate_font_desc` | `monospace Bold 16` | label font set.| ## Cursor Theme diff --git a/src/action/client.h b/src/action/client.h index 11602e70..d13cb5c6 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -95,4 +95,22 @@ void client_pending_force_kill(Client *c) { if (!c) return; kill(c->pid, SIGKILL); -} \ No newline at end of file +} + +void client_add_text_node(Client *c) { + c->text_node = mango_text_node_create(c->scene, config.textdata); + wlr_scene_node_lower_to_bottom(&c->text_node->scene_buffer->node); + wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, false); +} + +void client_add_titlebar_node(Client *c) { + MangoNodeData *mangonodedata = ecalloc(1, sizeof(MangoNodeData)); + mangonodedata->node_data = c; + mangonodedata->type = MANGO_TITLE_NODE; + + c->titlebar_node = mango_titlebar_node_create( + mangonodedata, layers[LyrDecorate], config.textdata, 0, 0); + wlr_scene_node_lower_to_bottom(&c->titlebar_node->scene_buffer->node); + wlr_scene_node_set_enabled(&c->titlebar_node->scene_buffer->node, false); + mango_titlebar_node_update(c->titlebar_node, client_get_title(c), 1.0); +} diff --git a/src/animation/client.h b/src/animation/client.h index e39a7d94..1c77ab3c 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -289,6 +289,16 @@ void apply_shield(Client *c, struct wlr_box clip_box) { } } +void global_draw_titlebar(Client *c, int32_t x, int32_t y, int32_t width, + int32_t height) { + if (!c->titlebar_node) + return; + + wlr_scene_node_set_position(&c->titlebar_node->scene_buffer->node, x, y); + wlr_scene_node_set_enabled(&c->titlebar_node->scene_buffer->node, true); + mango_titlebar_node_set_size(c->titlebar_node, width, height); +} + void apply_split_border(Client *c, bool hit_no_border) { if (c->iskilling || !c->mon || !client_surface(c)->mapped) @@ -544,8 +554,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); } diff --git a/src/animation/layer.h b/src/animation/layer.h index 993cfd12..476cba63 100644 --- a/src/animation/layer.h +++ b/src/animation/layer.h @@ -277,10 +277,10 @@ void layer_animation_next_tick(LayerSurface *l) { double opacity_eased_progress = find_animation_curve_at(animation_passed, OPAFADEIN); - double opacity = - MANGO_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 23986a44..859ee1b8 100644 --- a/src/animation/tag.h +++ b/src/animation/tag.h @@ -24,29 +24,31 @@ void set_tagin_animation(Monitor *m, Client *c) { c->animainit_geom.x = config.tag_animation_direction == VERTICAL ? c->animation.current.x : MANGO_MAX(c->mon->m.x + c->mon->m.width, - c->geom.x + c->mon->m.width); + c->geom.x + c->mon->m.width); c->animainit_geom.y = config.tag_animation_direction == VERTICAL ? MANGO_MAX(c->mon->m.y + c->mon->m.height, - c->geom.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 - : 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; + 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 - : MANGO_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 ? MANGO_MIN(c->mon->m.y - c->geom.height, - c->geom.y - c->mon->m.height) + c->geom.y - c->mon->m.height) : c->animation.current.y; resize(c, c->geom, 0); @@ -99,10 +101,10 @@ void set_tagout_animation(Monitor *m, Client *c) { c->pending.x = config.tag_animation_direction == VERTICAL ? c->animation.current.x : MANGO_MAX(c->mon->m.x + c->mon->m.width, - c->geom.x + c->mon->m.width); + c->geom.x + c->mon->m.width); c->pending.y = config.tag_animation_direction == VERTICAL ? MANGO_MAX(c->mon->m.y + c->mon->m.height, - c->geom.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 e01d750c..a34ca2e9 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -311,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]; @@ -387,7 +388,7 @@ typedef struct { struct xkb_context *ctx; struct xkb_keymap *keymap; - JumphitData jumhitdata; + TextDrawData textdata; } Config; typedef int32_t (*FuncType)(const Arg *); @@ -1706,50 +1707,76 @@ 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) { + } else if (strcmp(key, "text_decorate_font_desc") == 0) { + config->textdata.font_desc = strdup(value); + } else if (strcmp(key, "text_decorate_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 " + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "text_decorate_fg_color " "format: %s\n", value); return false; } else { - convert_hex_to_rgba(config->jumhitdata.fg_color, color); + convert_hex_to_rgba(config->textdata.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) { + } else if (strcmp(key, "text_decorate_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 " + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "text_decorate_bg_color " "format: %s\n", value); return false; } else { - convert_hex_to_rgba(config->jumhitdata.bg_color, color); + convert_hex_to_rgba(config->textdata.bg_color, color); } - } else if (strcmp(key, "jump_hit_border_color") == 0) { + } else if (strcmp(key, "text_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_hit_border_color " - "format: %s\n", - value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "text_decorate_focus_fg_color " + "format: %s\n", + value); return false; } else { - convert_hex_to_rgba(config->jumhitdata.border_color, color); + convert_hex_to_rgba(config->textdata.focus_fg_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, "text_decorate_focus_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "text_decorate_focus_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->textdata.focus_bg_color, color); + } + } else if (strcmp(key, "text_decorate_border_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "text_decorate_border_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->textdata.border_color, color); + } + } else if (strcmp(key, "text_decorate_border_width") == 0) { + config->textdata.border_width = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "text_decorate_corner_radius") == 0) { + config->textdata.corner_radius = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "text_decorate_padding_x") == 0) { + config->textdata.padding_x = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "text_decorate_padding_y") == 0) { + config->textdata.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) { @@ -1796,6 +1823,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) { @@ -3209,9 +3238,9 @@ 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.textdata.font_desc) { + free((void *)config.textdata.font_desc); + config.textdata.font_desc = NULL; } if (config.tablet_map_to_mon) { @@ -3375,19 +3404,18 @@ 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, 5, 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.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); + config.textdata.border_width = + CLAMP_INT(config.textdata.border_width, 0, 100); + config.textdata.corner_radius = + CLAMP_INT(config.textdata.corner_radius, 0, 100); + config.textdata.padding_x = CLAMP_INT(config.textdata.padding_x, 0, 100); + config.textdata.padding_y = CLAMP_INT(config.textdata.padding_y, 0, 100); } void set_value_default() { @@ -3475,6 +3503,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; @@ -3539,22 +3568,30 @@ 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.textdata.fg_color[0] = 0xc4 / 255.0f; + config.textdata.fg_color[1] = 0x93 / 255.0f; + config.textdata.fg_color[2] = 0x9d / 255.0f; + config.textdata.fg_color[3] = 1.0f; + config.textdata.bg_color[0] = 0x32 / 255.0f; + config.textdata.bg_color[1] = 0x32 / 255.0f; + config.textdata.bg_color[2] = 0x32 / 255.0f; + config.textdata.bg_color[3] = 1.0f; + config.textdata.focus_fg_color[0] = 0xed / 255.0f; + config.textdata.focus_fg_color[1] = 0xa6 / 255.0f; + config.textdata.focus_fg_color[2] = 0xb4 / 255.0f; + config.textdata.focus_fg_color[3] = 1.0f; + config.textdata.focus_bg_color[0] = 0x4e / 255.0f; + config.textdata.focus_bg_color[1] = 0x45 / 255.0f; + config.textdata.focus_bg_color[2] = 0x3c / 255.0f; + config.textdata.focus_bg_color[3] = 1.0f; + config.textdata.border_color[0] = 0x8b / 255.0f; + config.textdata.border_color[1] = 0xaa / 255.0f; + config.textdata.border_color[2] = 0x9b / 255.0f; + config.textdata.border_color[3] = 1.0f; + config.textdata.border_width = 4; + config.textdata.corner_radius = 5; + config.textdata.padding_x = 10; + config.textdata.padding_y = 10; config.rootcolor[0] = 0x32 / 255.0f; config.rootcolor[1] = 0x32 / 255.0f; @@ -3668,7 +3705,7 @@ bool parse_config(void) { config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; - config.jumhitdata.font_desc = NULL; + config.textdata.font_desc = NULL; config.tablet_map_to_mon = NULL; strcpy(config.keymode, "default"); @@ -3806,6 +3843,10 @@ void reapply_property(void) { c->bw = config.borderpx; } + mango_text_node_apply_config(c->text_node, &config.textdata); + mango_titlebar_node_apply_config(c->titlebar_node, + &config.textdata); + 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_define.h b/src/dispatch/bind_define.h index a199a92e..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) { @@ -762,7 +763,7 @@ int32_t smartmovewin(const Arg *arg) { } ny = tar == 99999 ? ny : tar; ny = MANGO_MIN(ny, c->mon->w.y + c->mon->w.height - c->geom.height - - c->mon->gappov); + c->mon->gappov); break; case LEFT: tar = -99999; @@ -801,7 +802,7 @@ int32_t smartmovewin(const Arg *arg) { } nx = tar == 99999 ? nx : tar; nx = MANGO_MIN(nx, c->mon->w.x + c->mon->w.width - c->geom.width - - c->mon->gappoh); + c->mon->gappoh); break; } @@ -1083,7 +1084,8 @@ int32_t switch_layout(const Arg *arg) { } for (ji = 0; ji < LENGTH(layouts); ji++) { - len = MANGO_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]; diff --git a/src/draw/text-node.c b/src/draw/text-node.c index 40cd2ff0..32164fbe 100644 --- a/src/draw/text-node.c +++ b/src/draw/text-node.c @@ -60,7 +60,7 @@ static const struct wlr_buffer_impl text_buffer_impl = { }; struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, - JumphitData data) { + TextDrawData data) { struct mango_text_node *node = calloc(1, sizeof(*node)); if (!node) return NULL; @@ -73,17 +73,23 @@ struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, 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 24"); + 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); @@ -92,6 +98,8 @@ struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, node->measure_layout = pango_layout_new(node->measure_context); node->measure_scale = 1.0f; + node->scene_buffer->node.data = NULL; + return node; } @@ -190,7 +198,7 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, 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 && @@ -198,12 +206,17 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, 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_padding_y == node->padding_y && + node->cached_focused == node->focused) { return; } @@ -215,12 +228,17 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, 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); @@ -241,13 +259,11 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, 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; - /* 加上边框后,乘以 scale 得到物理像素(表面尺寸),向上取整 */ int32_t required_pixel_w = (int32_t)((box_logical_w + 2 * node->border_width) * scale + 0.5f); int32_t required_pixel_h = @@ -279,41 +295,40 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, 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; - bool draw_bg = (node->bg_color[3] > 0.0f); + 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, node->bg_color[0], node->bg_color[1], - node->bg_color[2], node->bg_color[3]); + 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); @@ -323,7 +338,6 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, } } - /* 绘制文本 */ cairo_save(cr); double text_x = (node->border_width + node->padding_x) * scale; double text_y = (node->border_width + node->padding_y) * scale; @@ -336,15 +350,14 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, 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]); + 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]); @@ -370,7 +383,6 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, cairo_surface_flush(node->surface); cairo_destroy(cr); - /* 更新 wlr_buffer */ if (node->buffer) { wlr_buffer_drop(&node->buffer->base); node->buffer = NULL; @@ -391,4 +403,437 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, 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_text_node_set_focus(struct mango_text_node *node, bool focused) { + if (!node || node->focused == focused) + return; + node->focused = focused; + // 使用缓存的文本和缩放触发重绘(如果无文本则不重绘) + if (node->cached_text && node->cached_scale > 0.0f) { + mango_text_node_update(node, node->cached_text, node->cached_scale); + } +} + +struct mango_titlebar_node * +mango_titlebar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, + TextDrawData data, int32_t width, int32_t height) { + struct mango_titlebar_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_titlebar_node_destroy(struct mango_titlebar_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_titlebar_node_set_size(struct mango_titlebar_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_titlebar_node_update(node, redraw_text, redraw_scale); +} + +void mango_titlebar_node_update(struct mango_titlebar_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_titlebar_node_set_focus(struct mango_titlebar_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_titlebar_node_update(node, node->last_text, scale); + } +} + +void mango_titlebar_node_set_colors(struct mango_titlebar_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_titlebar_node_update(node, node->last_text, scale); + } +} + +void mango_text_node_apply_config(struct mango_text_node *node, + const TextDrawData *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_text_node_update(node, node->cached_text, node->cached_scale); + } +} + +void mango_titlebar_node_apply_config(struct mango_titlebar_node *node, + const TextDrawData *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_titlebar_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 index 8979a774..32b937a1 100644 --- a/src/draw/text-node.h +++ b/src/draw/text-node.h @@ -1,12 +1,27 @@ -#ifndef MANGO_TEXT_NODE_H -#define MANGO_TEXT_NODE_H +#ifndef TEXT_NODE_H +#define TEXT_NODE_H #include +#include #include -#include +#include +#include #include -// 自定义 wlr_buffer,仅用于包装 cairo surface,不负责 surface 的销毁 +// 原有结构体,假设已存在 +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; +} TextDrawData; + struct mango_text_buffer { struct wlr_buffer base; cairo_surface_t *surface; @@ -14,29 +29,39 @@ struct mango_text_buffer { struct mango_text_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, padding_y; + 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]; - float cached_border_width; - float cached_corner_radius; - float cached_padding_x, cached_padding_y; - char *cached_font_desc; + int32_t cached_border_width; + int32_t cached_corner_radius; + int32_t cached_padding_x; + int32_t cached_padding_y; + bool cached_focused; - cairo_surface_t *surface; - struct mango_text_buffer *buffer; - int32_t surface_pixel_w, surface_pixel_h; + bool focused; + // 测量 cairo_surface_t *measure_surface; cairo_t *measure_cr; PangoContext *measure_context; @@ -47,32 +72,93 @@ struct mango_text_node { int32_t logical_height; }; -typedef struct { +struct mango_titlebar_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; - const char *font_desc; -} JumphitData; + 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_text_node *mango_text_node_create(struct wlr_scene_tree *parent, - JumphitData data); + TextDrawData 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, int32_t width, int32_t radius); - void mango_text_node_set_padding(struct mango_text_node *node, int32_t pad_x, int32_t pad_y); +void mango_text_node_update(struct mango_text_node *node, const char *text, + float scale); -void mango_text_global_finish(void); +struct mango_titlebar_node * +mango_titlebar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, + TextDrawData data, int32_t width, int32_t height); +void mango_titlebar_node_destroy(struct mango_titlebar_node *node); +void mango_titlebar_node_set_size(struct mango_titlebar_node *node, + int32_t width, int32_t height); +void mango_titlebar_node_update(struct mango_titlebar_node *node, + const char *text, float scale); -#endif \ No newline at end of file +void mango_text_node_set_focus(struct mango_text_node *node, bool focused); +void mango_titlebar_node_set_focus(struct mango_titlebar_node *node, + bool focused); + +void mango_titlebar_node_set_colors(struct mango_titlebar_node *node, + const float fg[4], const float bg[4]); +void mango_text_node_apply_config(struct mango_text_node *node, + const TextDrawData *data); +void mango_titlebar_node_apply_config(struct mango_titlebar_node *node, + const TextDrawData *data); +#endif // TEXT_NODE_H \ No newline at end of file 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..f1c4967a 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_titlebar_node_set_focus(c->titlebar_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->text_node) { + client_add_text_node(c); + } + + if (m->pertag->ltidxs[m->pertag->curtag]->id == MONOCLE && + !c->titlebar_node) { + client_add_titlebar_node(c); + } + + if (c->titlebar_node && c->mon == m) { + wlr_scene_node_set_enabled(&c->titlebar_node->scene_buffer->node, + false); + } + if (c->mon == m && (c->isglobal || c->isunglobal)) { c->tags = m->tagset[m->seltags]; } diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 8788c210..4b8fa29d 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -545,32 +545,70 @@ void deck(Monitor *m) { } } -void // 17 -monocle(Monitor *m) { - Client *c = NULL; +void monocle(Monitor *m) { + Client *c, *fc; 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 titlebar_height = config.tab_bar_height; + int title_y = m->w.y + cur_gappov; + int main_y = title_y + titlebar_height + cur_gapiv; + int main_height = + m->w.height - 2 * cur_gappov - 2 * cur_gapiv - titlebar_height; + + int title_area_width = m->w.width - 2 * cur_gappoh; + int tw = (title_area_width - (n - 1) * cur_gapih) / n; + int title_x = m->w.x + cur_gappoh; + + 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); + + global_draw_titlebar(c, title_x, title_y, tw, titlebar_height); + title_x += tw + cur_gapih; } - if ((c = focustop(m))) - wlr_scene_node_raise_to_top(&c->scene->node); } // 网格布局窗口大小和位置计算 diff --git a/src/mango.c b/src/mango.c index 8da371d1..d81eee58 100644 --- a/src/mango.c +++ b/src/mango.c @@ -166,6 +166,7 @@ enum { AxisUp, AxisDown, AxisLeft, AxisRight }; // 滚轮滚动的方向 enum { LyrBg, LyrBottom, + LyrDecorate, LyrTile, LyrTop, LyrFadeOut, @@ -174,6 +175,9 @@ enum { LyrBlock, NUM_LAYERS }; /* scene layers */ + +enum mango_node_type { MANGO_TITLE_NODE, MANGO_TEXT_NODE }; + #ifdef XWAYLAND enum { NetWMWindowTypeDialog, @@ -240,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; @@ -263,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 { @@ -331,6 +340,7 @@ struct Client { struct wlr_scene_tree *overview_scene_surface; struct mango_text_node *text_node; + struct mango_titlebar_node *titlebar_node; struct wl_list link; struct wl_list flink; struct wl_list fadeout_link; @@ -397,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; @@ -851,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); @@ -1283,6 +1295,11 @@ void swallow(Client *c, Client *w) { overview_backup_surface(c); } + if (w->titlebar_node) { + wlr_scene_node_set_enabled(&w->titlebar_node->scene_buffer->node, + false); + } + /* 全局链表替换 */ wl_list_insert(&w->link, &c->link); wl_list_insert(&w->flink, &c->flink); @@ -2390,6 +2407,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; @@ -3810,17 +3838,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; } @@ -4319,6 +4347,7 @@ 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->overview_scene_surface = NULL; c->drop_direction = UNDIR; c->enable_drop_area_draw = false; @@ -4490,9 +4519,6 @@ mapnotify(struct wl_listener *listener, void *data) { : config.bordercolor); c->border[i]->node.data = c; } - 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( @@ -6569,6 +6595,7 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->stack_proportion = 0.0f; mango_text_node_destroy(c->text_node); + mango_titlebar_node_destroy(c->titlebar_node); wlr_scene_node_destroy(&c->scene->node); printstatus(IPC_WATCH_ARRANGGE); motionnotify(0, NULL, 0, 0, 0, 0); @@ -6716,6 +6743,7 @@ void updatetitle(struct wl_listener *listener, void *data) { const char *title; title = client_get_title(c); + mango_titlebar_node_update(c->titlebar_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) {