]> git.mdlowis.com Git - proto/labwc.git/commitdiff
theme: support basic vertical titlebar gradients
authorJohn Lindgren <john@jlindgren.net>
Tue, 22 Apr 2025 16:24:06 +0000 (12:24 -0400)
committerJohn Lindgren <john@jlindgren.net>
Wed, 18 Jun 2025 19:48:24 +0000 (15:48 -0400)
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.)

include/theme.h
src/theme.c

index 9fe2857ceed875ec6ecc00fa042b1081a5898460..92d4a99e3e396e9ee1547d7bfae5f9ad8483b990 100644 (file)
 
 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 */
index 90fb21c87edb7795cc89ef29878e7d7603f72250..b413757be8c2704c548f76f4301671af8ff2e2a7 100644 (file)
@@ -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) {