Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add to LineEdit and TextEdit a filter for valid input #7193

Open
acgc99 opened this issue Jul 3, 2023 · 14 comments
Open

Add to LineEdit and TextEdit a filter for valid input #7193

acgc99 opened this issue Jul 3, 2023 · 14 comments

Comments

@acgc99
Copy link

acgc99 commented Jul 3, 2023

Describe the project you are working on

A mobile app where user can track their progress in physical exercises.

Describe the problem or limitation you are having in your project

To introduce record training session info, I use LineEdit. The problem is that some info is only numeric, and LineEdit doesn't allow you to filter user input.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Add to LineEdit (and TextEdit) filter options: allow only integer numbers, real numbers, only text...

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

I used Kivy for Python UIs and I was able to handle this issue. I attach the corresponding documentation. Basically, you have to override the method that passes the new character to be written to the widget.

My proposal is to have a method like in Kivy, that gets the new character (and not the new text) and you determine if it is right or not. The workflow would be:

  1. User introduces a new character.
  2. The filter function determines if it is valid or not. If it is, it returns the character, else, returns"".
  3. That value is passed to the method that writes the new character.

If this enhancement will not be used often, can it be worked around with a few lines of script?

I can be solved using regular expressions, see this example.

The LineEdit and TextEdit nodes have the signal text_changed , but with this you get the new text, the workflow is:

  1. User introduces a new character and it is written.
  2. text_changed is triggered and in the connected function you have to check if the new text is valid.
  3. If it is not, you have to override the node text (hopefully this doesn't trigger text_changed).
  4. You might have to reset caret position.

Is there a reason why this should be core and not an add-on in the asset library?

This would also help in games where you have to write a number of coins to trade or something similar; or introducing names (without numbers).

@Calinou
Copy link
Member

Calinou commented Jul 3, 2023

Use a SpinBox node instead of LineEdit if you only want to allow numeric input.

Are there many situations where you need a multiline text field to only allow numeric input?

@KoBeWi
Copy link
Member

KoBeWi commented Jul 3, 2023

Note that SpinBox technically also allows non-numeric input, but it's automatically converted to numbers when accepted. The same behavior can be easily coded in LineEdit.

Real filtering would allow SpinBoxes to handle it better.

@Calinou
Copy link
Member

Calinou commented Jul 3, 2023

Real filtering would allow SpinBoxes to handle it better.

On the other hand, it would prevent you from writing expressions that use functions such as sqrt() (or even using * and / depending on how it's implemented).

@acgc99
Copy link
Author

acgc99 commented Jul 3, 2023

@Calinou

Use a SpinBox node instead of LineEdit if you only want to allow numeric input.

@KoBeWi

Note that SpinBox technically also allows non-numeric input, but it's automatically converted to numbers when accepted. The same behavior can be easily coded in LineEdit.

I didn't know that node, but playing arround, I found some issues I don't undertand/I would do in another way:

  1. If a write a letter (a-z, A-Z), and I press enter, letters disappear in general and I get the number that was before.
  2. If letters are placed between numbers... I didn't guessed the rule here.
  3. If some special character is written (I tried "*", "%"), pressing enter makes nothing (this includes placing more than one .). If there is a single "$" sign and a number, pressing enter "makes things" but only if it has numbers on the right.

My desire is to directly restrict what user can type, not applying some function to any text he/she has already written. For example, user can only write up to one "." in some "real number" mode, but none in some "integer number" mode.

@Calinou

Are there many situations where you need a multiline text field to only allow numeric input?

Not really, I was generalizing.

@KoBeWi
Copy link
Member

KoBeWi commented Jul 3, 2023

I think this can be implemented as a virtual method:

extends LineEdit
func _text_input(character):
    if character makes a valid number:
        return true
    return false

something like that.

@acgc99
Copy link
Author

acgc99 commented Jul 3, 2023

@KoBeWi that is one option.

Another option (like in Kivy), is that the filter method has to return the String to add to the text, if you don't want to add any character, return "". By this way you might also modify user input if by some reason you need to do that (I don't know: auto-uppercase, auto-lowercase, removing accents...)

@Calinou
Copy link
Member

Calinou commented Jul 3, 2023

I would go with @acgc99's solution. The method's signature could be _validate_input(text: String) -> String.

However, you mentioned that text should be added by the validation if it returns a non-empty string. Shouldn't the existing text be modified instead?

@acgc99
Copy link
Author

acgc99 commented Jul 3, 2023

@Calinou , the new character is not added by the validation, it is returned by the validation to the method that inserts the text.

This is the example in the case of Kivy with some small modifications:

class FloatInput(TextInput):

    pat = re.compile('[^0-9]')

    def filter_text(self, substring, from_undo=False):
        # substring is the user input, might be invalid
        # apply some algorithm
        pat = self.pat
        if '.' in self.text:
            s = re.sub(pat, '', substring)
        else:
            s = '.'.join(
                re.sub(pat, '', s)
                for s in substring.split('.', 1)
            )
        # now `s` is a valid string (it might be empty)
        return s


    def insert_text(self, substring, from_undo=False):
        # called when user types something
        s = self.filter_text(substring, from_undo)
        return super().insert_text(s, from_undo=from_undo)

You redefine the insert_text method and filter_text makes the validation.

Another issue that you might face is that if you have a numeric input field with 0. Therefore, if you type a number, first you need to check if it is a number, and if it is, then replace the 0 with that number, unless it is another 0 (do nothing instead). In that case you would have to modify the previous text in the filter function.

@acgc99
Copy link
Author

acgc99 commented Jul 17, 2023

I ended up doing an add-on.

I had to make multiple workarounds to make things work, although by how LineEdit and TextEdit handle the signals they have, there might be some small issues.

I still think that Godot base node signals should be changed as commented above.

@timothyqiu
Copy link
Member

timothyqiu commented Jul 19, 2023

A generalized version could be _should_change_text(from: int, to: int, replacement: String) -> bool.

It can validate when text is going to be inserted, deleted, or replaced.

  • from == to when inserting replacement
  • replacement.is_empty() when deleting text [from, to)

max_length can be implemented with this function too.

@acgc99
Copy link
Author

acgc99 commented Aug 5, 2023

If modifications are made, also pasting text or deleting/replacing text by selection should be filtered.

@acgc99
Copy link
Author

acgc99 commented Oct 19, 2023

Note that this proposal could benefit Godot Editor since, for example, Control.size.x can only be float but user can type anything. I think that on Editor, when an invalid value is given, the value that was before is placed again. This is somewhat uncomfortable, since I might want to set a big number (maybe for a custom exported property) and if I type one no-number character (or a float with two decimal points), everything is lost. This would prevent typing errors.

@Calinou
Copy link
Member

Calinou commented Oct 19, 2023

for example, Control.size.x can only be float but user can type anything.

You can enter an expression that uses letters but returns a float, such as pow(2.5, 4). As such, entering letters and other symbols should not be prevented.

@CsloudX
Copy link

CsloudX commented Apr 15, 2024

IMO, add a set_input_mask like Qt's QLineEdit was a good idea.
image
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants