flixel-ui icon indicating copy to clipboard operation
flixel-ui copied to clipboard

FlxInputText on Android: No soft keyboard

Open Shalmezad opened this issue 9 years ago • 23 comments

It was already mentioned on this issue: https://github.com/HaxeFlixel/flixel-ui/issues/41 But wanted to make a separate issue.

Basically, when tapping on a FlxInputText, the carrot appears, but the keyboard doesn't come up. I haven't tested with a hard keyboard yet, since all of my devices are soft-keyboard.

Looking at possible solutions now. It appears OpenFL has the capability to access the Java Native Interface. So may need to do something like this (within a #if android of course):

//When created:
        var JNIShowSoftKeyboard:Dynamic = JNI.createStaticMethod("flixel/addons/ui/KeyboardHandler", "showSoftKeyboard");
        var JNIHideSoftKeyboard:Dynamic = JNI.createStaticMethod("flixel/addons/ui/KeyboardHandler", "hideSoftKeyboard");
//When hasFocus:
        JNIShowSoftKeyboard();
//When !hasFocus:
        JNIHideSoftKeyboard();

Of course, will have to look at how Haxe handles native code. Especially since the normal way for handling the soft keyboard is:

InputMethodManager _imm = (InputMethodManager)SOME_CONTEXT.getSystemService(Service.INPUT_METHOD_SERVICE);
//then:
_imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
//or:
_imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY,0);

Which requires the current application context.

Shalmezad avatar Jul 25 '14 17:07 Shalmezad

Looks like we might be in luck. After some digging through, I found this: https://github.com/openfl/openfl-native/blob/master/flash/display/InteractiveObject.hx

Might be able to do something like:

//At top, in a #if android:
         private static var lime_display_object_request_soft_keyboard = Lib.load("lime", "lime_display_object_request_soft_keyboard",1);
//in set_hasFocus()
        #if android
          lime_display_object_request_soft_keyboard(this);
        #end

Shalmezad avatar Jul 25 '14 18:07 Shalmezad

Shouldn't that be #if mobile? Or does it only work for android?

Gama11 avatar Jul 25 '14 18:07 Gama11

I assume the lime based methods would be both iOS and android. I have xcode on my mac, so I can check on this when I get it working. Still trying to get the lime call to work within flixel-ui. Looking at the tests, it should be as simple as:

     sprite.requestSoftKeyboard();

But, that's with an openfl sprite. The sprites I've seen in flixel and flixel-ui so far have been flash sprites. Could create a new Sprite, but seems like a waste.

Shalmezad avatar Jul 25 '14 18:07 Shalmezad

Has there been any progress on this? This is a deal breaker for me, but I'd really like to use flixel.

samsieber avatar Sep 22 '14 23:09 samsieber

Not so far, but if this is a big issue for people I can try to push for it. I don't have an android device myself so someone else would have to add the missing feature, but I can help push for it to be more of a priority.

larsiusprime avatar Sep 23 '14 02:09 larsiusprime

You could just use an OpenFL input TextField, if you don't want to display anything on top of it.

Gama11 avatar Sep 23 '14 05:09 Gama11

Would love to close this if someone on android can help me resolve it. (I have no android device)

larsiusprime avatar Jan 19 '15 18:01 larsiusprime

I can help test as needed (got a couple devices that I know I can run on), but unfortunately don't have the time to go through and find the fix at the moment (unless you're willing to wait about 5 weeks).

Shalmezad avatar Jan 19 '15 18:01 Shalmezad

Hi, just pinging this issue to see if anybody got a flixel-based solution to this question or even a workaround for this to work as it should on mobile?

I can help test any solution / workaround in Android 4.4.4, 5.0.1 and iOS 7.

Tiago-Ling avatar May 06 '15 13:05 Tiago-Ling

One thing that could help is if someone could describe what they expect to happen on Android, vs what actually happens, vs what happens with regular OpenFL Input text fields. Since I don't have an android phone I'm totally in the dark here, but maybe it's a simple fix if I just knew what the standard behavior was.

larsiusprime avatar May 06 '15 14:05 larsiusprime

I'm doing this right now - will return here with feedback later today. What i need is the traditional soft-keyboard stuff:

  • When the user clicks on the textfield the soft keyboard appears;
  • When it appears, the screen contents move to keep the textfield on the screen if needed;
  • When the textfields loses focus the soft keyboard should also be dismissed;
  • Optionally the user can set the "return" key to dismiss the keyboard automatically.

Tiago-Ling avatar May 06 '15 14:05 Tiago-Ling

Small update regarding Android:

  • OpenFL 3.0.3 legacy has support for soft keyboard out of the box;
  • I'm using the following code to show allow editing of a TextField:
        var textfield = new TextField();
        textfield.x = 540;
        textfield.y = 440;
        textfield.type = TextFieldType.INPUT;
        textfield.textColor = 0x000000;
        textfield.border = true;
        textfield.borderColor = 0xFFFF00;
        textfield.background = true;
        textfield.backgroundColor = 0xFFFFFF;
        textfield.width = 200;
        textfield.height = 40;
        textfield.setTextFormat(new TextFormat(null, 32));

        //Mobile stuff
        #if (android || ios)
        textfield.needsSoftKeyboard = true;
                //This should work in "next" i think, but causes a compiler error legacy
        //textfield.softKeyboardInputAreaOfInterest = new Rectangle(540, 440, 200, 40);
                //This does not have any effect afaik (available on legacy only)
        textfield.moveForSoftKeyboard = true;
        #end

        FlxG.addChildBelowMouse(textfield);
  • The code above does not handle cases in which the soft keyboard appears over the input TextField with focus. So any text with its y position higher than a certain point will be hidden by the soft keyboard;
  • One small thing to note: as far as i have tested, the blinking caret is always black so it won't appear if the TextField background color is also black.

I'll keep testing to see if i can make things move when requesting the keyboard.

Tiago-Ling avatar May 06 '15 17:05 Tiago-Ling

Okay cool, so say in a normal OpenFL app, what happens? Does the entire openfl stage get moved upwards? How does it know how to move things?

We could solve this by moving the entire FlxGame (itself a Sprite on the OpenFL displaylist) up perhaps?

larsiusprime avatar May 06 '15 17:05 larsiusprime

In a normal OpenFL application, created using OpenFL 3.0.3, Lime 2.3.3 and Hxcpp 3.2.37 with the following code: http://pastebin.com/Vpd6tgUk

  • Next: TextField input does work in Flash and HTML5, but doesn't in Windows, Neko and Android (probably not working in all native targets). Problems specific to Android:
    • The softKeyboardInputAreaOdInterest does not work;
    • Pressing the back button does not close the application as it did in legacy;
    • The position of the TextField is different than in legacy, as if the stage size is shrunk;
  • Legacy: The TextField input works as expected, but unfortunately no movement of the application in relation to the soft keyboard.

Looks like there's still some work to be done for Android / mobile in next. I suppose we can manually move the FlxGame in legacy when showing the soft keyboard. However i did not find any related event in OpenFL for us to listen to (something like SoftKeyboardEvent.SHOW). There is this old repository here in which the guy uses native java code to do this.

Tiago-Ling avatar May 06 '15 18:05 Tiago-Ling

Cool, well let's check in with OpenFL and see what their plans are; open an issue with them if there's isn't one already (if there is, comment on it and link here?), once they have things sorted out, we can come up with a next-compatible solution.

I figure we can implement our own solution when the soft keyboard pops up, let's ping OpenFL about what events might be available to us. Worst case scenario we can just test whether the soft keyboard ALWAYS appears when an input textfield gets touched, and just drive the behaviour off of the "textfield clicked/touched" event or whatever, even though that's a bit sloppier.

larsiusprime avatar May 06 '15 18:05 larsiusprime

We can check in FOCUS_IN if the target is a textfield and have the property needsSoftKeyboard set to true - if so we must get the soft keyboard size (changes according to device, resolution, dpi, etc) and move the screen to accommodate it.

The first part which is listening to the focus event and checking if the textfield requires the soft keyboard is easy. Getting the actual size of the keyboard might require some JNI code. This question gives us more insight into the question: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android

Here is a sample which does work in Android (OpenFL legacy), the only thing missing is the exact amount to move the screen: http://pastebin.com/Q2WbwtvP

About issues in OpenFL, the ones most related to this are:

  • This one you created : https://github.com/openfl/openfl/issues/598
  • Maybe this one: https://github.com/openfl/openfl/issues/650

Do you think we need to open a separate issue for this one?

Tiago-Ling avatar May 06 '15 19:05 Tiago-Ling

I'm actually working with Joshua on openfl text fields right now, so maybe we'll finally have a solution for this.

larsiusprime avatar Aug 07 '15 11:08 larsiusprime

If you need any help testing it on android, let me know.

Shalmezad avatar Aug 07 '15 12:08 Shalmezad

Has anyone know this issue progress?

srel90 avatar May 08 '16 05:05 srel90

I had the same problem in my last project and as a workaround i managed to get the soft keyboard working by using an OpenFL TextField:

input = new openfl.text.TextField();
input.x = distance.x + (distance.width / 4);
input.y = 285;
input.type = openfl.text.TextFieldType.INPUT;
input.textColor = 0x000000;
input.border = true;
input.borderColor = 0xFFFF00;
input.background = true;
input.backgroundColor = 0xFFFFFF;
input.width = Std.int(distance.width / 2);
input.height = 38;
input.defaultTextFormat = new openfl.text.TextFormat(null, 32);
input.needsSoftKeyboard = true;
input.maxChars = 16;
input.moveForSoftKeyboard = true;
input.addEventListener(openfl.events.KeyboardEvent.KEY_DOWN, onMobileEnter);

FlxG.addChildBelowMouse(input);
FlxG.stage.focus = input;

onMobileEnter was just a function to save the data of the text field and dismiss the keyboard (non-relevant parts omitted):

function onMobileEnter(e:openfl.events.KeyboardEvent) {
    if (e.keyCode == 13) {
        remove(input);
        input.__dismissSoftKeyboard();
        input.removeEventListener(openfl.events.KeyboardEvent.KEY_DOWN, onMobileEnter);
        FlxG.removeChild(input);
    }
}

Hope this helps!

Tiago-Ling avatar May 08 '16 11:05 Tiago-Ling

Is there any way to help with this issue? I have a flixel project using haxeui for the interface and would prefer not having to use OpenFL TextFields.

I have 2 android devices to test on in case it's needed.

Gioele-Bencivenga avatar Nov 23 '20 14:11 Gioele-Bencivenga

I'm not sure if this is still relevant, but I fixed this issue by grabbing what @Tiago-Ling created, and I have been using my solution on both iOS and Android to great success. I created an FlxInputText wrapper (GFTextInput) that just defaults to FlxInputText on non-mobile, and, on mobile, uses an OpenFL textfield "under the hood" to fill-in an FlxInputText. So what we have is:

  • FlxInputText visuals
  • FlxInputText semantics for several things
  • OpenFL TextField "under-the-hood"
  • A "ClickArea" is placed over the FlxInputText to "trigger" the keyboard by focusing the "hidden" OpenFL TextField
  • When the field is focused, we also clear it (you can change this, obviously)
  • When enter is pressed, we remove focus from the element, thus sending the keyboard away
  • On every keypress, and on every update, we manually synchronize the state of the OpenFL field and FlxInputText (we copy the OpenFL text to FlxInputText)
  • We take additional precautions to support maxLength (still need to be improved, but good enough for my use case)

Here is my implementation:

package utils.ui;
import flixel.FlxG;
import flixel.group.FlxGroup;
import flixel.addons.ui.FlxInputText;
import flixel.util.FlxColor;

/**
* This is so hackish I might just marry it.
*
* On desktop/html5, this acts as (and IS) a regular input. I suspect that html5 should go the mobile route if it's
* running on an actual mobile device, but I'll check that later
*
* Anyway, on mobile, we use a set of devious hacks to get a virtual keyboard to popup on screen. That keyboard inputs
* to a hidden OpenFL field and we constantly copy the content of that field to our real/visible FlxInputText. We then
* mimick some properties of FlxInputText using getters and setters. We use a ClickArea to set focus on the hidden
* OpenFL field, and we also listen to enter keypresses to dismiss the keyboard by removing focus from the field. We
* also need special care to manually deal with maxLength, because the FlxText setter does not honour MaxLength on its
* own.
*
* Overall it's a very hacky solution on mobile, but it works!
**/
#if !mobile
typedef GFTextInput = FlxInputText;
#else
class GFTextInput extends FlxGroup {
    public final inputText : FlxInputText;
    private final _flTextField : openfl.text.TextField;
    private final clickArea : ClickArea;
    public var x(default,set) : Float;
    public var y(default,set) : Float;
    public var maxLength(default,set) : Int;
    public var text(get,set) : String;
    public function new(X:Float = 0, Y:Float = 0, Width:Int = 150, ?Text:String, size:Int = 8, TextColor:Int = FlxColor.BLACK,
                        BackgroundColor:Int = FlxColor.WHITE, EmbeddedFont:Bool = true) {
        super();
        x = X;
        y = Y;
        inputText = new FlxInputText(x,y,Width,Text,size,TextColor,BackgroundColor,EmbeddedFont);
        _flTextField = new openfl.text.TextField();
        _flTextField.needsSoftKeyboard = true;
        _flTextField.text = text;
        _flTextField.x = x;
        _flTextField.y = y;
        _flTextField.type = openfl.text.TextFieldType.INPUT;
        _flTextField.addEventListener(openfl.events.KeyboardEvent.KEY_DOWN, function (e:openfl.events.KeyboardEvent) {
            if (inputText.maxLength == 0 || _flTextField.text.length <= inputText.maxLength)
                inputText.text = _flTextField.text;
            if (e.keyCode == 13)
                FlxG.stage.focus = null;

        });
        clickArea = new ClickArea(x,y,Width,inputText.height, function () {
            text = '';
            FlxG.stage.focus = _flTextField;
        });
        add(inputText);
        add(clickArea);
        FlxG.addChildBelowMouse(_flTextField);
    }

    public override function update(elapsed) {
        if (inputText.maxLength == 0 || _flTextField.text.length <= inputText.maxLength)
            inputText.text = _flTextField.text;
        super.update(elapsed);
    }

    public function set_x(v:Float) {
        if (inputText != null)
            inputText.x = v;

        if (clickArea != null)
            clickArea.x =v;

        return x = v;
    }


    public function set_y(v:Float) {
        if (inputText != null)
            inputText.y = v;

        if (clickArea != null)
            clickArea.y = v;

        return y = v;
    }

    function set_text(value:String):String {
        inputText.text = value;
        _flTextField.text = value;

        return value;
    }

    function get_text(): String {
        return inputText.text;
    }

    function set_maxLength(v : Int): Int {
        return maxLength = inputText.maxLength = v;
    }

    public override function destroy() {
        FlxG.removeChild(_flTextField);
    }
}
#end

Also note that ClickArea is a class I implemented as below:

import flixel.addons.ui.FlxUIButton;
/**
* A clickarea. Basically an invisible button to put over things.
**/
class ClickArea extends FlxUIButton {
    public var onClick : Void->Void;
    public function new(x: Float, y: Float, width: Float, height: Float, onClick: Void->Void = null) {
        super(x, y, null, false, true);
        this.width = width;
        this.height = height;
        this.onClick = onClick;
        immovable = true;
        scrollFactor.set();
        onUp.callback = function () { if (this.onClick != null) this.onClick(); };
    }
}

Jorl17 avatar Feb 11 '22 03:02 Jorl17

@Jorl17 This is cool, IMO FlxInputText is pretty garbage, and instead it should be doing something like this to leverage a better input system to begin with. Those would be a ton of manual testing involved in this, but I think it would be really cool to rebuild FlxInputText from the ground up using openfl TextFields to get keyboard text input.

Tbh, I've been thinking about the benefits of this for a few weeks, and I hadn't even considered mobile keyboards yet

Geokureli avatar Feb 16 '22 20:02 Geokureli