]> git.mdlowis.com Git - proto/labwc.git/commitdiff
menu: refactor in preparation for richer menu design
authortokyo4j <hrak1529@gmail.com>
Sun, 10 Nov 2024 09:54:01 +0000 (18:54 +0900)
committerJohan Malm <johanmalm@users.noreply.github.com>
Wed, 13 Nov 2024 20:08:15 +0000 (20:08 +0000)
This commit separates the process of creating scene-trees of the menu
items into `{item,separator,title}_create_scene()`. This will make it
easier to draw richer menu designs like borders, paddings and rounded
corners.

include/menu/menu.h
src/menu/menu.c

index 4dd98796a90b09ce771b658f43ec02ec51c2ea57..3be301f9c5d30ba3c4bd65453ffbac6a62ab0af8 100644 (file)
@@ -37,11 +37,12 @@ struct menuitem {
        struct wl_list actions;
        char *execute;
        char *id; /* needed for pipemenus */
+       char *text;
+       const char *arrow;
        struct menu *parent;
        struct menu *submenu;
        bool selectable;
        enum menuitem_type type;
-       int height;
        int native_width;
        struct wlr_scene_tree *tree;
        struct menu_scene normal;
index 68c744f3893b7a659fcce56406e5b69c9807e273..3cbb94372596035fc88c4f452ba05d4353096320 100644 (file)
@@ -15,6 +15,7 @@
 #include "common/dir.h"
 #include "common/font.h"
 #include "common/list.h"
+#include "common/macros.h"
 #include "common/mem.h"
 #include "common/nodename.h"
 #include "common/scaled-font-buffer.h"
@@ -83,9 +84,6 @@ menu_create(struct server *server, const char *id, const char *label)
        menu->server = server;
        menu->is_pipemenu = waiting_for_pipe_menu;
        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);
        return menu;
 }
 
@@ -139,19 +137,27 @@ item_create(struct menu *menu, const char *text, bool show_arrow)
        menuitem->parent = menu;
        menuitem->selectable = true;
        menuitem->type = LAB_MENU_ITEM;
-       struct server *server = menu->server;
-       struct theme *theme = server->theme;
-
-       const char *arrow = show_arrow ? "›" : NULL;
-
-       menuitem->height = theme->menu_item_height;
+       menuitem->text = xstrdup(text);
+       menuitem->arrow = show_arrow ? "›" : NULL;
 
-       int x, y;
        menuitem->native_width = font_width(&rc.font_menuitem, text);
-       if (arrow) {
-               menuitem->native_width += font_width(&rc.font_menuitem, arrow);
+       if (menuitem->arrow) {
+               menuitem->native_width += font_width(&rc.font_menuitem, menuitem->arrow);
        }
 
+       wl_list_append(&menu->menuitems, &menuitem->link);
+       wl_list_init(&menuitem->actions);
+       return menuitem;
+}
+
+static void
+item_create_scene(struct menuitem *menuitem, int *item_y)
+{
+       assert(menuitem);
+       assert(menuitem->type == LAB_MENU_ITEM);
+       struct menu *menu = menuitem->parent;
+       struct theme *theme = menu->server->theme;
+
        /* Menu item root node */
        menuitem->tree = wlr_scene_tree_create(menu->scene_tree);
        node_descriptor_create(&menuitem->tree->node,
@@ -160,6 +166,8 @@ item_create(struct menu *menu, const char *text, bool show_arrow)
        /* Tree for each state to hold background and text buffer */
        menuitem->normal.tree = wlr_scene_tree_create(menuitem->tree);
        menuitem->selected.tree = wlr_scene_tree_create(menuitem->tree);
+       /* Hide selected state */
+       wlr_scene_node_set_enabled(&menuitem->selected.tree->node, false);
 
        /* Item background nodes */
        menuitem->normal.background = &wlr_scene_rect_create(
@@ -174,46 +182,34 @@ item_create(struct menu *menu, const char *text, bool show_arrow)
        /* Font nodes */
        menuitem->normal.buffer = scaled_font_buffer_create(menuitem->normal.tree);
        menuitem->selected.buffer = scaled_font_buffer_create(menuitem->selected.tree);
-       if (!menuitem->normal.buffer || !menuitem->selected.buffer) {
-               wlr_log(WLR_ERROR, "Failed to create menu item '%s'", text);
-               /*
-                * Destroying the root node will destroy everything,
-                * including the node descriptor and scaled_font_buffers.
-                */
-               wlr_scene_node_destroy(&menuitem->tree->node);
-               free(menuitem);
-               return NULL;
-       }
+       assert(menuitem->normal.buffer && menuitem->selected.buffer);
        menuitem->normal.text = &menuitem->normal.buffer->scene_buffer->node;
        menuitem->selected.text = &menuitem->selected.buffer->scene_buffer->node;
 
        /* Font buffers */
-       scaled_font_buffer_update(menuitem->normal.buffer, text, menuitem->native_width,
-               &rc.font_menuitem, theme->menu_items_text_color,
-               theme->menu_items_bg_color, arrow);
-       scaled_font_buffer_update(menuitem->selected.buffer, text, menuitem->native_width,
-               &rc.font_menuitem, theme->menu_items_active_text_color,
-               theme->menu_items_active_bg_color, arrow);
+       scaled_font_buffer_update(menuitem->normal.buffer, menuitem->text,
+               menuitem->native_width, &rc.font_menuitem,
+               theme->menu_items_text_color,
+               theme->menu_items_bg_color, menuitem->arrow);
+       scaled_font_buffer_update(menuitem->selected.buffer, menuitem->text,
+               menuitem->native_width, &rc.font_menuitem,
+               theme->menu_items_active_text_color,
+               theme->menu_items_active_bg_color, menuitem->arrow);
+       scaled_font_buffer_set_max_width(menuitem->normal.buffer,
+               menu->size.width - 2 * theme->menu_items_padding_x);
+       scaled_font_buffer_set_max_width(menuitem->selected.buffer,
+               menu->size.width - 2 * theme->menu_items_padding_x);
 
        /* Center font nodes */
-       x = theme->menu_items_padding_x;
-       y = (theme->menu_item_height - menuitem->normal.buffer->height) / 2;
+       int x = theme->menu_items_padding_x;
+       int y = (theme->menu_item_height - menuitem->normal.buffer->height) / 2;
        wlr_scene_node_set_position(menuitem->normal.text, x, y);
        y = (theme->menu_item_height - menuitem->selected.buffer->height) / 2;
        wlr_scene_node_set_position(menuitem->selected.text, x, y);
 
        /* Position the item in relation to its menu */
-       wlr_scene_node_set_position(&menuitem->tree->node, 0, menu->size.height);
-
-       /* Hide selected state */
-       wlr_scene_node_set_enabled(&menuitem->selected.tree->node, false);
-
-       /* Update menu extents */
-       menu->size.height += menuitem->height;
-
-       wl_list_append(&menu->menuitems, &menuitem->link);
-       wl_list_init(&menuitem->actions);
-       return menuitem;
+       wlr_scene_node_set_position(&menuitem->tree->node, 0, *item_y);
+       *item_y += theme->menu_item_height;
 }
 
 static struct menuitem *
@@ -224,81 +220,120 @@ separator_create(struct menu *menu, const char *label)
        menuitem->selectable = false;
        menuitem->type = string_null_or_empty(label) ? LAB_MENU_SEPARATOR_LINE
                : LAB_MENU_TITLE;
-       struct server *server = menu->server;
-       struct theme *theme = server->theme;
-
        if (menuitem->type == LAB_MENU_TITLE) {
-               menuitem->height = theme->menu_header_height;
+               menuitem->text = xstrdup(label);
                menuitem->native_width = font_width(&rc.font_menuheader, label);
-       } else if (menuitem->type == LAB_MENU_SEPARATOR_LINE) {
-               menuitem->height = theme->menu_separator_line_thickness +
-                               2 * theme->menu_separator_padding_height;
        }
 
+       wl_list_append(&menu->menuitems, &menuitem->link);
+       wl_list_init(&menuitem->actions);
+       return menuitem;
+}
+
+static void
+separator_create_scene(struct menuitem *menuitem, int *item_y)
+{
+       assert(menuitem);
+       assert(menuitem->type == LAB_MENU_SEPARATOR_LINE);
+       struct menu *menu = menuitem->parent;
+       struct theme *theme = menu->server->theme;
+       int height = theme->menu_separator_line_thickness
+               + 2 * theme->menu_separator_padding_height;
+
        /* Menu item root node */
        menuitem->tree = wlr_scene_tree_create(menu->scene_tree);
        node_descriptor_create(&menuitem->tree->node,
                LAB_NODE_DESC_MENUITEM, menuitem);
 
-       /* Tree to hold background and text/line buffer */
+       /* Tree to hold background and line buffer */
        menuitem->normal.tree = wlr_scene_tree_create(menuitem->tree);
 
        /* Item background nodes */
-       float *bg_color = menuitem->type == LAB_MENU_TITLE
-               ? theme->menu_title_bg_color : theme->menu_items_bg_color;
-       float *text_color = menuitem->type == LAB_MENU_TITLE
-               ? theme->menu_title_text_color : theme->menu_items_text_color;
        menuitem->normal.background = &wlr_scene_rect_create(
-               menuitem->normal.tree,
-               menu->size.width, menuitem->height, bg_color)->node;
+               menuitem->normal.tree, menu->size.width, height,
+               theme->menu_items_bg_color)->node;
 
-       /* Draw separator line or title */
-       if (menuitem->type == LAB_MENU_TITLE) {
-               menuitem->normal.buffer = scaled_font_buffer_create(menuitem->normal.tree);
-               if (!menuitem->normal.buffer) {
-                       wlr_log(WLR_ERROR, "Failed to create menu item '%s'", label);
-                       wlr_scene_node_destroy(&menuitem->tree->node);
-                       free(menuitem);
-                       return NULL;
-               }
-               menuitem->normal.text = &menuitem->normal.buffer->scene_buffer->node;
-               /* Font buffer */
-               scaled_font_buffer_update(menuitem->normal.buffer, label,
-                       menuitem->native_width, &rc.font_menuheader,
-                       text_color, bg_color, /* arrow */ NULL);
-               /* Center font nodes */
-               int x, y;
-               x = theme->menu_items_padding_x;
-               y = (theme->menu_header_height - menuitem->normal.buffer->height) / 2;
-               wlr_scene_node_set_position(menuitem->normal.text, x, y);
+       /* Draw separator line */
+       int line_width = menu->size.width - 2 * theme->menu_separator_padding_width;
+       menuitem->normal.text = &wlr_scene_rect_create(
+               menuitem->normal.tree, line_width,
+               theme->menu_separator_line_thickness,
+               theme->menu_separator_color)->node;
+
+       /* Vertically center-align separator line */
+       wlr_scene_node_set_position(menuitem->normal.text,
+               theme->menu_separator_padding_width,
+               theme->menu_separator_padding_height);
+       wlr_scene_node_set_position(&menuitem->tree->node, 0, *item_y);
+       *item_y += height;
+}
+
+static void
+title_create_scene(struct menuitem *menuitem, int *item_y)
+{
+       assert(menuitem);
+       assert(menuitem->type == LAB_MENU_TITLE);
+       struct menu *menu = menuitem->parent;
+       struct theme *theme = menu->server->theme;
+       float *bg_color = theme->menu_title_bg_color;
+       float *text_color = theme->menu_title_text_color;
+
+       /* Menu item root node */
+       menuitem->tree = wlr_scene_tree_create(menu->scene_tree);
+       node_descriptor_create(&menuitem->tree->node,
+               LAB_NODE_DESC_MENUITEM, menuitem);
+
+       /* Tree to hold background and text buffer */
+       menuitem->normal.tree = wlr_scene_tree_create(menuitem->tree);
+
+       /* Background */
+       menuitem->normal.background = &wlr_scene_rect_create(
+               menuitem->normal.tree, menu->size.width,
+               theme->menu_header_height, bg_color)->node;
+
+       /* Draw separator title */
+       menuitem->normal.buffer = scaled_font_buffer_create(menuitem->normal.tree);
+       assert(menuitem->normal.buffer);
+       menuitem->normal.text = &menuitem->normal.buffer->scene_buffer->node;
+       scaled_font_buffer_update(menuitem->normal.buffer, menuitem->text,
+               menuitem->native_width, &rc.font_menuheader,
+               text_color, bg_color, /* arrow */ NULL);
+       scaled_font_buffer_set_max_width(menuitem->normal.buffer,
+               menu->size.width - 2 * theme->menu_items_padding_x);
+
+       int title_x = 0;
+       if (theme->menu_title_text_justify == LAB_JUSTIFY_CENTER) {
+               title_x = (menu->size.width - menuitem->native_width) / 2;
+               title_x = MAX(title_x, 0);
        } else {
-               int nominal_width = theme->menu_min_width;
-               menuitem->normal.text = &wlr_scene_rect_create(
-                       menuitem->normal.tree, nominal_width,
-                       theme->menu_separator_line_thickness,
-                       theme->menu_separator_color)->node;
-               wlr_scene_node_set_position(&menuitem->tree->node, 0, menu->size.height);
-               /* Vertically center-align separator line */
-               wlr_scene_node_set_position(menuitem->normal.text,
-                       theme->menu_separator_padding_width,
-                       theme->menu_separator_padding_height);
-       }
-       wlr_scene_node_set_position(&menuitem->tree->node, 0, menu->size.height);
-
-       menu->size.height += menuitem->height;
-       wl_list_append(&menu->menuitems, &menuitem->link);
-       wl_list_init(&menuitem->actions);
-       return menuitem;
+               title_x = theme->menu_items_padding_x;
+       }
+       int title_y = (theme->menu_header_height - menuitem->normal.buffer->height) / 2;
+       wlr_scene_node_set_position(menuitem->normal.text, title_x, title_y);
+
+       wlr_scene_node_set_position(&menuitem->tree->node, 0, *item_y);
+       *item_y += theme->menu_header_height;
 }
 
 static void
-menu_update_width(struct menu *menu)
+menu_update_scene(struct menu *menu)
 {
        struct menuitem *item;
        struct theme *theme = menu->server->theme;
-       int max_width = theme->menu_min_width;
+
+       if (menu->scene_tree) {
+               wlr_scene_node_destroy(&menu->scene_tree->node);
+               wl_list_for_each(item, &menu->menuitems, link) {
+                       item->tree = NULL;
+                       item->normal = (struct menu_scene){0};
+                       item->selected = (struct menu_scene){0};
+               }
+       }
+       menu->scene_tree = wlr_scene_tree_create(menu->server->menu_tree);
+       wlr_scene_node_set_enabled(&menu->scene_tree->node, false);
 
        /* Get widest menu item, clamped by menu_max_width */
+       int max_width = theme->menu_min_width;
        wl_list_for_each(item, &menu->menuitems, link) {
                if (item->native_width > max_width) {
                        max_width = item->native_width < theme->menu_max_width
@@ -307,58 +342,23 @@ menu_update_width(struct menu *menu)
        }
        menu->size.width = max_width + 2 * theme->menu_items_padding_x;
 
-       /*
-        * TODO: This function is getting a bit unwieldy. Consider calculating
-        * the menu-window width up-front to avoid this post_processing() and
-        * second-bite-of-the-cherry stuff
-        */
-
        /* Update all items for the new size */
+       int item_y = 0;
        wl_list_for_each(item, &menu->menuitems, link) {
-               wlr_scene_rect_set_size(
-                       wlr_scene_rect_from_node(item->normal.background),
-                       menu->size.width, item->height);
-
-               /*
-                * Separator lines are special because they change width with
-                * the menu.
-                */
-               if (item->type == LAB_MENU_SEPARATOR_LINE) {
-                       int width = menu->size.width
-                               - 2 * theme->menu_separator_padding_width;
-                       wlr_scene_rect_set_size(
-                               wlr_scene_rect_from_node(item->normal.text),
-                               width, theme->menu_separator_line_thickness);
-               } else if (item->type == LAB_MENU_TITLE) {
-                       if (item->native_width > max_width) {
-                               scaled_font_buffer_set_max_width(item->normal.buffer,
-                                       max_width);
-                       }
-                       if (theme->menu_title_text_justify == LAB_JUSTIFY_CENTER) {
-                               int x, y;
-                               x = (menu->size.width - item->native_width) / 2;
-                               x = x < 0 ? 0 : x;
-                               y = (theme->menu_header_height - item->normal.buffer->height) / 2;
-                               wlr_scene_node_set_position(item->normal.text, x, y);
-                       }
-               }
-
-               if (item->selectable) {
-                       /* Only selectable items have item->selected.background */
-                       wlr_scene_rect_set_size(
-                               wlr_scene_rect_from_node(item->selected.background),
-                               menu->size.width, item->height);
-               }
-
-               if (item->native_width > max_width || item->submenu || item->execute) {
-                       scaled_font_buffer_set_max_width(item->normal.buffer,
-                               max_width);
-                       if (item->selectable) {
-                               scaled_font_buffer_set_max_width(item->selected.buffer,
-                                       max_width);
-                       }
+               assert(!item->tree);
+               switch (item->type) {
+               case LAB_MENU_ITEM:
+                       item_create_scene(item, &item_y);
+                       break;
+               case LAB_MENU_SEPARATOR_LINE:
+                       separator_create_scene(item, &item_y);
+                       break;
+               case LAB_MENU_TITLE:
+                       title_create_scene(item, &item_y);
+                       break;
                }
        }
+       menu->size.height = item_y;
 }
 
 static void
@@ -366,7 +366,7 @@ post_processing(struct server *server)
 {
        struct menu *menu;
        wl_list_for_each(menu, &server->menus, link) {
-               menu_update_width(menu);
+               menu_update_scene(menu);
        }
 }
 
@@ -426,6 +426,7 @@ item_destroy(struct menuitem *item)
        wlr_scene_node_destroy(&item->tree->node);
        free(item->execute);
        free(item->id);
+       free(item->text);
        free(item);
 }
 
@@ -885,25 +886,15 @@ menu_hide_submenu(struct server *server, const char *id)
                return;
        }
        wl_list_for_each(menu, &server->menus, link) {
-               bool should_reposition = false;
                struct menuitem *item, *next;
                wl_list_for_each_safe(item, next, &menu->menuitems, link) {
                        if (item->submenu == hide_menu) {
                                item_destroy(item);
-                               should_reposition = true;
                        }
                }
-
-               if (!should_reposition) {
-                       continue;
-               }
-               /* Re-position items vertically */
-               menu->size.height = 0;
-               wl_list_for_each(item, &menu->menuitems, link) {
-                       wlr_scene_node_set_position(&item->tree->node, 0,
-                               menu->size.height);
-                       menu->size.height += item->height;
-               }
+       }
+       if (hide_menu->scene_tree) {
+               menu_update_scene(hide_menu);
        }
 }
 
@@ -952,7 +943,7 @@ update_client_send_to_menu(struct server *server)
                fill_item("to.action", workspace->name);
        }
 
-       menu_update_width(menu);
+       menu_update_scene(menu);
 }
 
 static void
@@ -1024,7 +1015,7 @@ update_client_list_combined_menu(struct server *server)
                fill_item("to.action", workspace->name);
        }
        buf_reset(&buffer);
-       menu_update_width(menu);
+       menu_update_scene(menu);
 }
 
 static void