menu: Dynamically adjust menu width based on widest item

Adds two new theme vars:
- menu.width.min (menu will never be smaller than this)
- menu.width.max (menu will never be wider than this + padding)

A fixed menu width can be achieved by setting
menu.width.min == menu.width.max.
This commit is contained in:
Consolatis 2022-12-05 14:38:16 +01:00 committed by Johan Malm
parent f0666ba5c9
commit d00327bc32
7 changed files with 115 additions and 14 deletions

View file

@ -59,6 +59,14 @@ A theme consists of a themerc file and optionally some xbm icons.
Vertical offset in pixels between submenus and their parents. Positive
values for downwards and negative for upwards. Default is 0.
*menu.width.min*
Minimal width for menus. Default is 20.
A fixed width can be achieved by setting .min and .max to the same value.
*menu.width.max*
Maximal width for menus. Default is 200.
A fixed width can be achieved by setting .min and .max to the same value.
*window.active.border.color*
Border color of active window

View file

@ -25,7 +25,7 @@ struct scaled_font_buffer {
/**
* Create an auto scaling font buffer, providing a wlr_scene_buffer node for
* display. It gets destroyed automatically when the backing scaled_scene_buffer
* is being destoyed which in turn happens automatically when the backing
* is being destroyed which in turn happens automatically when the backing
* wlr_scene_buffer (or one of its parents) is being destroyed.
*
* To actually show some text, scaled_font_buffer_update() has to be called.
@ -48,4 +48,12 @@ struct scaled_font_buffer *scaled_font_buffer_create(struct wlr_scene_tree *pare
void scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text,
int max_width, struct font *font, float *color, const char *arrow);
/**
* Update the max width of an existing auto scaling font buffer
* and force a new render.
*
* No steps are taken to detect if its actually required to render a new buffer.
*/
void scaled_font_buffer_set_max_width(struct scaled_font_buffer *self, int max_width);
#endif /* __LAB_COMMON_SCALED_FONT_BUFFER_H */

View file

@ -33,6 +33,7 @@ struct menuitem {
struct menu *submenu;
bool selectable;
int height;
int native_width;
struct wlr_scene_tree *tree;
struct menu_scene normal;
struct menu_scene selected;

View file

@ -53,6 +53,9 @@ struct theme {
float menu_items_active_bg_color[4];
float menu_items_active_text_color[4];
int menu_min_width;
int menu_max_width;
int menu_separator_line_thickness;
int menu_separator_padding_width;
int menu_separator_padding_height;

View file

@ -90,3 +90,10 @@ scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text,
/* Invalidate cache and force a new render */
scaled_scene_buffer_invalidate_cache(self->scaled_buffer);
}
void
scaled_font_buffer_set_max_width(struct scaled_font_buffer *self, int max_width)
{
self->max_width = max_width;
scaled_scene_buffer_invalidate_cache(self->scaled_buffer);
}

View file

@ -17,14 +17,13 @@
#include "common/mem.h"
#include "common/nodename.h"
#include "common/scaled_font_buffer.h"
#include "common/scene-helpers.h"
#include "common/string-helpers.h"
#include "labwc.h"
#include "menu/menu.h"
#include "node.h"
#include "theme.h"
#define MENUWIDTH (110)
/* state-machine variables for processing <item></item> */
static bool in_item;
static struct menuitem *current_item;
@ -52,7 +51,7 @@ menu_create(struct server *server, const char *id, const char *label)
menu->label = xstrdup(label ? label : id);
menu->parent = current_menu;
menu->server = server;
menu->size.width = MENUWIDTH;
menu->size.width = server->theme->menu_min_width;
/* menu->size.height will be kept up to date by adding items */
menu->scene_tree = wlr_scene_tree_create(server->menu_tree);
wlr_scene_node_set_enabled(&menu->scene_tree->node, false);
@ -75,6 +74,59 @@ menu_get_by_id(const char *id)
return NULL;
}
static void
menu_update_width(struct menu *menu)
{
struct menuitem *item;
struct theme *theme = menu->server->theme;
int max_width = theme->menu_min_width;
/* Get widest menu item, clamped by menu_max_width */
wl_list_for_each(item, &menu->menuitems, link) {
if (item->native_width > max_width) {
max_width = item->native_width < theme->menu_max_width
? item->native_width : theme->menu_max_width;
}
}
menu->size.width = max_width + 2 * theme->menu_item_padding_x;
/* Update all items for the new size */
wl_list_for_each(item, &menu->menuitems, link) {
wlr_scene_rect_set_size(
lab_wlr_scene_get_rect(item->normal.background),
menu->size.width, item->height);
if (!item->selected.background) {
/* This is a separator. They don't have a selected background. */
wlr_scene_rect_set_size(
lab_wlr_scene_get_rect(item->normal.text),
menu->size.width - 2 * theme->menu_separator_padding_width,
theme->menu_separator_line_thickness);
} else {
/* Usual menu item */
wlr_scene_rect_set_size(
lab_wlr_scene_get_rect(item->selected.background),
menu->size.width, item->height);
if (item->native_width > max_width || item->submenu) {
scaled_font_buffer_set_max_width(item->normal.buffer,
max_width);
scaled_font_buffer_set_max_width(item->selected.buffer,
max_width);
}
}
}
}
static void
post_processing(struct server *server)
{
struct menu *menu;
for (int i = 0; i < nr_menus; ++i) {
menu = menus + i;
menu_update_width(menu);
}
}
static struct menuitem *
item_create(struct menu *menu, const char *text, bool show_arrow)
{
@ -84,6 +136,8 @@ item_create(struct menu *menu, const char *text, bool show_arrow)
struct server *server = menu->server;
struct theme *theme = server->theme;
const char *arrow = show_arrow ? "" : NULL;
if (!menu->item_height) {
menu->item_height = font_height(&rc.font_menuitem)
+ 2 * theme->menu_item_padding_y;
@ -91,7 +145,10 @@ item_create(struct menu *menu, const char *text, bool show_arrow)
menuitem->height = menu->item_height;
int x, y;
int item_max_width = MENUWIDTH - 2 * theme->menu_item_padding_x;
menuitem->native_width = font_width(&rc.font_menuitem, text);
if (arrow) {
menuitem->native_width += font_width(&rc.font_menuitem, arrow);
}
/* Menu item root node */
menuitem->tree = wlr_scene_tree_create(menu->scene_tree);
@ -105,11 +162,11 @@ item_create(struct menu *menu, const char *text, bool show_arrow)
/* Item background nodes */
menuitem->normal.background = &wlr_scene_rect_create(
menuitem->normal.tree,
MENUWIDTH, menu->item_height,
menu->size.width, menu->item_height,
theme->menu_items_bg_color)->node;
menuitem->selected.background = &wlr_scene_rect_create(
menuitem->selected.tree,
MENUWIDTH, menu->item_height,
menu->size.width, menu->item_height,
theme->menu_items_active_bg_color)->node;
/* Font nodes */
@ -129,10 +186,9 @@ item_create(struct menu *menu, const char *text, bool show_arrow)
menuitem->selected.text = &menuitem->selected.buffer->scene_buffer->node;
/* Font buffers */
const char *arrow = show_arrow ? "" : NULL;
scaled_font_buffer_update(menuitem->normal.buffer, text, item_max_width,
scaled_font_buffer_update(menuitem->normal.buffer, text, menuitem->native_width,
&rc.font_menuitem, theme->menu_items_text_color, arrow);
scaled_font_buffer_update(menuitem->selected.buffer, text, item_max_width,
scaled_font_buffer_update(menuitem->selected.buffer, text, menuitem->native_width,
&rc.font_menuitem, theme->menu_items_active_text_color, arrow);
/* Center font nodes */
@ -173,10 +229,10 @@ separator_create(struct menu *menu, const char *label)
menuitem->normal.tree = wlr_scene_tree_create(menuitem->tree);
menuitem->normal.background = &wlr_scene_rect_create(
menuitem->normal.tree,
MENUWIDTH, menuitem->height,
menu->size.width, menuitem->height,
theme->menu_items_bg_color)->node;
int width = MENUWIDTH - 2 * theme->menu_separator_padding_width;
int width = menu->size.width - 2 * theme->menu_separator_padding_width;
menuitem->normal.text = &wlr_scene_rect_create(
menuitem->normal.tree,
width > 0 ? width : 0,
@ -511,7 +567,7 @@ menu_configure(struct menu *menu, int lx, int ly, enum menu_align align)
}
if (align & LAB_MENU_OPEN_LEFT) {
lx -= MENUWIDTH - theme->menu_overlap_x;
lx -= menu->size.width - theme->menu_overlap_x;
}
if (align & LAB_MENU_OPEN_TOP) {
ly -= menu->size.height;
@ -530,7 +586,7 @@ menu_configure(struct menu *menu, int lx, int ly, enum menu_align align)
continue;
}
if (align & LAB_MENU_OPEN_RIGHT) {
new_lx = lx + MENUWIDTH - theme->menu_overlap_x;
new_lx = lx + menu->size.width - theme->menu_overlap_x;
} else {
new_lx = lx;
}
@ -642,6 +698,7 @@ menu_init(struct server *server)
{
init_rootmenu(server);
init_windowmenu(server);
post_processing(server);
}
void

View file

@ -131,6 +131,9 @@ theme_builtin(struct theme *theme)
theme->menu_item_padding_x = 7;
theme->menu_item_padding_y = 4;
theme->menu_min_width = 20;
theme->menu_max_width = 200;
theme->menu_separator_line_thickness = 1;
theme->menu_separator_padding_width = 6;
theme->menu_separator_padding_height = 3;
@ -260,6 +263,13 @@ entry(struct theme *theme, const char *key, const char *value)
theme->window_inactive_button_close_unpressed_image_color);
}
if (match(key, "menu.width.min")) {
theme->menu_min_width = atoi(value);
}
if (match(key, "menu.width.max")) {
theme->menu_max_width = atoi(value);
}
if (match(key, "menu.items.bg.color")) {
parse_hexstr(value, theme->menu_items_bg_color);
}
@ -488,6 +498,13 @@ post_processing(struct theme *theme)
theme->title_height = rc.corner_radius + 1;
}
if (theme->menu_max_width < theme->menu_min_width) {
wlr_log(WLR_ERROR,
"Adjusting menu.width.max: .max (%d) lower than .min (%d)",
theme->menu_max_width, theme->menu_min_width);
theme->menu_max_width = theme->menu_min_width;
}
/* Inherit OSD settings if not set */
if (theme->osd_bg_color[0] == FLT_MIN) {
memcpy(theme->osd_bg_color,