]> git.mdlowis.com Git - proto/labwc.git/commitdiff
theme: refine the management of corner buttons
authortokyo4j <hrak1529@gmail.com>
Fri, 4 Oct 2024 06:27:25 +0000 (15:27 +0900)
committerJohan Malm <johanmalm@users.noreply.github.com>
Sat, 5 Oct 2024 19:53:51 +0000 (20:53 +0100)
- The builtin hover effect is now unrounded when the window is tiled.
- All the corner button icons including ones provided by the user are
  now rounded when the window is not tiled.
- Fixed the bug that the window menu button as a fallback of the window
  icon is not correctly rounded.

include/ssd-internal.h
include/ssd.h
include/theme.h
src/ssd/ssd-part.c
src/ssd/ssd-titlebar.c
src/theme.c

index 14bd3265142069bfeb27853717a7d31517a8422d..a0786dfc5e30d1860ce0e695c9150a181ccdabee 100644 (file)
 struct ssd_button {
        struct view *view;
        enum ssd_part_type type;
-       struct wlr_scene_node *normal;
-       struct wlr_scene_node *hover;
-       struct wlr_scene_node *toggled;
-       struct wlr_scene_node *toggled_hover;
-       struct wlr_scene_tree *icon_tree;
-       struct wlr_scene_tree *hover_tree;
+       /*
+        * Bitmap of lab_button_state that represents a combination of
+        * hover/toggled/rounded states.
+        */
+       uint8_t state_set;
+       /*
+        * Button nodes for each combination of hover/toggled/rounded states.
+        * nodes[state_set] should be displayed.
+        */
+       struct wlr_scene_node *nodes[LAB_BS_ALL + 1];
 
        struct wl_listener destroy;
 };
@@ -140,13 +144,10 @@ struct ssd_part *add_scene_rect(
 struct ssd_part *add_scene_buffer(
        struct wl_list *list, enum ssd_part_type type,
        struct wlr_scene_tree *parent, struct wlr_buffer *buffer, int x, int y);
-struct ssd_part *add_scene_button(
-       struct wl_list *part_list, enum ssd_part_type type,
-       struct wlr_scene_tree *parent, struct wlr_buffer *icon_buffer,
-       struct wlr_buffer *hover_buffer, int x, struct view *view);
-void add_toggled_icon(struct ssd_button *button, struct wl_list *part_list,
-       enum ssd_part_type type, struct wlr_buffer *icon_buffer,
-       struct wlr_buffer *hover_buffer);
+struct ssd_part *add_scene_button(struct wl_list *part_list,
+       enum ssd_part_type type, struct wlr_scene_tree *parent,
+       struct lab_data_buffer *buffers[LAB_BS_ALL + 1], int x,
+       struct view *view);
 void update_window_icon_buffer(struct wlr_scene_node *button_node,
        struct wlr_buffer *buffer);
 
index daad81a56a297ecb3b1a70b5b8f45b937dbf6382..d5b66c087547e80161a079782c8b56d32cfdc81f 100644 (file)
  */
 enum ssd_part_type {
        LAB_SSD_NONE = 0,
-       LAB_SSD_BUTTON_CLOSE,
+
+       LAB_SSD_BUTTON_CLOSE = 1,
        LAB_SSD_BUTTON_MAXIMIZE,
        LAB_SSD_BUTTON_ICONIFY,
        LAB_SSD_BUTTON_WINDOW_ICON,
        LAB_SSD_BUTTON_WINDOW_MENU,
        LAB_SSD_BUTTON_SHADE,
        LAB_SSD_BUTTON_OMNIPRESENT,
-       LAB_SSD_BUTTON, /* only for internal use */
+       /* only for internal use */
+       LAB_SSD_BUTTON_FIRST = LAB_SSD_BUTTON_CLOSE,
+       LAB_SSD_BUTTON_LAST = LAB_SSD_BUTTON_OMNIPRESENT,
+       LAB_SSD_BUTTON,
+
        LAB_SSD_PART_TITLEBAR,
        LAB_SSD_PART_TITLEBAR_CORNER_RIGHT,
        LAB_SSD_PART_TITLEBAR_CORNER_LEFT,
index 7f3eac901ee411158ddf10aa9ac16012c5675c50..3fbe44047f9cdb721f44cdd1b130b391a61c6c93 100644 (file)
@@ -10,6 +10,7 @@
 
 #include <stdio.h>
 #include <wlr/render/wlr_renderer.h>
+#include "ssd.h"
 
 enum lab_justification {
        LAB_JUSTIFY_LEFT,
@@ -30,6 +31,14 @@ struct theme_snapping_overlay {
        float border_color[3][4];
 };
 
+enum lab_button_state {
+       LAB_BS_HOVERD = 1 << 0,
+       LAB_BS_TOGGLED = 1 << 1,
+       LAB_BS_ROUNDED = 1 << 2,
+
+       LAB_BS_ALL = LAB_BS_HOVERD | LAB_BS_TOGGLED | LAB_BS_ROUNDED,
+};
+
 struct theme {
        int border_width;
 
@@ -65,21 +74,6 @@ struct theme {
        /* the shape of the hover effect */
        enum lab_shape window_button_hover_bg_shape;
 
-       /* button colors */
-       float window_active_button_menu_unpressed_image_color[4];
-       float window_active_button_iconify_unpressed_image_color[4];
-       float window_active_button_max_unpressed_image_color[4];
-       float window_active_button_close_unpressed_image_color[4];
-       float window_active_button_shade_unpressed_image_color[4];
-       float window_active_button_omnipresent_unpressed_image_color[4];
-       float window_inactive_button_menu_unpressed_image_color[4];
-       float window_inactive_button_iconify_unpressed_image_color[4];
-       float window_inactive_button_max_unpressed_image_color[4];
-       float window_inactive_button_close_unpressed_image_color[4];
-       float window_inactive_button_shade_unpressed_image_color[4];
-       float window_inactive_button_omnipresent_unpressed_image_color[4];
-       /* TODO: add pressed and hover colors for buttons */
-
        int menu_item_padding_x;
        int menu_item_padding_y;
        int menu_item_height;
@@ -128,47 +122,26 @@ struct theme {
        float window_active_shadow_color[4];
        float window_inactive_shadow_color[4];
 
+       struct {
+               /*
+                * The texture of a window buttons for each hover/toggled/rounded
+                * state. This can be accessed like:
+                *
+                * buttons[LAB_SSD_BUTTON_ICONIFY][LAB_BS_HOVERD | LAB_BS_TOGGLED]
+                *
+                * Elements in buttons[0] are all NULL since LAB_SSD_BUTTON_FIRST is 1.
+                */
+               struct lab_data_buffer *buttons
+                       [LAB_SSD_BUTTON_LAST + 1][LAB_BS_ALL + 1];
+
+               /* TODO: add toggled/hover/pressed/disabled colors for buttons */
+               float button_colors[LAB_SSD_BUTTON_LAST + 1][4];
+
+               /* TODO: move other window.(in)active.* entries to here */
+
+       } window[2]; /* indexed by THEME_INACTIVE and THEME_ACTIVE */
+
        /* textures */
-       struct lab_data_buffer *button_close_active_unpressed;
-       struct lab_data_buffer *button_maximize_active_unpressed;
-       struct lab_data_buffer *button_restore_active_unpressed;
-       struct lab_data_buffer *button_iconify_active_unpressed;
-       struct lab_data_buffer *button_menu_active_unpressed;
-       struct lab_data_buffer *button_shade_active_unpressed;
-       struct lab_data_buffer *button_unshade_active_unpressed;
-       struct lab_data_buffer *button_omnipresent_active_unpressed;
-       struct lab_data_buffer *button_exclusive_active_unpressed;
-
-       struct lab_data_buffer *button_close_inactive_unpressed;
-       struct lab_data_buffer *button_maximize_inactive_unpressed;
-       struct lab_data_buffer *button_restore_inactive_unpressed;
-       struct lab_data_buffer *button_iconify_inactive_unpressed;
-       struct lab_data_buffer *button_menu_inactive_unpressed;
-       struct lab_data_buffer *button_shade_inactive_unpressed;
-       struct lab_data_buffer *button_unshade_inactive_unpressed;
-       struct lab_data_buffer *button_omnipresent_inactive_unpressed;
-       struct lab_data_buffer *button_exclusive_inactive_unpressed;
-
-       /* hover variants are optional and may be NULL */
-       struct lab_data_buffer *button_close_active_hover;
-       struct lab_data_buffer *button_maximize_active_hover;
-       struct lab_data_buffer *button_restore_active_hover;
-       struct lab_data_buffer *button_iconify_active_hover;
-       struct lab_data_buffer *button_menu_active_hover;
-       struct lab_data_buffer *button_shade_active_hover;
-       struct lab_data_buffer *button_unshade_active_hover;
-       struct lab_data_buffer *button_omnipresent_active_hover;
-       struct lab_data_buffer *button_exclusive_active_hover;
-
-       struct lab_data_buffer *button_close_inactive_hover;
-       struct lab_data_buffer *button_maximize_inactive_hover;
-       struct lab_data_buffer *button_restore_inactive_hover;
-       struct lab_data_buffer *button_iconify_inactive_hover;
-       struct lab_data_buffer *button_menu_inactive_hover;
-       struct lab_data_buffer *button_shade_inactive_hover;
-       struct lab_data_buffer *button_unshade_inactive_hover;
-       struct lab_data_buffer *button_omnipresent_inactive_hover;
-       struct lab_data_buffer *button_exclusive_inactive_hover;
 
        struct lab_data_buffer *corner_top_left_active_normal;
        struct lab_data_buffer *corner_top_right_active_normal;
@@ -190,6 +163,9 @@ struct theme {
        int mag_border_width;
 };
 
+#define THEME_INACTIVE 0
+#define THEME_ACTIVE 1
+
 struct server;
 
 /**
index d647a272a8e756b5d37b504c96ef66452326f440..57b2e63f94f5b4b2165aa5ee5dd4342c7629cf1d 100644 (file)
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
 
 #include <assert.h>
+#include "buffer.h"
 #include "common/list.h"
 #include "common/mem.h"
 #include "labwc.h"
@@ -123,8 +124,9 @@ update_window_icon_buffer(struct wlr_scene_node *button_node,
 
 struct ssd_part *
 add_scene_button(struct wl_list *part_list, enum ssd_part_type type,
-               struct wlr_scene_tree *parent, struct wlr_buffer *icon_buffer,
-               struct wlr_buffer *hover_buffer, int x, struct view *view)
+               struct wlr_scene_tree *parent,
+               struct lab_data_buffer *buffers[LAB_BS_ALL + 1],
+               int x, struct view *view)
 {
        struct ssd_part *button_root = add_scene_part(part_list, type);
        parent = wlr_scene_tree_create(parent);
@@ -137,76 +139,35 @@ add_scene_button(struct wl_list *part_list, enum ssd_part_type type,
                rc.theme->window_button_width, rc.theme->title_height, 0, 0,
                invisible);
 
-       /* Icon */
-       struct wlr_scene_tree *icon_tree = wlr_scene_tree_create(parent);
-       struct wlr_box icon_geo = get_scale_box(icon_buffer,
-               rc.theme->window_button_width, rc.theme->title_height);
-       struct ssd_part *icon_part = add_scene_buffer(part_list, type,
-               icon_tree, icon_buffer, icon_geo.x, icon_geo.y);
-
-       /* Make sure big icons are scaled down if necessary */
-       wlr_scene_buffer_set_dest_size(
-               wlr_scene_buffer_from_node(icon_part->node),
-               icon_geo.width, icon_geo.height);
-
-       /* Hover icon */
-       struct wlr_scene_tree *hover_tree = wlr_scene_tree_create(parent);
-       wlr_scene_node_set_enabled(&hover_tree->node, false);
-       struct wlr_box hover_geo = get_scale_box(hover_buffer,
-               rc.theme->window_button_width, rc.theme->title_height);
-       struct ssd_part *hover_part = add_scene_buffer(part_list, type,
-               hover_tree, hover_buffer, hover_geo.x, hover_geo.y);
-
-       /* Make sure big icons are scaled down if necessary */
-       wlr_scene_buffer_set_dest_size(
-               wlr_scene_buffer_from_node(hover_part->node),
-               hover_geo.width, hover_geo.height);
+       /* Icons */
+       struct wlr_scene_node *nodes[LAB_BS_ALL + 1] = {0};
+       for (uint8_t state_set = 0; state_set <= LAB_BS_ALL; state_set++) {
+               if (!buffers[state_set]) {
+                       continue;
+               }
+               struct wlr_buffer *icon_buffer = &buffers[state_set]->base;
+               struct wlr_box icon_geo = get_scale_box(icon_buffer,
+                       rc.theme->window_button_width, rc.theme->title_height);
+               struct ssd_part *icon_part = add_scene_buffer(part_list, type,
+                       parent, icon_buffer, icon_geo.x, icon_geo.y);
+               /* Make sure big icons are scaled down if necessary */
+               wlr_scene_buffer_set_dest_size(
+                       wlr_scene_buffer_from_node(icon_part->node),
+                       icon_geo.width, icon_geo.height);
+               wlr_scene_node_set_enabled(icon_part->node, false);
+               nodes[state_set] = icon_part->node;
+       }
+       /* Initially show non-hover, non-toggled, unrounded variant */
+       wlr_scene_node_set_enabled(nodes[0], true);
 
        struct ssd_button *button = ssd_button_descriptor_create(button_root->node);
        button->type = type;
        button->view = view;
-       button->normal = icon_part->node;
-       button->hover = hover_part->node;
-       button->toggled = NULL;
-       button->toggled_hover = NULL;
-       button->icon_tree = icon_tree;
-       button->hover_tree = hover_tree;
+       button->state_set = 0;
+       memcpy(button->nodes, nodes, sizeof(nodes));
        return button_root;
 }
 
-void
-add_toggled_icon(struct ssd_button *button, struct wl_list *part_list,
-               enum ssd_part_type type, struct wlr_buffer *icon_buffer,
-               struct wlr_buffer *hover_buffer)
-{
-       /* Alternate icon */
-       struct wlr_box icon_geo = get_scale_box(icon_buffer,
-               rc.theme->window_button_width, rc.theme->title_height);
-
-       struct ssd_part *alticon_part = add_scene_buffer(part_list, type,
-               button->icon_tree, icon_buffer, icon_geo.x, icon_geo.y);
-
-       wlr_scene_buffer_set_dest_size(
-               wlr_scene_buffer_from_node(alticon_part->node),
-               icon_geo.width, icon_geo.height);
-
-       wlr_scene_node_set_enabled(alticon_part->node, false);
-
-       struct wlr_box hover_geo = get_scale_box(hover_buffer,
-               rc.theme->window_button_width, rc.theme->title_height);
-       struct ssd_part *althover_part = add_scene_buffer(part_list, type,
-               button->hover_tree, hover_buffer, hover_geo.x, hover_geo.y);
-
-       wlr_scene_buffer_set_dest_size(
-               wlr_scene_buffer_from_node(althover_part->node),
-               hover_geo.width, hover_geo.height);
-
-       wlr_scene_node_set_enabled(althover_part->node, false);
-
-       button->toggled = alticon_part->node;
-       button->toggled_hover = althover_part->node;
-}
-
 struct ssd_part *
 ssd_get_part(struct wl_list *part_list, enum ssd_part_type type)
 {
index c63bd5f9f44d3be210128a50c5eb5c74ed92c067..ca5a8dde25536175ff69b4ea46becadf8f7f3614 100644 (file)
@@ -26,95 +26,6 @@ static void set_squared_corners(struct ssd *ssd, bool enable);
 static void set_alt_button_icon(struct ssd *ssd, enum ssd_part_type type, bool enable);
 static void update_visible_buttons(struct ssd *ssd);
 
-static void
-add_button(struct ssd *ssd, struct ssd_sub_tree *subtree, enum ssd_part_type type, int x)
-{
-       struct view *view = ssd->view;
-       struct theme *theme = view->server->theme;
-       struct wlr_scene_tree *parent = subtree->tree;
-       bool active = subtree == &ssd->titlebar.active;
-
-       struct ssd_part *btn_root;
-       struct ssd_button *btn;
-
-       switch (type) {
-       case LAB_SSD_BUTTON_WINDOW_ICON: /* fallthrough */
-       case LAB_SSD_BUTTON_WINDOW_MENU:
-               add_scene_button(&subtree->parts, type, parent,
-                       active ? &theme->button_menu_active_unpressed->base
-                               : &theme->button_menu_inactive_unpressed->base,
-                       active ? &theme->button_menu_active_hover->base
-                               : &theme->button_menu_inactive_hover->base,
-                       x, view);
-               break;
-       case LAB_SSD_BUTTON_ICONIFY:
-               add_scene_button(&subtree->parts, type, parent,
-                       active ? &theme->button_iconify_active_unpressed->base
-                               : &theme->button_iconify_inactive_unpressed->base,
-                       active ? &theme->button_iconify_active_hover->base
-                               : &theme->button_iconify_inactive_hover->base,
-                       x, view);
-               break;
-       case LAB_SSD_BUTTON_MAXIMIZE:
-               /* Maximize button has an alternate state when maximized */
-               btn_root = add_scene_button(&subtree->parts, type, parent,
-                       active ? &theme->button_maximize_active_unpressed->base
-                               : &theme->button_maximize_inactive_unpressed->base,
-                       active ? &theme->button_maximize_active_hover->base
-                               : &theme->button_maximize_inactive_hover->base,
-                       x, view);
-               btn = node_ssd_button_from_node(btn_root->node);
-               add_toggled_icon(btn, &subtree->parts, LAB_SSD_BUTTON_MAXIMIZE,
-                       active ? &theme->button_restore_active_unpressed->base
-                               : &theme->button_restore_inactive_unpressed->base,
-                       active ? &theme->button_restore_active_hover->base
-                               : &theme->button_restore_inactive_hover->base);
-               break;
-       case LAB_SSD_BUTTON_SHADE:
-               /* Shade button has an alternate state when shaded */
-               btn_root = add_scene_button(&subtree->parts, type, parent,
-                       active ? &theme->button_shade_active_unpressed->base
-                               : &theme->button_shade_inactive_unpressed->base,
-                       active ? &theme->button_shade_active_hover->base
-                               : &theme->button_shade_inactive_hover->base,
-                       x, view);
-               btn = node_ssd_button_from_node(btn_root->node);
-               add_toggled_icon(btn, &subtree->parts, LAB_SSD_BUTTON_SHADE,
-                       active ? &theme->button_unshade_active_unpressed->base
-                               : &theme->button_unshade_inactive_unpressed->base,
-                       active ? &theme->button_unshade_active_hover->base
-                               : &theme->button_unshade_inactive_hover->base);
-               break;
-       case LAB_SSD_BUTTON_OMNIPRESENT:
-               /* Omnipresent button has an alternate state when enabled */
-               btn_root = add_scene_button(&subtree->parts, type, parent,
-                       active ? &theme->button_omnipresent_active_unpressed->base
-                               : &theme->button_omnipresent_inactive_unpressed->base,
-                       active ? &theme->button_omnipresent_active_hover->base
-                               : &theme->button_omnipresent_inactive_hover->base,
-                       x, view);
-               btn = node_ssd_button_from_node(btn_root->node);
-               add_toggled_icon(btn, &subtree->parts, LAB_SSD_BUTTON_OMNIPRESENT,
-                       active ? &theme->button_exclusive_active_unpressed->base
-                               : &theme->button_exclusive_inactive_unpressed->base,
-                       active ? &theme->button_exclusive_active_hover->base
-                               : &theme->button_exclusive_inactive_hover->base);
-               break;
-       case LAB_SSD_BUTTON_CLOSE:
-               add_scene_button(&subtree->parts, type, parent,
-                       active ? &theme->button_close_active_unpressed->base
-                               : &theme->button_close_inactive_unpressed->base,
-                       active ? &theme->button_close_active_hover->base
-                               : &theme->button_close_inactive_hover->base,
-                       x, view);
-               break;
-       default:
-               assert(false && "invalid titlebar part");
-               wlr_log(WLR_ERROR, "invalid titlebar type");
-               abort();
-       }
-}
-
 void
 ssd_titlebar_create(struct ssd *ssd)
 {
@@ -158,18 +69,27 @@ ssd_titlebar_create(struct ssd *ssd)
                        corner_top_right, width - corner_width,
                        -rc.theme->border_width);
 
+               int active = (subtree == &ssd->titlebar.active) ?
+                               THEME_ACTIVE : THEME_INACTIVE;
+
                /* Buttons */
                struct title_button *b;
                int x = theme->padding_width;
                wl_list_for_each(b, &rc.title_buttons_left, link) {
-                       add_button(ssd, subtree, b->type, x);
+                       struct lab_data_buffer **buffers =
+                               theme->window[active].buttons[b->type];
+                       add_scene_button(&subtree->parts, b->type, parent,
+                               buffers, x, view);
                        x += theme->window_button_width + theme->window_button_spacing;
                }
 
                x = width - theme->padding_width + theme->window_button_spacing;
                wl_list_for_each_reverse(b, &rc.title_buttons_right, link) {
                        x -= theme->window_button_width + theme->window_button_spacing;
-                       add_button(ssd, subtree, b->type, x);
+                       struct lab_data_buffer **buffers =
+                               theme->window[active].buttons[b->type];
+                       add_scene_button(&subtree->parts, b->type, parent,
+                               buffers, x, view);
                }
        } FOR_EACH_END
 
@@ -178,6 +98,8 @@ ssd_titlebar_create(struct ssd *ssd)
        ssd_update_title(ssd);
        ssd_update_window_icon(ssd);
 
+       set_squared_corners(ssd, false);
+
        bool maximized = view->maximized == VIEW_AXIS_BOTH;
        if (maximized) {
                set_squared_corners(ssd, true);
@@ -199,6 +121,25 @@ ssd_titlebar_create(struct ssd *ssd)
        }
 }
 
+static void
+update_button_state(struct ssd_button *button, enum lab_button_state state,
+               bool enable)
+{
+       if (enable) {
+               button->state_set |= state;
+       } else {
+               button->state_set &= ~state;
+       }
+       /* Switch the displayed icon buffer to the new one */
+       for (uint8_t state_set = 0; state_set <= LAB_BS_ALL; state_set++) {
+               if (!button->nodes[state_set]) {
+                       continue;
+               }
+               wlr_scene_node_set_enabled(
+                       button->nodes[state_set], button->state_set == state_set);
+       }
+}
+
 static void
 set_squared_corners(struct ssd *ssd, bool enable)
 {
@@ -222,6 +163,21 @@ set_squared_corners(struct ssd *ssd, bool enable)
 
                part = ssd_get_part(&subtree->parts, LAB_SSD_PART_TITLEBAR_CORNER_RIGHT);
                wlr_scene_node_set_enabled(part->node, !enable);
+
+               /* (Un)round the corner buttons */
+               struct title_button *title_button;
+               wl_list_for_each(title_button, &rc.title_buttons_left, link) {
+                       part = ssd_get_part(&subtree->parts, title_button->type);
+                       struct ssd_button *button = node_ssd_button_from_node(part->node);
+                       update_button_state(button, LAB_BS_ROUNDED, !enable);
+                       break;
+               }
+               wl_list_for_each_reverse(title_button, &rc.title_buttons_right, link) {
+                       part = ssd_get_part(&subtree->parts, title_button->type);
+                       struct ssd_button *button = node_ssd_button_from_node(part->node);
+                       update_button_state(button, LAB_BS_ROUNDED, !enable);
+                       break;
+               }
        } FOR_EACH_END
 }
 
@@ -239,16 +195,7 @@ set_alt_button_icon(struct ssd *ssd, enum ssd_part_type type, bool enable)
                }
 
                button = node_ssd_button_from_node(part->node);
-
-               if (button->toggled) {
-                       wlr_scene_node_set_enabled(button->toggled, enable);
-                       wlr_scene_node_set_enabled(button->normal, !enable);
-               }
-
-               if (button->toggled_hover) {
-                       wlr_scene_node_set_enabled(button->toggled_hover, enable);
-                       wlr_scene_node_set_enabled(button->hover, !enable);
-               }
+               update_button_state(button, LAB_BS_TOGGLED, enable);
        } FOR_EACH_END
 }
 
@@ -583,14 +530,6 @@ ssd_update_title(struct ssd *ssd)
        ssd_update_title_positions(ssd, offset_left, offset_right);
 }
 
-static void
-ssd_button_set_hover(struct ssd_button *button, bool enabled)
-{
-       assert(button);
-       wlr_scene_node_set_enabled(&button->hover_tree->node, enabled);
-       wlr_scene_node_set_enabled(&button->icon_tree->node, !enabled);
-}
-
 void
 ssd_update_button_hover(struct wlr_scene_node *node,
                struct ssd_hover_state *hover_state)
@@ -611,12 +550,12 @@ ssd_update_button_hover(struct wlr_scene_node *node,
 
 disable_old_hover:
        if (hover_state->button) {
-               ssd_button_set_hover(hover_state->button, false);
+               update_button_state(hover_state->button, LAB_BS_HOVERD, false);
                hover_state->view = NULL;
                hover_state->button = NULL;
        }
        if (button) {
-               ssd_button_set_hover(button, true);
+               update_button_state(button, LAB_BS_HOVERD, true);
                hover_state->view = button->view;
                hover_state->button = button;
        }
@@ -677,15 +616,20 @@ ssd_update_window_icon(struct ssd *ssd)
 
        struct ssd_sub_tree *subtree;
        FOR_EACH_STATE(ssd, subtree) {
-               struct ssd_part *part =
-                       ssd_get_part(&subtree->parts, LAB_SSD_BUTTON_WINDOW_ICON);
+               struct ssd_part *part = ssd_get_part(
+                       &subtree->parts, LAB_SSD_BUTTON_WINDOW_ICON);
                if (!part) {
                        break;
                }
 
+               /* Replace all the buffers in the button with the window icon */
                struct ssd_button *button = node_ssd_button_from_node(part->node);
-               update_window_icon_buffer(button->normal, &icon_buffer->base);
-               update_window_icon_buffer(button->hover, &icon_buffer->base);
+               for (uint8_t state_set = 0; state_set <= LAB_BS_ALL; state_set++) {
+                       if (button->nodes[state_set]) {
+                               update_window_icon_buffer(button->nodes[state_set],
+                                       &icon_buffer->base);
+                       }
+               }
        } FOR_EACH_END
 
        wlr_buffer_drop(&icon_buffer->base);
index f3b328b0059b8ac2fb401780396980427904fd25..bfcaa3dc597cbe7805e60e87a70c46dcd7f902d4 100644 (file)
@@ -45,10 +45,8 @@ struct button {
        const char *name;
        const char *alt_name;
        const char *fallback_button;  /* built-in 6x6 button */
-       struct {
-               struct lab_data_buffer **buffer;
-               float *rgba;
-       } active, inactive;
+       enum ssd_part_type type;
+       uint8_t state_set;
 };
 
 enum corner {
@@ -82,50 +80,10 @@ zdrop(struct lab_data_buffer **buffer)
        }
 }
 
-static bool
-match_button_by_name(struct title_button *b, const char *icon_name)
-{
-       assert(icon_name);
-       return (b->type == LAB_SSD_BUTTON_WINDOW_MENU && !strcmp(icon_name, "menu"))
-               || (b->type == LAB_SSD_BUTTON_MAXIMIZE && !strcmp(icon_name, "max"))
-               || (b->type == LAB_SSD_BUTTON_MAXIMIZE && !strcmp(icon_name, "max_toggled"))
-               || (b->type == LAB_SSD_BUTTON_ICONIFY && !strcmp(icon_name, "iconify"))
-               || (b->type == LAB_SSD_BUTTON_CLOSE && !strcmp(icon_name, "close"))
-               || (b->type == LAB_SSD_BUTTON_SHADE && !strcmp(icon_name, "shade"))
-               || (b->type == LAB_SSD_BUTTON_SHADE && !strcmp(icon_name, "shade_toggled"))
-               || (b->type == LAB_SSD_BUTTON_OMNIPRESENT && !strcmp(icon_name, "desk"))
-               || (b->type == LAB_SSD_BUTTON_OMNIPRESENT && !strcmp(icon_name, "desk_toggled"));
-}
-
-static enum corner
-corner_from_icon_name(const char *icon_name)
-{
-       assert(icon_name);
-
-       struct title_button *b;
-       wl_list_for_each(b, &rc.title_buttons_left, link) {
-               if (match_button_by_name(b, icon_name)) {
-                       return LAB_CORNER_TOP_LEFT;
-               }
-               break;
-       }
-       wl_list_for_each_reverse(b, &rc.title_buttons_right, link) {
-               if (match_button_by_name(b, icon_name)) {
-                       return LAB_CORNER_TOP_RIGHT;
-               }
-               break;
-       }
-       return LAB_CORNER_UNKNOWN;
-}
-
-static void
-create_hover_fallback(struct theme *theme, const char *icon_name,
-               struct lab_data_buffer **hover_buffer,
-               struct lab_data_buffer *icon_buffer)
+static struct lab_data_buffer *
+copy_icon_buffer(struct theme *theme, struct lab_data_buffer *icon_buffer)
 {
-       assert(icon_name);
        assert(icon_buffer);
-       assert(!*hover_buffer);
 
        struct surface_context icon =
                get_cairo_surface_from_lab_data_buffer(icon_buffer);
@@ -151,75 +109,100 @@ create_hover_fallback(struct theme *theme, const char *icon_name,
        }
        int buffer_width = (double)width * scale;
        int buffer_height = (double)height * scale;
+       struct lab_data_buffer *buffer = buffer_create_cairo(
+               buffer_width, buffer_height, 1.0, true);
+       cairo_t *cairo = buffer->cairo;
 
-       *hover_buffer = buffer_create_cairo(buffer_width, buffer_height, 1.0, true);
-
-       cairo_t *cairo = (*hover_buffer)->cairo;
-       cairo_surface_t *surf = cairo_get_target(cairo);
-
-       /* Background */
        cairo_set_source_surface(cairo, icon.surface,
                (buffer_width - icon_width) / 2, (buffer_height - icon_height) / 2);
        cairo_paint(cairo);
 
-       /* Switch from buffer scale to theme scale */
-       cairo_save(cairo);
+       /*
+        * Scale cairo context so that we can draw hover overlay or rounded
+        * corner on this buffer in the scene coordinates.
+        */
        cairo_scale(cairo, scale, scale);
 
+       if (icon.is_duplicate) {
+               cairo_surface_destroy(icon.surface);
+       }
+
+       return buffer;
+}
+
+static void
+create_hover_fallback(struct theme *theme,
+               struct lab_data_buffer **hover_buffer,
+               struct lab_data_buffer *icon_buffer)
+{
+       assert(icon_buffer);
+       assert(!*hover_buffer);
+
+       int width = theme->window_button_width;
+       int height = theme->title_height;
+
+       *hover_buffer = copy_icon_buffer(theme, icon_buffer);
+       cairo_t *cairo = (*hover_buffer)->cairo;
+
        /* Overlay (pre-multiplied alpha) */
        float overlay_color[4] = { 0.15f, 0.15f, 0.15f, 0.3f};
+       set_cairo_color(cairo, overlay_color);
        int radius = MIN(width, height) / 2;
-       enum corner corner = corner_from_icon_name(icon_name);
 
        switch (theme->window_button_hover_bg_shape) {
        case LAB_CIRCLE:
-               set_cairo_color(cairo, overlay_color);
                cairo_arc(cairo, width / 2, height / 2, radius, 0 * deg, 360 * deg);
-               cairo_fill(cairo);
                break;
        case LAB_RECTANGLE:
-               set_cairo_color(cairo, overlay_color);
                cairo_rectangle(cairo, 0, 0, width, height);
-               if (corner == LAB_CORNER_UNKNOWN) {
-                       cairo_fill(cairo);
-               } else {
-                       /*
-                        * Round the hover overlay of corner buttons by
-                        * cropping the region within the window border.
-                        */
-                       float white[4] = {1, 1, 1, 1};
-                       struct rounded_corner_ctx rounded_ctx = {
-                               .box = &(struct wlr_box){
-                                       .width = theme->padding_width + width,
-                                       .height = height,
-                               },
-                               .radius = rc.corner_radius,
-                               .line_width = theme->border_width,
-                               .fill_color = white,
-                               .border_color = white,
-                               .corner = corner,
-                       };
-                       struct lab_data_buffer *mask_buffer =
-                               rounded_rect(&rounded_ctx);
-                       int mask_offset;
-                       if (corner == LAB_CORNER_TOP_LEFT) {
-                               mask_offset = -theme->padding_width;
-                       } else {
-                               mask_offset = 0;
-                       }
-                       cairo_mask_surface(cairo,
-                               cairo_get_target(mask_buffer->cairo),
-                               mask_offset, 0);
-                       wlr_buffer_drop(&mask_buffer->base);
-               }
                break;
        }
-       cairo_surface_flush(surf);
-       cairo_restore(cairo);
 
-       if (icon.is_duplicate) {
-               cairo_surface_destroy(icon.surface);
+       cairo_fill(cairo);
+       cairo_surface_flush(cairo_get_target(cairo));
+}
+
+static void
+create_rounded_buffer(struct theme *theme, enum corner corner,
+               struct lab_data_buffer **rounded_buffer,
+               struct lab_data_buffer *icon_buffer)
+{
+       *rounded_buffer = copy_icon_buffer(theme, icon_buffer);
+       cairo_t *cairo = (*rounded_buffer)->cairo;
+
+       int width = theme->window_button_width;
+       int height = theme->title_height;
+
+       /*
+        * Round the hover overlay of corner buttons by
+        * cropping the region within the window border.
+        */
+       float white[4] = {1, 1, 1, 1};
+       struct rounded_corner_ctx rounded_ctx = {
+               .box = &(struct wlr_box){
+                       .width = theme->padding_width + width,
+                       .height = height,
+               },
+               .radius = rc.corner_radius,
+               .line_width = theme->border_width,
+               .fill_color = white,
+               .border_color = white,
+               .corner = corner,
+       };
+       int mask_offset;
+       if (corner == LAB_CORNER_TOP_LEFT) {
+               mask_offset = -theme->padding_width;
+       } else {
+               mask_offset = 0;
        }
+       struct lab_data_buffer *mask_buffer = rounded_rect(&rounded_ctx);
+       cairo_set_operator(cairo, CAIRO_OPERATOR_DEST_IN);
+       cairo_set_source_surface(cairo,
+               cairo_get_target(mask_buffer->cairo), mask_offset, 0);
+       cairo_paint(cairo);
+
+       cairo_surface_flush(cairo_get_target(cairo));
+       wlr_buffer_drop(&mask_buffer->base);
 }
 
 /*
@@ -252,6 +235,93 @@ get_button_filename(char *buf, size_t len, const char *name, const char *postfix
        paths_destroy(&paths);
 }
 
+static void
+load_button(struct theme *theme, struct button *b, int active)
+{
+       struct lab_data_buffer *(*buttons)[LAB_BS_ALL + 1] =
+               theme->window[active].buttons;
+       struct lab_data_buffer **buffer = &buttons[b->type][b->state_set];
+       float *rgba = theme->window[active].button_colors[b->type];
+       char filename[4096];
+
+       zdrop(buffer);
+
+       /* PNG */
+       get_button_filename(filename, sizeof(filename), b->name,
+               active ? "-active.png" : "-inactive.png");
+       img_png_load(filename, buffer);
+
+#if HAVE_RSVG
+       /* SVG */
+       if (!*buffer) {
+               int size = theme->title_height - 2 * theme->padding_height;
+               get_button_filename(filename, sizeof(filename), b->name,
+                       active ? "-active.svg" : "-inactive.png");
+               img_svg_load(filename, buffer, size);
+       }
+#endif
+
+       /* XBM */
+       if (!*buffer) {
+               get_button_filename(filename, sizeof(filename), b->name, ".xbm");
+               img_xbm_load(filename, buffer, rgba);
+       }
+
+       /*
+        * XBM (alternative name)
+        * For example max_hover_toggled instead of max_toggled_hover
+        */
+       if (!*buffer && b->alt_name) {
+               get_button_filename(filename, sizeof(filename),
+                       b->alt_name, ".xbm");
+               img_xbm_load(filename, buffer, rgba);
+       }
+
+       /*
+        * Builtin bitmap
+        *
+        * Applicable to basic buttons such as max, max_toggled and iconify.
+        * There are no bitmap fallbacks for *_hover icons.
+        */
+       if (!*buffer && b->fallback_button) {
+               img_xbm_from_bitmap(b->fallback_button, buffer, rgba);
+       }
+
+       /*
+        * If hover-icons do not exist, add fallbacks by copying the non-hover
+        * variant and then adding an overlay.
+        */
+       if (!*buffer && (b->state_set & LAB_BS_HOVERD)) {
+               uint8_t non_hover_state_set = b->state_set & ~LAB_BS_HOVERD;
+               create_hover_fallback(theme, buffer,
+                       buttons[b->type][non_hover_state_set]);
+       }
+
+       struct title_button *leftmost_button = NULL;
+       wl_list_for_each(leftmost_button,
+                       &rc.title_buttons_left, link) {
+               break;
+       }
+       struct title_button *rightmost_button = NULL;
+       wl_list_for_each_reverse(rightmost_button,
+                       &rc.title_buttons_right, link) {
+               break;
+       }
+
+       /*
+        * If the loaded button is at the corner of the titlebar, also create
+        * rounded variants.
+        */
+       uint8_t rounded_state_set = b->state_set | LAB_BS_ROUNDED;
+       if (leftmost_button && leftmost_button->type == b->type) {
+               create_rounded_buffer(theme, LAB_CORNER_TOP_LEFT,
+                       &buttons[b->type][rounded_state_set], *buffer);
+       } else if (rightmost_button && rightmost_button->type == b->type) {
+               create_rounded_buffer(theme, LAB_CORNER_TOP_RIGHT,
+                       &buttons[b->type][rounded_state_set], *buffer);
+       }
+}
+
 /*
  * We use the following button filename schema: "BUTTON [TOGGLED] [STATE]"
  * with the words separated by underscore, and the following meaning:
@@ -287,236 +357,115 @@ load_buttons(struct theme *theme)
 {
        struct button buttons[] = { {
                .name = "menu",
+               .type = LAB_SSD_BUTTON_WINDOW_MENU,
+               .state_set = 0,
+               .fallback_button = (const char[]){ 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00 },
+       }, {
+               /* menu icon is loaded again as a fallback of window icon */
+               .name = "menu",
+               .type = LAB_SSD_BUTTON_WINDOW_ICON,
+               .state_set = 0,
                .fallback_button = (const char[]){ 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00 },
-               .active.buffer = &theme->button_menu_active_unpressed,
-               .active.rgba = theme->window_active_button_menu_unpressed_image_color,
-               .inactive.buffer = &theme->button_menu_inactive_unpressed,
-               .inactive.rgba = theme->window_inactive_button_menu_unpressed_image_color,
        }, {
                .name = "iconify",
+               .type = LAB_SSD_BUTTON_ICONIFY,
+               .state_set = 0,
                .fallback_button = (const char[]){ 0x00, 0x00, 0x00, 0x00, 0x3f, 0x3f },
-               .active.buffer = &theme->button_iconify_active_unpressed,
-               .active.rgba = theme->window_active_button_iconify_unpressed_image_color,
-               .inactive.buffer = &theme->button_iconify_inactive_unpressed,
-               .inactive.rgba = theme->window_inactive_button_iconify_unpressed_image_color,
        }, {
                .name = "max",
+               .type = LAB_SSD_BUTTON_MAXIMIZE,
+               .state_set = 0,
                .fallback_button = (const char[]){ 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f },
-               .active.buffer = &theme->button_maximize_active_unpressed,
-               .active.rgba = theme->window_active_button_max_unpressed_image_color,
-               .inactive.buffer = &theme->button_maximize_inactive_unpressed,
-               .inactive.rgba = theme->window_inactive_button_max_unpressed_image_color,
        }, {
                .name = "max_toggled",
+               .type = LAB_SSD_BUTTON_MAXIMIZE,
+               .state_set = LAB_BS_TOGGLED,
                .fallback_button = (const char[]){ 0x3e, 0x22, 0x2f, 0x29, 0x39, 0x0f },
-               .active.buffer = &theme->button_restore_active_unpressed,
-               .active.rgba = theme->window_active_button_max_unpressed_image_color,
-               .inactive.buffer = &theme->button_restore_inactive_unpressed,
-               .inactive.rgba = theme->window_inactive_button_max_unpressed_image_color,
        }, {
                .name = "shade",
+               .type = LAB_SSD_BUTTON_SHADE,
+               .state_set = 0,
                .fallback_button = (const char[]){ 0x3f, 0x3f, 0x00, 0x0c, 0x1e, 0x3f },
-               .active.buffer = &theme->button_shade_active_unpressed,
-               .active.rgba = theme->window_active_button_shade_unpressed_image_color,
-               .inactive.buffer = &theme->button_shade_inactive_unpressed,
-               .inactive.rgba = theme->window_inactive_button_shade_unpressed_image_color,
        }, {
                .name = "shade_toggled",
+               .type = LAB_SSD_BUTTON_SHADE,
+               .state_set = LAB_BS_TOGGLED,
                .fallback_button = (const char[]){ 0x3f, 0x3f, 0x00, 0x3f, 0x1e, 0x0c },
-               .active.buffer = &theme->button_unshade_active_unpressed,
-               .active.rgba = theme->window_active_button_shade_unpressed_image_color,
-               .inactive.buffer = &theme->button_unshade_inactive_unpressed,
-               .inactive.rgba = theme->window_inactive_button_shade_unpressed_image_color,
        }, {
                .name = "desk",
+               .type = LAB_SSD_BUTTON_OMNIPRESENT,
+               .state_set = 0,
                .fallback_button = (const char[]){ 0x33, 0x33, 0x00, 0x00, 0x33, 0x33 },
-               .active.buffer = &theme->button_omnipresent_active_unpressed,
-               .active.rgba = theme->window_active_button_omnipresent_unpressed_image_color,
-               .inactive.buffer = &theme->button_omnipresent_inactive_unpressed,
-               .inactive.rgba = theme->window_inactive_button_omnipresent_unpressed_image_color,
        }, {
                .name = "desk_toggled",
+               .type = LAB_SSD_BUTTON_OMNIPRESENT,
+               .state_set = LAB_BS_TOGGLED,
                .fallback_button = (const char[]){ 0x00, 0x1e, 0x1a, 0x16, 0x1e, 0x00 },
-               .active.buffer = &theme->button_exclusive_active_unpressed,
-               .active.rgba = theme->window_active_button_omnipresent_unpressed_image_color,
-               .inactive.buffer = &theme->button_exclusive_inactive_unpressed,
-               .inactive.rgba = theme->window_inactive_button_omnipresent_unpressed_image_color,
        }, {
                .name = "close",
+               .type = LAB_SSD_BUTTON_CLOSE,
+               .state_set = 0,
                .fallback_button = (const char[]){ 0x33, 0x3f, 0x1e, 0x1e, 0x3f, 0x33 },
-               .active.buffer = &theme->button_close_active_unpressed,
-               .active.rgba = theme->window_active_button_close_unpressed_image_color,
-               .inactive.buffer = &theme->button_close_inactive_unpressed,
-               .inactive.rgba = theme->window_inactive_button_close_unpressed_image_color,
        }, {
                .name = "menu_hover",
+               .type = LAB_SSD_BUTTON_WINDOW_MENU,
+               .state_set = LAB_BS_HOVERD,
+               /* no fallback (non-hover variant is used instead) */
+       }, {
+               /* menu_hover icon is loaded again as a fallback of window icon */
+               .name = "menu_hover",
+               .type = LAB_SSD_BUTTON_WINDOW_ICON,
+               .state_set = LAB_BS_HOVERD,
                /* no fallback (non-hover variant is used instead) */
-               .active.buffer = &theme->button_menu_active_hover,
-               .active.rgba = theme->window_active_button_menu_unpressed_image_color,
-               .inactive.buffer = &theme->button_menu_inactive_hover,
-               .inactive.rgba = theme->window_inactive_button_menu_unpressed_image_color,
        }, {
                .name = "iconify_hover",
+               .type = LAB_SSD_BUTTON_ICONIFY,
+               .state_set = LAB_BS_HOVERD,
                /* no fallback (non-hover variant is used instead) */
-               .active.buffer = &theme->button_iconify_active_hover,
-               .active.rgba = theme->window_active_button_iconify_unpressed_image_color,
-               .inactive.buffer = &theme->button_iconify_inactive_hover,
-               .inactive.rgba = theme->window_inactive_button_iconify_unpressed_image_color,
        }, {
                .name = "max_hover",
+               .type = LAB_SSD_BUTTON_MAXIMIZE,
+               .state_set = LAB_BS_HOVERD,
                /* no fallback (non-hover variant is used instead) */
-               .active.buffer = &theme->button_maximize_active_hover,
-               .active.rgba = theme->window_active_button_max_unpressed_image_color,
-               .inactive.buffer = &theme->button_maximize_inactive_hover,
-               .inactive.rgba = theme->window_inactive_button_max_unpressed_image_color,
        }, {
                .name = "max_toggled_hover",
                .alt_name = "max_hover_toggled",
+               .type = LAB_SSD_BUTTON_MAXIMIZE,
+               .state_set = LAB_BS_TOGGLED | LAB_BS_HOVERD,
                /* no fallback (non-hover variant is used instead) */
-               .active.buffer = &theme->button_restore_active_hover,
-               .active.rgba = theme->window_active_button_max_unpressed_image_color,
-               .inactive.buffer = &theme->button_restore_inactive_hover,
-               .inactive.rgba = theme->window_inactive_button_max_unpressed_image_color,
        }, {
                .name = "shade_hover",
+               .type = LAB_SSD_BUTTON_SHADE,
+               .state_set = LAB_BS_HOVERD,
                /* no fallback (non-hover variant is used instead) */
-               .active.buffer = &theme->button_shade_active_hover,
-               .active.rgba = theme->window_active_button_shade_unpressed_image_color,
-               .inactive.buffer = &theme->button_shade_inactive_hover,
-               .inactive.rgba = theme->window_inactive_button_shade_unpressed_image_color,
        }, {
                .name = "shade_toggled_hover",
                .alt_name = "shade_hover_toggled",
+               .type = LAB_SSD_BUTTON_SHADE,
+               .state_set = LAB_BS_TOGGLED | LAB_BS_HOVERD,
                /* no fallback (non-hover variant is used instead) */
-               .active.buffer = &theme->button_unshade_active_hover,
-               .active.rgba = theme->window_active_button_shade_unpressed_image_color,
-               .inactive.buffer = &theme->button_unshade_inactive_hover,
-               .inactive.rgba = theme->window_inactive_button_shade_unpressed_image_color,
        }, {
                .name = "desk_hover",
                /* no fallback (non-hover variant is used instead) */
-               .active.buffer = &theme->button_omnipresent_active_hover,
-               .active.rgba = theme->window_active_button_omnipresent_unpressed_image_color,
-               .inactive.buffer = &theme->button_omnipresent_inactive_hover,
-               .inactive.rgba = theme->window_inactive_button_omnipresent_unpressed_image_color,
+               .type = LAB_SSD_BUTTON_OMNIPRESENT,
+               .state_set = LAB_BS_HOVERD,
        }, {
                .name = "desk_toggled_hover",
                .alt_name = "desk_hover_toggled",
+               .type = LAB_SSD_BUTTON_OMNIPRESENT,
+               .state_set = LAB_BS_TOGGLED | LAB_BS_HOVERD,
                /* no fallback (non-hover variant is used instead) */
-               .active.buffer = &theme->button_exclusive_active_hover,
-               .active.rgba = theme->window_active_button_omnipresent_unpressed_image_color,
-               .inactive.buffer = &theme->button_exclusive_inactive_hover,
-               .inactive.rgba = theme->window_inactive_button_omnipresent_unpressed_image_color,
        }, {
                .name = "close_hover",
+               .type = LAB_SSD_BUTTON_CLOSE,
+               .state_set = LAB_BS_HOVERD,
                /* no fallback (non-hover variant is used instead) */
-               .active.buffer = &theme->button_close_active_hover,
-               .active.rgba = theme->window_active_button_close_unpressed_image_color,
-               .inactive.buffer = &theme->button_close_inactive_hover,
-               .inactive.rgba = theme->window_inactive_button_close_unpressed_image_color,
        }, };
 
-       char filename[4096] = {0};
        for (size_t i = 0; i < ARRAY_SIZE(buttons); ++i) {
                struct button *b = &buttons[i];
-
-               zdrop(b->active.buffer);
-               zdrop(b->inactive.buffer);
-
-               /* PNG */
-               get_button_filename(filename, sizeof(filename), b->name, "-active.png");
-               img_png_load(filename, b->active.buffer);
-               get_button_filename(filename, sizeof(filename), b->name, "-inactive.png");
-               img_png_load(filename, b->inactive.buffer);
-
-#if HAVE_RSVG
-               /* SVG */
-               int size = theme->title_height - 2 * theme->padding_height;
-               if (!*b->active.buffer) {
-                       get_button_filename(filename, sizeof(filename), b->name, "-active.svg");
-                       img_svg_load(filename, b->active.buffer, size);
-               }
-               if (!*b->inactive.buffer) {
-                       get_button_filename(filename, sizeof(filename), b->name, "-inactive.svg");
-                       img_svg_load(filename, b->inactive.buffer, size);
-               }
-#endif
-
-               /* XBM */
-               get_button_filename(filename, sizeof(filename), b->name, ".xbm");
-               if (!*b->active.buffer) {
-                       img_xbm_load(filename, b->active.buffer, b->active.rgba);
-               }
-               if (!*b->inactive.buffer) {
-                       img_xbm_load(filename, b->inactive.buffer, b->inactive.rgba);
-               }
-
-               /*
-                * XBM (alternative name)
-                * For example max_hover_toggled instead of max_toggled_hover
-                */
-               if (b->alt_name) {
-                       get_button_filename(filename, sizeof(filename), b->alt_name, ".xbm");
-               }  else {
-                       filename[0] = '\0';
-               }
-               if (!*b->active.buffer) {
-                       img_xbm_load(filename, b->active.buffer, b->active.rgba);
-               }
-               if (!*b->inactive.buffer) {
-                       img_xbm_load(filename, b->inactive.buffer, b->inactive.rgba);
-               }
-
-               /*
-                * Builtin bitmap
-                *
-                * Applicable to basic buttons such as max, max_toggled and
-                * iconify. There are no bitmap fallbacks for *_hover icons.
-                */
-               if (!b->fallback_button) {
-                       continue;
-               }
-               if (!*b->active.buffer) {
-                       img_xbm_from_bitmap(b->fallback_button,
-                               b->active.buffer, b->active.rgba);
-               }
-               if (!*b->inactive.buffer) {
-                       img_xbm_from_bitmap(b->fallback_button,
-                               b->inactive.buffer, b->inactive.rgba);
-               }
-       }
-
-       /*
-        * If hover-icons do not exist, add fallbacks by copying the non-hover
-        * variant (base) and then adding an overlay.
-        */
-       for (size_t i = 0; i < ARRAY_SIZE(buttons); i++) {
-               struct button *hover_button = &buttons[i];
-
-               if (!strstr(hover_button->name, "_hover")) {
-                       continue;
-               }
-
-               /* If name=='foo_hover', basename='foo' */
-               char basename[64]  = {0};
-               snprintf(basename, sizeof(basename), "%s", hover_button->name);
-               trim_last_field(basename, '_');
-               for (size_t j = 0; j < ARRAY_SIZE(buttons); j++) {
-                       struct button *base = &buttons[j];
-                       if (!strcmp(basename, base->name)) {
-                               if (!*hover_button->active.buffer) {
-                                       create_hover_fallback(theme, basename,
-                                               hover_button->active.buffer,
-                                               *base->active.buffer);
-                               }
-                               if (!*hover_button->inactive.buffer) {
-                                       create_hover_fallback(theme, basename,
-                                               hover_button->inactive.buffer,
-                                               *base->inactive.buffer);
-                               }
-                               break;
-                       }
-               }
+               load_button(theme, b, THEME_INACTIVE);
+               load_button(theme, b, THEME_ACTIVE);
        }
 }
 
@@ -645,30 +594,13 @@ theme_builtin(struct theme *theme, struct server *server)
        theme->window_button_spacing = 0;
        theme->window_button_hover_bg_shape = LAB_RECTANGLE;
 
-       parse_hexstr("#000000",
-               theme->window_active_button_menu_unpressed_image_color);
-       parse_hexstr("#000000",
-               theme->window_active_button_iconify_unpressed_image_color);
-       parse_hexstr("#000000",
-               theme->window_active_button_max_unpressed_image_color);
-       parse_hexstr("#000000",
-               theme->window_active_button_shade_unpressed_image_color);
-       parse_hexstr("#000000",
-               theme->window_active_button_omnipresent_unpressed_image_color);
-       parse_hexstr("#000000",
-               theme->window_active_button_close_unpressed_image_color);
-       parse_hexstr("#000000",
-               theme->window_inactive_button_menu_unpressed_image_color);
-       parse_hexstr("#000000",
-               theme->window_inactive_button_iconify_unpressed_image_color);
-       parse_hexstr("#000000",
-               theme->window_inactive_button_max_unpressed_image_color);
-       parse_hexstr("#000000",
-               theme->window_inactive_button_shade_unpressed_image_color);
-       parse_hexstr("#000000",
-               theme->window_inactive_button_omnipresent_unpressed_image_color);
-       parse_hexstr("#000000",
-               theme->window_inactive_button_close_unpressed_image_color);
+       for (enum ssd_part_type type = LAB_SSD_BUTTON_FIRST;
+                       type <= LAB_SSD_BUTTON_LAST; type++) {
+               parse_hexstr("#000000",
+                       theme->window[THEME_INACTIVE].button_colors[type]);
+               parse_hexstr("#000000",
+                       theme->window[THEME_ACTIVE].button_colors[type]);
+       }
 
        theme->window_active_shadow_size = 60;
        theme->window_inactive_shadow_size = 40;
@@ -855,82 +787,72 @@ entry(struct theme *theme, const char *key, const char *value)
 
        /* universal button */
        if (match_glob(key, "window.active.button.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_active_button_menu_unpressed_image_color);
-               parse_hexstr(value,
-                       theme->window_active_button_iconify_unpressed_image_color);
-               parse_hexstr(value,
-                       theme->window_active_button_max_unpressed_image_color);
-               parse_hexstr(value,
-                       theme->window_active_button_shade_unpressed_image_color);
-               parse_hexstr(value,
-                       theme->window_active_button_omnipresent_unpressed_image_color);
-               parse_hexstr(value,
-                       theme->window_active_button_close_unpressed_image_color);
+               for (enum ssd_part_type type = LAB_SSD_BUTTON_FIRST;
+                               type <= LAB_SSD_BUTTON_LAST; type++) {
+                       parse_hexstr(value,
+                               theme->window[THEME_ACTIVE].button_colors[type]);
+               }
        }
        if (match_glob(key, "window.inactive.button.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_inactive_button_menu_unpressed_image_color);
-               parse_hexstr(value,
-                       theme->window_inactive_button_iconify_unpressed_image_color);
-               parse_hexstr(value,
-                       theme->window_inactive_button_max_unpressed_image_color);
-               parse_hexstr(value,
-                       theme->window_inactive_button_shade_unpressed_image_color);
-               parse_hexstr(value,
-                       theme->window_inactive_button_omnipresent_unpressed_image_color);
-               parse_hexstr(value,
-                       theme->window_inactive_button_close_unpressed_image_color);
+               for (enum ssd_part_type type = LAB_SSD_BUTTON_FIRST;
+                               type <= LAB_SSD_BUTTON_LAST; type++) {
+                       parse_hexstr(value,
+                               theme->window[THEME_INACTIVE].button_colors[type]);
+               }
        }
 
        /* individual buttons */
        if (match_glob(key, "window.active.button.menu.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_active_button_menu_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_ACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_WINDOW_MENU]);
+               parse_hexstr(value, theme->window[THEME_ACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_WINDOW_ICON]);
        }
        if (match_glob(key, "window.active.button.iconify.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_active_button_iconify_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_ACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_ICONIFY]);
        }
        if (match_glob(key, "window.active.button.max.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_active_button_max_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_ACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_MAXIMIZE]);
        }
        if (match_glob(key, "window.active.button.shade.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_active_button_shade_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_ACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_SHADE]);
        }
        if (match_glob(key, "window.active.button.desk.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_active_button_omnipresent_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_ACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_OMNIPRESENT]);
        }
        if (match_glob(key, "window.active.button.close.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_active_button_close_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_ACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_CLOSE]);
        }
        if (match_glob(key, "window.inactive.button.menu.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_inactive_button_menu_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_INACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_WINDOW_MENU]);
+               parse_hexstr(value, theme->window[THEME_INACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_WINDOW_ICON]);
        }
        if (match_glob(key, "window.inactive.button.iconify.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_inactive_button_iconify_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_INACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_ICONIFY]);
        }
        if (match_glob(key, "window.inactive.button.max.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_inactive_button_max_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_INACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_MAXIMIZE]);
        }
        if (match_glob(key, "window.inactive.button.shade.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_inactive_button_shade_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_INACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_SHADE]);
        }
        if (match_glob(key, "window.inactive.button.desk.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_inactive_button_omnipresent_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_INACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_OMNIPRESENT]);
        }
        if (match_glob(key, "window.inactive.button.close.unpressed.image.color")) {
-               parse_hexstr(value,
-                       theme->window_inactive_button_close_unpressed_image_color);
+               parse_hexstr(value, theme->window[THEME_INACTIVE]
+                       .button_colors[LAB_SSD_BUTTON_CLOSE]);
        }
 
        /* window drop-shadows */
@@ -1657,45 +1579,16 @@ theme_init(struct theme *theme, struct server *server, const char *theme_name)
 void
 theme_finish(struct theme *theme)
 {
-       zdrop(&theme->button_close_active_unpressed);
-       zdrop(&theme->button_maximize_active_unpressed);
-       zdrop(&theme->button_restore_active_unpressed);
-       zdrop(&theme->button_shade_active_unpressed);
-       zdrop(&theme->button_unshade_active_unpressed);
-       zdrop(&theme->button_omnipresent_active_unpressed);
-       zdrop(&theme->button_exclusive_active_unpressed);
-       zdrop(&theme->button_iconify_active_unpressed);
-       zdrop(&theme->button_menu_active_unpressed);
-
-       zdrop(&theme->button_close_inactive_unpressed);
-       zdrop(&theme->button_maximize_inactive_unpressed);
-       zdrop(&theme->button_restore_inactive_unpressed);
-       zdrop(&theme->button_shade_inactive_unpressed);
-       zdrop(&theme->button_unshade_inactive_unpressed);
-       zdrop(&theme->button_omnipresent_inactive_unpressed);
-       zdrop(&theme->button_exclusive_inactive_unpressed);
-       zdrop(&theme->button_iconify_inactive_unpressed);
-       zdrop(&theme->button_menu_inactive_unpressed);
-
-       zdrop(&theme->button_close_active_hover);
-       zdrop(&theme->button_maximize_active_hover);
-       zdrop(&theme->button_restore_active_hover);
-       zdrop(&theme->button_shade_active_hover);
-       zdrop(&theme->button_unshade_active_hover);
-       zdrop(&theme->button_omnipresent_active_hover);
-       zdrop(&theme->button_exclusive_active_hover);
-       zdrop(&theme->button_iconify_active_hover);
-       zdrop(&theme->button_menu_active_hover);
-
-       zdrop(&theme->button_close_inactive_hover);
-       zdrop(&theme->button_maximize_inactive_hover);
-       zdrop(&theme->button_restore_inactive_hover);
-       zdrop(&theme->button_shade_inactive_hover);
-       zdrop(&theme->button_unshade_inactive_hover);
-       zdrop(&theme->button_omnipresent_inactive_hover);
-       zdrop(&theme->button_exclusive_inactive_hover);
-       zdrop(&theme->button_iconify_inactive_hover);
-       zdrop(&theme->button_menu_inactive_hover);
+       for (enum ssd_part_type type = LAB_SSD_BUTTON_FIRST;
+                       type <= LAB_SSD_BUTTON_LAST; type++) {
+               for (uint8_t state_set = 0; state_set <= LAB_BS_ALL;
+                               state_set++) {
+                       zdrop(&theme->window[THEME_INACTIVE]
+                               .buttons[type][state_set]);
+                       zdrop(&theme->window[THEME_ACTIVE]
+                               .buttons[type][state_set]);
+               }
+       }
 
        zdrop(&theme->corner_top_left_active_normal);
        zdrop(&theme->corner_top_left_inactive_normal);