From 990706b081ce1d552a91064f4c336ad2fd4e20bd Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Tue, 22 Apr 2025 12:20:15 -0400 Subject: [PATCH] ssd: allow arbitrary cairo pattern as titlebar background The titlebar background is now first rendered to a 1px wide buffer, then stretched horizontally. This allows vertical gradients to be used. --- include/theme.h | 14 +++++++++++- src/ssd/ssd-titlebar.c | 23 +++++++++---------- src/theme.c | 50 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/include/theme.h b/include/theme.h index 283538d4..9fe2857c 100644 --- a/include/theme.h +++ b/include/theme.h @@ -8,7 +8,7 @@ #ifndef LABWC_THEME_H #define LABWC_THEME_H -#include +#include #include #include "ssd.h" @@ -89,6 +89,18 @@ struct theme { struct lab_img *button_imgs [LAB_SSD_BUTTON_LAST + 1][LAB_BS_ALL + 1]; + /* + * The titlebar background is specified as a cairo_pattern + * and then also rendered into a 1px wide buffer, which is + * stretched horizontally across the titlebar. + * + * This approach enables vertical gradients while saving + * some memory vs. rendering the entire titlebar into an + * image. It does not work for horizontal gradients. + */ + cairo_pattern_t *titlebar_pattern; + struct lab_data_buffer *titlebar_fill; + struct lab_data_buffer *corner_top_left_normal; struct lab_data_buffer *corner_top_right_normal; diff --git a/src/ssd/ssd-titlebar.c b/src/ssd/ssd-titlebar.c index 84d28f75..e57134ad 100644 --- a/src/ssd/ssd-titlebar.c +++ b/src/ssd/ssd-titlebar.c @@ -35,8 +35,8 @@ ssd_titlebar_create(struct ssd *ssd) int width = view->current.width; int corner_width = ssd_get_corner_width(); - float *color; struct wlr_scene_tree *parent; + struct wlr_buffer *titlebar_fill; struct wlr_buffer *corner_top_left; struct wlr_buffer *corner_top_right; int active; @@ -49,7 +49,7 @@ ssd_titlebar_create(struct ssd *ssd) parent = subtree->tree; active = (subtree == &ssd->titlebar.active) ? THEME_ACTIVE : THEME_INACTIVE; - color = theme->window[active].title_bg_color; + titlebar_fill = &theme->window[active].titlebar_fill->base; corner_top_left = &theme->window[active].corner_top_left_normal->base; corner_top_right = &theme->window[active].corner_top_right_normal->base; wlr_scene_node_set_enabled(&parent->node, active); @@ -57,9 +57,8 @@ ssd_titlebar_create(struct ssd *ssd) wl_list_init(&subtree->parts); /* Background */ - add_scene_rect(&subtree->parts, LAB_SSD_PART_TITLEBAR, parent, - MAX(width - corner_width * 2, 0), theme->titlebar_height, - corner_width, 0, color); + add_scene_buffer(&subtree->parts, LAB_SSD_PART_TITLEBAR, parent, + titlebar_fill, corner_width, 0); add_scene_buffer(&subtree->parts, LAB_SSD_PART_TITLEBAR_CORNER_LEFT, parent, corner_top_left, -rc.theme->border_width, -rc.theme->border_width); add_scene_buffer(&subtree->parts, LAB_SSD_PART_TITLEBAR_CORNER_RIGHT, parent, @@ -151,7 +150,8 @@ set_squared_corners(struct ssd *ssd, bool enable) FOR_EACH_STATE(ssd, subtree) { part = ssd_get_part(&subtree->parts, LAB_SSD_PART_TITLEBAR); wlr_scene_node_set_position(part->node, x, 0); - wlr_scene_rect_set_size(wlr_scene_rect_from_node(part->node), + wlr_scene_buffer_set_dest_size( + wlr_scene_buffer_from_node(part->node), MAX(width - 2 * x, 0), theme->titlebar_height); part = ssd_get_part(&subtree->parts, LAB_SSD_PART_TITLEBAR_CORNER_LEFT); @@ -299,8 +299,8 @@ ssd_titlebar_update(struct ssd *ssd) int bg_offset = maximized || squared ? 0 : corner_width; FOR_EACH_STATE(ssd, subtree) { part = ssd_get_part(&subtree->parts, LAB_SSD_PART_TITLEBAR); - wlr_scene_rect_set_size( - wlr_scene_rect_from_node(part->node), + wlr_scene_buffer_set_dest_size( + wlr_scene_buffer_from_node(part->node), MAX(width - bg_offset * 2, 0), theme->titlebar_height); x = theme->window_titlebar_padding_width; @@ -458,7 +458,7 @@ ssd_update_title(struct ssd *ssd) bool title_unchanged = state->text && !strcmp(title, state->text); const float *text_color; - const float *bg_color; + const float bg_color[4] = {0, 0, 0, 0}; /* ignored */ struct font *font = NULL; struct ssd_part *part; struct ssd_sub_tree *subtree; @@ -474,7 +474,6 @@ ssd_update_title(struct ssd *ssd) THEME_ACTIVE : THEME_INACTIVE; dstate = active ? &state->active : &state->inactive; text_color = theme->window[active].label_text_color; - bg_color = theme->window[active].title_bg_color; font = active ? &rc.font_activewindow : &rc.font_inactivewindow; if (title_bg_width <= 0) { @@ -492,7 +491,9 @@ ssd_update_title(struct ssd *ssd) if (!part) { /* Initialize part and wlr_scene_buffer without attaching a buffer */ part = add_scene_part(&subtree->parts, LAB_SSD_PART_TITLE); - part->buffer = scaled_font_buffer_create(subtree->tree); + part->buffer = scaled_font_buffer_create_for_titlebar( + subtree->tree, theme->titlebar_height, + theme->window[active].titlebar_pattern); if (part->buffer) { part->node = &part->buffer->scene_buffer->node; } else { diff --git a/src/theme.c b/src/theme.c index abc13010..90fb21c8 100644 --- a/src/theme.c +++ b/src/theme.c @@ -53,7 +53,7 @@ struct rounded_corner_ctx { struct wlr_box *box; double radius; double line_width; - float *fill_color; + cairo_pattern_t *fill_pattern; float *border_color; enum corner corner; }; @@ -1109,10 +1109,24 @@ rounded_rect(struct rounded_corner_ctx *ctx) } cairo_close_path(cairo); cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); - set_cairo_color(cairo, ctx->fill_color); + /* + * We need to offset the fill pattern vertically by the border + * width to line up with the rest of the titlebar. This is done + * by applying a transformation matrix to the pattern temporarily. + * It would be better to copy the pattern, but cairo does not + * provide a simple way to this. + */ + cairo_matrix_t matrix; + cairo_matrix_init_translate(&matrix, 0, -ctx->line_width); + cairo_pattern_set_matrix(ctx->fill_pattern, &matrix); + cairo_set_source(cairo, ctx->fill_pattern); cairo_fill_preserve(cairo); cairo_stroke(cairo); + /* Reset the fill pattern transformation matrix afterward */ + cairo_matrix_init_identity(&matrix); + cairo_pattern_set_matrix(ctx->fill_pattern, &matrix); + /* * Stroke horizontal and vertical borders, shown by Xs and Ys * respectively in the figure below: @@ -1215,6 +1229,33 @@ out: return buffer; } +static struct lab_data_buffer * +create_titlebar_fill(cairo_pattern_t *pattern, int height) +{ + /* create 1px wide buffer to be stretched horizontally */ + struct lab_data_buffer *fill = buffer_create_cairo(1, height, 1); + + cairo_t *cairo = cairo_create(fill->surface); + cairo_set_source(cairo, pattern); + cairo_paint(cairo); + cairo_surface_flush(fill->surface); + cairo_destroy(cairo); + + return fill; +} + +static void +create_backgrounds(struct theme *theme) +{ + for (int active = THEME_INACTIVE; active <= THEME_ACTIVE; active++) { + theme->window[active].titlebar_pattern = color_to_pattern( + theme->window[active].title_bg_color); + theme->window[active].titlebar_fill = create_titlebar_fill( + theme->window[active].titlebar_pattern, + theme->titlebar_height); + } +} + static void create_corners(struct theme *theme) { @@ -1232,7 +1273,7 @@ create_corners(struct theme *theme) .box = &box, .radius = rc.corner_radius, .line_width = theme->border_width, - .fill_color = theme->window[active].title_bg_color, + .fill_pattern = theme->window[active].titlebar_pattern, .border_color = theme->window[active].border_color, .corner = LAB_CORNER_TOP_LEFT, }; @@ -1567,6 +1608,7 @@ theme_init(struct theme *theme, struct server *server, const char *theme_name) paths_destroy(&paths); post_processing(theme); + create_backgrounds(theme); create_corners(theme); load_buttons(theme); create_shadows(theme); @@ -1593,6 +1635,8 @@ theme_finish(struct theme *theme) } for (int active = THEME_INACTIVE; active <= THEME_ACTIVE; active++) { + zfree_pattern(theme->window[active].titlebar_pattern); + zdrop(&theme->window[active].titlebar_fill); zdrop(&theme->window[active].corner_top_left_normal); zdrop(&theme->window[active].corner_top_right_normal); zdrop(&theme->window[active].shadow_corner_top); -- 2.52.0