theom

InputText dead key support

Recommended Posts

Here's what I've got so far. What I'm trying to achieve is to be able to very easily configure different custom key mappings outside of Babylon and to be able to switch between them at runtime.

This is all done in a callback I give to InputText. I can modify the entered character and event prevent it from being added to the input. This enables me, for example, to easily add dead key support or to just allow numbers to be entered. I think this also solves the "input mask" case we talked about here: 

Here's an incomplete dead key handler:

let input = new gui.InputText();
input.onBeforeKeyAdd = (target, key) => {
    if (target.deadKey) {
        switch (key) {
            case "a":
                key = "á";
                break;
            case "A":
                key = "Á";
                break;
        }
        
        target.deadKey = false;
    }
    return { add: true, key: key };
};

And here's a handler that only allows numbers to be entered:

let input = new gui.InputText();
input.onBeforeKeyAdd = (target, key) => {
    let add = false;
    if (key >= "0" && key <= "9") {
        add = true;
    }
    return { add: add, key: key };
};

What I'd also like to do is to bundle a set of these key modifiers along with Babylon so user's can pick the ones they need but I'm unsure of the best way to do that (where to put them). Any ideas?

 

Share this post


Link to post
Share on other sites

:)

A dead key is a key that does not output anything when hit, but is used to modify the next key. For example, Icelandic has several characters that have a diacritic above them (á, í, é ...) and we use a dead key to enter those characters (dead key (diacritic) + a = á).

What I did in the InputText was to keep the state of the dead key to enable the key modifier to act on it if it likes.

Share this post


Link to post
Share on other sites

Just to make sure we are talking about the same thing: I'm wondering where it's best to keep the various callback definitions the InputText can be configured to use. For example, for those who might want to use the Icelandic version of the dead key callback they can pick it up from this place and plug it into their instances of InputText; if they want to limit the input to only numbers they can pick up the number input mask and plug it in, etc. Sorry if this was obvious to you.

Given this 'plugin' nature, does it make sense to keep the callback definitions in the InputText class (I assume you meant that class when you say textbox)? I was more seeing this as something maintained externally to the InputText which then could be built up over time to include several different keyboard "mappings" and input masks. Maybe have some directory structure like this in the controls directory:

  • InputTextModifiers
    • KeyboardMapping
    • InputMasks

Maybe I'm taking this too far here but to me it doesn't feel right to keep the callbacks within the InputText class. Or I misunderstood you :)

Share this post


Link to post
Share on other sites

I think DK means that those onBeforeKeyAdd types of methods are useful for others.  We could use them to intercept the keypress to prevent event propagation - we do that normally in html (maybe 'onKeyDown') by calling evt.preventDefault() or whatever.  That could activate deadkey in your case (shift + space?) or disallow non-numeric input depending on the implementation as you have done.

So, we could change the current method - or perhaps there is a better place.  I have not fully looked through the GUI code (https://github.com/BabylonJS/Babylon.js/blob/master/gui/src/2D/controls/inputText.ts#L314😞

public processKeyboard(evt: KeyboardEvent): void {
     this.processKey(evt.keyCode, evt.key);
}

Then add what you have (using maybe something like a template method pattern) to make it pluggable into InputText.

public processKeyboard(evt: KeyboardEvent): void {
   // in your onBeforeKeyAdd you need to check:
   //  var shiftKeyPressed = evt.shiftKey, but also caps-lock can be enabled on Virtual Keyboard mode, so that may be confusing!
   let beforeResult = this.onBeforeKeyAdd(evt: KeyboardEvent);
   if (beforeResult.add === true) {
     this.processKey(evt.keyCode, evt.key);
   }
}

That logic means you would need a default implementation, like perhaps:

onBeforeKeyAdd = (target, key) => {
    return { add: true, key: key };
};

Otherwise you need to check if there is an implementation or not.  I think you could go a step further and change the virtual keyboard or long-press virtual keyboard buttons like phone keyboard modifiers - looks like a fun project!

Share this post


Link to post
Share on other sites

@brianzinn @Deltakosh It seems to me that we are talking about the same thing, i.e. re-use of the key modifiers. The way I have done this in InputText is very similar to what @brianzinn suggests, except I check for the presence of the callback:

class InputText
...

private _onBeforeKeyAdd: (target: InputText, key: string) => { add: boolean, key: string };

...

/** Sets the callback that's called before the entered key is added to the input text */
public set onBeforeKeyAdd(cb: (target: InputText, key: string) => { add: boolean, key: string }) {
    this._onBeforeKeyAdd = cb;
}

...

public processKey(keyCode: number, key?: string) {

    // Specific cases
    switch (keyCode) {
        ...
        case 222: // Dead
            this.deadKey = true;
            return;
    }

    // Printable characters
    if (
        (keyCode === -1) ||                     // Direct access
        (keyCode === 32) ||                     // Space
        (keyCode > 47 && keyCode < 58) ||       // Numbers
        (keyCode > 64 && keyCode < 91) ||       // Letters
        (keyCode > 185 && keyCode < 193) ||     // Special characters
        (keyCode > 218 && keyCode < 223) ||    // Special characters
        (keyCode > 95 && keyCode < 112)) {      // Numpad
        let add = true;
        if (this._onBeforeKeyAdd) {
            if (key) {
                ({ add, key } = this._onBeforeKeyAdd(this, key));
            }
            if (!key) {
                add = false;
            }
        }
        if (add) {
            if (this._cursorOffset === 0) {
                this.text += key;
            } else {
                let insertPosition = this._text.length - this._cursorOffset;

                this.text = this._text.slice(0, insertPosition) + key +
                    this._text.slice(insertPosition);
            }
        }
    }
}

What confuses me is how the virtual keyboard comes into this? Is all keyboard input, desktop and mobile, routed through the virtual keyboard?

Share this post


Link to post
Share on other sites

1. I first looked into using an observable but dismissed it because I wanted to return a value to the InputText key processing code at the point of the call to the modifier. I couldn't see how to do that using an observable. But let me mull it over a bit.

2. Yes. Are there more than one virtual keyboard?

Share this post


Link to post
Share on other sites

1. Every observable can make change to the event passed as parameter

2. I also thought about the phone or tablet virtual keyboard. But never mind, I'm with you now, so in the virtual keyboard case we also need to expose the dead keys as new letters to display on the layout. I honestly did not put too much thoughts on it so far but as the keyboard call the processKey function on the InputText we should be fine to add what we need there

Share this post


Link to post
Share on other sites

1. Exactly. I'll see how I can deliver the feedback that way. And thanks for the example.

2. I'm focusing on the desktop at the moment, but the virtual keyboard is something we need to look into eventually.

Share this post


Link to post
Share on other sites

I have a working version that uses an observable.

I'll have to do some more testing and once I'm satisfied with it I'll send you a PR.

Share this post


Link to post
Share on other sites
1 hour ago, theom said:

the virtual keyboard is something we need to look into eventually

It's a lot more effort when you consider the virtual keyboard, but really interested to see your PR.  If you want to see how the virtual keyboard is created here is the default keyboard:
https://github.com/BabylonJS/Babylon.js/blob/master/gui/src/2D/controls/virtualKeyboard.ts#L226
People in VR mode or mobile devices likely don't have keyboard input otherwise, but if you are focusing on Desktop that is OK, too :)

Share this post


Link to post
Share on other sites

Yes, it's true that making the virtual keyboard more configurable is a bit more work, but it doesn't look that bad. We just need to create different layouts for each country (new Create.. methods) and it's probably best to get the keyPressObserver to behave like a desktop keyboard regarding the special keys. That way the InputText can stay the same and do its thing.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.