ssd: unround hover effects when the window is tiled

This also  fixes another problem that the window menu button as the
fallback of the window icon is not rounded correctly.

Now the translucent hover effect is stored separately from the non-hover
icons and rendered on top of them. This might incur some overhead on
pixman renderer, but it's not obvious in my testing.
This commit is contained in:
tokyo4j 2024-10-02 16:04:36 +09:00
parent 915d638f8a
commit 35d6ada229
5 changed files with 150 additions and 145 deletions

View file

@ -18,12 +18,18 @@
struct ssd_button { struct ssd_button {
struct view *view; struct view *view;
enum ssd_part_type type; enum ssd_part_type type;
struct wlr_scene_node *normal; struct wlr_scene_node *normal;
struct wlr_scene_node *hover;
struct wlr_scene_node *toggled; 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_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; struct wl_listener destroy;
}; };

View file

@ -22,6 +22,12 @@ enum lab_shape {
LAB_CIRCLE, LAB_CIRCLE,
}; };
enum ssd_corner {
LAB_CORNER_UNKNOWN = 0,
LAB_CORNER_TOP_LEFT,
LAB_CORNER_TOP_RIGHT,
};
struct theme_snapping_overlay { struct theme_snapping_overlay {
bool bg_enabled; bool bg_enabled;
bool border_enabled; bool border_enabled;
@ -170,6 +176,10 @@ struct theme {
struct lab_data_buffer *button_omnipresent_inactive_hover; struct lab_data_buffer *button_omnipresent_inactive_hover;
struct lab_data_buffer *button_exclusive_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_left_active_normal;
struct lab_data_buffer *corner_top_right_active_normal; struct lab_data_buffer *corner_top_right_active_normal;
struct lab_data_buffer *corner_top_left_inactive_normal; struct lab_data_buffer *corner_top_left_inactive_normal;

View file

@ -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, rc.theme->window_button_width, rc.theme->title_height, 0, 0,
invisible); invisible);
struct wlr_scene_tree *untoggled_tree = wlr_scene_tree_create(parent);
/* Icon */ /* Icon */
struct wlr_scene_tree *icon_tree = wlr_scene_tree_create(parent);
struct wlr_box icon_geo = get_scale_box(icon_buffer, struct wlr_box icon_geo = get_scale_box(icon_buffer,
rc.theme->window_button_width, rc.theme->title_height); rc.theme->window_button_width, rc.theme->title_height);
struct ssd_part *icon_part = add_scene_buffer(part_list, type, 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 */ /* Make sure big icons are scaled down if necessary */
wlr_scene_buffer_set_dest_size( 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); icon_geo.width, icon_geo.height);
/* Hover icon */ /* 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, struct wlr_box hover_geo = get_scale_box(hover_buffer,
rc.theme->window_button_width, rc.theme->title_height); rc.theme->window_button_width, rc.theme->title_height);
struct ssd_part *hover_part = add_scene_buffer(part_list, type, 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 */ /* Make sure big icons are scaled down if necessary */
wlr_scene_buffer_set_dest_size( wlr_scene_buffer_set_dest_size(
wlr_scene_buffer_from_node(hover_part->node), wlr_scene_buffer_from_node(hover_part->node),
hover_geo.width, hover_geo.height); 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); struct ssd_button *button = ssd_button_descriptor_create(button_root->node);
button->type = type; button->type = type;
button->view = view; 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->hover = hover_part->node;
button->toggled = NULL; button->toggled = NULL;
button->toggled_hover = NULL; button->toggled_hover = NULL;
button->icon_tree = icon_tree; button->untoggled_tree = untoggled_tree;
button->hover_tree = hover_tree; button->toggled_tree = toggled_tree;
return button_root; 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); rc.theme->window_button_width, rc.theme->title_height);
struct ssd_part *alticon_part = add_scene_buffer(part_list, type, 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_set_dest_size(
wlr_scene_buffer_from_node(alticon_part->node), wlr_scene_buffer_from_node(alticon_part->node),
icon_geo.width, icon_geo.height); 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, struct wlr_box hover_geo = get_scale_box(hover_buffer,
rc.theme->window_button_width, rc.theme->title_height); rc.theme->window_button_width, rc.theme->title_height);
struct ssd_part *althover_part = add_scene_buffer(part_list, type, 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_set_dest_size(
wlr_scene_buffer_from_node(althover_part->node), wlr_scene_buffer_from_node(althover_part->node),

View file

@ -24,6 +24,7 @@
static void set_squared_corners(struct ssd *ssd, bool enable); 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_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 update_visible_buttons(struct ssd *ssd);
static void static void
@ -174,6 +175,7 @@ ssd_titlebar_create(struct ssd *ssd)
} FOR_EACH_END } FOR_EACH_END
update_visible_buttons(ssd); update_visible_buttons(ssd);
set_hover_overlays_squared(ssd, false);
ssd_update_title(ssd); ssd_update_title(ssd);
ssd_update_window_icon(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); part = ssd_get_part(&subtree->parts, LAB_SSD_PART_TITLEBAR_CORNER_RIGHT);
wlr_scene_node_set_enabled(part->node, !enable); wlr_scene_node_set_enabled(part->node, !enable);
} FOR_EACH_END } FOR_EACH_END
set_hover_overlays_squared(ssd, enable);
} }
static void 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); button = node_ssd_button_from_node(part->node);
if (button->toggled) { if (button->toggled) {
wlr_scene_node_set_enabled(button->toggled, enable); wlr_scene_node_set_enabled(&button->toggled_tree->node, enable);
wlr_scene_node_set_enabled(button->normal, !enable); wlr_scene_node_set_enabled(&button->untoggled_tree->node, !enable);
}
if (button->toggled_hover) {
wlr_scene_node_set_enabled(button->toggled_hover, enable);
wlr_scene_node_set_enabled(button->hover, !enable);
} }
} FOR_EACH_END } FOR_EACH_END
} }
@ -583,12 +582,98 @@ ssd_update_title(struct ssd *ssd)
ssd_update_title_positions(ssd, offset_left, offset_right); 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 static void
ssd_button_set_hover(struct ssd_button *button, bool enabled) ssd_button_set_hover(struct ssd_button *button, bool enabled)
{ {
assert(button); 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 void

View file

@ -51,19 +51,13 @@ struct button {
} active, inactive; } active, inactive;
}; };
enum corner {
LAB_CORNER_UNKNOWN = 0,
LAB_CORNER_TOP_LEFT,
LAB_CORNER_TOP_RIGHT,
};
struct rounded_corner_ctx { struct rounded_corner_ctx {
struct wlr_box *box; struct wlr_box *box;
double radius; double radius;
double line_width; double line_width;
float *fill_color; float *fill_color;
float *border_color; float *border_color;
enum corner corner; enum ssd_corner corner;
}; };
#define zero_array(arr) memset(arr, 0, sizeof(arr)) #define zero_array(arr) memset(arr, 0, sizeof(arr))
@ -82,91 +76,19 @@ zdrop(struct lab_data_buffer **buffer)
} }
} }
static bool static struct lab_data_buffer *
match_button_by_name(struct title_button *b, const char *icon_name) 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 width = theme->window_button_width;
int height = theme->title_height; int height = theme->title_height;
if (width && height) { struct lab_data_buffer *hover_buffer = buffer_create_cairo(width, height, 1.0, true);
/*
* 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;
}
}
*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); 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}; float overlay_color[4] = { 0.15f, 0.15f, 0.15f, 0.3f};
int radius = MIN(width, height) / 2; int radius = MIN(width, height) / 2;
enum corner corner = corner_from_icon_name(icon_name);
switch (theme->window_button_hover_bg_shape) { switch (theme->window_button_hover_bg_shape) {
case LAB_CIRCLE: case LAB_CIRCLE:
@ -217,9 +139,7 @@ create_hover_fallback(struct theme *theme, const char *icon_name,
} }
cairo_surface_flush(surf); cairo_surface_flush(surf);
if (icon.is_duplicate) { return hover_buffer;
cairo_surface_destroy(icon.surface);
}
} }
/* /*
@ -416,6 +336,13 @@ load_buttons(struct theme *theme)
.inactive.rgba = theme->window_inactive_button_close_unpressed_image_color, .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}; char filename[4096] = {0};
for (size_t i = 0; i < ARRAY_SIZE(buttons); ++i) { for (size_t i = 0; i < ARRAY_SIZE(buttons); ++i) {
struct button *b = &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 * Applicable to basic buttons such as max, max_toggled and
* iconify. There are no bitmap fallbacks for *_hover icons. * iconify. There are no bitmap fallbacks for *_hover icons.
*/ */
if (!b->fallback_button) { if (!*b->active.buffer && b->fallback_button) {
continue;
}
if (!*b->active.buffer) {
img_xbm_from_bitmap(b->fallback_button, img_xbm_from_bitmap(b->fallback_button,
b->active.buffer, b->active.rgba); b->active.buffer, b->active.rgba);
} }
if (!*b->inactive.buffer) { if (!*b->inactive.buffer && b->fallback_button) {
img_xbm_from_bitmap(b->fallback_button, img_xbm_from_bitmap(b->fallback_button,
b->inactive.buffer, b->inactive.rgba); b->inactive.buffer, b->inactive.rgba);
} }
}
/* /*
* If hover-icons do not exist, add fallbacks by copying the non-hover * When *_hover icon is not provided, set hover overlay instead.
* variant (base) and then adding an overlay. * This is rendered on top of non-hover icons. Their shapes are
* updated on SSD creation.
*/ */
for (size_t i = 0; i < ARRAY_SIZE(buttons); i++) { if (!*b->active.buffer) {
struct button *hover_button = &buttons[i]; *b->active.buffer = theme->button_hover_overlay_middle;
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;
} }
if (!*b->inactive.buffer) {
*b->inactive.buffer = theme->button_hover_overlay_middle;
} }
} }
} }