src/menu/menu.c: support keyboard driven selection

Fixes: #1058
Requested-by: @stefonarch
This commit is contained in:
Consolatis 2023-08-30 11:20:46 +02:00 committed by Johan Malm
parent a9ab8ebdaf
commit 1703b4d6cc
3 changed files with 198 additions and 16 deletions

View file

@ -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);

View file

@ -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++) {

View file

@ -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)
{