]> git.mdlowis.com Git - proto/labwc.git/commitdiff
menu: support submenus
authorJohan Malm <jgm323@gmail.com>
Tue, 2 Nov 2021 18:31:19 +0000 (18:31 +0000)
committerJohan Malm <jgm323@gmail.com>
Tue, 2 Nov 2021 18:31:19 +0000 (18:31 +0000)
Support submenus defined as follows:

<menu id="submenu" label="Submenu">
  <item label="item1.1"></item>
  <item label="item1.2"></item>
</menu>

<menu id="root-menu" label="">
  <menu id="submenu" />
  <item label="item0"></item>
</menu>

include/menu/menu.h
src/main.c
src/menu/menu.c
src/output.c

index f9280a7c273cb165e421bc64d3ecfb438dd184c8..2a28cd826439c01886d166fc16cc8c791422c1e2 100644 (file)
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
 #ifndef __LABWC_MENU_H
 #define __LABWC_MENU_H
 
@@ -7,6 +8,7 @@
 struct menuitem {
        char *action;
        char *command;
+       struct menu *submenu;
        struct wlr_box box;
        struct {
                struct wlr_texture *active;
@@ -18,14 +20,18 @@ struct menuitem {
        struct wl_list link; /* menu::menuitems */
 };
 
+/* This could be the root-menu or a submenu */
 struct menu {
-       struct server *server;
+       char *id;
+       char *label;
+       bool visible;
        struct wlr_box box;
        struct wl_list menuitems;
+       struct server *server;
 };
 
-void menu_init_rootmenu(struct server *server, struct menu *menu);
-void menu_finish(struct menu *menu);
+void menu_init_rootmenu(struct server *server);
+void menu_finish(void);
 
 /* menu_move - move to position (x, y) */
 void menu_move(struct menu *menu, int x, int y);
index 435010d1b225e99ec2b7c7911b050043361de586..ced17f768979130b04f2220368c283844d3a4fd2 100644 (file)
@@ -71,8 +71,7 @@ main(int argc, char *argv[])
        theme_init(&theme, server.renderer, rc.theme_name);
        server.theme = &theme;
 
-       struct menu rootmenu = { 0 };
-       menu_init_rootmenu(&server, &rootmenu);
+       menu_init_rootmenu(&server);
 
        session_autostart_init();
        if (startup_cmd) {
@@ -83,7 +82,7 @@ main(int argc, char *argv[])
 
        server_finish(&server);
 
-       menu_finish(&rootmenu);
+       menu_finish();
        theme_finish(&theme);
        rcxml_finish();
        font_finish();
index d6db59093d5951039148e5ba01709484851ab185..83b38524a90a6037a0f5df4a4753da3306d05a3c 100644 (file)
 #include "menu/menu.h"
 #include "theme.h"
 
+#define MENUWIDTH (110)
+#define MENU_ITEM_PADDING_Y (4)
+#define MENU_ITEM_PADDING_X (7)
+
 /* state-machine variables for processing <item></item> */
 static bool in_item;
 static struct menuitem *current_item;
 
-#define MENUWIDTH (110)
-#define MENU_ITEM_PADDING_Y (4)
-#define MENU_ITEM_PADDING_X (7)
+static struct menu *current_menu;
+
+/* vector for <menu id="" label=""> elements */
+static struct menu *menus;
+static int nr_menus, alloc_menus;
+
+static struct menu *
+menu_create(struct server *server, const char *id, const char *label)
+{
+       if (nr_menus == alloc_menus) {
+               alloc_menus = (alloc_menus + 16) * 2;
+               menus = realloc(menus, alloc_menus * sizeof(struct menu));
+       }
+       struct menu *menu = menus + nr_menus;
+       memset(menu, 0, sizeof(*menu));
+       nr_menus++;
+       wl_list_init(&menu->menuitems);
+       menu->id = strdup(id);
+       menu->label = strdup(label);
+       menu->server = server;
+       return menu;
+}
+
+static struct menu *
+get_menu_by_id(const char *id)
+{
+       struct menu *menu;
+       for (int i = 0; i < nr_menus; ++i) {
+               menu = menus + i;
+               if (!strcmp(menu->id, id)) {
+                       return menu;
+               }
+       }
+       return NULL;
+}
 
 static struct menuitem *
-menuitem_create(struct server *server, struct menu *menu, const char *text)
+item_create(struct menu *menu, const char *text)
 {
        struct menuitem *menuitem = calloc(1, sizeof(struct menuitem));
        if (!menuitem) {
                return NULL;
        }
+       struct server *server = menu->server;
        struct theme *theme = server->theme;
        struct font font = {
                .name = rc.font_name_menuitem,
@@ -58,26 +95,27 @@ menuitem_create(struct server *server, struct menu *menu, const char *text)
        return menuitem;
 }
 
-static void fill_item(char *nodename, char *content, struct menu *menu)
+/*
+ * Handle the following:
+ * <item label="">
+ *   <action name="">
+ *     <command></command>
+ *   </action>
+ * </item>
+ */
+static void
+fill_item(char *nodename, char *content)
 {
        string_truncate_at_pattern(nodename, ".item.menu");
 
-       if (getenv("LABWC_DEBUG_MENU_NODENAMES")) {
-               printf("%s: %s\n", nodename, content);
-       }
-
-       /*
-        * Handle the following:
-        * <item label="">
-        *   <action name="">
-        *     <command></command>
-        *   </action>
-        * </item>
-        */
+       /* <item label=""> defines the start of a new item */
        if (!strcmp(nodename, "label")) {
-               current_item = menuitem_create(menu->server, menu, content);
+               current_item = item_create(current_menu, content);
+       }
+       if (!current_item) {
+               wlr_log(WLR_ERROR, "expect <item label=\"\"> element first");
+               return;
        }
-       assert(current_item);
        if (!strcmp(nodename, "name.action")) {
                current_item->action = strdup(content);
        } else if (!strcmp(nodename, "command.action")) {
@@ -86,78 +124,100 @@ static void fill_item(char *nodename, char *content, struct menu *menu)
 }
 
 static void
-entry(xmlNode *node, char *nodename, char *content, struct menu *menu)
+entry(xmlNode *node, char *nodename, char *content)
 {
-       static bool in_root_menu;
-
-       if (!nodename) {
+       if (!nodename || !content) {
                return;
        }
        string_truncate_at_pattern(nodename, ".openbox_menu");
-       if (!content) {
-               return;
-       }
-       if (!strcmp(nodename, "id.menu")) {
-               in_root_menu = !strcmp(content, "root-menu") ? true : false;
-       }
-
-       /* We only handle the root-menu for the time being */
-       if (!in_root_menu) {
-               return;
-       }
        if (in_item) {
-               fill_item(nodename, content, menu);
+               fill_item(nodename, content);
        }
 }
 
 static void
-process_node(xmlNode *node, struct menu *menu)
+process_node(xmlNode *node)
 {
-       char *content;
        static char buffer[256];
-       char *name;
 
-       content = (char *)node->content;
+       char *content = (char *)node->content;
        if (xmlIsBlankNode(node)) {
                return;
        }
-       name = nodename(node, buffer, sizeof(buffer));
-       entry(node, name, content, menu);
+       char *name = nodename(node, buffer, sizeof(buffer));
+       entry(node, name, content);
 }
 
-static void xml_tree_walk(xmlNode *node, struct menu *menu);
+static void xml_tree_walk(xmlNode *node, struct server *server);
 
 static void
-traverse(xmlNode *n, struct menu *menu)
+traverse(xmlNode *n, struct server *server)
 {
        xmlAttr *attr;
 
-       process_node(n, menu);
+       process_node(n);
        for (attr = n->properties; attr; attr = attr->next) {
-               xml_tree_walk(attr->children, menu);
+               xml_tree_walk(attr->children, server);
+       }
+       xml_tree_walk(n->children, server);
+}
+
+/*
+ * <menu> elements have three different roles:
+ *  * Definition of (sub)menu - has ID, LABEL and CONTENT
+ *  * Menuitem of pipemenu type - has EXECUTE and LABEL
+ *  * Menuitem of submenu type - has ID only
+ */
+static void
+handle_menu_element(xmlNode *n, struct server *server)
+{
+       char *label = (char *)xmlGetProp(n, (const xmlChar *)"label");
+       char *execute = (char *)xmlGetProp(n, (const xmlChar *)"execute");
+       char *id = (char *)xmlGetProp(n, (const xmlChar *)"id");
+
+       if (execute) {
+               wlr_log(WLR_ERROR, "we do not support pipemenus");
+       } else if (label && id) {
+               current_menu = menu_create(server, id, label);
+               /* TODO: deal with nested menu definitions */
+       } else if (id) {
+               struct menu *menu = get_menu_by_id(id);
+               if (menu) {
+                       current_item = item_create(current_menu, menu->label);
+                       current_item->submenu = menu;
+               } else {
+                       wlr_log(WLR_ERROR, "no menu with id '%s'", id);
+               }
        }
-       xml_tree_walk(n->children, menu);
+       zfree(label);
+       zfree(execute);
+       zfree(id);
 }
 
 static void
-xml_tree_walk(xmlNode *node, struct menu *menu)
+xml_tree_walk(xmlNode *node, struct server *server)
 {
        for (xmlNode *n = node; n && n->name; n = n->next) {
                if (!strcasecmp((char *)n->name, "comment")) {
                        continue;
                }
+               if (!strcasecmp((char *)n->name, "menu")) {
+                       handle_menu_element(n, server);
+                       traverse(n, server);
+                       continue;
+               }
                if (!strcasecmp((char *)n->name, "item")) {
                        in_item = true;
-                       traverse(n, menu);
+                       traverse(n, server);
                        in_item = false;
                        continue;
                }
-               traverse(n, menu);
+               traverse(n, server);
        }
 }
 
 static void
-parse_xml(const char *filename, struct menu *menu)
+parse_xml(const char *filename, struct server *server)
 {
        FILE *stream;
        char *line = NULL;
@@ -188,79 +248,148 @@ parse_xml(const char *filename, struct menu *menu)
        xmlDoc *d = xmlParseMemory(b.buf, b.len);
        if (!d) {
                wlr_log(WLR_ERROR, "xmlParseMemory()");
-               exit(EXIT_FAILURE);
+               goto err;
        }
-       xml_tree_walk(xmlDocGetRootElement(d), menu);
+       xml_tree_walk(xmlDocGetRootElement(d), server);
        xmlFreeDoc(d);
        xmlCleanupParser();
+err:
        free(b.buf);
 }
 
-void
-menu_init_rootmenu(struct server *server, struct menu *menu)
+static void
+menu_configure(struct menu *menu, int x, int y)
 {
-       static bool has_run;
+       menu->box.x = x;
+       menu->box.y = y;
 
-       if (!has_run) {
-               LIBXML_TEST_VERSION
-               wl_list_init(&menu->menuitems);
-               server->rootmenu = menu;
-               menu->server = server;
+       int offset = 0;
+       struct menuitem *menuitem;
+       wl_list_for_each_reverse (menuitem, &menu->menuitems, link) {
+               menuitem->box.x = menu->box.x;
+               menuitem->box.y = menu->box.y + offset;
+               offset += menuitem->box.height;
+               if (menuitem->submenu) {
+                       /* TODO: add offset to rc.xml */
+                       menu_configure(menuitem->submenu,
+                               menuitem->box.x + MENUWIDTH + 10,
+                               menuitem->box.y);
+               }
        }
 
-       parse_xml("menu.xml", menu);
+       menu->box.width = MENUWIDTH;
+       menu->box.height = offset;
+}
+
+void
+menu_init_rootmenu(struct server *server)
+{
+       parse_xml("menu.xml", server);
+       server->rootmenu = get_menu_by_id("root-menu");
 
        /* Default menu if no menu.xml found */
-       if (wl_list_empty(&menu->menuitems)) {
-               current_item = menuitem_create(server, menu, "Reconfigure");
+       if (!server->rootmenu) {
+               server->rootmenu = menu_create(server, "root-menu", "");
+       }
+       if (wl_list_empty(&server->rootmenu->menuitems)) {
+               current_item = item_create(server->rootmenu, "Reconfigure");
                current_item->action = strdup("Reconfigure");
-               current_item = menuitem_create(server, menu, "Exit");
+               current_item = item_create(server->rootmenu, "Exit");
                current_item->action = strdup("Exit");
        }
-       menu_move(menu, 100, 100);
+
+       server->rootmenu->visible = true;
+       menu_configure(server->rootmenu, 100, 100);
 }
 
 void
-menu_finish(struct menu *menu)
+menu_finish(void)
 {
-       struct menuitem *menuitem, *next;
-       wl_list_for_each_safe(menuitem, next, &menu->menuitems, link) {
-               zfree(menuitem->action);
-               zfree(menuitem->command);
-               wl_list_remove(&menuitem->link);
-               free(menuitem);
+       struct menu *menu;
+       for (int i = 0; i < nr_menus; ++i) {
+               menu = menus + i;
+               struct menuitem *item, *next;
+               wl_list_for_each_safe(item, next, &menu->menuitems, link) {
+                       zfree(item->action);
+                       zfree(item->command);
+                       wl_list_remove(&item->link);
+                       free(item);
+               }
+       }
+       zfree(menus);
+       alloc_menus = 0;
+       nr_menus = 0;
+}
+
+static void
+close_all_submenus(struct menu *menu)
+{
+       struct menuitem *item;
+       wl_list_for_each (item, &menu->menuitems, link) {
+               if (item->submenu) {
+                       item->submenu->visible = false;
+                       close_all_submenus(item->submenu);
+               }
        }
 }
 
-/*
- * TODO: call this menu_configure and return the size of the menu so that
- * a background color can be rendered
- */
 void
 menu_move(struct menu *menu, int x, int y)
 {
-       menu->box.x = x;
-       menu->box.y = y;
+       assert(menu);
+       close_all_submenus(menu);
+       menu_configure(menu, x, y);
+}
 
-       int offset = 0;
-       struct menuitem *menuitem;
-       wl_list_for_each_reverse (menuitem, &menu->menuitems, link) {
-               menuitem->box.x = menu->box.x;
-               menuitem->box.y = menu->box.y + offset;
-               offset += menuitem->box.height;
+/* TODO: consider renaming function to menu_process_cursor_motion */
+void
+menu_set_selected(struct menu *menu, int x, int y)
+{
+       if (!menu->visible) {
+               return;
        }
 
-       menu->box.width = MENUWIDTH;
-       menu->box.height = offset;
+       struct menuitem *item;
+       wl_list_for_each (item, &menu->menuitems, link) {
+               item->selected = wlr_box_contains_point(&item->box, x, y);
+
+               if (!item->selected) {
+                       if (item->submenu && item->submenu->visible) {
+                               /*
+                                * Handle the case where a submenu is already
+                                * open.
+                                */
+                               item->selected = true;
+                               menu_set_selected(item->submenu, x, y);
+                       }
+                       continue;
+               }
+
+               /* We're now on an item that has mouse-focus */
+               if (item->submenu) {
+                       if (item->submenu->visible) {
+                               /* do nothing - submenu already open */
+                       } else {
+                               /* open submenu */
+                               close_all_submenus(menu);
+                               item->submenu->visible = true;
+                               menu_set_selected(item->submenu, x, y);
+                       }
+               } else {
+                       close_all_submenus(menu);
+               }
+       }
 }
 
-void
-menu_set_selected(struct menu *menu, int x, int y)
+static void
+menu_clear_selection(struct menu *menu)
 {
-       struct menuitem *menuitem;
-       wl_list_for_each (menuitem, &menu->menuitems, link) {
-               menuitem->selected =
-                       wlr_box_contains_point(&menuitem->box, x, y);
+       struct menuitem *item;
+       wl_list_for_each (item, &menu->menuitems, link) {
+               item->selected = false;
+               if (item->submenu) {
+                       menu_clear_selection(item->submenu);
+               }
        }
 }
 
@@ -269,16 +398,20 @@ menu_action_selected(struct server *server, struct menu *menu)
 {
        struct menuitem *menuitem;
        wl_list_for_each (menuitem, &menu->menuitems, link) {
-               if (menuitem->selected) {
+               if (menuitem->selected && !menuitem->submenu) {
                        action(server, menuitem->action, menuitem->command);
                        break;
                }
+               if (menuitem->submenu) {
+                       menu_action_selected(server, menuitem->submenu);
+               }
        }
+       menu_clear_selection(menu);
 }
 
 void
 menu_reconfigure(struct server *server, struct menu *menu)
 {
-       menu_finish(menu);
-       menu_init_rootmenu(server, menu);
+       menu_finish();
+       menu_init_rootmenu(server);
 }
index cad131e69729fe269bc6a226dfe93c592b58b215..e70fd47855f146ba4f8c9861be422f78854188be 100644 (file)
@@ -608,8 +608,11 @@ render_deco(struct view *view, struct output *output,
 }
 
 static void
-render_rootmenu(struct output *output, pixman_region32_t *output_damage)
+render_menu(struct output *output, pixman_region32_t *damage, struct menu *menu)
 {
+       if (!menu->visible) {
+               return;
+       }
        struct server *server = output->server;
        struct theme *theme = server->theme;
        float matrix[9];
@@ -620,12 +623,11 @@ render_rootmenu(struct output *output, pixman_region32_t *output_damage)
                &ox, &oy);
 
        /* background */
-       render_rect(output, output_damage, &server->rootmenu->box,
-               theme->menu_items_bg_color);
+       render_rect(output, damage, &menu->box, theme->menu_items_bg_color);
 
        /* items */
        struct menuitem *menuitem;
-       wl_list_for_each (menuitem, &server->rootmenu->menuitems, link) {
+       wl_list_for_each (menuitem, &menu->menuitems, link) {
                struct wlr_box box = {
                        .x = menuitem->box.x + menuitem->texture.offset_x + ox,
                        .y = menuitem->box.y + menuitem->texture.offset_y + oy,
@@ -636,14 +638,18 @@ render_rootmenu(struct output *output, pixman_region32_t *output_damage)
                wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL,
                        0, output->wlr_output->transform_matrix);
                if (menuitem->selected) {
-                       render_rect(output, output_damage, &menuitem->box,
+                       render_rect(output, damage, &menuitem->box,
                                theme->menu_items_active_bg_color);
-                       render_texture(output->wlr_output, output_damage,
+                       render_texture(output->wlr_output, damage,
                                menuitem->texture.active, NULL, &box, matrix);
                } else {
-                       render_texture(output->wlr_output, output_damage,
+                       render_texture(output->wlr_output, damage,
                                menuitem->texture.inactive, NULL, &box, matrix);
                }
+               /* render submenus */
+               if (menuitem->submenu) {
+                       render_menu(output, damage, menuitem->submenu);
+               }
        }
 }
 
@@ -808,7 +814,7 @@ output_render(struct output *output, pixman_region32_t *damage)
                &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]);
 
        if (output->server->input_mode == LAB_INPUT_STATE_MENU) {
-               render_rootmenu(output, damage);
+               render_menu(output, damage, server->rootmenu);
        }
 
 renderer_end: