First step toward titlebars

This commit is contained in:
David Delarosa 2026-05-11 00:21:59 +03:00 committed by David Delarosa
parent 8216cacb3b
commit 602ff97410
3 changed files with 238 additions and 6 deletions

View file

@ -450,7 +450,6 @@ void apply_split_border(Client *c, bool hit_no_border) {
} }
struct wlr_box fullgeom = c->animation.current; struct wlr_box fullgeom = c->animation.current;
// 一但在GEZERO如果使用无符号那么其他数据也会转换为无符号导致没有负数出错
int32_t bw = (int32_t)c->bw; int32_t bw = (int32_t)c->bw;
int32_t right_offset, bottom_offset, left_offset, top_offset; 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); 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) { void apply_border(Client *c) {
if (!c || c->iskilling || !client_surface(c)->mapped) if (!c || c->iskilling || !client_surface(c)->mapped)
return; return;
@ -536,8 +604,10 @@ void apply_border(Client *c) {
c->fake_no_border = true; c->fake_no_border = true;
} else if (hit_no_border && !config.smartgaps) { } else if (hit_no_border && !config.smartgaps) {
wlr_scene_rect_set_size(c->border, 0, 0); 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; c->fake_no_border = true;
apply_titlebar(c, c->animation.current.width, current_corner_location);
return; return;
} else if (!c->isfullscreen && VISIBLEON(c, c->mon)) { } else if (!c->isfullscreen && VISIBLEON(c, c->mon)) {
c->bw = c->isnoborder ? 0 : config.borderpx; c->bw = c->isnoborder ? 0 : config.borderpx;
@ -606,12 +676,16 @@ void apply_border(Client *c) {
.corners = current_corner_location, .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_rect_set_size(c->border, rect_width, rect_height);
wlr_scene_node_set_position(&c->border->node, rect_x, rect_y); wlr_scene_node_set_position(&c->border->node, rect_x, rect_y);
wlr_scene_rect_set_corner_radius(c->border, config.border_radius, wlr_scene_rect_set_corner_radius(c->border, config.border_radius,
current_corner_location); current_corner_location);
wlr_scene_rect_set_clipped_region(c->border, clipped_region); 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) { 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; c->geom;
client_get_clip(c, &clip_box); client_get_clip(c, &clip_box);
clip_box.height = GEZERO(clip_box.height - titlebar_active_height());
offset = clip_to_hide(c, &clip_box); offset = clip_to_hide(c, &clip_box);
@ -924,7 +999,7 @@ void client_apply_clip(Client *c, float factor) {
.x = geometry.x, .x = geometry.x,
.y = geometry.y, .y = geometry.y,
.width = width, .width = width,
.height = height, .height = height - titlebar_active_height(),
}; };
if (client_is_x11(c)) { if (client_is_x11(c)) {
@ -1364,7 +1439,7 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) {
// c->geom 是真实的窗口大小和位置,跟过度的动画无关,用于计算布局 // c->geom 是真实的窗口大小和位置,跟过度的动画无关,用于计算布局
if (!c->mon->isoverview || !config.ov_no_resize) { if (!c->mon->isoverview || !config.ov_no_resize) {
c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw, 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) { if (c->configure_serial != 0) {
@ -1382,6 +1457,7 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) {
client_draw_shadow(c); client_draw_shadow(c);
apply_border(c); apply_border(c);
client_get_clip(c, &clip); client_get_clip(c, &clip);
clip.height = GEZERO(clip.height - titlebar_active_height());
wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip);
return; return;
} }

View file

@ -346,6 +346,14 @@ typedef struct {
float globalcolor[4]; float globalcolor[4];
float overlaycolor[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; int32_t log_level;
uint32_t capslock; uint32_t capslock;
@ -2078,6 +2086,47 @@ bool parse_option(Config *config, char *key, char *value) {
} else { } else {
convert_hex_to_rgba(config->overlaycolor, color); 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) { } else if (strcmp(key, "monitorrule") == 0) {
config->monitor_rules = config->monitor_rules =
realloc(config->monitor_rules, (config->monitor_rules_count + 1) * 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.focused_opacity = CLAMP_FLOAT(config.focused_opacity, 0.0f, 1.0f);
config.unfocused_opacity = config.unfocused_opacity =
CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f);
config.tabdata.border_width = config.tabdata.border_width =
CLAMP_INT(config.tabdata.border_width, 0, 100); CLAMP_INT(config.tabdata.border_width, 0, 100);
config.tabdata.corner_radius = config.tabdata.corner_radius =
@ -3613,6 +3661,12 @@ void override_config(void) {
CLAMP_INT(config.jumplabeldata.padding_x, 0, 100); CLAMP_INT(config.jumplabeldata.padding_x, 0, 100);
config.jumplabeldata.padding_y = config.jumplabeldata.padding_y =
CLAMP_INT(config.jumplabeldata.padding_y, 0, 100); 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() { void set_value_default() {
@ -3879,6 +3933,23 @@ void set_value_default() {
config.overlaycolor[1] = 0xa5 / 255.0f; config.overlaycolor[1] = 0xa5 / 255.0f;
config.overlaycolor[2] = 0x7c / 255.0f; config.overlaycolor[2] = 0x7c / 255.0f;
config.overlaycolor[3] = 1.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) { void set_default_key_bindings(Config *config) {

View file

@ -339,6 +339,8 @@ struct Client {
struct wlr_scene_tree *overview_scene_surface; struct wlr_scene_tree *overview_scene_surface;
struct mango_jump_label_node *jump_label_node; struct mango_jump_label_node *jump_label_node;
struct mango_tab_bar_node *tab_bar_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 link;
struct wl_list flink; struct wl_list flink;
struct wl_list fadeout_link; struct wl_list fadeout_link;
@ -984,6 +986,7 @@ static int32_t rzcorner;
static int32_t grabcx, grabcy; /* client-relative */ static int32_t grabcx, grabcy; /* client-relative */
static int32_t drag_begin_cursorx, drag_begin_cursory; /* client-relative */ static int32_t drag_begin_cursorx, drag_begin_cursory; /* client-relative */
static bool start_drag_window = false; static bool start_drag_window = false;
static Client *hovered_client = NULL;
static int32_t last_apply_drap_time = 0; static int32_t last_apply_drap_time = 0;
static struct wlr_output_layout *output_layout; static struct wlr_output_layout *output_layout;
@ -2411,6 +2414,36 @@ bool handle_buttonpress(struct wlr_pointer_button_event *event) {
mods = mods | hard_mods; 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++) { for (ji = 0; ji < config.mouse_bindings_count; ji++) {
if (config.mouse_bindings_count < 1) if (config.mouse_bindings_count < 1)
break; break;
@ -2461,6 +2494,10 @@ bool handle_buttonpress(struct wlr_pointer_button_event *event) {
} }
return true; return true;
} else { } else {
if (start_drag_window && grabc) {
grabc = NULL;
start_drag_window = false;
}
cursor_mode = CurNormal; cursor_mode = CurNormal;
} }
break; break;
@ -3800,6 +3837,8 @@ destroynotify(struct wl_listener *listener, void *data) {
wl_list_remove(&c->map.link); wl_list_remove(&c->map.link);
wl_list_remove(&c->unmap.link); wl_list_remove(&c->unmap.link);
} }
if (c == hovered_client)
hovered_client = NULL;
free(c); 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_lower_to_bottom(&c->shadow->node);
wlr_scene_node_set_enabled(&c->shadow->node, true); 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)) if (config.new_is_master && selmon && !is_scroller_layout(selmon))
// tile at the top // tile at the top
wl_list_insert(&clients, &c->link); // 新窗口是master,头部入栈 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); 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 */ /* Update drag icon's position */
wlr_scene_node_set_position(&drag_icon->node, (int32_t)round(cursor->x), wlr_scene_node_set_position(&drag_icon->node, (int32_t)round(cursor->x),
(int32_t)round(cursor->y)); (int32_t)round(cursor->y));
@ -5234,6 +5310,12 @@ void setborder_color(Client *c) {
memcpy(c->opacity_animation.target_border_color, border_color, memcpy(c->opacity_animation.target_border_color, border_color,
sizeof(c->opacity_animation.target_border_color)); sizeof(c->opacity_animation.target_border_color));
client_set_border_color(c, 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) { 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))) (!c->mon || VISIBLEON(c, c->mon)))
init_fadeout_client(c); init_fadeout_client(c);
if (c == hovered_client)
hovered_client = NULL;
// If the client is in a stack, remove it from the stack // If the client is in a stack, remove it from the stack
if (c->swallowedby) { if (c->swallowedby) {