Repository: lvgl/lvgl (C)

Issue:
Group navigation with directional events from a keypad input device
### Problem to solve

Directional keypad devices, like the one pictured below, are mapped to directional key event (e.g. LV_KEY_UP).

![Image](https://github.com/user-attachments/assets/2443b976-595a-4a7d-a8ae-673538bce212)

However group navigation (as handled in `indev_keypad_proc`) expects `LV_KEY_NEXT`/`LV_KEY_PREV` events.

### Success criteria

Being able to use a keypad indev to navigate widgets in a group.

### Solution outline

Add a new function for specifying the desired indev behavior:

```c
typedef enum {
    LV_INDEV_NAV_REMAP_HOR = 1 << 0,
    LV_INDEV_NAV_REMAP_HOR_REV = 1 << 1,
    LV_INDEV_NAV_REMAP_VER = 1 << 2,
    LV_INDEV_NAV_REMAP_VER_REV = 1 << 3,
} lv_indev_nav_map;

/**
 * Map directional 
 * @param indev pointer to a keypad input device
 * @param enable enable/disable event remapping
 * @param map mapping bitfield
 */
void lv_indev_nav_map(lv_indev_t * indev, uint8_t map);
```

Example usage:

```c
lv_indev_t * my_indev;

...

// Disable mapping (current behavior)
lv_indev_nav_map(my_indev, 0);

...

// Remap LV_KEY_LEFT/RIGHT to LV_KEY_PREV/NEXT
lv_indev_nav_map(my_indev, LV_INDEV_NAV_REMAP_HOR);

// Remap LV_KEY_LEFT/RIGHT to LV_KEY_NEXT/PREV (inverted direction)
lv_indev_nav_map(my_indev, LV_INDEV_NAV_REMAP_HOR_REV);

// Remap LV_KEY_LEFT/RIGHT to LV_KEY_PREV/NEXT, LV_KEY_UP/DOWN to LV_KEY_PREV/NEXT
lv_indev_nav_map(my_indev, LV_INDEV_NAV_REMAP_HOR | LV_INDEV_NAV_REMAP_VER);
```

### Rabbit holes

* `lv_group_set_editing`'s documentation could be confusing for some users, as it indicates that a group has an editing mode and a navigation mode

### Testing

Will be tested on a private project of mine, not sure how it can be added to LVGL testsuite as I haven't played with that yet.

### Teaching

Document `lv_indev_nav_map` with the above examples.

### Considerations

This issue has been briefly discussed on the LVGL forum: https://forum.lvgl.io/t/create-a-lv-menu-that-responds-to-keypad-input/20081


Discussion: Thank you for opening this tracking issue
I've written an implementation of this proposal (see #8458), I haven't got the chance to test it yet. I edited my original message to reflect some of the changes I did in the PR.



Fix (patch):
diff --git a/docs/src/details/main-modules/indev/keypad.rst b/docs/src/details/main-modules/indev/keypad.rst
index bb8b4d78d0bb..05f6da9ebc8a 100644
--- a/docs/src/details/main-modules/indev/keypad.rst
+++ b/docs/src/details/main-modules/indev/keypad.rst
@@ -87,3 +87,41 @@ The most important special keys in your :cpp:func:`read_cb` function are:
 
 You should translate some of the read keys to these special keys to support navigation
 in a group and interact with selected widgets.
+
+Key remapping
+*************
+
+Some applications require assigning physical buttons to multiple key events depending
+on the context: arrow keys may be used in a screen to navigate across widgets, and
+used to interact with one widget in another screen. Key remapping functionality of
+indev can be used to implement this behavior.
+
+You must first define a remapping callback, then invoke
+:cpp:func:`lv_indev_set_key_remap_cb` to enable it.
+
+.. code-block:: c
+
+    lv_key_t remap_left_right_into_prev_next(lv_indev_t * indev, lv_key_t key)
+    {
+        LV_UNUSED(indev);
+
+        switch (key) {
+        case LV_KEY_LEFT:
+            return LV_KEY_PREV;
+        case LV_KEY_RIGHT:
+            return LV_KEY_NEXT;
+        default:
+            return key;
+        }
+    }
+
+.. code-block:: c
+
+    /* in your setup code */
+    lv_indev_set_key_remap_cb(indev, remap_left_right_into_prev_next);
+
+    /* later, to disable remapping */
+    lv_indev_set_key_remap_cb(indev, NULL);
+
+.. note::
+   The remap callback is invoked only for keypad input devices. It will not remap keys from other input device types.
diff --git a/src/indev/lv_indev.c b/src/indev/lv_indev.c
index a4b16b5a0c43..e16e5f2fc88f 100644
--- a/src/indev/lv_indev.c
+++ b/src/indev/lv_indev.c
@@ -141,6 +141,7 @@ lv_indev_t * lv_indev_create(void)
     indev->gesture_limit        = LV_INDEV_DEF_GESTURE_LIMIT;
     indev->gesture_min_velocity = LV_INDEV_DEF_GESTURE_MIN_VELOCITY;
     indev->rotary_sensitivity  = LV_INDEV_DEF_ROTARY_SENSITIVITY;
+    indev->key_remap_cb         = NULL;
 
 #if LV_USE_GESTURE_RECOGNITION
     lv_indev_gesture_init(indev);
@@ -674,6 +675,16 @@ lv_result_t lv_indev_send_event(lv_indev_t * indev, lv_event_code_t code, void *
     return lv_event_push_and_send(&indev->event_list, code, indev, param);
 }
 
+void lv_indev_set_key_remap_cb(lv_indev_t * indev, lv_indev_key_remap_cb_t remap_cb)
+{
+    if(!indev) {
+        LV_LOG_WARN("Can't remap key on a NULL indev");
+        return;
+    }
+
+    indev->key_remap_cb = remap_cb;
+}
+
 /**********************
  *   STATIC FUNCTIONS
  **********************/
@@ -762,6 +773,11 @@ static void indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data)
         i->keypad.last_state = LV_INDEV_STATE_RELEASED; /*To skip the processing of release*/
     }
 
+    /* Remap key using callback */
+    if(i->key_remap_cb) {
+        data->key = i->key_remap_cb(i, data->key);
+    }
+
     /*Save the last key. *It must be done here else `lv_indev_get_key` will return the last key in events*/
     uint32_t prev_key = i->keypad.last_key;
     i->keypad.last_key = data->key;
diff --git a/src/indev/lv_indev.h b/src/indev/lv_indev.h
index b59b0cd4a48b..2da2a837d89c 100644
--- a/src/indev/lv_indev.h
+++ b/src/indev/lv_indev.h
@@ -77,6 +77,9 @@ typedef struct {
 
 typedef void (*lv_indev_read_cb_t)(lv_indev_t * indev, lv_indev_data_t * data);
 
+/** Indev key remapping callback */
+typedef lv_key_t (*lv_indev_key_remap_cb_t)(lv_indev_t * indev, lv_key_t key);
+
 /**********************
  * GLOBAL PROTOTYPES
  **********************/
@@ -442,6 +445,13 @@ uint32_t lv_indev_remove_event_cb_with_user_data(lv_indev_t * indev, lv_event_cb
  */
 lv_result_t lv_indev_send_event(lv_indev_t * indev, lv_event_code_t code, void * param);
 
+/**
+ * Set key remapping callback (LV_INDEV_TYPE_KEYPAD)
+ * @param indev         pointer to an indev
+ * @param remap_cb      remapping function callback. Use NULL to disable callback.
+ */
+void lv_indev_set_key_remap_cb(lv_indev_t * indev, lv_indev_key_remap_cb_t remap_cb);
+
 /**********************
  *      MACROS
  **********************/
diff --git a/src/indev/lv_indev_private.h b/src/indev/lv_indev_private.h
index 6c8f5c246655..781f6a2bc1e4 100644
--- a/src/indev/lv_indev_private.h
+++ b/src/indev/lv_indev_private.h
@@ -123,6 +123,9 @@ struct _lv_indev_t {
     lv_event_list_t event_list;
     lv_anim_t * scroll_throw_anim;
 
+    /**< Key remapping callback */
+    lv_indev_key_remap_cb_t key_remap_cb;
+
 #if LV_USE_GESTURE_RECOGNITION
     lv_indev_gesture_recognizer_t recognizers[LV_INDEV_GESTURE_CNT];
     lv_indev_gesture_type_t cur_gesture;