From a7f9680cda02dcd4b58cddc08808d58c84ff5417 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Fri, 5 Jul 2024 12:45:29 -0400 Subject: [PATCH] ssd: scale titlebar buttons to fit for very small views --- include/ssd-internal.h | 7 +++-- src/ssd/ssd-border.c | 10 ++++--- src/ssd/ssd-extents.c | 8 +++--- src/ssd/ssd-part.c | 52 +++++++++++++++++++++++++--------- src/ssd/ssd-titlebar.c | 54 ++++++++++++++++++++++++------------ src/ssd/ssd.c | 63 ++++++++++++++++++++++++++++-------------- 6 files changed, 132 insertions(+), 62 deletions(-) diff --git a/include/ssd-internal.h b/include/ssd-internal.h index fda196e6..afca07fc 100644 --- a/include/ssd-internal.h +++ b/include/ssd-internal.h @@ -65,6 +65,7 @@ struct ssd { /* The top of the view, containing buttons, title, .. */ struct { int height; + int button_width; struct wlr_scene_tree *tree; struct ssd_sub_tree active; struct ssd_sub_tree inactive; @@ -129,15 +130,15 @@ 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, struct wlr_buffer *hover_buffer, - int x, struct view *view); + int x, int width, 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 wlr_buffer *hover_buffer, int width); 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, - struct wlr_buffer *hover_buffer, int x, struct view *view); + struct wlr_buffer *hover_buffer, int x, int width, struct view *view); /* SSD internal helpers */ struct ssd_part *ssd_get_part( diff --git a/src/ssd/ssd-border.c b/src/ssd/ssd-border.c index fb1ac283..c20526f8 100644 --- a/src/ssd/ssd-border.c +++ b/src/ssd/ssd-border.c @@ -22,6 +22,7 @@ ssd_border_create(struct ssd *ssd) int width = view->current.width; int height = view_effective_height(view, /* use_pending */ false); int full_width = width + 2 * theme->border_width; + int button_width = ssd->titlebar.button_width; float *color; struct wlr_scene_tree *parent; @@ -48,8 +49,8 @@ ssd_border_create(struct ssd *ssd) add_scene_rect(&subtree->parts, LAB_SSD_PART_BOTTOM, parent, full_width, theme->border_width, 0, height, color); add_scene_rect(&subtree->parts, LAB_SSD_PART_TOP, parent, - width - 2 * theme->window_button_width, theme->border_width, - theme->border_width + theme->window_button_width, + width - 2 * button_width, theme->border_width, + theme->border_width + button_width, -(ssd->titlebar.height + theme->border_width), color); } FOR_EACH_END @@ -93,6 +94,7 @@ ssd_border_update(struct ssd *ssd) int width = view->current.width; int height = view_effective_height(view, /* use_pending */ false); int full_width = width + 2 * theme->border_width; + int button_width = ssd->titlebar.button_width; /* * From here on we have to cover the following border scenarios: @@ -121,10 +123,10 @@ ssd_border_update(struct ssd *ssd) : 0; int top_width = ssd->titlebar.height <= 0 || ssd->state.was_tiled_not_maximized ? full_width - : width - 2 * theme->window_button_width; + : width - 2 * button_width; int top_x = ssd->titlebar.height <= 0 || ssd->state.was_tiled_not_maximized ? 0 - : theme->border_width + theme->window_button_width; + : theme->border_width + button_width; struct ssd_part *part; struct wlr_scene_rect *rect; diff --git a/src/ssd/ssd-extents.c b/src/ssd/ssd-extents.c index 274b5f50..ff3f6fe2 100644 --- a/src/ssd/ssd-extents.c +++ b/src/ssd/ssd-extents.c @@ -32,8 +32,8 @@ ssd_extents_create(struct ssd *ssd) struct theme *theme = view->server->theme; struct wl_list *part_list = &ssd->extents.parts; int extended_area = SSD_EXTENDED_AREA; - int corner_size = extended_area + theme->border_width + - theme->window_button_width / 2; + int corner_size = extended_area + theme->border_width + + ssd->titlebar.button_width / 2; ssd->extents.tree = wlr_scene_tree_create(ssd->tree); struct wlr_scene_tree *parent = ssd->extents.tree; @@ -110,8 +110,8 @@ ssd_extents_update(struct ssd *ssd) int full_height = height + theme->border_width * 2 + ssd->titlebar.height; int full_width = width + 2 * theme->border_width; int extended_area = SSD_EXTENDED_AREA; - int corner_size = extended_area + theme->border_width + - theme->window_button_width / 2; + int corner_size = extended_area + theme->border_width + + ssd->titlebar.button_width / 2; int side_width = full_width + extended_area * 2 - corner_size * 2; int side_height = full_height + extended_area * 2 - corner_size * 2; diff --git a/src/ssd/ssd-part.c b/src/ssd/ssd-part.c index 5c903609..99572e40 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, - struct wlr_buffer *hover_buffer, int x, struct view *view) + struct wlr_buffer *hover_buffer, int x, int width, struct view *view) { int offset_x; float invisible[4] = { 0, 0, 0, 0 }; @@ -106,17 +106,29 @@ add_scene_button_corner(struct wl_list *part_list, enum ssd_part_type type, * Background, x and y adjusted for border_width which is * already included in rendered theme.c / corner_buffer */ - add_scene_buffer(part_list, corner_type, parent, corner_buffer, - -offset_x, -rc.theme->border_width); + struct ssd_part *corner_part = + add_scene_buffer(part_list, corner_type, parent, corner_buffer, + -offset_x, -rc.theme->border_width); + + /* + * Scale down corner if needed for very small views. The + * rendering will not be perfect, but this is an abnormal case + * so we probably don't care. + */ + wlr_scene_buffer_set_dest_size( + wlr_scene_buffer_from_node(corner_part->node), + width + rc.theme->border_width, + rc.theme->title_height + 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, hover_buffer, 0, view); + add_scene_button(part_list, type, parent, invisible, icon_buffer, + hover_buffer, 0, width, view); return button_root; } static struct wlr_box get_scale_box(struct wlr_buffer *buffer, double container_width, - double container_height) + double container_height, double max_scale) { struct wlr_box icon_geo = { .width = buffer->width, @@ -127,6 +139,7 @@ get_scale_box(struct wlr_buffer *buffer, double container_width, if (icon_geo.width && icon_geo.height) { double scale = MIN(container_width / icon_geo.width, container_height / icon_geo.height); + scale = MIN(scale, max_scale); if (scale < 1.0f) { icon_geo.width = (double)icon_geo.width * scale; icon_geo.height = (double)icon_geo.height * scale; @@ -144,7 +157,7 @@ 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, struct wlr_buffer *hover_buffer, - int x, struct view *view) + int x, int width, struct view *view) { struct ssd_part *button_root = add_scene_part(part_list, type); parent = wlr_scene_tree_create(parent); @@ -153,13 +166,19 @@ add_scene_button(struct wl_list *part_list, enum ssd_part_type type, /* Background */ struct ssd_part *bg_rect = add_scene_rect(part_list, type, parent, - rc.theme->window_button_width, rc.theme->title_height, 0, 0, - bg_color); + width, rc.theme->title_height, 0, 0, bg_color); /* Icon */ struct wlr_scene_tree *icon_tree = wlr_scene_tree_create(parent); + /* + * Always scale icon down proportionally if the button is + * narrowed from the default (as for a very small view). The + * hover icon is already pre-rendered in a default-size buffer + * and so doesn't need this extra step. + */ + double max_icon_scale = (double)width / rc.theme->window_button_width; struct wlr_box icon_geo = get_scale_box(icon_buffer, - rc.theme->window_button_width, rc.theme->title_height); + width, rc.theme->title_height, max_icon_scale); struct ssd_part *icon_part = add_scene_buffer(part_list, type, icon_tree, icon_buffer, icon_geo.x, icon_geo.y); @@ -172,7 +191,7 @@ add_scene_button(struct wl_list *part_list, enum ssd_part_type type, 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); + width, rc.theme->title_height, /*max_scale*/ 1.0); struct ssd_part *hover_part = add_scene_buffer(part_list, type, hover_tree, hover_buffer, hover_geo.x, hover_geo.y); @@ -197,11 +216,18 @@ add_scene_button(struct wl_list *part_list, enum ssd_part_type type, 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 wlr_buffer *hover_buffer, int width) { /* Alternate icon */ + /* + * Always scale icon down proportionally if the button is + * narrowed from the default (as for a very small view). The + * hover icon is already pre-rendered in a default-size buffer + * and so doesn't need this extra step. + */ + double max_icon_scale = (double)width / rc.theme->window_button_width; struct wlr_box icon_geo = get_scale_box(icon_buffer, - rc.theme->window_button_width, rc.theme->title_height); + width, rc.theme->title_height, max_icon_scale); struct ssd_part *alticon_part = add_scene_buffer(part_list, type, button->icon_tree, icon_buffer, icon_geo.x, icon_geo.y); @@ -213,7 +239,7 @@ add_toggled_icon(struct ssd_button *button, struct wl_list *part_list, 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); + width, rc.theme->title_height, /*max_scale*/ 1.0); struct ssd_part *althover_part = add_scene_buffer(part_list, type, button->hover_tree, hover_buffer, hover_geo.x, hover_geo.y); diff --git a/src/ssd/ssd-titlebar.c b/src/ssd/ssd-titlebar.c index 70d9ca6b..581bd971 100644 --- a/src/ssd/ssd-titlebar.c +++ b/src/ssd/ssd-titlebar.c @@ -27,6 +27,7 @@ ssd_titlebar_create(struct ssd *ssd) struct view *view = ssd->view; struct theme *theme = view->server->theme; int width = view->current.width; + int button_width = ssd->titlebar.button_width; float *color; struct wlr_scene_tree *parent; @@ -90,29 +91,30 @@ ssd_titlebar_create(struct ssd *ssd) /* Title */ add_scene_rect(&subtree->parts, LAB_SSD_PART_TITLEBAR, parent, - width - theme->window_button_width * SSD_BUTTON_COUNT, - theme->title_height, theme->window_button_width, 0, color); + width - button_width * SSD_BUTTON_COUNT, theme->title_height, + button_width, 0, color); /* 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, menu_button_hover, 0, view); + LAB_SSD_BUTTON_WINDOW_MENU, LAB_SSD_PART_CORNER_TOP_LEFT, + parent, corner_top_left, menu_button_unpressed, + menu_button_hover, 0, button_width, view); add_scene_button(&subtree->parts, LAB_SSD_BUTTON_ICONIFY, parent, color, iconify_button_unpressed, iconify_button_hover, - width - theme->window_button_width * 3, view); + width - button_width * 3, button_width, view); /* Maximize button has an alternate state when maximized */ struct ssd_part *btn_max_root = add_scene_button( &subtree->parts, LAB_SSD_BUTTON_MAXIMIZE, parent, color, maximize_button_unpressed, maximize_button_hover, - width - theme->window_button_width * 2, view); + width - button_width * 2, button_width, view); struct ssd_button *btn_max = node_ssd_button_from_node(btn_max_root->node); add_toggled_icon(btn_max, &subtree->parts, LAB_SSD_BUTTON_MAXIMIZE, - restore_button_unpressed, restore_button_hover); + restore_button_unpressed, restore_button_hover, button_width); add_scene_button_corner(&subtree->parts, LAB_SSD_BUTTON_CLOSE, LAB_SSD_PART_CORNER_TOP_RIGHT, parent, corner_top_right, close_button_unpressed, close_button_hover, - width - theme->window_button_width * 1, view); + width - button_width * 1, button_width, view); } FOR_EACH_END ssd_update_title(ssd); @@ -189,8 +191,9 @@ void ssd_titlebar_update(struct ssd *ssd) { struct view *view = ssd->view; - int width = view->current.width; struct theme *theme = view->server->theme; + int width = view->current.width; + int button_width = ssd->titlebar.button_width; bool maximized = view->maximized == VIEW_AXIS_BOTH; bool tiled_not_maximized = view_is_tiled_and_notify_tiled(ssd->view) @@ -218,25 +221,25 @@ ssd_titlebar_update(struct ssd *ssd) case LAB_SSD_PART_TITLEBAR: wlr_scene_rect_set_size( wlr_scene_rect_from_node(part->node), - width - theme->window_button_width * SSD_BUTTON_COUNT, + width - button_width * SSD_BUTTON_COUNT, theme->title_height); continue; case LAB_SSD_BUTTON_ICONIFY: if (is_direct_child(part->node, subtree)) { wlr_scene_node_set_position(part->node, - width - theme->window_button_width * 3, 0); + width - button_width * 3, 0); } continue; case LAB_SSD_BUTTON_MAXIMIZE: if (is_direct_child(part->node, subtree)) { wlr_scene_node_set_position(part->node, - width - theme->window_button_width * 2, 0); + width - button_width * 2, 0); } continue; case LAB_SSD_PART_CORNER_TOP_RIGHT: if (is_direct_child(part->node, subtree)) { wlr_scene_node_set_position(part->node, - width - theme->window_button_width * 1, 0); + width - button_width * 1, 0); } continue; default: @@ -270,6 +273,20 @@ ssd_titlebar_destroy(struct ssd *ssd) ssd->titlebar.tree = NULL; } +static int +get_title_width(int view_width, int button_width) +{ + /* + * Always return 0 if the button width is narrower than default. + * Simply computing (width - button_width * SSD_BUTTON_COUNT) + * gives undesired widths of 1 to (SSD_BUTTON_COUNT - 1) pixels + * due to rounding errors (since button_width is an integer). + */ + int theme_button_width = rc.theme->window_button_width; + return (button_width < theme_button_width) ? 0 + : view_width - theme_button_width * SSD_BUTTON_COUNT; +} + /* * For ssd_update_title* we do not early out because * .active and .inactive may result in different sizes @@ -288,7 +305,8 @@ ssd_update_title_positions(struct ssd *ssd) struct view *view = ssd->view; struct theme *theme = view->server->theme; int width = view->current.width; - int title_bg_width = width - theme->window_button_width * SSD_BUTTON_COUNT; + int button_width = ssd->titlebar.button_width; + int title_bg_width = get_title_width(width, button_width); int x, y; int buffer_height, buffer_width; @@ -304,7 +322,7 @@ ssd_update_title_positions(struct ssd *ssd) buffer_width = part->buffer ? part->buffer->width : 0; buffer_height = part->buffer ? part->buffer->height : 0; - x = theme->window_button_width; + x = button_width; y = (theme->title_height - buffer_height) / 2; if (title_bg_width <= 0) { @@ -314,7 +332,7 @@ ssd_update_title_positions(struct ssd *ssd) wlr_scene_node_set_enabled(part->node, true); if (theme->window_label_text_justify == LAB_JUSTIFY_CENTER) { - if (buffer_width + theme->window_button_width * 2 <= title_bg_width) { + if (buffer_width + button_width * 2 <= title_bg_width) { /* Center based on the full width */ x = (width - buffer_width) / 2; } else { @@ -357,8 +375,8 @@ ssd_update_title(struct ssd *ssd) struct ssd_part *part; struct ssd_sub_tree *subtree; struct ssd_state_title_width *dstate; - int title_bg_width = view->current.width - - theme->window_button_width * SSD_BUTTON_COUNT; + int title_bg_width = get_title_width(view->current.width, + ssd->titlebar.button_width); FOR_EACH_STATE(ssd, subtree) { if (subtree == &ssd->titlebar.active) { diff --git a/src/ssd/ssd.c b/src/ssd/ssd.c index 6382983d..1d2982ab 100644 --- a/src/ssd/ssd.c +++ b/src/ssd/ssd.c @@ -176,6 +176,15 @@ ssd_resize_edges(enum ssd_part_type type) } } +static int +get_button_width(int view_width) +{ + /* view_width may be 0 if view isn't mapped yet */ + int theme_button_width = rc.theme->window_button_width; + return (view_width > 0 && view_width < theme_button_width * SSD_BUTTON_COUNT) + ? view_width / SSD_BUTTON_COUNT : theme_button_width; +} + struct ssd * ssd_create(struct view *view, bool active) { @@ -186,6 +195,7 @@ ssd_create(struct view *view, bool active) ssd->tree = wlr_scene_tree_create(view->scene_tree); wlr_scene_node_lower_to_bottom(&ssd->tree->node); ssd->titlebar.height = view->server->theme->title_height; + ssd->titlebar.button_width = get_button_width(view->current.width); ssd_shadow_create(ssd); ssd_extents_create(ssd); /* @@ -223,6 +233,21 @@ ssd_update_margin(struct ssd *ssd) ssd->margin = ssd_thickness(ssd->view); } +/* + * Clears pointers to this view's SSD from the global hover state. + * Must be called before the SSD or any buttons are destroyed. + */ +static void +ssd_reset_hover_state(struct ssd *ssd) +{ + struct ssd_hover_state *hover_state = + ssd->view->server->ssd_hover_state; + if (hover_state->view == ssd->view) { + hover_state->view = NULL; + hover_state->button = NULL; + } +} + void ssd_update_geometry(struct ssd *ssd) { @@ -233,20 +258,9 @@ ssd_update_geometry(struct ssd *ssd) struct wlr_box cached = ssd->state.geometry; struct wlr_box current = ssd->view->current; - int min_view_width = rc.theme->window_button_width * SSD_BUTTON_COUNT; int eff_width = current.width; int eff_height = view_effective_height(ssd->view, /* use_pending */ false); - if (eff_width > 0 && eff_width < min_view_width) { - /* - * Prevent negative values in calculations like - * `width - SSD_BUTTON_WIDTH * SSD_BUTTON_COUNT` - */ - wlr_log(WLR_ERROR, - "view width is smaller than its minimal value"); - return; - } - if (eff_width == cached.width && eff_height == cached.height) { if (current.x != cached.x || current.y != cached.y) { /* Dynamically resize extents based on position and usable_area */ @@ -274,6 +288,22 @@ ssd_update_geometry(struct ssd *ssd) } return; } + + /* + * Rather than resizing/re-rendering individual buttons and + * icons in ssd_titlebar_update(), it's simpler to just recreate + * them all. This case is only hit for abnormally small views, + * so it's probably not worth optimizing further. + */ + int button_width = get_button_width(current.width); + if (button_width != ssd->titlebar.button_width) { + /* Reset hover state before recreating buttons */ + ssd_reset_hover_state(ssd); + ssd_titlebar_destroy(ssd); + ssd->titlebar.button_width = button_width; + ssd_titlebar_create(ssd); + } + ssd_extents_update(ssd); ssd_titlebar_update(ssd); ssd_border_update(ssd); @@ -302,16 +332,9 @@ ssd_destroy(struct ssd *ssd) return; } - /* Maybe reset hover view */ - struct view *view = ssd->view; - struct ssd_hover_state *hover_state; - hover_state = view->server->ssd_hover_state; - if (hover_state->view == view) { - hover_state->view = NULL; - hover_state->button = NULL; - } - /* Destroy subcomponents */ + /* Reset hover state before destroying */ + ssd_reset_hover_state(ssd); ssd_titlebar_destroy(ssd); ssd_border_destroy(ssd); ssd_extents_destroy(ssd);