From: Johan Malm Date: Tue, 2 Nov 2021 18:31:19 +0000 (+0000) Subject: menu: support submenus X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=b878db57a7c84ff8daf6a442caca013615aa5bd2;p=proto%2Flabwc.git menu: support submenus Support submenus defined as follows: --- diff --git a/include/menu/menu.h b/include/menu/menu.h index f9280a7c..2a28cd82 100644 --- a/include/menu/menu.h +++ b/include/menu/menu.h @@ -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); diff --git a/src/main.c b/src/main.c index 435010d1..ced17f76 100644 --- a/src/main.c +++ b/src/main.c @@ -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(); diff --git a/src/menu/menu.c b/src/menu/menu.c index d6db5909..83b38524 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -19,21 +19,58 @@ #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 */ 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 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: + * + * + * + * + * + */ +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: - * - * - * - * - * - */ + /* 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 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); +} + +/* + * 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); } diff --git a/src/output.c b/src/output.c index cad131e6..e70fd478 100644 --- a/src/output.c +++ b/src/output.c @@ -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: