diff --git a/include/ssd-internal.h b/include/ssd-internal.h index 14bd3265..1de79ae3 100644 --- a/include/ssd-internal.h +++ b/include/ssd-internal.h @@ -18,12 +18,18 @@ 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; + /* + * Hover icons provided by user or builtin translucent hover overlay. + * Hover overlays are rendered on top of normal/toggled nodes. + */ + struct wlr_scene_node *hover; struct wlr_scene_node *toggled_hover; - struct wlr_scene_tree *icon_tree; - struct wlr_scene_tree *hover_tree; + + struct wlr_scene_tree *untoggled_tree; + struct wlr_scene_tree *toggled_tree; struct wl_listener destroy; }; diff --git a/include/theme.h b/include/theme.h index 7f3eac90..dc2ae8d0 100644 --- a/include/theme.h +++ b/include/theme.h @@ -22,6 +22,12 @@ enum lab_shape { LAB_CIRCLE, }; +enum ssd_corner { + LAB_CORNER_UNKNOWN = 0, + LAB_CORNER_TOP_LEFT, + LAB_CORNER_TOP_RIGHT, +}; + struct theme_snapping_overlay { bool bg_enabled; bool border_enabled; @@ -170,6 +176,10 @@ struct theme { struct lab_data_buffer *button_omnipresent_inactive_hover; struct lab_data_buffer *button_exclusive_inactive_hover; + struct lab_data_buffer *button_hover_overlay_left; + struct lab_data_buffer *button_hover_overlay_right; + struct lab_data_buffer *button_hover_overlay_middle; + 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/ssd/ssd-part.c b/src/ssd/ssd-part.c index d647a272..2369a90a 100644 --- a/src/ssd/ssd-part.c +++ b/src/ssd/ssd-part.c @@ -137,12 +137,13 @@ 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); + struct wlr_scene_tree *untoggled_tree = wlr_scene_tree_create(parent); + /* 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); + untoggled_tree, icon_buffer, icon_geo.x, icon_geo.y); /* Make sure big icons are scaled down if necessary */ wlr_scene_buffer_set_dest_size( @@ -150,18 +151,20 @@ add_scene_button(struct wl_list *part_list, enum ssd_part_type type, 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); - + untoggled_tree, hover_buffer, hover_geo.x, hover_geo.y); + wlr_scene_node_set_enabled(hover_part->node, false); /* 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); + /* Empty if add_toggled_icon() is not subsequently called */ + struct wlr_scene_tree *toggled_tree = wlr_scene_tree_create(parent); + wlr_scene_node_set_enabled(&toggled_tree->node, false); + struct ssd_button *button = ssd_button_descriptor_create(button_root->node); button->type = type; button->view = view; @@ -169,8 +172,8 @@ add_scene_button(struct wl_list *part_list, enum ssd_part_type type, button->hover = hover_part->node; button->toggled = NULL; button->toggled_hover = NULL; - button->icon_tree = icon_tree; - button->hover_tree = hover_tree; + button->untoggled_tree = untoggled_tree; + button->toggled_tree = toggled_tree; return button_root; } @@ -184,18 +187,16 @@ add_toggled_icon(struct ssd_button *button, struct wl_list *part_list, 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); + button->toggled_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); + button->toggled_tree, hover_buffer, hover_geo.x, hover_geo.y); wlr_scene_buffer_set_dest_size( wlr_scene_buffer_from_node(althover_part->node), diff --git a/src/ssd/ssd-titlebar.c b/src/ssd/ssd-titlebar.c index b3a72b35..bb578856 100644 --- a/src/ssd/ssd-titlebar.c +++ b/src/ssd/ssd-titlebar.c @@ -24,6 +24,7 @@ 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 set_hover_overlays_squared(struct ssd *ssd, bool squared); static void update_visible_buttons(struct ssd *ssd); static void @@ -174,6 +175,7 @@ ssd_titlebar_create(struct ssd *ssd) } FOR_EACH_END update_visible_buttons(ssd); + set_hover_overlays_squared(ssd, false); ssd_update_title(ssd); ssd_update_window_icon(ssd); @@ -223,6 +225,8 @@ 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); } FOR_EACH_END + + set_hover_overlays_squared(ssd, enable); } static void @@ -241,13 +245,8 @@ 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); + wlr_scene_node_set_enabled(&button->toggled_tree->node, enable); + wlr_scene_node_set_enabled(&button->untoggled_tree->node, !enable); } } FOR_EACH_END } @@ -583,12 +582,98 @@ ssd_update_title(struct ssd *ssd) ssd_update_title_positions(ssd, offset_left, offset_right); } +static bool +is_hover_overlay_buffer(struct wlr_scene_node *node) +{ + struct wlr_scene_buffer *scene_buffer = + wlr_scene_buffer_from_node(node); + return scene_buffer->buffer == &rc.theme->button_hover_overlay_left->base + || scene_buffer->buffer == &rc.theme->button_hover_overlay_right->base + || scene_buffer->buffer == &rc.theme->button_hover_overlay_middle->base; +} + +static struct wlr_buffer * +hover_overlay_for_corner(enum ssd_corner corner) +{ + switch (corner) { + case LAB_CORNER_TOP_LEFT: + return &rc.theme->button_hover_overlay_left->base; + case LAB_CORNER_TOP_RIGHT: + return &rc.theme->button_hover_overlay_right->base; + case LAB_CORNER_UNKNOWN: + return &rc.theme->button_hover_overlay_middle->base; + default: + assert(false); + abort(); + } +} + +static void +set_hover_overlay(struct ssd_button *button, enum ssd_corner corner) +{ + assert(button); + + /* + * When button->(toggled_)hover is the builtin hover overlay (not an + * icon provided by user), update its shape. + */ + if (is_hover_overlay_buffer(button->hover)) { + wlr_scene_buffer_set_buffer( + wlr_scene_buffer_from_node(button->hover), + hover_overlay_for_corner(corner)); + } + if (button->toggled_hover + && is_hover_overlay_buffer(button->toggled_hover)) { + wlr_scene_buffer_set_buffer( + wlr_scene_buffer_from_node(button->toggled_hover), + hover_overlay_for_corner(corner)); + } +} + +/* Update the shape of hover overlay on corner buttons */ +static void +set_hover_overlays_squared(struct ssd *ssd, bool squared) +{ + struct ssd_part *part; + struct ssd_sub_tree *subtree; + + FOR_EACH_STATE(ssd, subtree) { + struct title_button *b; + wl_list_for_each(b, &rc.title_buttons_left, link) { + part = ssd_get_part(&subtree->parts, b->type); + struct ssd_button *button = node_ssd_button_from_node(part->node); + set_hover_overlay(button, + squared ? LAB_CORNER_UNKNOWN : LAB_CORNER_TOP_LEFT); + break; + } + wl_list_for_each_reverse(b, &rc.title_buttons_right, link) { + part = ssd_get_part(&subtree->parts, b->type); + struct ssd_button *button = node_ssd_button_from_node(part->node); + set_hover_overlay(button, + squared ? LAB_CORNER_UNKNOWN : LAB_CORNER_TOP_RIGHT); + break; + } + } FOR_EACH_END +} + 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); + + /* + * Keep showing non-hover icon when the hover icon is not provided and + * hover overlay is shown on top of it. + */ + wlr_scene_node_set_enabled(button->normal, + is_hover_overlay_buffer(button->hover) || !enabled); + wlr_scene_node_set_enabled(button->hover, enabled); + + if (button->toggled) { + wlr_scene_node_set_enabled(button->toggled, + is_hover_overlay_buffer(button->toggled_hover) || !enabled); + wlr_scene_node_set_enabled(button->toggled_hover, enabled); + } } void diff --git a/src/theme.c b/src/theme.c index 50980af1..7196b38c 100644 --- a/src/theme.c +++ b/src/theme.c @@ -51,19 +51,13 @@ struct button { } active, inactive; }; -enum corner { - LAB_CORNER_UNKNOWN = 0, - LAB_CORNER_TOP_LEFT, - LAB_CORNER_TOP_RIGHT, -}; - struct rounded_corner_ctx { struct wlr_box *box; double radius; double line_width; float *fill_color; float *border_color; - enum corner corner; + enum ssd_corner corner; }; #define zero_array(arr) memset(arr, 0, sizeof(arr)) @@ -82,91 +76,19 @@ zdrop(struct lab_data_buffer **buffer) } } -static bool -match_button_by_name(struct title_button *b, const char *icon_name) +static struct lab_data_buffer * +create_hover_overlay(struct theme *theme, enum ssd_corner corner) { - 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) -{ - assert(icon_name); - assert(icon_buffer); - assert(!*hover_buffer); - - struct surface_context icon = - get_cairo_surface_from_lab_data_buffer(icon_buffer); - int icon_width = cairo_image_surface_get_width(icon.surface); - int icon_height = cairo_image_surface_get_height(icon.surface); - int width = theme->window_button_width; int height = theme->title_height; - if (width && height) { - /* - * Proportionately increase size of hover_buffer if the - * non-hover 'donor' buffer is larger than the allocated space. - * It will get scaled down again by wlroots when rendered and as - * required by the current output scale. - * - * This ensures that icons > width or > height keep their aspect - * ratio and are rendered the same as without the hover overlay. - */ - double scale = MAX((double)icon_width / width, - (double)icon_height / height); - if (scale > 1.0f) { - width = (double)width * scale; - height = (double)height * scale; - } - } + struct lab_data_buffer *hover_buffer = buffer_create_cairo(width, height, 1.0, true); - *hover_buffer = buffer_create_cairo(width, height, 1.0, true); - - cairo_t *cairo = (*hover_buffer)->cairo; + cairo_t *cairo = hover_buffer->cairo; cairo_surface_t *surf = cairo_get_target(cairo); - /* Background */ - cairo_set_source_surface(cairo, icon.surface, - (width - icon_width) / 2, (height - icon_height) / 2); - cairo_paint(cairo); - - /* Overlay (pre-multiplied alpha) */ float overlay_color[4] = { 0.15f, 0.15f, 0.15f, 0.3f}; 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: @@ -217,9 +139,7 @@ create_hover_fallback(struct theme *theme, const char *icon_name, } cairo_surface_flush(surf); - if (icon.is_duplicate) { - cairo_surface_destroy(icon.surface); - } + return hover_buffer; } /* @@ -416,6 +336,13 @@ load_buttons(struct theme *theme) .inactive.rgba = theme->window_inactive_button_close_unpressed_image_color, }, }; + theme->button_hover_overlay_left = + create_hover_overlay(theme, LAB_CORNER_TOP_LEFT); + theme->button_hover_overlay_right = + create_hover_overlay(theme, LAB_CORNER_TOP_RIGHT); + theme->button_hover_overlay_middle = + create_hover_overlay(theme, LAB_CORNER_UNKNOWN); + char filename[4096] = {0}; for (size_t i = 0; i < ARRAY_SIZE(buttons); ++i) { struct button *b = &buttons[i]; @@ -473,49 +400,25 @@ load_buttons(struct theme *theme) * 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) { + if (!*b->active.buffer && b->fallback_button) { img_xbm_from_bitmap(b->fallback_button, b->active.buffer, b->active.rgba); } - if (!*b->inactive.buffer) { + if (!*b->inactive.buffer && b->fallback_button) { 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; + /* + * When *_hover icon is not provided, set hover overlay instead. + * This is rendered on top of non-hover icons. Their shapes are + * updated on SSD creation. + */ + if (!*b->active.buffer) { + *b->active.buffer = theme->button_hover_overlay_middle; } - - /* 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; - } + if (!*b->inactive.buffer) { + *b->inactive.buffer = theme->button_hover_overlay_middle; } } }