mirror of
https://github.com/labwc/labwc.git
synced 2025-10-29 05:40:24 -04:00
src/menu/menu.c: support keyboard driven selection
Fixes: #1058 Requested-by: @stefonarch
This commit is contained in:
parent
a9ab8ebdaf
commit
1703b4d6cc
3 changed files with 198 additions and 16 deletions
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
158
src/menu/menu.c
158
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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue