From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 30 Aug 2023 09:20:46 +0000 (+0200) Subject: src/menu/menu.c: support keyboard driven selection X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=1703b4d6cc83dc1a7210db87f7fd53dfd89e67b7;p=proto%2Flabwc.git src/menu/menu.c: support keyboard driven selection Fixes: #1058 Requested-by: @stefonarch --- diff --git a/include/menu/menu.h b/include/menu/menu.h index 490bcefb..fd614fe2 100644 --- a/include/menu/menu.h +++ b/include/menu/menu.h @@ -62,6 +62,13 @@ struct menu { struct view *triggered_by_view; /* may be NULL */ }; +/* For keyboard support */ +void menu_item_select_next(struct server *server); +void menu_item_select_previous(struct server *server); +void menu_submenu_enter(struct server *server); +void menu_submenu_leave(struct server *server); +bool menu_call_selected_actions(struct server *server); + void menu_init(struct server *server); void menu_finish(void); diff --git a/src/keyboard.c b/src/keyboard.c index 8f2d321b..7a3190cc 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -6,6 +6,7 @@ #include "idle.h" #include "key-state.h" #include "labwc.h" +#include "menu/menu.h" #include "regions.h" #include "view.h" #include "workspaces.h" @@ -125,6 +126,42 @@ struct keysyms { int nr_syms; }; +static void +handle_menu_keys(struct server *server, struct keysyms *syms) +{ + assert(server->input_mode == LAB_INPUT_STATE_MENU); + + for (int i = 0; i < syms->nr_syms; i++) { + switch (syms->syms[i]) { + case XKB_KEY_Down: + menu_item_select_next(server); + break; + case XKB_KEY_Up: + menu_item_select_previous(server); + break; + case XKB_KEY_Right: + menu_submenu_enter(server); + break; + case XKB_KEY_Left: + menu_submenu_leave(server); + break; + case XKB_KEY_Return: + if (menu_call_selected_actions(server)) { + menu_close_root(server); + cursor_update_focus(server); + } + break; + case XKB_KEY_Escape: + menu_close_root(server); + cursor_update_focus(server); + break; + default: + continue; + } + break; + } +} + static bool handle_compositor_keybindings(struct keyboard *keyboard, struct wlr_keyboard_key_event *event) @@ -212,6 +249,18 @@ handle_compositor_keybindings(struct keyboard *keyboard, } } + if (server->input_mode == LAB_INPUT_STATE_MENU) { + /* + * Usually, release events are already caught via _press_event_was_bound(). + * But to be on the safe side we will simply ignore them here as well. + */ + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + handle_menu_keys(server, &translated); + } + handled = true; + goto out; + } + if (server->osd_state.cycle_view) { if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { for (int i = 0; i < translated.nr_syms; i++) { diff --git a/src/menu/menu.c b/src/menu/menu.c index 4b3b6286..5c7712f1 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -37,6 +37,8 @@ static struct menu *current_menu; static struct menu *menus; static int nr_menus, alloc_menus; +/* TODO: split this whole file into parser.c and actions.c*/ + static struct menu * menu_create(struct server *server, const char *id, const char *label) { @@ -841,22 +843,14 @@ menu_open(struct menu *menu, int x, int y) menu->server->input_mode = LAB_INPUT_STATE_MENU; } -void -menu_process_cursor_motion(struct wlr_scene_node *node) +static void +menu_process_item_selection(struct menuitem *item) { - assert(node && node->data); - struct menuitem *item = node_menuitem_from_node(node); - - /* This function shall only be called for menu nodes */ assert(item); if (!item->selectable) { return; } - if (node == &item->selected.tree->node) { - /* We are on an already selected item */ - return; - } /* We are on an item that has new mouse-focus */ menu_set_selection(item->parent, item); @@ -868,6 +862,8 @@ menu_process_cursor_motion(struct wlr_scene_node *node) if (item->submenu) { /* Sync the triggering view */ item->submenu->triggered_by_view = item->parent->triggered_by_view; + /* Ensure the submenu has its parent set correctly */ + item->submenu->parent = item->parent; /* And open the new submenu tree */ wlr_scene_node_set_enabled( &item->submenu->scene_tree->node, true); @@ -875,14 +871,61 @@ menu_process_cursor_motion(struct wlr_scene_node *node) item->parent->selection.menu = item->submenu; } -bool -menu_call_actions(struct wlr_scene_node *node) +/* Get the deepest submenu with active item selection or the root menu itself */ +static struct menu * +get_selection_leaf(struct server *server) { - assert(node && node->data); - struct menuitem *item = node_menuitem_from_node(node); + struct menu *menu = server->menu_current; + if (!menu) { + return NULL; + } - if (item->submenu) { - /* We received a click on an item that just opens a submenu */ + while (menu->selection.menu) { + if (!menu->selection.menu->selection.item) { + return menu; + } + menu = menu->selection.menu; + } + + return menu; +} + +/* Selects the next or previous sibling of the currently selected item */ +static void +menu_item_select(struct server *server, bool forward) +{ + struct menu *menu = get_selection_leaf(server); + if (!menu) { + return; + } + + struct menuitem *item = NULL; + struct menuitem *selection = menu->selection.item; + struct wl_list *start = selection ? &selection->link : &menu->menuitems; + struct wl_list *current = start; + while (!item || !item->selectable) { + current = forward ? current->next : current->prev; + if (current == start) { + return; + } + if (current == &menu->menuitems) { + /* Allow wrap around */ + item = NULL; + continue; + } + item = wl_container_of(current, item, link); + } + + menu_process_item_selection(item); +} + +static bool +menu_execute_item(struct menuitem *item) +{ + assert(item); + + if (item->submenu || !item->selectable) { + /* We received a click on a separator or item that just opens a submenu */ return false; } @@ -900,6 +943,89 @@ menu_call_actions(struct wlr_scene_node *node) return true; } +/* Keyboard based selection */ +void +menu_item_select_next(struct server *server) +{ + menu_item_select(server, /* forward */ true); +} + +void +menu_item_select_previous(struct server *server) +{ + menu_item_select(server, /* forward */ false); +} + +bool +menu_call_selected_actions(struct server *server) +{ + struct menu *menu = get_selection_leaf(server); + if (!menu || !menu->selection.item) { + return false; + } + + return menu_execute_item(menu->selection.item); +} + +/* Selects the first item on the submenu attached to the current selection */ +void +menu_submenu_enter(struct server *server) +{ + struct menu *menu = get_selection_leaf(server); + if (!menu || !menu->selection.menu) { + return; + } + + struct wl_list *start = &menu->selection.menu->menuitems; + struct wl_list *current = start; + struct menuitem *item = NULL; + while (!item || !item->selectable) { + current = current->next; + if (current == start) { + return; + } + item = wl_container_of(current, item, link); + } + + menu_process_item_selection(item); +} + +/* Re-selects the selected item on the parent menu of the current selection */ +void +menu_submenu_leave(struct server *server) +{ + struct menu *menu = get_selection_leaf(server); + if (!menu || !menu->parent || !menu->parent->selection.item) { + return; + } + + menu_process_item_selection(menu->parent->selection.item); +} + +/* Mouse based selection */ +void +menu_process_cursor_motion(struct wlr_scene_node *node) +{ + assert(node && node->data); + struct menuitem *item = node_menuitem_from_node(node); + + if (item->selectable && node == &item->selected.tree->node) { + /* We are on an already selected item */ + return; + } + + menu_process_item_selection(item); +} + +bool +menu_call_actions(struct wlr_scene_node *node) +{ + assert(node && node->data); + struct menuitem *item = node_menuitem_from_node(node); + + return menu_execute_item(item); +} + void menu_close_root(struct server *server) {