diff --git a/src/animation/client.h b/src/animation/client.h index 79544cd2..2483119d 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -450,7 +450,6 @@ void apply_split_border(Client *c, bool hit_no_border) { } struct wlr_box fullgeom = c->animation.current; - // 一但在GEZERO如果使用无符号,那么其他数据也会转换为无符号导致没有负数出错 int32_t bw = (int32_t)c->bw; int32_t right_offset, bottom_offset, left_offset, top_offset; @@ -503,6 +502,75 @@ void apply_split_border(Client *c, bool hit_no_border) { border_right_y); } +static int32_t titlebar_active_height(void) { + return config.enable_titlebars ? (int32_t)config.titlebar_height : 0; +} + +void update_titlebar_hover(Client *c); + +void apply_titlebar(Client *c, int32_t rect_width, + enum corner_location corners) { + int32_t tb = titlebar_active_height(); + bool show_titlebar = tb > 0 && !c->isfullscreen; + if (!c->titlebar_bg) + return; + wlr_scene_node_set_enabled(&c->titlebar_bg->node, show_titlebar); + if (show_titlebar) { + int32_t tb_margin = (int32_t)config.titlebar_button_margin; + int32_t tb_btn_size = (int32_t)config.titlebar_button_size; + + wlr_scene_rect_set_size(c->titlebar_bg, rect_width, tb); + wlr_scene_node_set_position(&c->titlebar_bg->node, 0, 0); + wlr_scene_rect_set_corner_radius(c->titlebar_bg, config.border_radius, + corners & ~CORNER_LOCATION_BOTTOM); + + if (c->titlebar_close) { + int32_t btn_x = rect_width - tb_margin - tb_btn_size; + int32_t btn_y = tb_margin; + int32_t btn_h = tb - 2 * tb_margin; + wlr_scene_rect_set_size(c->titlebar_close, tb_btn_size, btn_h); + wlr_scene_node_set_position(&c->titlebar_close->node, btn_x, btn_y); + update_titlebar_hover(c); + wlr_scene_node_set_enabled(&c->titlebar_close->node, true); + } + } else if (c->titlebar_close) { + wlr_scene_node_set_enabled(&c->titlebar_close->node, false); + } +} + +void update_titlebar_hover(Client *c) { + if (!c || !c->titlebar_close || !c->titlebar_bg) + return; + int32_t tb = titlebar_active_height(); + if (tb <= 0 || c->isfullscreen) { + wlr_scene_node_set_enabled(&c->titlebar_close->node, false); + return; + } + wlr_scene_node_set_enabled(&c->titlebar_close->node, true); + + int32_t tb_margin = (int32_t)config.titlebar_button_margin; + int32_t tb_btn_size = (int32_t)config.titlebar_button_size; + int32_t btn_x = c->animation.current.width - tb_margin - tb_btn_size; + int32_t btn_y = tb_margin; + int32_t btn_h = tb - 2 * tb_margin; + + bool hover = cursor->x >= c->animation.current.x + btn_x && + cursor->x < c->animation.current.x + btn_x + tb_btn_size && + cursor->y >= c->animation.current.y + btn_y && + cursor->y < c->animation.current.y + btn_y + btn_h; + + if (hover) { + float hover_color[4] = {config.title_close_color[0] * 0.5f + 0.5f, + config.title_close_color[1] * 0.5f + 0.5f, + config.title_close_color[2] * 0.5f + 0.5f, + config.title_close_color[3]}; + wlr_scene_rect_set_color(c->titlebar_close, hover_color); + } else { + wlr_scene_rect_set_color(c->titlebar_close, config.title_close_color); + } +} +} + void apply_border(Client *c) { if (!c || c->iskilling || !client_surface(c)->mapped) return; @@ -536,8 +604,10 @@ void apply_border(Client *c) { c->fake_no_border = true; } else if (hit_no_border && !config.smartgaps) { wlr_scene_rect_set_size(c->border, 0, 0); - wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, + c->bw + titlebar_active_height()); c->fake_no_border = true; + apply_titlebar(c, c->animation.current.width, current_corner_location); return; } else if (!c->isfullscreen && VISIBLEON(c, c->mon)) { c->bw = c->isnoborder ? 0 : config.borderpx; @@ -606,12 +676,16 @@ void apply_border(Client *c) { .corners = current_corner_location, }; - wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + int32_t tb = titlebar_active_height(); + int32_t surf_y_off = tb > 0 ? tb : c->bw; + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, surf_y_off); wlr_scene_rect_set_size(c->border, rect_width, rect_height); wlr_scene_node_set_position(&c->border->node, rect_x, rect_y); wlr_scene_rect_set_corner_radius(c->border, config.border_radius, current_corner_location); wlr_scene_rect_set_clipped_region(c->border, clipped_region); + + apply_titlebar(c, c->animation.current.width, current_corner_location); } struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { @@ -892,6 +966,7 @@ void client_apply_clip(Client *c, float factor) { c->geom; client_get_clip(c, &clip_box); + clip_box.height = GEZERO(clip_box.height - titlebar_active_height()); offset = clip_to_hide(c, &clip_box); @@ -924,7 +999,7 @@ void client_apply_clip(Client *c, float factor) { .x = geometry.x, .y = geometry.y, .width = width, - .height = height, + .height = height - titlebar_active_height(), }; if (client_is_x11(c)) { @@ -1364,7 +1439,7 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { // c->geom 是真实的窗口大小和位置,跟过度的动画无关,用于计算布局 if (!c->mon->isoverview || !config.ov_no_resize) { c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw, - c->geom.height - 2 * c->bw); + c->geom.height - 2 * c->bw - titlebar_active_height()); } if (c->configure_serial != 0) { @@ -1382,6 +1457,7 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { client_draw_shadow(c); apply_border(c); client_get_clip(c, &clip); + clip.height = GEZERO(clip.height - titlebar_active_height()); wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); return; } diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 61af421b..99083de1 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -346,6 +346,14 @@ typedef struct { float globalcolor[4]; float overlaycolor[4]; + int32_t enable_titlebars; + uint32_t titlebar_height; + uint32_t titlebar_button_size; + uint32_t titlebar_button_margin; + float title_color[4]; + float title_unfocused_color[4]; + float title_close_color[4]; + int32_t log_level; uint32_t capslock; @@ -2078,6 +2086,47 @@ bool parse_option(Config *config, char *key, char *value) { } else { convert_hex_to_rgba(config->overlaycolor, color); } + } else if (strcmp(key, "enable_titlebars") == 0) { + config->enable_titlebars = atoi(value); + } else if (strcmp(key, "titlebar_height") == 0) { + config->titlebar_height = atoi(value); + } else if (strcmp(key, "titlebar_button_size") == 0) { + config->titlebar_button_size = atoi(value); + } else if (strcmp(key, "titlebar_button_margin") == 0) { + config->titlebar_button_margin = atoi(value); + } else if (strcmp(key, "title_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid title_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->title_color, color); + } + } else if (strcmp(key, "title_unfocused_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "title_unfocused_color format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->title_unfocused_color, color); + } + } else if (strcmp(key, "title_close_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid title_close_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->title_close_color, color); + } } else if (strcmp(key, "monitorrule") == 0) { config->monitor_rules = realloc(config->monitor_rules, (config->monitor_rules_count + 1) * @@ -3597,7 +3646,6 @@ void override_config(void) { config.focused_opacity = CLAMP_FLOAT(config.focused_opacity, 0.0f, 1.0f); config.unfocused_opacity = CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); - config.tabdata.border_width = CLAMP_INT(config.tabdata.border_width, 0, 100); config.tabdata.corner_radius = @@ -3613,6 +3661,12 @@ void override_config(void) { CLAMP_INT(config.jumplabeldata.padding_x, 0, 100); config.jumplabeldata.padding_y = CLAMP_INT(config.jumplabeldata.padding_y, 0, 100); + config.enable_titlebars = CLAMP_INT(config.enable_titlebars, 0, 1); + config.titlebar_height = CLAMP_INT(config.titlebar_height, 10, 200); + config.titlebar_button_size = + CLAMP_INT(config.titlebar_button_size, 4, 100); + config.titlebar_button_margin = + CLAMP_INT(config.titlebar_button_margin, 0, 50); } void set_value_default() { @@ -3879,6 +3933,23 @@ void set_value_default() { config.overlaycolor[1] = 0xa5 / 255.0f; config.overlaycolor[2] = 0x7c / 255.0f; config.overlaycolor[3] = 1.0f; + + config.enable_titlebars = 0; + config.titlebar_height = 30; + config.titlebar_button_size = 16; + config.titlebar_button_margin = 4; + config.title_color[0] = 0xc6 / 255.0f; + config.title_color[1] = 0x6b / 255.0f; + config.title_color[2] = 0x25 / 255.0f; + config.title_color[3] = 1.0f; + config.title_unfocused_color[0] = 0x44 / 255.0f; + config.title_unfocused_color[1] = 0x44 / 255.0f; + config.title_unfocused_color[2] = 0x44 / 255.0f; + config.title_unfocused_color[3] = 1.0f; + config.title_close_color[0] = 0xad / 255.0f; + config.title_close_color[1] = 0x40 / 255.0f; + config.title_close_color[2] = 0x1f / 255.0f; + config.title_close_color[3] = 1.0f; } void set_default_key_bindings(Config *config) { diff --git a/src/mango.c b/src/mango.c index 5549686c..48c4d74a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -339,6 +339,8 @@ struct Client { struct wlr_scene_tree *overview_scene_surface; struct mango_jump_label_node *jump_label_node; struct mango_tab_bar_node *tab_bar_node; + struct wlr_scene_rect *titlebar_bg; + struct wlr_scene_rect *titlebar_close; struct wl_list link; struct wl_list flink; struct wl_list fadeout_link; @@ -984,6 +986,7 @@ static int32_t rzcorner; static int32_t grabcx, grabcy; /* client-relative */ static int32_t drag_begin_cursorx, drag_begin_cursory; /* client-relative */ static bool start_drag_window = false; +static Client *hovered_client = NULL; static int32_t last_apply_drap_time = 0; static struct wlr_output_layout *output_layout; @@ -2411,6 +2414,36 @@ bool handle_buttonpress(struct wlr_pointer_button_event *event) { mods = mods | hard_mods; + if (config.enable_titlebars && event->button == BTN_LEFT && c && + !c->isfullscreen && !client_is_unmanaged(c)) { + int32_t tb_height = (int32_t)config.titlebar_height; + if (cursor->y >= c->geom.y && cursor->y < c->geom.y + tb_height) { + int32_t tb_margin = (int32_t)config.titlebar_button_margin; + int32_t tb_btn_size = (int32_t)config.titlebar_button_size; + int32_t close_x = + c->geom.x + c->geom.width - tb_margin - tb_btn_size; + int32_t close_y = c->geom.y + tb_margin; + if (cursor->x >= close_x && cursor->x < close_x + tb_btn_size && + cursor->y >= close_y && + cursor->y < close_y + tb_height - 2 * tb_margin) { + pending_kill_client(c); + return; + } + grabc = c; + if (grabc->isfloating == 0) { + grabc->drag_to_tile = true; + exit_scroller_stack(grabc); + setfloating(grabc, 1); + grabc->drag_tile_float_backup_geom = grabc->float_geom; + grabc->old_stack_inner_per = 0.0f; + grabc->old_master_inner_per = 0.0f; + set_size_per(grabc->mon, grabc); + } + start_drag_window = true; + return; + } + } + for (ji = 0; ji < config.mouse_bindings_count; ji++) { if (config.mouse_bindings_count < 1) break; @@ -2461,6 +2494,10 @@ bool handle_buttonpress(struct wlr_pointer_button_event *event) { } return true; } else { + if (start_drag_window && grabc) { + grabc = NULL; + start_drag_window = false; + } cursor_mode = CurNormal; } break; @@ -3800,6 +3837,8 @@ destroynotify(struct wl_listener *listener, void *data) { wl_list_remove(&c->map.link); wl_list_remove(&c->unmap.link); } + if (c == hovered_client) + hovered_client = NULL; free(c); } @@ -4607,6 +4646,18 @@ mapnotify(struct wl_listener *listener, void *data) { wlr_scene_node_lower_to_bottom(&c->shadow->node); wlr_scene_node_set_enabled(&c->shadow->node, true); + c->titlebar_bg = wlr_scene_rect_create(c->scene, 0, 0, config.title_color); + wlr_scene_node_raise_to_top(&c->titlebar_bg->node); + wlr_scene_node_set_enabled(&c->titlebar_bg->node, config.enable_titlebars); + + c->titlebar_close = + wlr_scene_rect_create(c->scene, 0, 0, config.title_close_color); + wlr_scene_node_raise_to_top(&c->titlebar_close->node); + wlr_scene_node_set_enabled(&c->titlebar_close->node, + config.enable_titlebars); + + wlr_scene_node_raise_to_top(&c->border->node); + if (config.new_is_master && selmon && !is_scroller_layout(selmon)) // tile at the top wl_list_insert(&clients, &c->link); // 新窗口是master,头部入栈 @@ -4843,6 +4894,31 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, sy = cursor->y - (l ? l->scene->node.y : w->geom.y); } + /* Update titlebar close button hover */ + if (hovered_client && hovered_client != c) { + update_titlebar_hover(hovered_client); + } + if (config.enable_titlebars) { + hovered_client = c; + if (c) + update_titlebar_hover(c); + } + + /* Initiate titlebar drag on first motion */ + if (cursor_mode == CurPressed && start_drag_window && grabc) { + if (grabc->drag_to_tile && config.drag_tile_small) { + grabc->geom.x = cursor->x - 150; + grabc->geom.y = cursor->y - 150; + grabc->geom.width = 300; + grabc->geom.height = 300; + resize(grabc, grabc->geom, 1); + } + grabcx = cursor->x - grabc->geom.x; + grabcy = cursor->y - grabc->geom.y; + cursor_mode = CurMove; + wlr_cursor_set_xcursor(cursor, cursor_mgr, "grab"); + } + /* Update drag icon's position */ wlr_scene_node_set_position(&drag_icon->node, (int32_t)round(cursor->x), (int32_t)round(cursor->y)); @@ -5234,6 +5310,12 @@ void setborder_color(Client *c) { memcpy(c->opacity_animation.target_border_color, border_color, sizeof(c->opacity_animation.target_border_color)); client_set_border_color(c, border_color); + + if (c->titlebar_bg) { + float *title_color = (c == selmon->sel) ? config.title_color + : config.title_unfocused_color; + wlr_scene_rect_set_color(c->titlebar_bg, title_color); + } } void exchange_two_client(Client *c1, Client *c2) { @@ -6488,6 +6570,9 @@ void unmapnotify(struct wl_listener *listener, void *data) { (!c->mon || VISIBLEON(c, c->mon))) init_fadeout_client(c); + if (c == hovered_client) + hovered_client = NULL; + // If the client is in a stack, remove it from the stack if (c->swallowedby) {