Skip to content

Commit ec01950

Browse files
authored
Merge pull request #2620 from pythonarcade/gui/validated-input
gui: add restricted input
2 parents dab1e62 + 2276c9e commit ec01950

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Example of using experimental UIRestrictedInputText.
2+
3+
If Arcade and Python are properly installed, you can run this example with:
4+
python -m arcade.examples.gui.exp_restricted_input
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import arcade
10+
from arcade.gui import UIAnchorLayout, UIBoxLayout, UIView
11+
from arcade.gui.experimental.restricted_input import UIIntInput
12+
13+
14+
class MyView(UIView):
15+
def __init__(self):
16+
super().__init__()
17+
self.background_color = arcade.uicolor.BLUE_BELIZE_HOLE
18+
19+
root = self.ui.add(UIAnchorLayout())
20+
bars = root.add(UIBoxLayout(space_between=10))
21+
22+
# UIWidget based progress bar
23+
self.input_field = UIIntInput(width=300, height=40, font_size=22)
24+
bars.add(self.input_field)
25+
26+
27+
def main():
28+
window = arcade.Window(antialiasing=False)
29+
window.show_view(MyView())
30+
arcade.run()
31+
32+
33+
if __name__ == "__main__":
34+
main()
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""
2+
This is an experimental implementation of a restricted input field.
3+
If the implementation is successful, the feature will be merged into the existing UIInputText class.
4+
"""
5+
6+
from typing import Optional
7+
8+
from arcade.gui import UIEvent, UIInputText
9+
10+
11+
class UIRestrictedInput(UIInputText):
12+
"""
13+
A text input field that restricts the input to a certain type.
14+
15+
This class is meant to be subclassed to create custom input fields
16+
that restrict the input by providing a custom validation method.
17+
18+
Invalid inputs are dropped.
19+
"""
20+
21+
@property
22+
def text(self):
23+
"""Text of the input field."""
24+
return self.doc.text
25+
26+
@text.setter
27+
def text(self, text: str):
28+
if not self.validate(text):
29+
# if the text is invalid, do not update the text
30+
return
31+
32+
# we can not call super().text = text here: https://bugs.python.org/issue14965
33+
UIInputText.text.__set__(self, text) # type: ignore
34+
35+
def on_event(self, event: UIEvent) -> Optional[bool]:
36+
# check if text changed during event handling,
37+
# if so we need to validate the new text
38+
old_text = self.text
39+
pos = self.caret.position
40+
41+
result = super().on_event(event)
42+
if not self.validate(self.text):
43+
self.text = old_text
44+
self.caret.position = pos
45+
46+
return result
47+
48+
def validate(self, text) -> bool:
49+
"""Override this method to add custom validation logic.
50+
51+
Be aware that an empty string should always be valid.
52+
"""
53+
return True
54+
55+
56+
class UIIntInput(UIRestrictedInput):
57+
def validate(self, text) -> bool:
58+
if text == "":
59+
return True
60+
61+
try:
62+
int(text)
63+
return True
64+
except ValueError:
65+
return False
66+
67+
68+
class UIFloatInput(UIRestrictedInput):
69+
def validate(self, text) -> bool:
70+
if text == "":
71+
return True
72+
73+
try:
74+
float(text)
75+
return True
76+
except ValueError:
77+
return False
78+
79+
80+
class UIRegexInput(UIRestrictedInput):
81+
def __init__(self, *args, pattern: str = r".*", **kwargs):
82+
super().__init__()
83+
self.pattern = pattern
84+
85+
def validate(self, text: str) -> bool:
86+
if text == "":
87+
return True
88+
89+
import re
90+
91+
return re.match(self.pattern, text) is not None
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from arcade.gui.experimental import UIPasswordInput
2+
from arcade.gui.experimental.restricted_input import UIRestrictedInput, UIIntInput, UIRegexInput
3+
4+
5+
def test_restricted_input_ignore_invalid_input(ui):
6+
class FailingInput(UIRestrictedInput):
7+
def validate(self, text) -> bool:
8+
return text == ""
9+
10+
fi = ui.add(FailingInput())
11+
12+
# WHEN
13+
ui.click(fi.center_x, fi.center_y)
14+
for l in "abcdef-.,1234567890":
15+
ui.type_text(l)
16+
17+
assert fi.text == ""
18+
19+
20+
def test_int_input_accepts_only_digits(ui):
21+
fi = ui.add(UIIntInput())
22+
23+
# WHEN
24+
ui.click(fi.center_x, fi.center_y)
25+
for l in "abcdef-.,1234567890":
26+
ui.type_text(l)
27+
28+
assert fi.text == "1234567890"
29+
30+
31+
def test_float_input_accepts_only_float(ui):
32+
fi = ui.add(UIIntInput())
33+
34+
# WHEN
35+
ui.click(fi.center_x, fi.center_y)
36+
for l in "abcdef-.,1234567890":
37+
ui.type_text(l)
38+
39+
assert fi.text == "1234567890"
40+
41+
42+
def test_regex_input_accepts_only_matching_patterns(ui):
43+
fi = ui.add(UIRegexInput(pattern="^[0-9]+$"))
44+
45+
# WHEN
46+
ui.click(fi.center_x, fi.center_y)
47+
for l in "abcdef-.,1234567890":
48+
ui.type_text(l)
49+
50+
assert fi.text == "1234567890"
51+

0 commit comments

Comments
 (0)