HP Forums
04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - Printable Version

+- HP Forums (https://www.hpmuseum.org/forum)
+-- Forum: HP Software Libraries (/forum-10.html)
+--- Forum: HP Prime Software Library (/forum-15.html)
+--- Thread: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] (/thread-8262.html)

Pages: 1 2


RE: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - Mosrod - 08-21-2019 09:17 PM

The program works perfectly on my updated Prime G2,
Remember that you have to enter user mode - Shift-Help (2 times to lock it) in order to use the program.


RE: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - cesarep - 08-06-2020 02:39 AM

It's working fine on the last firmware 20200116, mine is a rev C
and it is just great, thank you so much!!


RE: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - Stevetuc - 08-06-2020 08:17 PM

(08-21-2019 09:17 PM)Mosrod Wrote:  The program works perfectly on my updated Prime G2,
Remember that you have to enter user mode - Shift-Help (2 times to lock it) in order to use the program.

Which program version..patched or OP?


RE: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - techlover - 08-14-2020 07:03 PM

Hi everyone,

I know this thread is already a bit older but thank you for the update and all your information. For me, everything works perfectly well.

Best wishes!


RE: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - insomagent - 10-28-2020 10:02 PM

I made a small update to support changing between light and dark mode.


RE: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - mushman - 01-04-2022 02:23 AM

Here's a patched version with some changes:
- Textbox follows selected color theme as well as light/dark theme.
- Removed box with layout info and moved shift/alpha icons to the right (more screen is visible).
- Toggle keyboard with [On] button instead of Alpha+Dot.

Cheers!


RE: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - IHarwell - 01-04-2022 08:32 PM

(01-04-2022 02:23 AM)mushman Wrote:  Here's a patched version with some changes:
- Textbox follows selected color theme as well as light/dark theme.
- Removed box with layout info and moved shift/alpha icons to the right (more screen is visible).
- Toggle keyboard with [On] button instead of Alpha+Dot.

Cheers!

Thanks for the update!

I'm relatively new here, so please forgive me if this isn't where this should go.

I'm getting an error on line 205. Did you mean to use the "time" system variable, or the "ticks" function?

I also noticed a few things that could use improvement (IMO, others' preferences may vary):
  1. Perhaps draw icons on the title bar like the Prime does when you press the appropriate button?
  2. Detect whether the current font size rather than making everything large enough to handle the largest font. (See Easy Sto II code for an example of this that works pretty well. It took some time to get it running reliably, so I wouldn't recommend writing it from scratch unless you know of a better way to do it.)
  3. Use the "Shift" key to move to capital letters instead of "Alpha" (How the Prime does it natively)
  4. Perhaps add a "Backspace" and/or "Enter" button on the keyboard?
  5. If the user holds down either the "Shift" or "Alpha" keys, exit the mode when they're released and don't exit the mode while it's "Del" key with the "Shift" key down, which should delete the next character and leave the Shift mode on.
  6. Perhaps add an option for it to remember the last mode it was in between calls?
  7. Add pass-through support for the Apps, Home, Cas, Symb, Plot, Num, Help, View, and Menu buttons. Most built-in features exit whatever mode they're in and execute those buttons directly when they're pressed.
  8. Take the lower commands on the screen on entry and redraw them above the keyboard when running it. This would only apply to the Home and CAS views, I think.
  9. Move to Python for much faster drawing routines? It can be done here since it looks like they aren't trying to interact with the command line directly or read screen pixels from within the input loop. The Python methods for reading key states is quite efficient, but would require a poll loop rather than reading the key buffer. It would introduce a roughly fixed 15-20 ms latency on entry, but would get the execution loop to run significantly faster if my testing is any indication.
  10. Change the exit routine to leave when the "On" key is released rather than when it's pressed, as it currently exits, the calculator sees that "On" is pressed, and immediately runs it again.


I can take a look at making those changes if any of them sound good to other people. Most of them are really simple to do from what I've seen of the code. Just let me know the numbers that sound worthwhile? I'd prefer to avoid making unwanted changes if I can avoid it.

** EDIT: I just checked a few things out, and it looks like the touchscreen reliably registers taps that are just outside the screen area. If people think it's a reasonable thing to do, it should be feasible to leave the current layout as-is, and just add the backspace button to one of the half-keys on the right edge of the keyboard.


RE: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - IHarwell - 01-05-2022 04:53 AM

(01-04-2022 08:32 PM)IHarwell Wrote:  
  1. Perhaps draw icons on the title bar like the Prime does when you press the appropriate button?
  2. Detect whether the current font size rather than making everything large enough to handle the largest font. (See Easy Sto II code for an example of this that works pretty well. It took some time to get it running reliably, so I wouldn't recommend writing it from scratch unless you know of a better way to do it.)
  3. Use the "Shift" key to move to capital letters instead of "Alpha" (How the Prime does it natively)
  4. Perhaps add a "Backspace" and/or "Enter" button on the keyboard?
  5. If the user holds down either the "Shift" or "Alpha" keys, exit the mode when they're released and don't exit the mode while it's "Del" key with the "Shift" key down, which should delete the next character and leave the Shift mode on.
  6. Perhaps add an option for it to remember the last mode it was in between calls?
  7. Add pass-through support for the Apps, Home, Cas, Symb, Plot, Num, Help, View, and Menu buttons. Most built-in features exit whatever mode they're in and execute those buttons directly when they're pressed.
  8. Take the lower commands on the screen on entry and redraw them above the keyboard when running it. This would only apply to the Home and CAS views, I think.
  9. Move to Python for much faster drawing routines? It can be done here since it looks like they aren't trying to interact with the command line directly or read screen pixels from within the input loop. The Python methods for reading key states is quite efficient, but would require a poll loop rather than reading the key buffer. It would introduce a roughly fixed 15-20 ms latency on entry, but would get the execution loop to run significantly faster if my testing is any indication.
  10. Change the exit routine to leave when the "On" key is released rather than when it's pressed, as it currently exits, the calculator sees that "On" is pressed, and immediately runs it again.

Okay. Here's a minor update. It addresses the error in the previous version as well as incorporating some of the easier items on the list.

It addresses #10 using a flag to indicate if the program is interrupted. Turns out hitting "On" just exits the program immediately.

It addresses #5, as that just made sense to do.

#2 was actually a bug in the previous version. It had the wrong value for small fonts, so it wasn't detecting those correctly.

#6 was already a thing and I just missed it. XD

I'll see if I can work in #7 tomorrow. The others depend heavily on preference or are major overhauls.


RE: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - IHarwell - 01-05-2022 08:28 PM

Okay, I was working on number 7 and ran across the feature that's built into the "View" key.

It's pretty handy, and it's good enough it's worth keeping instead of using passthrough. I added three more options to the list to further improve it. Now, you can go directly to the precise keyboard you want rather than having to hit the alpha key once you've arrived at one.

Two other small changes:
- The keys now scale with the size of the font you have the calculator set to.
- There's now a mouse offset property that you can use to preprocess mouse input if you're getting the wrong thing to show up when you think you're pressing the right key. It's an issue mainly at the small font size, where your finger obstructs the buttons.

Lastly, I forgot to mention another change I made in the previous version, so I'll mention it here. I reduced the length of the "wait" call in the flashing routine and increased the number of iterations between flashes. This was mainly to improve responsiveness, but I'm not totally sure if it made a difference.


RE: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - IHarwell - 01-07-2022 03:51 AM

Okay. This will be my final update for a while.

In this one, you're able to type on screen MUCH faster than before. I added a mouse buffer so it can now handle two simultaneous taps. I suspect that typing speed was limited in previous versions because it would ignore all additional taps until you let up with the first one. People were probably hitting the second letter before releasing the first.

I also threw in a lookup table for the buttons, so the hardware button section is much more concise now. Perhaps it's not as easy to read, but it did make it more responsive.

I also set it up to change the cursor color in dark mode so you can actually see it now.

There was a bug where it wouldn't be able to determine the current font size if you had the calculator set to almost any number format other than "normal". That's fixed now, too.

Unfortunately, the touch screen code is much more involved, so the file size actually went up a bit. My apologies for the somewhat excessive nested "if" statements in this one. There's probably a more graceful method, but I wasn't seeing it at the time I wrote this one.

Here's the code for anyone who wants to go through it:
Code:

#pragma mode( separator(.,;) integer(h32) )

// ===========================================================================​========================
// Author:      F. Freire
// Thanks to:    primer(Author of the vkb)
// Patched by:    mushman
//
// Name:        Virtual Keys
// Description:     Virtual Keys is a Virtual Keyboard to make typing on HP Prime faster
// Version:       1.3c
// Create date:     04 July 2017
// Firmware Target:   10637 -> 29/08/2016
//
// Virtual_Keys Thread: http://www.hpmuseum.org/forum/thread-8262.html
// vkb Thread:          http://www.hpmuseum.org/forum/thread-6948.html
//
// Assigned to User Key -> [On]
// ===========================================================================​========================

drawkb(); drawtxt(); chkkey(); addchar(); setup();
movecurs(); getmouse(); dropchar(); delchar(); check_display();
clearchar(); flashkey(); kbd_type_x(); kbd_type_y();
drawatpos(); text_width(); text_height(); txtwcurs(); redrawtxt();

LOCAL dirty_shutdown;
LOCAL dirt;
LOCAL t; // Start y keyboard position
LOCAL stay:=0; // chkkey Behaviour
LOCAL txt; // Screen Text
LOCAL curs; // Cursor position
LOCAL qoffs:={64,96}; // Qwerty offset (64 upper/96 lower)
LOCAL goffs:={-32,0}; // Greek Offset (913 upper/945 lower)
LOCAL soffs:={0,0}; // Symbol Offset - Not applied
LOCAL alphaOn:=0; // Alpha mode
LOCAL shiftOn:=0; // Shift mode
LOCAL flash_time:=50;
LOCAL flashed:=0; // Key Flash
LOCAL r:=1; // Release key
LOCAL vk_height; // Text box height
LOCAL st; // Animation STEP (0-4)
LOCAL posc; // Cursor position
LOCAL btnsz; // buttonsize.
LOCAL shift_down_mode;
LOCAL alpha_down_mode;
LOCAL shift_held_mode;
LOCAL alpha_held_mode;
LOCAL alpha_layout_keys  := {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26};
LOCAL qwerty_layout_keys := {17,23,5,18,20,25,21,9,15,16,1,19,4,6,7,8,10,11,12,26,24,3,22,2,14,13};
LOCAL greek_layout_keys  := {945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,96​3,964,965,966,967,968,969,1237};
LOCAL symb_layout_keys   := {64,35,36,38,47,124,33,63,45,95,8734,57347,8804,60,62,8805,-10,8800,177,9654,8594,8737,-16,-17,65285,8710};
LOCAL symb_layout_keys_alphaOn := {8320,8321,8322,8323,8324,8325,8326,8327,8328,8329,57348,178,179,-11,-12,-13,-14,-15,94,176,8242,8243,46,58,170,8706};
LOCAL bypass_chars := {"== ","AND  ","OR  ","EQ()","NOT  ","XOR  ","''",""""""};
LOCAL bypass_chars_kbd := {"==","AND","OR","EQ","NOT","XOR","''",""""""};
LOCAL math_hold;
LOCAL aloffs; // Active Layout Offset Holder
LOCAL offset; // Actual Offset number
LOCAL active_layout_keys;
LOCAL active_layout := 1; // 1- Qwerty/ 2- Greek / 3- Symb
LOCAL aA_key, aA_x; // Posição no teclado do aA. / Posição x do aA.
LOCAL Sym_key, Sym_x; // Posição no teclado do Sym. / Posição x do Sym.
LOCAL kbd_x,kbd_y,kbd_size; // #Colunas do teclado. / #Linhas do teclado. / #Teclas.
LOCAL selected_layout_text:={"Qwerty","Greek","Symbolic","⇧Qwerty","⇧Greek","⇧Symbolic"};
LOCAL ht:={10,12,16,20,22,24,26}; // text_height() Table
// Physical Key Codes - Reference
LOCAL K_APPS=0, K_SYMB=1, K_ARROWUP=2, K_HELP=3, K_ESC=4, K_HOME=5;
LOCAL K_PLOT=6, K_ARROWLEFT=7, K_ARROWRIGHT=8, K_VIEW=9, K_CAS=10;
LOCAL K_NUM=11, K_ARROWDOWN=12, K_MENU=13;
LOCAL K_A=14, K_B=15, K_C=16, K_D=17, K_E=18, K_DEL=19;
LOCAL K_F=20, K_G=21, K_H=22, K_I=23, K_J=24, K_K=25;
LOCAL K_L=26, K_M=27, K_N=28, K_O=29, K_ENTER=30;
LOCAL K_P=31, K_Q=32, K_R=33, K_S=34, K_T=35;
LOCAL K_ALPHA=36, K_U=37, K_V=38, K_W=39, K_X=40;
LOCAL K_SHIFT=41, K_Y=42, K_Z=43, K_HASH=44, K_DOUBLEDOT=45;
LOCAL K_OFF=46, K_NOTES=47, K_EQUAL=48, K_UNDERSCORE=49, K_ANS=50;
CONST specials:={30,36,41,46};
CONST shift_specials:={34,36};

CONST shift_text:={" NTHROOT ","ASIN()","ACOS()","ATAN()","e^()","ALOG()","√","ABS()","''","EVAL()","","▶","7","{}","","","","4","[]","","∡","","1","","π",":","","""""","=","_","Ans"};
CONST norm_text:={"^","SIN()","COS()","TAN()","LN()","LOG()","^2","−","()",",","","ᴇ","7","8","9","/","","4","5","6","*","","1","2","3","-","","0","."," ","+"};


// Colors
LOCAL c_gray:=#AAAAAAh, c_white:=#FFFFFFh, c_purple:=#C8BFE7h, c_blue:=#3399FFh, c_black:=#000000h, c_dgray:=#AAAAAAh, c_lgray:=#AAAAAAh;
LOCAL c_darkblue:=#0066FFh, c_orange:=#FF6600h;
LOCAL c_cursor;
LOCAL color_textfieldbg:=Theme(1);
LOCAL theme:=Theme;

LOCAL mouseLoc1:={};
LOCAL mouseLoc2:={};

LOCAL mouseBuffer:={};

LOCAL buttonSize:={32,22};

CONST theme_colors:={{#FEABA0h,#410808h},{#FED897h,#412C08h},{#FBED8Bh,#454521h},​{#CEF6A6h,#0A3B08h},{#ADCCFFh,#1D394Bh},{#EDC9FDh,#32134Dh}};

LOCAL theme_checked:=0;
// Checks and Changes values for the current Keyboard Layout
check_layout()
BEGIN
    IF NOT theme_checked THEN
        theme_checked:=1;
        color_textfieldbg:=theme_colors(Theme(2),Theme(1));
        c_cursor:=c_white * BITSR(Theme(1),1);
        c_gray:= BITXOR(c_gray,c_cursor);
    END;

    CASE
        IF active_layout == 1 THEN //Qwerty
            active_layout_keys:=qwerty_layout_keys;
            aloffs:=qoffs;
            btnsz:= 32;
            aA_key:= 21; aA_x:= 0.5;
            kbd_x:= 10; kbd_y:= 4; kbd_size:= 40;
            Sym_key:=29; Sym_x:=8.5;
        END;
        IF active_layout == 2 THEN //Greek
            active_layout_keys:=greek_layout_keys;
            aloffs:=goffs;
            btnsz:= 32;
            aA_key:= 21; aA_x:= 0.5;
            kbd_x:= 10; kbd_y:= 4; kbd_size:= 40;
            Sym_key:=29; Sym_x:=8.5;
        END;
        IF active_layout == 3 THEN //Symb
            active_layout_keys:=when(alphaOn,symb_layout_keys_alphaOn,symb_layout_keys)​;
            aloffs:=soffs;
            btnsz:= 32;
            aA_key:= 21; aA_x:= 0.5;
            kbd_x:= 10; kbd_y:= 4; kbd_size:= 40;
            Sym_key:=29; Sym_x:=8.5;
        END;
    END;
    t:= 240-kbd_y*buttonSize(2);
    offset:=aloffs(alphaOn);
END;

change_alpha()
BEGIN
    alphaOn:=NOT (alphaOn XOR alpha_held_mode);
    IF active_layout==3 THEN // Changes Symbols
        active_layout_keys:=when(alphaOn,symb_layout_keys_alphaOn,symb_layout_keys)​;
    ELSE
        offset:=aloffs(alphaOn);
    END;
    drawkb();
    check_display();
END;

change_shift(n)
BEGIN
    IF (shift_held_mode) THEN RETURN; END;
    IF n THEN
        shiftOn:=1-shiftOn;
    ELSE
        shiftOn:=0;
    END;
    check_display()
END;

check_display()
BEGIN
    dirt:=1;
    drawtxt();
END;

change_layout(n) // 1- Qwerty/ 2- Greek / 3- Symb
BEGIN
    CASE
        IF n==-2 THEN
            active_layout:=when(active_layout==2,1,2);
        END;
        IF n==-3 THEN
            active_layout:=when(active_layout==3,1,3);
        END;
        IF n==-4 THEN
            active_layout:=when(active_layout==3,1,1+active_layout);
        END;
        active_layout:=n;
    END;
    BLIT_P(G0,G7);
    check_layout();
    drawkb();
    check_display();
END;

layout_choise()
BEGIN
    LOCAL n;
    CHOOSE(n,"Choose Layout",selected_layout_text);
    LOCAL main:= ((n-1) MOD 3)+1;
    alphaOn:=n>3;
    change_layout(main);
END;

check_key(ch) // Bypass Offset for some characters
BEGIN
    CASE
        // Bypass Layout
        // IF ch == THEN ch:=; END;
        IF ch == 930 THEN ch:=978; END;
        IF ch == 1205 THEN ch:=1236; END;
        IF ch ≤ -10 THEN RETURN(bypass_chars(-ch-9)); END;
    END;
    RETURN CHAR(ch);
END;

// Main Behaviour
virtual_keys()
BEGIN
    LOCAL c;
    setup();
    REPEAT
        drawtxt();
        c:=chkkey();
        IF (c ≠ "") THEN addchar(c); END;
    UNTIL stay;
    //toggle_time:=time();
    IF stay==1 THEN
        RETURN(txt);
    ELSE
        RETURN "";
    END;
END;

// Handles the Keys
chkkey()
BEGIN
    LOCAL k,m;
    k:=GETKEY();
    IF shift_held_mode AND NOT iskeydown(K_SHIFT) THEN
        shift_held_mode:=0;
        shift_down_mode:=0;
        change_shift(0);
    END;
    IF alpha_held_mode AND NOT iskeydown(K_ALPHA) THEN
        alpha_held_mode:=0;
        alpha_down_mode:=0;
        change_alpha();
    END;
    IF shift_down_mode AND NOT iskeydown(K_SHIFT) THEN
        shift_down_mode:=0;
    END;
    IF alpha_down_mode AND NOT iskeydown(K_ALPHA) THEN
        alpha_down_mode:=0;
    END;
    IF k≠−1 THEN
        IF (k<>K_SHIFT) AND shift_down_mode THEN
            shift_held_mode:=1;
        END;
        IF (k<>K_ALPHA) AND alpha_down_mode THEN
            alpha_held_mode:=1;
        END;
        IF (k<=19 OR (shiftOn AND (POS(shift_specials,k)>0)) OR (POS(specials,k)>0)) THEN
            CASE // Physical Keyboard / stay=2 Exits the Keyboard without text, stay=1 Enters the text.
                // Arrows [< >]
                IF k==K_ARROWLEFT THEN
                    IF shiftOn THEN movecurs(-curs); change_shift(0);
                    ELSE movecurs(-1); END;
                END;
                IF k==K_ARROWRIGHT THEN
                    IF shiftOn THEN movecurs(DIM(txt)-curs); change_shift(0);
                    ELSE movecurs(1); END;
                END;
                // Common Keys [Esc View] [Math xtθn ab/c Backspace] [x^y SIN COS TAN LN LOG] [x^2 +/- () , Enter] [EEX] [ALPHA] [Shift] [On Dot Space]
                // Special Keys
                IF k==K_ESC THEN // Esc (Without shiftOn exits the kbd)
                    IF shiftOn THEN change_shift(0); clearchar();
                    ELSE
                        WHILE iskeydown(K_ESC) DO wait(0.001); END;
                        stay:=2;
                    END;
                END;
                IF k==K_VIEW THEN // View
                    layout_choise();
                END;
                // Row 1
                IF k==K_C THEN // Math
                    change_layout(-3);
                END;
                IF k==K_D THEN // xtθn
                    change_layout(-2);
                END;
                IF k==K_E THEN // ab/c
                    change_layout(1);
                END;
                IF k==K_OFF THEN // Exits the kbd *** NEVER HIT, OS AUTO-EXITS PROGRAM
                    stay:=2;
                    RETURN "";
                END;
                IF k==K_DEL THEN // Backspace
                    IF shiftOn THEN change_shift(0); delchar(); RETURN("");
                    ELSE dropchar(); RETURN(""); END;
                END;
                IF k==K_ENTER THEN stay:=1; END; // Enter (Enters the text)
                IF k==K_ALPHA THEN
                    change_alpha();
                    alpha_down_mode:=1;
                END;
                IF k==K_SHIFT THEN
                    change_shift(1);
                    shift_down_mode:=1;
                END;
                IF k==K_S THEN
                    IF shiftOn THEN change_shift(0); change_layout(-3);
                    ELSE RETURN ("9"); END;
                END;
                IF k==K_W THEN
                    IF shiftOn THEN change_shift(0); change_layout(-3);
                    ELSE RETURN ("6"); END;
                END;
            END;
        ELSE
            IF shiftOn THEN change_shift(0); RETURN shift_text(k-19);
            ELSE RETURN norm_text(k-19); END;
        END;
    END;
    IF stay THEN RETURN (""); END;

    //Virtual Keyboard
    m:=getmouse();
    IF (m>0)AND(shift_down_mode AND iskeydown(K_SHIFT)) THEN shift_held_mode:=1; END;
    IF (m>0)AND(alpha_down_mode AND iskeydown(K_ALPHA)) THEN alpha_held_mode:=1; END;
    IF m==aA_key THEN // Aa Handle
        m:=0;
        change_alpha();
    END;
    IF m==Sym_key THEN // Sym Handle
        change_layout(-4);
    END;
    IF m>0 THEN
        IF m<29 OR m==31 OR m==32 OR m==38 OR m==39 OR (m>=33 AND m<=37) THEN
            flashkey(m);
        END;
        IF m==20 OR m==21 OR m>28 THEN
            CASE
            IF m==31 THEN RETURN ("="); END;
            IF m==32 THEN RETURN (":= "); END;
            IF m>=33 AND m<=37 THEN RETURN (" "); END;
            IF m==38 THEN RETURN (";"); END;
            IF m==39 THEN RETURN (","); END;
            RETURN ("");
            END;
        ELSE
            IF m>21 THEN
                RETURN check_key(active_layout_keys(m-2)+offset);
            ELSE
                RETURN check_key(active_layout_keys(m)+offset);
            END;
        END;
    END;
    RETURN ("");
END;

doflash(a)
BEGIN
    LOCAL x:= kbd_type_x(a);
    LOCAL y:= kbd_type_y(a);
    IF y==3 AND x>=2.5 AND x<=6.5 THEN // Space Bar
        INVERT_P(2.5*buttonSize(1)+1,t+y*buttonSize(2)+1,(6.5+1)*buttonSize(1)-1,t+(y+1)*buttonSize(2)-1);
    ELSE
        INVERT_P(x*buttonSize(1)+1,t+y*buttonSize(2)+1,(x+1)*buttonSize(1)-1,t+(y+1)*buttonSize(2)-1);
    END;
END;

unflash()
BEGIN
    IF flashed THEN
        doflash(flashed-1);
        flashed:=0;
    END;
END;

flashkey(c) // Do a flash on key
BEGIN
    LOCAL a=c-1;
    unflash();
    flashed:=c;
    doflash(a);
END;

//LOCAL mouseLoc1;
//LOCAL mouseLoc2;

//LOCAL mouseBuffer:={};
// Get "key" from mouse
getmouse()
BEGIN
    //LOCAL x:=MOUSE(0);
    LOCAL mVals:=MOUSE();
    LOCAL ml1Present:=0;
    LOCAL ml2Present:=0;
    LOCAL coord;
    IF (Size(mVals(1))==0) AND (SIZE(mVals(2))==0) THEN
        r:=1;
        mouseBuffer:={};
    END;

    // No presses in log.
    IF (SIZE(mouseLoc1)==0) AND (SIZE(mouseLoc2)==0) THEN
        IF (SIZE(mVals(1))<>0) THEN
            mouseBuffer(SIZE(mouseBuffer)+1):=mVals(1);
            ml1Present:=1;
            mouseLoc1:=mVals(1);
        END;
        IF (SIZE(mVals(2))<>0) THEN
            mouseBuffer(SIZE(mouseBuffer)+1):=mVals(2);
            IF (NOT ml1Present) THEN
                ml1Present:=1;
                mouseLoc1:=mVals(2);
            ELSE
                ml2Present:=1;
                mouseLoc2:=mVals(2);
            END;
        END;
    ELSE
        IF (SIZE(mouseLoc1)<>0) AND (SIZE(mouseLoc2)<>0) THEN
            // Two presses in log
            IF (SIZE(mVals(1))<>0) THEN
                IF (IsSameClick(mVals(1),mouseLoc1)) THEN
                    ml1Present:=1;
                ELSE
                    IF (IsSameClick(mVals(1),mouseLoc2)) THEN
                        ml2Present:=1;
                    ELSE
                        // mVals(1) is a new click, so we find which to replace.
                        IF (SIZE(mVals(2))==0) THEN
                            // mVals(1) is the only click in the list.
                            ml1Present:=1;
                            mouseLoc1:=mVals(1);
                            mouseBuffer(SIZE(mouseBuffer)+1):=mVals(1);
                        ELSE
                            IF (IsSameClick(mVals(2),mouseLoc1)) THEN
                                ml1Present:=1;
                                ml2Present:=1;
                                mouseLoc2:=mVals(1);
                                mouseBuffer(SIZE(mouseBuffer)+1):=mVals(1);
                            ELSE
                                IF (IsSameClick(mVals(2),mouseLoc2)) THEN
                                    ml1Present:=1;
                                    ml2Present:=1;
                                    mouseLoc1:=mVals(1);
                                    mouseBuffer(SIZE(mouseBuffer)+1):=mVals(1);
                                ELSE
                                    ml1Present:=1;
                                    ml2Present:=1;
                                    mouseLoc1:=mVals(1);
                                    mouseLoc2:=mVals(2);
                                    mouseBuffer(SIZE(mouseBuffer)+1):=mVals(1);
                                    mouseBuffer(SIZE(mouseBuffer)+1):=mVals(2);
                                END;
                            END;
                        END;
                    END;
                END;
            END;
            IF (SIZE(mVals(2)<>0)) THEN
                IF (ml1Present AND NOT ml2Present) THEN
                    IF (IsSameClick(mVals(2),mouseLoc2)) THEN
                        ml2Present:=1;
                    ELSE
                        ml2Present:=1;
                        mouseLoc2:=mVals(2);
                        mouseBuffer(SIZE(mouseBuffer)+1):=mVals(2);
                    END;
                ELSE
                    IF (ml2Present AND NOT ml1Present) THEN
                        IF (IsSameClick(mVals(2),mouseLoc1)) THEN
                            ml1Present:=1;
                        ELSE
                            ml1Present:=1;
                            mouseLoc1:=mVals(2);
                            mouseBuffer(SIZE(mouseBuffer)+1):=mVals(2);
                        END;
                    END;
                END;
            END;
        ELSE
            // At least one free spot.
            IF (SIZE(mouseLoc1)==0) AND (SIZE(mouseLoc2)<>0) THEN
                mouseLoc1:=mouseLoc2;
                mouseLoc2:={};
            END;
            // ml2 is free
            IF (SIZE(mVals(1))<>0) THEN
                IF (IsSameClick(mVals(1),mouseLoc1)) THEN
                    ml1Present:=1;
                ELSE
                    mouseLoc2:=mVals(1);
                    ml2Present:=1;
                    mouseBuffer(SIZE(mouseBuffer)+1):=mVals(1);
                END;
                IF (SIZE(mVals(2))<>0) THEN
                    IF (ml1Present) THEN
                        mouseLoc2:=mVals(2);
                        ml2Present:=1;
                        mouseBuffer(SIZE(mouseBuffer)+1):=mVals(2);
                    ELSE
                        IF (ml2Present) THEN
                            IF (IsSameClick(mouseLoc1,mVals(2))) THEN
                                ml1Present:=1;
                            ELSE
                                ml1Present:=1;
                                mouseLoc1:=mVals(2);
                                mouseBuffer(SIZE(mouseBuffer)+1):=mVals(2);
                            END;
                        END;
                    END;
                END;
            END;
        END;
    END;

    IF (NOT ml1Present) THEN
        mouseLoc1:={};
    END;
    IF (NOT ml2Present) THEN
        mouseLoc2:={};
    END;
    IF (SIZE(mouseBuffer)>0) THEN
        coord:=mouseBuffer(1);
        mouseBuffer:=SUPPRESS(mouseBuffer,1);
        RETURN btnFromCoords(coord(1),coord(2));
    END;
    RETURN 0;
END;

btnFromCoords(x,y)
BEGIN
    IF y>t THEN
        y:=IP((y-t)/buttonSize(2));
        x:=min(x,buttonSize(1)*(kbd_x-1));
        IF y > 0 THEN
            x:=IP(x/(buttonSize(1)*0.5));
            IF x < 3 THEN
                x:=0;
            ELSE
                x:=IP((x-1)/2);
            END;
        ELSE // Always run for first line
            x:=IP(x/buttonSize(1));
        END;
        RETURN 1+x+(y*kbd_x);
    END;
END;

IsNewClick(click)
BEGIN
    LOCAL isNew:=1;
    IF (SIZE(mouseBuffer) >= 1) AND (SIZE(mouseBuffer(1)) <> 0) THEN
        isNew:=(mouseBuffer(1,3)==click(3)) AND (mouseBuffer(1,4)==click(4));
    END;
    IF (SIZE(mouseBuffer) >= 2) AND (SIZE(mouseBuffer(2)) <> 0) THEN
        isNew:=isNew OR ((mouseBuffer(2,3)==click(3)) AND (mouseBuffer(2,4)==click(4)));
    END;
    RETURN isNew;
END;

IsSameClick(clk1, clk2)
BEGIN
    RETURN (clk1(3)==clk2(3)) AND (clk1(4)==clk2(4));
END;

// Move cursor
movecurs(d)
BEGIN
    LOCAL n:=curs+d;
    IF n<0 THEN  n:=0; END;
    IF n> DIM(txt) THEN n:=DIM(txt); END;
    curs:=n;
    dirt:=1; // Set to Redraw
END;

drawtxt()
BEGIN
    IF dirt THEN // Redraw
        redrawtxt();
        st:=0;
        dirt:=0;
    ELSE
        IF (st<>0) AND ((st MOD 4)==0) THEN
            unflash();
        END;
        CASE
            IF st==0 THEN
                LINE_P(posc,t-vk_height+2,posc,t-4,c_cursor);
            END;
            IF st==40 THEN
                LINE_P(posc,t-vk_height+2,posc,t-4,color_textfieldbg);
            END;
            IF st==80 THEN
                st:=-1;
            END;
        END;
        st:=st+1;
    END;
    WAIT(0.01);
END;

redrawtxt()
BEGIN
    LOCAL t1,t2; // Text parts
    LOCAL left_t:=2;
    RECT_P(0,t-vk_height,kbd_x*buttonSize(1)-1,t-1,c_gray,color_textfieldbg); // Clear text
    IF curs>0 THEN t1:=LEFT(txt,curs); ELSE t1:=""; END;
    IF curs==DIM(txt) THEN t2:=""; ELSE t2:=MID(txt,1+curs); END;
    //posc:=left_t+text_width(t1,0);
    posc:=TEXTOUT_P(t1,left_t,t-vk_height+left_t);
    TEXTOUT_P(t2,posc,t-vk_height+left_t);
    
    IF shiftOn THEN
        TEXTOUT_P("↑S",280,t-vk_height+left_t,0,c_darkblue);
    END;
    IF alphaOn THEN
        TEXTOUT_P("↑AZ",295,t-vk_height+left_t,0,c_orange);
    END;
END;

text_width(t,font_size)
BEGIN
    LOCAL wi;
    wi:=TEXTOUT_P(t,G9,0,0,font_size,0,320,0);
    RETURN wi;
END;

text_height(font_size)
BEGIN
    LOCAL wi,fs;
    wi:=TEXTOUT_P("1",G9,0,0,font_size,0,320,0);
    CASE
        IF wi==8 THEN fs:=3; END; // Higher priority first, Normal Font
        IF wi==7 THEN fs:=2; END; // Small Font
        IF wi==9 THEN fs:=4; END; // Big Font
        IF wi==5 THEN fs:=1; END;
        IF wi==10 THEN fs:=5; END;
        IF wi==11 THEN fs:=6; END;
        fs:=7;
    END;
    RETURN ht(fs);
END;

txtwcurs(c)
BEGIN
    LOCAL a:="";
    LOCAL b:=MID(txt,1+curs);
    IF curs>0 THEN a:=LEFT(txt,curs); END;
    IF curs==DIM(txt) THEN b:=""; END;
    RETURN a+c+b;
END;

// Add a char to the string
addchar(c)
BEGIN
    txt:=txtwcurs(c);
    IF DIM(c) > 2 THEN
        movecurs(DIM(c)-1);
        IF RIGHT(c,1)==" " THEN
            delchar();
        END;
    ELSE
        movecurs(1);
    END;
END;

dropchar()
BEGIN
    LOCAL a:="";
    LOCAL b:=MID(txt,1+curs);
    IF curs>1 THEN a:=LEFT(txt,curs-1); END;
    IF curs==DIM(txt) THEN b:=""; END;
    txt:=a+b;
    movecurs(-1);
END;

delchar()
BEGIN
    LOCAL a:=LEFT(txt,curs);
    LOCAL b:="";
    IF curs<(DIM(txt)-1) THEN b:=MID(txt,2+curs); END;
    IF curs==0 THEN a:=""; END;
    txt:=a+b;
    movecurs(0);
END;

clearchar()
BEGIN
    txt:=("");
    movecurs(0);
END;

kbd_type_x(n)
BEGIN
    RETURN (n MOD 10) + 0.5*(n>=10);
    CASE
        IF n<10 THEN
            RETURN (n);
        END;
        IF n>=10 AND n<20 THEN
            RETURN (n-9.5);
        END;
        IF n>=20 AND n<30 THEN
            RETURN (n-19.5);
        END;
        IF n>=30 AND n<40 THEN
            RETURN (n-29.5);
        END;
    END;
END;

kbd_type_y(n)
BEGIN
    RETURN FLOOR(n/10);
    CASE
        IF n<10 THEN
            RETURN(0);
        END;
        IF n>=10 AND n<20 THEN
            RETURN(1);
        END;
        IF n>=20 AND n<30 THEN
            RETURN(2);
        END;
        IF n>=30 AND n<40 THEN
            RETURN(3);
        END;
    END;
END;

// Draws the Keyboard
drawkb()
BEGIN
    LOCAL a;
    LOCAL b:=-1;
    //buttonSize(2):=text_height(0)+10;
    RECT_P(0,t,kbd_x*buttonSize(1),240);
    // Horizontal lines
    FOR a FROM 0 TO kbd_y*buttonSize(2) STEP buttonSize(2) DO
        IF a == kbd_y*buttonSize(2) THEN
            LINE_P(0,t+a-1,kbd_x*buttonSize(1),t+a-1);
        ELSE
            LINE_P(0,t+a,kbd_x*buttonSize(1),t+a);
        END;
    END;
    // Vertical lines
    FOR a FROM 0 TO kbd_x*buttonSize(1) STEP buttonSize(1) DO
        IF a==0 OR a==kbd_x*buttonSize(1) THEN
            IF a == kbd_x*buttonSize(1) THEN a:=kbd_x*buttonSize(1)-1;END;
            LINE_P(a,t,a,240);
        ELSE
            LINE_P(a,t,a,240-(kbd_y-1)*buttonSize(2));
            LINE_P(a-buttonSize(1)/2,t+buttonSize(2),a-buttonSize(1)/2,240-buttonSize(2));
            IF a == (kbd_x-1)*buttonSize(1) THEN
                LINE_P(a+buttonSize(1)/2,t+buttonSize(2),a+buttonSize(1)/2,240-buttonSize(2));
            END;
        END;
    END;
    // Draws last line
    LINE_P(0.5*buttonSize(1),240-buttonSize(2),0.5*buttonSize(1),240);
    LINE_P(1.5*buttonSize(1),240-buttonSize(2),1.5*buttonSize(1),240);
    LINE_P(2.5*buttonSize(1),240-buttonSize(2),2.5*buttonSize(1),240);
    LINE_P(7.5*buttonSize(1),240-buttonSize(2),7.5*buttonSize(1),240);
    LINE_P(8.5*buttonSize(1),240-buttonSize(2),8.5*buttonSize(1),240);
    LINE_P(9.5*buttonSize(1),240-buttonSize(2),9.5*buttonSize(1),240);
    // Draws Letters over Keys   ae=1237 AE=1236
    FOR a FROM 0 TO (kbd_size-2) DO
        LOCAL x:= kbd_type_x(a);
        LOCAL y:= kbd_type_y(a);
        IF a<>19 AND a<>20 AND a<28 THEN
            b:=b+1;
            math_hold:=active_layout_keys(b+1);
            IF active_layout==3 AND math_hold ≤ -10 THEN
                math_hold:=bypass_chars_kbd(-math_hold-9);
                drawatpos(x,y,math_hold);
            ELSE
                drawatpos(x,y,check_key(math_hold+offset));
            END;
        END;
    END;
    // Draws aA Key
    RECT_P(aA_x*buttonSize(1)+1,t+2*buttonSize(2)+1,(aA_x+1)*buttonSize(1)-1,t+3*buttonSize(2)-1,c_gray);
    CASE
        IF active_layout==1 THEN
            drawatpos(aA_x,2,"aA");
        END;
        IF active_layout==2 THEN
            drawatpos(aA_x,2,"αΑ");
        END;
        drawatpos(aA_x,2,"++");
    END;
    // Draws Special Keys
    IF active_layout > 0 THEN
        RECT_P(Sym_x*buttonSize(1)+1,t+2*buttonSize(2)+1,(Sym_x+1)*buttonSize(1)-1,t+3*buttonSize(2)-1,c_gray,c_gray);
        CASE
            IF active_layout==1 THEN
                drawatpos(Sym_x,2,"αβγ");
            END;
            IF active_layout==2 THEN
                drawatpos(Sym_x,2,"Sym");
            END;
            drawatpos(Sym_x,2,"abc");
        END;
        drawatpos(0.5,3,"=");
        drawatpos(1.5,3,":=");
        drawatpos(4.5,3,"Space");
        drawatpos(7.5,3,";");
        drawatpos(8.5,3,",");
    END;
    vk_height:=text_height(0)+3;
END;

// Draws Key - Converts keyboard position to screen position
drawatpos(x,y,c)
BEGIN
    TEXTOUT_P(c,G0,(x+0.5)*buttonSize(1)-(text_width(c,0)/2),t+(y+0.5)*buttonSize(2)-(text_height(0)/2));
END;

// Virtual_Keys Init
setup()
BEGIN
    DIMGROB_P(G7,320,240);
    DIMGROB_P(G9,320,240);
    BLIT_P(G7,G0);
    stay:=0; curs:=0; txt:=""; dirt:=1;
    shift_down_mode:=iskeydown(K_SHIFT);
    alpha_down_mode:=iskeydown(K_ALPHA);
    buttonSize(2):=text_height(0)*2;
    vk_height:=text_height(0)+3;
    
    check_layout();
    drawkb();
    check_display();
END;

// Show or clear the keyboard : [On]
KEY K_On()
BEGIN
    IF (dirty_shutdown) THEN
        dirty_shutdown:=0;
        RETURN "";
    END;
    dirty_shutdown:=1;
    LOCAL val:=virtual_keys();
    dirty_shutdown:=0;
    RETURN val;
END;



RE: 04-July-2017 Virtual Keys - Virtual Keyboard v1.3b [Updated] - mushman - 01-07-2022 09:24 AM

Awesome update!
Since multiple people are working on this and it seems like a great project, I took liberty to put it on git so we can collaborate easier. When the original author shows up, I'll transfer the project.

https://gitlab.com/predragmandic/vkb

I made additional commit changing '=' and ':=' keys to ':' and '='. Doubledot is used a lot in python and pascal people can still write ':=' fairly easy. If people don't like this change, I'm fine to revert and use separate patch privately.