From c79b8ba8a772ba6210f0508194c38884b90add16 Mon Sep 17 00:00:00 2001 From: Simon Long Date: Wed, 6 Dec 2023 20:33:26 +0000 Subject: [PATCH] Support hover icons in titlebar (#1280) Allow both max_toggled_hover and max_hover_toggled names for icons --- docs/labwc-theme.5.scd | 10 ++++ include/button/button-xbm.h | 4 +- include/ssd-internal.h | 14 ++++- include/theme.h | 14 +++++ src/button/button-xbm.c | 16 +++++- src/ssd/ssd_part.c | 67 +++++++++++++++++++---- src/ssd/ssd_titlebar.c | 58 +++++++++++++++++--- src/theme.c | 105 +++++++++++++++++++++++++++++++++--- 8 files changed, 258 insertions(+), 30 deletions(-) diff --git a/docs/labwc-theme.5.scd b/docs/labwc-theme.5.scd index 73be8d27..efd07259 100644 --- a/docs/labwc-theme.5.scd +++ b/docs/labwc-theme.5.scd @@ -197,6 +197,16 @@ file within a particular theme. The following xbm buttons are supported: - iconify.xbm - close.xbm - menu.xbm +- max_toggled.xbm + +Additional icons can be defined to be shown when the mouse pointer is hovering +over the button in question: + +- max_hover.xbm +- iconify_hover.xbm +- close_hover.xbm +- menu_hover.xbm +- max_hover_toggled.xbm One advantage of xbm buttons over other formats is that they change color based on the theme. Other formats use the suffices "-active" and "-inactive" to align diff --git a/include/button/button-xbm.h b/include/button/button-xbm.h index 862a926d..8b864bd0 100644 --- a/include/button/button-xbm.h +++ b/include/button/button-xbm.h @@ -5,7 +5,7 @@ struct lab_data_buffer; /* button_xbm_load - Convert xbm file to buffer with cairo surface */ -void button_xbm_load(const char *button_name, struct lab_data_buffer **buffer, - char *fallback_button, float *rgba); +void button_xbm_load(const char *button_name, const char *alt_name, + struct lab_data_buffer **buffer, char *fallback_button, float *rgba); #endif /* LABWC_BUTTON_XBM_H */ diff --git a/include/ssd-internal.h b/include/ssd-internal.h index e52ae49d..bfc11018 100644 --- a/include/ssd-internal.h +++ b/include/ssd-internal.h @@ -5,6 +5,7 @@ #include #include "common/macros.h" #include "ssd.h" +#include "view.h" #define FOR_EACH(tmp, ...) \ { \ @@ -17,7 +18,10 @@ 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_node *background; struct wl_listener destroy; @@ -96,6 +100,8 @@ struct ssd_part { struct ssd_hover_state { struct view *view; struct wlr_scene_node *node; + struct wlr_scene_node *old_node; + int maximized; }; struct wlr_buffer; @@ -115,12 +121,16 @@ struct ssd_part *add_scene_buffer( struct ssd_part *add_scene_button( struct wl_list *part_list, enum ssd_part_type type, struct wlr_scene_tree *parent, float *bg_color, - struct wlr_buffer *icon_buffer, int x, struct view *view); + struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer, + int x, struct view *view); +void +add_toggled_icon(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_corner( struct wl_list *part_list, enum ssd_part_type type, enum ssd_part_type corner_type, struct wlr_scene_tree *parent, struct wlr_buffer *corner_buffer, struct wlr_buffer *icon_buffer, - int x, struct view *view); + struct wlr_buffer *hover_buffer, int x, struct view *view); /* SSD internal helpers */ struct ssd_part *ssd_get_part( diff --git a/include/theme.h b/include/theme.h index e211d0f0..e814f451 100644 --- a/include/theme.h +++ b/include/theme.h @@ -79,14 +79,28 @@ struct theme { /* 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_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_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_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 *corner_top_left_active_normal; struct lab_data_buffer *corner_top_right_active_normal; struct lab_data_buffer *corner_top_left_inactive_normal; diff --git a/src/button/button-xbm.c b/src/button/button-xbm.c index 9d558332..6435d654 100644 --- a/src/button/button-xbm.c +++ b/src/button/button-xbm.c @@ -259,8 +259,8 @@ parse_xbm_builtin(const char *button, int size) } void -button_xbm_load(const char *button_name, struct lab_data_buffer **buffer, - char *fallback_button, float *rgba) +button_xbm_load(const char *button_name, const char *alt_name, + struct lab_data_buffer **buffer, char *fallback_button, float *rgba) { struct pixmap pixmap = {0}; if (*buffer) { @@ -282,6 +282,18 @@ button_xbm_load(const char *button_name, struct lab_data_buffer **buffer, free(tokens); } } + if (!pixmap.data && *alt_name) { + button_filename(alt_name, filename, sizeof(filename)); + char *token_buffer = grab_file(filename); + if (token_buffer) { + struct token *tokens = tokenize_xbm(token_buffer); + free(token_buffer); + pixmap = parse_xbm_tokens(tokens); + if (tokens) { + free(tokens); + } + } + } if (!pixmap.data) { pixmap = parse_xbm_builtin(fallback_button, 6); } diff --git a/src/ssd/ssd_part.c b/src/ssd/ssd_part.c index 4c72d291..ea53c104 100644 --- a/src/ssd/ssd_part.c +++ b/src/ssd/ssd_part.c @@ -82,7 +82,7 @@ struct ssd_part * add_scene_button_corner(struct wl_list *part_list, enum ssd_part_type type, enum ssd_part_type corner_type, struct wlr_scene_tree *parent, struct wlr_buffer *corner_buffer, struct wlr_buffer *icon_buffer, - int x, struct view *view) + struct wlr_buffer *hover_buffer, int x, struct view *view) { int offset_x; float invisible[4] = { 0, 0, 0, 0 }; @@ -108,7 +108,7 @@ add_scene_button_corner(struct wl_list *part_list, enum ssd_part_type type, -offset_x, -rc.theme->border_width); /* Finally just put a usual theme button on top, using an invisible hitbox */ - add_scene_button(part_list, type, parent, invisible, icon_buffer, 0, view); + add_scene_button(part_list, type, parent, invisible, icon_buffer, hover_buffer, 0, view); return button_root; } @@ -141,11 +141,9 @@ get_scale_box(struct wlr_buffer *buffer, double container_width, struct ssd_part * add_scene_button(struct wl_list *part_list, enum ssd_part_type type, struct wlr_scene_tree *parent, float *bg_color, - struct wlr_buffer *icon_buffer, int x, struct view *view) + struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer, + int x, struct view *view) { - struct wlr_scene_node *hover; - float hover_bg[4] = {0.15f, 0.15f, 0.15f, 0.3f}; - struct ssd_part *button_root = add_scene_part(part_list, type); parent = wlr_scene_tree_create(parent); button_root->node = &parent->node; @@ -166,19 +164,66 @@ add_scene_button(struct wl_list *part_list, enum ssd_part_type type, wlr_scene_buffer_from_node(icon_part->node), icon_geo.width, icon_geo.height); - /* Hover overlay */ - hover = add_scene_rect(part_list, type, parent, SSD_BUTTON_WIDTH, - rc.theme->title_height, 0, 0, hover_bg)->node; - wlr_scene_node_set_enabled(hover, false); + /* Hover icon */ + struct wlr_box hover_geo = get_scale_box(hover_buffer, + SSD_BUTTON_WIDTH, rc.theme->title_height); + struct ssd_part *hover_part = add_scene_buffer(part_list, type, + parent, 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); + + wlr_scene_node_set_enabled(hover_part->node, false); struct ssd_button *button = ssd_button_descriptor_create(button_root->node); button->type = type; button->view = view; - button->hover = hover; + button->normal = icon_part->node; + button->hover = hover_part->node; button->background = bg_rect->node; + button->toggled = NULL; + button->toggled_hover = NULL; return button_root; } +void +add_toggled_icon(struct wl_list *part_list, enum ssd_part_type type, + struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer) +{ + struct ssd_part *part = ssd_get_part(part_list, type); + struct ssd_button *button = node_ssd_button_from_node(part->node); + struct wlr_scene_tree *parent = wlr_scene_tree_from_node(part->node); + + /* Alternate icon */ + struct wlr_box icon_geo = get_scale_box(icon_buffer, + SSD_BUTTON_WIDTH, rc.theme->title_height); + + struct ssd_part *alticon_part = add_scene_buffer(part_list, type, + parent, 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, + SSD_BUTTON_WIDTH, rc.theme->title_height); + struct ssd_part *althover_part = add_scene_buffer(part_list, type, + parent, 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) { diff --git a/src/ssd/ssd_titlebar.c b/src/ssd/ssd_titlebar.c index 55cfe51b..7b2ef2f3 100644 --- a/src/ssd/ssd_titlebar.c +++ b/src/ssd/ssd_titlebar.c @@ -34,8 +34,15 @@ ssd_titlebar_create(struct ssd *ssd) struct wlr_buffer *menu_button_unpressed; struct wlr_buffer *iconify_button_unpressed; struct wlr_buffer *maximize_button_unpressed; + struct wlr_buffer *restore_button_unpressed; struct wlr_buffer *close_button_unpressed; + struct wlr_buffer *menu_button_hover; + struct wlr_buffer *iconify_button_hover; + struct wlr_buffer *maximize_button_hover; + struct wlr_buffer *restore_button_hover; + struct wlr_buffer *close_button_hover; + ssd->titlebar.tree = wlr_scene_tree_create(ssd->tree); struct ssd_sub_tree *subtree; @@ -51,6 +58,12 @@ ssd_titlebar_create(struct ssd *ssd) iconify_button_unpressed = &theme->button_iconify_active_unpressed->base; close_button_unpressed = &theme->button_close_active_unpressed->base; maximize_button_unpressed = &theme->button_maximize_active_unpressed->base; + restore_button_unpressed = &theme->button_restore_active_unpressed->base; + menu_button_hover = &theme->button_menu_active_hover->base; + iconify_button_hover = &theme->button_iconify_active_hover->base; + close_button_hover = &theme->button_close_active_hover->base; + maximize_button_hover = &theme->button_maximize_active_hover->base; + restore_button_hover = &theme->button_restore_active_hover->base; } else { color = theme->window_inactive_title_bg_color; corner_top_left = &theme->corner_top_left_inactive_normal->base; @@ -59,7 +72,13 @@ ssd_titlebar_create(struct ssd *ssd) iconify_button_unpressed = &theme->button_iconify_inactive_unpressed->base; maximize_button_unpressed = &theme->button_maximize_inactive_unpressed->base; + restore_button_unpressed = &theme->button_restore_inactive_unpressed->base; close_button_unpressed = &theme->button_close_inactive_unpressed->base; + menu_button_hover = &theme->button_menu_inactive_hover->base; + iconify_button_hover = &theme->button_iconify_inactive_hover->base; + close_button_hover = &theme->button_close_inactive_hover->base; + maximize_button_hover = &theme->button_maximize_inactive_hover->base; + restore_button_hover = &theme->button_restore_inactive_hover->base; wlr_scene_node_set_enabled(&parent->node, false); } wl_list_init(&subtree->parts); @@ -71,16 +90,18 @@ ssd_titlebar_create(struct ssd *ssd) /* Buttons */ add_scene_button_corner(&subtree->parts, LAB_SSD_BUTTON_WINDOW_MENU, LAB_SSD_PART_CORNER_TOP_LEFT, parent, - corner_top_left, menu_button_unpressed, 0, view); + corner_top_left, menu_button_unpressed, menu_button_hover, 0, view); add_scene_button(&subtree->parts, LAB_SSD_BUTTON_ICONIFY, parent, - color, iconify_button_unpressed, + color, iconify_button_unpressed, iconify_button_hover, width - SSD_BUTTON_WIDTH * 3, view); add_scene_button(&subtree->parts, LAB_SSD_BUTTON_MAXIMIZE, parent, - color, maximize_button_unpressed, + color, maximize_button_unpressed, maximize_button_hover, width - SSD_BUTTON_WIDTH * 2, view); + add_toggled_icon(&subtree->parts, LAB_SSD_BUTTON_MAXIMIZE, + restore_button_unpressed, restore_button_hover); add_scene_button_corner(&subtree->parts, LAB_SSD_BUTTON_CLOSE, LAB_SSD_PART_CORNER_TOP_RIGHT, parent, - corner_top_right, close_button_unpressed, + corner_top_right, close_button_unpressed, close_button_hover, width - SSD_BUTTON_WIDTH * 1, view); } FOR_EACH_END @@ -159,10 +180,22 @@ ssd_titlebar_update(struct ssd *ssd) width - SSD_BUTTON_WIDTH * 3, 0); } continue; - case LAB_SSD_BUTTON_MAXIMIZE: + case LAB_SSD_BUTTON_MAXIMIZE: if (is_direct_child(part->node, subtree)) { wlr_scene_node_set_position(part->node, width - SSD_BUTTON_WIDTH * 2, 0); + struct ssd_button *button = + node_ssd_button_from_node(part->node); + if (button->toggled) { + wlr_scene_node_set_enabled(button->normal, + !maximized); + wlr_scene_node_set_enabled(button->toggled, + maximized); + wlr_scene_node_set_enabled(button->hover, + false); + wlr_scene_node_set_enabled(button->toggled_hover, + false); + } } continue; case LAB_SSD_PART_CORNER_TOP_RIGHT: @@ -365,13 +398,24 @@ ssd_update_button_hover(struct wlr_scene_node *node, disable_old_hover: if (hover_state->node) { wlr_scene_node_set_enabled(hover_state->node, false); + if (hover_state->maximized == (int)hover_state->view->maximized + || hover_state->maximized == -1) { + wlr_scene_node_set_enabled(hover_state->old_node, true); + } hover_state->view = NULL; hover_state->node = NULL; } if (button) { - wlr_scene_node_set_enabled(button->hover, true); + bool maximized = button->view->maximized; + if (maximized && !button->toggled) { + maximized = false; + } + wlr_scene_node_set_enabled(maximized ? button->toggled_hover : button->hover, true); hover_state->view = button->view; - hover_state->node = button->hover; + hover_state->node = maximized ? button->toggled_hover : button->hover; + hover_state->old_node = maximized ? button->toggled : button->normal; + hover_state->maximized = button->toggled ? (int)button->view->maximized : -1; + wlr_scene_node_set_enabled(maximized ? button->toggled : button->normal, false); } } diff --git a/src/theme.c b/src/theme.c index 8c9c6cad..1df54389 100644 --- a/src/theme.c +++ b/src/theme.c @@ -39,6 +39,7 @@ struct button { const char *name; + const char *alt_name; char fallback_button[6]; /* built-in 6x6 button */ struct { struct lab_data_buffer **buffer; @@ -60,7 +61,7 @@ load_buttons(struct theme *theme) { struct button buttons[] = { { - "menu", + "menu", NULL, { 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00 }, { &theme->button_menu_active_unpressed, @@ -72,7 +73,7 @@ load_buttons(struct theme *theme) }, }, { - "iconify", + "iconify", NULL, { 0x00, 0x00, 0x00, 0x00, 0x3f, 0x3f }, { &theme->button_iconify_active_unpressed, @@ -84,7 +85,7 @@ load_buttons(struct theme *theme) }, }, { - "max", + "max", NULL, { 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f }, { &theme->button_maximize_active_unpressed, @@ -96,7 +97,19 @@ load_buttons(struct theme *theme) }, }, { - "close", + "max_toggled", NULL, + { 0x3e, 0x22, 0x2f, 0x29, 0x39, 0x0f }, + { + &theme->button_restore_active_unpressed, + theme->window_active_button_max_unpressed_image_color, + }, + { + &theme->button_restore_inactive_unpressed, + theme->window_inactive_button_max_unpressed_image_color, + }, + }, + { + "close", NULL, { 0x33, 0x3f, 0x1e, 0x1e, 0x3f, 0x33 }, { &theme->button_close_active_unpressed, @@ -107,9 +120,70 @@ load_buttons(struct theme *theme) theme->window_inactive_button_close_unpressed_image_color, }, }, + { + "menu_hover", NULL, + { 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00 }, + { + &theme->button_menu_active_hover, + theme->window_active_button_menu_unpressed_image_color, + }, + { + &theme->button_menu_inactive_hover, + theme->window_inactive_button_menu_unpressed_image_color, + }, + }, + { + "iconify_hover", NULL, + { 0x00, 0x00, 0x00, 0x00, 0x3f, 0x3f }, + { + &theme->button_iconify_active_hover, + theme->window_active_button_iconify_unpressed_image_color, + }, + { + &theme->button_iconify_inactive_hover, + theme->window_inactive_button_iconify_unpressed_image_color, + }, + }, + { + "max_hover", NULL, + { 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f }, + { + &theme->button_maximize_active_hover, + theme->window_active_button_max_unpressed_image_color, + }, + { + &theme->button_maximize_inactive_hover, + theme->window_inactive_button_max_unpressed_image_color, + }, + }, + { + "max_hover_toggled", "max_toggled_hover", + { 0x3e, 0x22, 0x2f, 0x29, 0x39, 0x0f }, + { + &theme->button_restore_active_hover, + theme->window_active_button_max_unpressed_image_color, + }, + { + &theme->button_restore_inactive_hover, + theme->window_inactive_button_max_unpressed_image_color, + }, + }, + { + "close_hover", NULL, + { 0x33, 0x3f, 0x1e, 0x1e, 0x3f, 0x33 }, + { + &theme->button_close_active_hover, + theme->window_active_button_close_unpressed_image_color, + }, + { + &theme->button_close_inactive_hover, + theme->window_inactive_button_close_unpressed_image_color, + }, + }, }; char filename[4096] = {0}; + char alt_filename[4096] = {0}; for (size_t i = 0; i < ARRAY_SIZE(buttons); ++i) { struct button *b = &buttons[i]; @@ -119,8 +193,16 @@ load_buttons(struct theme *theme) /* Try png icon first */ snprintf(filename, sizeof(filename), "%s-active.png", b->name); button_png_load(filename, b->active.buffer); + if (!*b->active.buffer && b->alt_name) { + snprintf(filename, sizeof(filename), "%s-active.png", b->alt_name); + button_png_load(filename, b->active.buffer); + } snprintf(filename, sizeof(filename), "%s-inactive.png", b->name); button_png_load(filename, b->inactive.buffer); + if (!*b->inactive.buffer && b->alt_name) { + snprintf(filename, sizeof(filename), "%s-inactive.png", b->alt_name); + button_png_load(filename, b->inactive.buffer); + } #if HAVE_RSVG /* Then try svg icon */ @@ -129,20 +211,31 @@ load_buttons(struct theme *theme) snprintf(filename, sizeof(filename), "%s-active.svg", b->name); button_svg_load(filename, b->active.buffer, size); } + if (!*b->active.buffer && b->alt_name) { + snprintf(filename, sizeof(filename), "%s-active.svg", b->alt_name); + button_svg_load(filename, b->active.buffer, size); + } if (!*b->inactive.buffer) { snprintf(filename, sizeof(filename), "%s-inactive.svg", b->name); button_svg_load(filename, b->inactive.buffer, size); } + if (!*b->active.buffer && b->alt_name) { + snprintf(filename, sizeof(filename), "%s-inactive.svg", b->alt_name); + button_svg_load(filename, b->inactive.buffer, size); + } #endif /* If there were no png/svg buttons, use xbm */ snprintf(filename, sizeof(filename), "%s.xbm", b->name); + if (b->alt_name) { + snprintf(alt_filename, sizeof(alt_filename), "%s.xbm", b->alt_name); + } if (!*b->active.buffer) { - button_xbm_load(filename, b->active.buffer, + button_xbm_load(filename, alt_filename, b->active.buffer, b->fallback_button, b->active.rgba); } if (!*b->inactive.buffer) { - button_xbm_load(filename, b->inactive.buffer, + button_xbm_load(filename, alt_filename, b->inactive.buffer, b->fallback_button, b->inactive.rgba); } }