From: John Lindgren Date: Tue, 22 Apr 2025 16:24:06 +0000 (-0400) Subject: theme: support basic vertical titlebar gradients X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=ec145a14caf3ced4c5ac3e96a2ac0d4aa2fb3eb1;p=proto%2Flabwc.git theme: support basic vertical titlebar gradients Only Vertical and SplitVertical gradients are supported, and only for window.*.title.bg. Not supported at this time: - horizontal or diagonal gradients - gradients for window.*.label.bg, buttons, or menus - any type of border (raised, sunken, etc.) --- diff --git a/include/theme.h b/include/theme.h index 9fe2857c..92d4a99e 100644 --- a/include/theme.h +++ b/include/theme.h @@ -14,6 +14,16 @@ struct lab_img; +/* + * Openbox defines 7 types of Gradient background in addition to Solid. + * Currently, labwc supports only Vertical and SplitVertical. + */ +enum lab_gradient { + LAB_GRADIENT_NONE, /* i.e. Solid */ + LAB_GRADIENT_VERTICAL, + LAB_GRADIENT_SPLITVERTICAL, +}; + enum lab_justification { LAB_JUSTIFY_LEFT, LAB_JUSTIFY_CENTER, @@ -38,6 +48,16 @@ enum lab_button_state { LAB_BS_ALL = LAB_BS_HOVERD | LAB_BS_TOGGLED | LAB_BS_ROUNDED, }; +struct theme_background { + /* gradient type or none/solid */ + enum lab_gradient gradient; + /* gradient stops */ + float color[4]; + float color_split_to[4]; + float color_to[4]; + float color_to_split_to[4]; +}; + struct theme { int border_width; @@ -66,12 +86,14 @@ struct theme { * THEME_INACTIVE and THEME_ACTIVE. */ struct { + /* title background pattern (solid or gradient) */ + struct theme_background title_bg; + /* TODO: add toggled/hover/pressed/disabled colors for buttons */ float button_colors[LAB_SSD_BUTTON_LAST + 1][4]; float border_color[4]; float toggled_keybinds_color[4]; - float title_bg_color[4]; float label_text_color[4]; /* window drop-shadows */ diff --git a/src/theme.c b/src/theme.c index 90fb21c8..b413757b 100644 --- a/src/theme.c +++ b/src/theme.c @@ -26,7 +26,6 @@ #include "common/match.h" #include "common/mem.h" #include "common/parse-bool.h" -#include "common/parse-double.h" #include "common/string-helpers.h" #include "config/rcxml.h" #include "img/img.h" @@ -494,6 +493,29 @@ parse_color(const char *str, float *rgba) } } +static enum lab_gradient +parse_gradient(const char *str) +{ + /* + * Parsing of "texture" strings is very loose, following Openbox: + * just a case-insensitive match of substrings of interest, with + * no regard for ordering nor whitespace. + */ + char *lower = g_ascii_strdown(str, -1); + enum lab_gradient gradient = LAB_GRADIENT_NONE; + + if (strstr(lower, "gradient")) { + if (strstr(lower, "splitvertical")) { + gradient = LAB_GRADIENT_SPLITVERTICAL; + } else if (strstr(lower, "vertical")) { + gradient = LAB_GRADIENT_VERTICAL; + } + } + + g_free(lower); + return gradient; +} + static enum lab_justification parse_justification(const char *str) { @@ -530,8 +552,16 @@ theme_builtin(struct theme *theme, struct server *server) parse_hexstr("#ff0000", theme->window_toggled_keybinds_color); - parse_hexstr("#e1dedb", theme->window[THEME_ACTIVE].title_bg_color); - parse_hexstr("#f6f5f4", theme->window[THEME_INACTIVE].title_bg_color); + theme->window[THEME_ACTIVE].title_bg.gradient = LAB_GRADIENT_NONE; + theme->window[THEME_INACTIVE].title_bg.gradient = LAB_GRADIENT_NONE; + parse_hexstr("#e1dedb", theme->window[THEME_ACTIVE].title_bg.color); + parse_hexstr("#f6f5f4", theme->window[THEME_INACTIVE].title_bg.color); + theme->window[THEME_ACTIVE].title_bg.color_split_to[0] = FLT_MIN; + theme->window[THEME_INACTIVE].title_bg.color_split_to[0] = FLT_MIN; + theme->window[THEME_ACTIVE].title_bg.color_to[0] = FLT_MIN; + theme->window[THEME_INACTIVE].title_bg.color_to[0] = FLT_MIN; + theme->window[THEME_ACTIVE].title_bg.color_to_split_to[0] = FLT_MIN; + theme->window[THEME_INACTIVE].title_bg.color_to_split_to[0] = FLT_MIN; parse_hexstr("#000000", theme->window[THEME_ACTIVE].label_text_color); parse_hexstr("#000000", theme->window[THEME_INACTIVE].label_text_color); @@ -688,11 +718,35 @@ entry(struct theme *theme, const char *key, const char *value) parse_color(value, theme->window_toggled_keybinds_color); } + if (match_glob(key, "window.active.title.bg")) { + theme->window[THEME_ACTIVE].title_bg.gradient = parse_gradient(value); + } + if (match_glob(key, "window.inactive.title.bg")) { + theme->window[THEME_INACTIVE].title_bg.gradient = parse_gradient(value); + } if (match_glob(key, "window.active.title.bg.color")) { - parse_color(value, theme->window[THEME_ACTIVE].title_bg_color); + parse_color(value, theme->window[THEME_ACTIVE].title_bg.color); } if (match_glob(key, "window.inactive.title.bg.color")) { - parse_color(value, theme->window[THEME_INACTIVE].title_bg_color); + parse_color(value, theme->window[THEME_INACTIVE].title_bg.color); + } + if (match_glob(key, "window.active.title.bg.color.splitTo")) { + parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_split_to); + } + if (match_glob(key, "window.inactive.title.bg.color.splitTo")) { + parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_split_to); + } + if (match_glob(key, "window.active.title.bg.colorTo")) { + parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_to); + } + if (match_glob(key, "window.inactive.title.bg.colorTo")) { + parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_to); + } + if (match_glob(key, "window.active.title.bg.colorTo.splitTo")) { + parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_to_split_to); + } + if (match_glob(key, "window.inactive.title.bg.colorTo.splitTo")) { + parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_to_split_to); } if (match_glob(key, "window.active.label.text.color")) { @@ -1229,6 +1283,49 @@ out: return buffer; } +static void +add_color_stop_rgba_premult(cairo_pattern_t *pattern, float offset, + const float c[4]) +{ + float alpha = c[3]; + + if (alpha == 0.0f) { + cairo_pattern_add_color_stop_rgba(pattern, offset, 0, 0, 0, 0); + } else { + cairo_pattern_add_color_stop_rgba(pattern, offset, + c[0] / alpha, c[1] / alpha, c[2] / alpha, alpha); + } +} + +static cairo_pattern_t * +create_titlebar_pattern(const struct theme_background *bg, int height) +{ + cairo_pattern_t *pattern; + + switch (bg->gradient) { + case LAB_GRADIENT_VERTICAL: + pattern = cairo_pattern_create_linear(0, 0, 0, height); + add_color_stop_rgba_premult(pattern, 0, bg->color); + add_color_stop_rgba_premult(pattern, 1, bg->color_to); + break; + + case LAB_GRADIENT_SPLITVERTICAL: + pattern = cairo_pattern_create_linear(0, 0, 0, height); + add_color_stop_rgba_premult(pattern, 0, bg->color_split_to); + add_color_stop_rgba_premult(pattern, 0.5, bg->color); + add_color_stop_rgba_premult(pattern, 0.5, bg->color_to); + add_color_stop_rgba_premult(pattern, 1, bg->color_to_split_to); + break; + + case LAB_GRADIENT_NONE: + default: + pattern = color_to_pattern(bg->color); + break; + } + + return pattern; +} + static struct lab_data_buffer * create_titlebar_fill(cairo_pattern_t *pattern, int height) { @@ -1248,8 +1345,9 @@ 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_pattern = create_titlebar_pattern( + &theme->window[active].title_bg, + theme->titlebar_height); theme->window[active].titlebar_fill = create_titlebar_fill( theme->window[active].titlebar_pattern, theme->titlebar_height); @@ -1444,6 +1542,33 @@ create_shadows(struct theme *theme) create_shadow(theme, THEME_ACTIVE); } +static void +copy_color_scaled(float dest[4], const float src[4], float scale) +{ + /* RGB values are premultiplied so must not exceed alpha */ + dest[0] = fminf(src[0] * scale, src[3]); + dest[1] = fminf(src[1] * scale, src[3]); + dest[2] = fminf(src[2] * scale, src[3]); + dest[3] = src[3]; /* alpha */ +} + +static void +fill_background_colors(struct theme_background *bg) +{ + /* color.splitTo is color * 5/4, per Openbox theme spec */ + if (bg->color_split_to[0] == FLT_MIN) { + copy_color_scaled(bg->color_split_to, bg->color, 1.25f); + } + /* colorTo has no default in Openbox; just re-use "color" */ + if (bg->color_to[0] == FLT_MIN) { + memcpy(bg->color_to, bg->color, sizeof(bg->color_to)); + } + /* colorTo.splitTo is colorTo * 17/16, per Openbox theme spec */ + if (bg->color_to_split_to[0] == FLT_MIN) { + copy_color_scaled(bg->color_to_split_to, bg->color_to, 1.0625f); + } +} + static void fill_colors_with_osd_theme(struct theme *theme, float colors[3][4]) { @@ -1469,6 +1594,9 @@ post_processing(struct theme *theme) { theme->titlebar_height = get_titlebar_height(theme); + fill_background_colors(&theme->window[THEME_INACTIVE].title_bg); + fill_background_colors(&theme->window[THEME_ACTIVE].title_bg); + theme->menu_item_height = font_height(&rc.font_menuitem) + 2 * theme->menu_items_padding_y; @@ -1518,7 +1646,7 @@ post_processing(struct theme *theme) /* Inherit OSD settings if not set */ if (theme->osd_bg_color[0] == FLT_MIN) { memcpy(theme->osd_bg_color, - theme->window[THEME_ACTIVE].title_bg_color, + theme->window[THEME_ACTIVE].title_bg.color, sizeof(theme->osd_bg_color)); } if (theme->osd_border_width == INT_MIN) {