keyboard: point-mode virtual keyboard for touchscreens

rockbox-gerrit-noreply--- via rockbox-cvs <[email protected]>
Newsgroups gmane.comp.systems.archos.rockbox.cvs
Message-ID <[email protected]>
commit ec59f570ddc04349b7df500d364c0b8a36739c27
Author: Michael McAllister <[email protected]>
Date:   Sat Jul 25 23:47:58 2026 +0100

    keyboard: point-mode virtual keyboard for touchscreens
    
    On large touchscreens the software keyboard can now run in "point mode". Keys are laid out finger-sized and tapped directly instead of being navigated with a cursor. Targets opt in with HAVE_KBD_POINT_MODE.
    
    Key size is derived from LCD_DPI so a key is about 5mm across whatever the panel density, falling back to a fixed size where DPI is unknown.
    
    A phone-style default layout in the UI font, across three flip pages of eight lines. Page two is page one shifted (i.e caps). Page three completes Latin-1 and adds additional accented letters common to the other European locales.
    
    Space is drawn as an icon, since the UI font has no glyph for it.
    
    Change-Id: Ibe2e305b3c22b1f9152358cf3864b9445b67b463
    Co-Author: Claude Opus 4.8

diff --git a/apps/features.txt b/apps/features.txt
index 5453ec86bc..0c4719ca11 100644
--- a/apps/features.txt
+++ b/apps/features.txt
@@ -82,6 +82,10 @@ lcd_sleep
 morse_input
 #endif
 
+#if defined(HAVE_KBD_POINT_MODE)
+kbd_point_mode
+#endif
+
 #if defined(HAVE_PITCHCONTROL)
 pitchscreen
 #endif
diff --git a/apps/recorder/keyboard.c b/apps/recorder/keyboard.c
index ee47470bb1..cb00fef74f 100644
--- a/apps/recorder/keyboard.c
+++ b/apps/recorder/keyboard.c
@@ -47,8 +47,23 @@
 #define DEFAULT_MARGIN 6
 #define KBD_BUF_SIZE 500
 
+/* Sentinel picker cell, drawn as an icon (the UI font has no glyph for it):
+ * KBD_SPACE inserts a space. */
+#define KBD_SPACE 0x2423
+
 #ifdef HAVE_TOUCHSCREEN
+/* Increase grid size so it responds better to taps */
+#ifdef HAVE_KBD_POINT_MODE
+#ifndef MIN_GRID_SIZE
+#if defined(LCD_DPI) && (LCD_DPI > 0)
+#define MIN_GRID_SIZE   ((LCD_DPI * 50 + 127) / 254)
+#else
+#define MIN_GRID_SIZE   44
+#endif
+#endif
+#else
 #define MIN_GRID_SIZE   16
+#endif
 #define GRID_SIZE(s, x)   \
         ((s) == SCREEN_MAIN && MIN_GRID_SIZE > (x) ? MIN_GRID_SIZE: (x))
 #endif
@@ -389,6 +404,29 @@ static ucschar_t get_kbd_ch(struct keyboard_parameters *pm, int x, int y)
     k = k * pm->max_chars + x;
     return (*pbuf != 0xFEFF && k < *pbuf)? pbuf[k+1]: ' ';
 }
+
+#ifdef HAVE_TOUCHSCREEN
+/* Number of real characters in the grid row drawn at picker row y. */
+static int get_kbd_row_len(struct keyboard_parameters *pm, int y)
+{
+    unsigned int n, i, k = pm->page*pm->lines + y;
+    ucschar_t *pbuf;
+    for (pbuf = pm->kbd_buf_ptr; (i = *pbuf) != 0xFEFF; pbuf += i + 1)
+    {
+        n = i ? (i + pm->max_chars - 1) / pm->max_chars : 1;
+        if (k < n)
+        {
+            int len = (int)i - (int)(k * pm->max_chars);
+            if (len > (int)pm->max_chars)
+                len = pm->max_chars;
+            return len < 0 ? 0 : len;
+        }
+        k -= n;
+    }
+    return 0;
+}
+#endif /* HAVE_TOUCHSCREEN */
+
 static void kbd_calc_pm_params(struct keyboard_parameters *pm,
                             struct screen *sc, struct edit_state *state);
 static void kbd_calc_vp_params(struct keyboard_parameters *pm,
@@ -430,10 +468,14 @@ int kbd_input(char* text, int buflen, ucschar_t *kbd)
     }
 
 #ifdef HAVE_TOUCHSCREEN
+    enum touchscreen_mode old_mode = touchscreen_get_mode();
+#ifdef HAVE_KBD_POINT_MODE
+    touchscreen_set_mode(TOUCHSCREEN_POINT);
+#else
     /* keyboard is unusuable in pointing mode so force 3x3 for now.
      * TODO - fix properly by using a bigger font and changing the layout */
-    enum touchscreen_mode old_mode = touchscreen_get_mode();
     touchscreen_set_mode(TOUCHSCREEN_BUTTON);
+#endif
 #endif
     /* initialize state */
     state.text = text;
@@ -458,6 +500,48 @@ int kbd_input(char* text, int buflen, ucschar_t *kbd)
             const unsigned char *p;
             int len = 0;
 
+#ifdef HAVE_KBD_POINT_MODE
+            if (touchscreen_get_mode() == TOUCHSCREEN_POINT)
+            {
+#if (LCD_WIDTH >= 480) && (LCD_HEIGHT >= 640)
+            /* Three pages of eight lines. Page two is page one shifted, cell
+             * for cell; page three completes Latin-1 and adds the accented
+             * letters common to the other European locales. The space bar
+             * occupies the same cells on every page. Ten columns need the
+             * width, and all eight lines have to be visible at once. */
+                p = "1234567890\n"
+                    "qwertyuiop\n"
+                    "asdfghjkl;\n"
+                    "zxcvbnm,./\n"
+                    "àáâäåçèéêë\n"
+                    "ìíîïñòóôöù\n"
+                    "úûüÿ'æøðþý\n"
+                    "`-=␣␣␣␣[]\\\n"
+                    "!@#$%^&*()\n"
+                    "QWERTYUIOP\n"
+                    "ASDFGHJKL:\n"
+                    "ZXCVBNM<>?\n"
+                    "ÀÁÂÄÅÇÈÉÊË\n"
+                    "ÌÍÎÏÑÒÓÔÖÙ\n"
+                    "ÚÛÜŸ\"ÆØÐÞÝ\n"
+                    "~_+␣␣␣␣{}|\n"
+                    "¡¢£¤¥¦§¨©ª\n"
+                    "«¬®¯°±²³´µ\n"
+                    "¶·¸¹º»¼½¾¿\n"
+                    "×÷ßÃãÕõ€Œœ\n"
+                    "ĐđČčĎďĚěŇň\n"
+                    "ŘřŠšŤťŮůŽž\n"
+                    "ĄąĆćĘꣳŃń\n"
+                    "ŚśŹ␣␣␣␣źŻż";
+                pm->default_lines = 8;
+                pm->max_line_len = 10;
+#else
+# error "HAVE_KBD_POINT_MODE: no point-mode layout for this screen size"
+#endif
+            }
+            else
+#endif
+            {
 #if LCD_WIDTH >= 160 && LCD_HEIGHT >= 96
             struct screen *sc = &screens[l];
 
@@ -528,6 +612,7 @@ int kbd_input(char* text, int buflen, ucschar_t *kbd)
                 pm->max_line_len = 18;
             }
 #endif
+            }
             pbuf = pm->kbd_buf;
             while (*p)
             {
@@ -849,6 +934,11 @@ static void kbd_calc_pm_params(struct keyboard_parameters *pm,
                                 (touchscreen_get_mode() == TOUCHSCREEN_POINT));
 #endif
 
+#ifdef HAVE_TOUCHSCREEN
+    if (touchscreen_get_mode() == TOUCHSCREEN_POINT)
+        pm->curfont = sc->getuifont();
+    else
+#endif
     pm->curfont = pm->default_lines ? FONT_SYSFIXED : sc->getuifont();
     font = font_get(pm->curfont);
     pm->font_h = font->height;
@@ -907,6 +997,11 @@ static void kbd_calc_vp_params(struct keyboard_parameters *pm,
     icon_w = get_icon_width(sc->screen_type);
 
     sc_w = vp->width; /**sc->getwidth();**/
+#ifdef HAVE_TOUCHSCREEN
+    if (touchscreen_get_mode() == TOUCHSCREEN_POINT)
+        pm->font_w = sc_w / pm->max_line_len;   /* fill width, allow >10 cols */
+    else
+#endif
     if (pm->font_w < sc_w / pm->max_line_len)
         pm->font_w = sc_w / pm->max_line_len;
     pm->max_chars = sc_w / pm->font_w;
@@ -1075,22 +1170,47 @@ static void kbd_draw_picker(struct keyboard_parameters *pm,
         {
             for (i = 0; i < pm->max_chars; i++)
             {
+                int cx = i*pm->font_w, cy = j*pm->font_h;
                 ch = get_kbd_ch(pm, i, j);
+                if (ch == KBD_SPACE)
+                {
+                    /* space bar: a bar with short upturned ends. Cells join;
+                     * the run's first/last cell adds the vertical end tick. */
+                    int th = MAX(2, pm->font_h/14);
+                    int by = cy + (pm->font_h*2)/3;
+                    int tk = pm->font_h/6;
+                    int rx = cx + pm->font_w - th;
+                    sc->fillrect(cx, by, pm->font_w, th);
+                    if (i == 0 || get_kbd_ch(pm, i-1, j) != KBD_SPACE)
+                        sc->fillrect(cx, by - tk, th, tk + th);
+                    if (get_kbd_ch(pm, i+1, j) != KBD_SPACE)
+                        sc->fillrect(rx, by - tk, th, tk + th);
+                    continue;
+                }
                 utf8 = utf8encode(ch, outline);
                 *utf8 = 0;
 
                 sc->getstringsize(outline, &w, &h);
-                sc->putsxy(i*pm->font_w + (pm->font_w-w) / 2,
-                           j*pm->font_h + (pm->font_h-h) / 2, outline);
+                sc->putsxy(cx + (pm->font_w-w) / 2,
+                           cy + (pm->font_h-h) / 2, outline);
             }
         }
 
         if (!pm->line_edit)
         {
-            /* highlight the key that has focus */
+            /* highlight the focused key; the space bar spans several cells and
+             * highlights as one */
+            int hx = pm->x, hw = 1;
+            if (get_kbd_ch(pm, hx, pm->y) == KBD_SPACE)
+            {
+                while (hx > 0 && get_kbd_ch(pm, hx - 1, pm->y) == KBD_SPACE)
+                    hx--;
+                while (get_kbd_ch(pm, hx + hw, pm->y) == KBD_SPACE)
+                    hw++;
+            }
             sc->set_drawmode(DRMODE_COMPLEMENT);
-            sc->fillrect(pm->font_w*pm->x, pm->font_h*pm->y,
-                         pm->font_w, pm->font_h);
+            sc->fillrect(pm->font_w*hx, pm->font_h*pm->y,
+                         pm->font_w*hw, pm->font_h);
             sc->set_drawmode(DRMODE_SOLID);
         }
     }
@@ -1337,8 +1457,8 @@ static void kbd_draw_buttons(struct keyboard_parameters *pm, struct screen *sc)
     /* draw buttons */
     if (pm->pages > 1)
     {
-        /* button to flip page. */
-        vp.y = pm->lines*pm->font_h;
+        /* button to flip page */
+        vp.y = pm->kbd_viewports[eKBD_VP_PICKER].y + pm->lines*pm->font_h;
         sc->hline(0, sc_w - 1, 0);
         sc->putsxy(0, text_y, ">");
     }
@@ -1378,22 +1498,32 @@ static int keyboard_touchscreen(struct keyboard_parameters *pm,
 #endif
     if (x < 0 || y < 0)
         return ACTION_NONE;
-    if (y < pm->lines*pm->font_h)
+    /* The picker grid is drawn in its own viewport, below the edit line, so
+     * translate the absolute touch into that viewport (see kbd_draw_picker). */
+    struct viewport *picker_vp = &pm->kbd_viewports[eKBD_VP_PICKER];
+    int picker_x = x - picker_vp->x;
+    int picker_y = y - picker_vp->y;
+    if (picker_y >= 0 && picker_y < pm->lines*pm->font_h)
     {
-        if (x/pm->font_w < pm->max_chars)
+        int row = picker_y/pm->font_h;
+        /* ignore taps past the end of a short row */
+        if (picker_x >= 0 && picker_x/pm->font_w < get_kbd_row_len(pm, row))
         {
             /* picker area */
             state->changed = CHANGED_PICKER;
-            pm->x = x/pm->font_w;
-            pm->y = y/pm->font_h;
+            pm->x = picker_x/pm->font_w;
+            pm->y = row;
             pm->line_edit = false;
             if (button == BUTTON_REL)
                 return ACTION_KBD_SELECT;
         }
     }
-    else if (y < pm->main_y - pm->keyboard_margin)
+    else if (pm->pages > 1 && picker_y >= pm->lines*pm->font_h &&
+             picker_y < pm->lines*pm->font_h +
+                        GRID_SIZE(sc->screen_type, sc->getcharheight()))
     {
-        /* button to flip page */
+        /* flip-page button, drawn just below the grid; test the same band in
+         * picker-relative coords so ">" is tappable. */
         if (button == BUTTON_REL)
             return ACTION_KBD_PAGE_FLIP;
     }
@@ -1432,6 +1562,9 @@ static void kbd_insert_selected(struct keyboard_parameters *pm,
     /* find input char */
     ucschar_t ch = get_kbd_ch(pm, pm->x, pm->y);
 
+    if (ch == KBD_SPACE)
+        ch = ' ';
+
     /* check for hangul input */
     if (ch >= 0x3131 && ch <= 0x3163)
     {
@@ -1552,6 +1685,18 @@ static void kbd_move_picker_horizontal(struct keyboard_parameters *pm,
     state->changed = CHANGED_PICKER;
 
     pm->x += dir;
+    /* The space bar spans several cells but highlights and inserts as one key,
+     * so step over the whole run instead of stopping inside it, which looks
+     * like the cursor is stuck. */
+    if (pm->x >= 0 && pm->x < pm->max_chars &&
+        get_kbd_ch(pm, pm->x, pm->y) == KBD_SPACE &&
+        get_kbd_ch(pm, pm->x - dir, pm->y) == KBD_SPACE)
+    {
+        while (pm->x >= 0 && pm->x < pm->max_chars &&
+               get_kbd_ch(pm, pm->x, pm->y) == KBD_SPACE)
+            pm->x += dir;
+    }
+
     if (pm->x < 0)
     {
         if (!global_settings.list_wraparound && pm->page == 0)
diff --git a/firmware/export/config/hibyr1.h b/firmware/export/config/hibyr1.h
index bb20b0b1a2..6a24de6e2e 100644
--- a/firmware/export/config/hibyr1.h
+++ b/firmware/export/config/hibyr1.h
@@ -37,6 +37,8 @@
 #ifndef BOOTLOADER
 #define HAVE_BUTTON_DATA
 #define HAVE_TOUCHSCREEN
+/* Finger-sized keys, tapped directly, instead of a navigated cursor */
+#define HAVE_KBD_POINT_MODE
 #endif
 
 #ifndef CONFIG_BACKLIGHT_FADING
diff --git a/firmware/export/config/hibyr3proii.h b/firmware/export/config/hibyr3proii.h
index 92d338aee2..b4e2baa6dd 100644
--- a/firmware/export/config/hibyr3proii.h
+++ b/firmware/export/config/hibyr3proii.h
@@ -37,6 +37,8 @@
 #ifndef BOOTLOADER
 #define HAVE_BUTTON_DATA
 #define HAVE_TOUCHSCREEN
+/* Finger-sized keys, tapped directly, instead of a navigated cursor */
+#define HAVE_KBD_POINT_MODE
 #endif
 
 #ifndef CONFIG_BACKLIGHT_FADING
diff --git a/manual/rockbox_interface/browsing_and_playing.tex b/manual/rockbox_interface/browsing_and_playing.tex
index ddf632d379..4ea4d65000 100644
--- a/manual/rockbox_interface/browsing_and_playing.tex
+++ b/manual/rockbox_interface/browsing_and_playing.tex
@@ -211,6 +211,16 @@ The virtual keyboard can be easily changed by making a text file
 with the required layout. More information on how to achieve this can be found
 on the Rockbox website at \wikilink{LoadableKeyboardLayouts}.
 
+\opt{kbd_point_mode}{
+  On this player the keyboard is laid out for direct touch where the keys are sized
+  for a fingertip and are tapped individually, rather than being selected
+  with the 3\,\texttimes\,3 grid used on other touchscreen targets. By default the
+  characters are spread over three pages with lower case and accented
+  letters, their upper-case forms in the same positions, and a third page
+  of punctuation and additional European characters. Tap
+  \textbf{\textgreater} below the keys to change page.
+}
+
 \opt{morse_input}{
   Also you can switch to Morse code input mode by changing the
   \setting{Use Morse Code Input} setting%
diff --git a/manual/rockbox_interface/images/ss-virtual-keyboard-480x720x16.png b/manual/rockbox_interface/images/ss-virtual-keyboard-480x720x16.png
new file mode 100644
index 0000000000..9dec07971a
Binary files /dev/null and b/manual/rockbox_interface/images/ss-virtual-keyboard-480x720x16.png differ
diff --git a/manual/rockbox_interface/images/ss-virtual-keyboard-480x800x16.png b/manual/rockbox_interface/images/ss-virtual-keyboard-480x800x16.png
new file mode 100644
index 0000000000..02dd6f710f
Binary files /dev/null and b/manual/rockbox_interface/images/ss-virtual-keyboard-480x800x16.png differ
-- 
rockbox-cvs mailing list
[email protected]
https://lists.haxx.se/mailman/listinfo/rockbox-cvs
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.