Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions arcade/examples/gui/exp_restricted_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Example of using experimental UIRestrictedInputText.

If Arcade and Python are properly installed, you can run this example with:
python -m arcade.examples.gui.exp_restricted_input
"""

from __future__ import annotations

import arcade
from arcade.gui import UIAnchorLayout, UIBoxLayout, UIView
from arcade.gui.experimental.restricted_input import UIIntInput


class MyView(UIView):
def __init__(self):
super().__init__()
self.background_color = arcade.uicolor.BLUE_BELIZE_HOLE

root = self.ui.add(UIAnchorLayout())
bars = root.add(UIBoxLayout(space_between=10))

# UIWidget based progress bar
self.input_field = UIIntInput(width=300, height=40, font_size=22)
bars.add(self.input_field)


def main():
window = arcade.Window(antialiasing=False)
window.show_view(MyView())
arcade.run()


if __name__ == "__main__":
main()
91 changes: 91 additions & 0 deletions arcade/gui/experimental/restricted_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""
This is an experimental implementation of a restricted input field.
If the implementation is successful, the feature will be merged into the existing UIInputText class.
"""

from typing import Optional

from arcade.gui import UIEvent, UIInputText


class UIRestrictedInput(UIInputText):
"""
A text input field that restricts the input to a certain type.

This class is meant to be subclassed to create custom input fields
that restrict the input by providing a custom validation method.

Invalid inputs are dropped.
"""

@property
def text(self):
"""Text of the input field."""
return self.doc.text

@text.setter
def text(self, text: str):
if not self.validate(text):
# if the text is invalid, do not update the text
return

# we can not call super().text = text here: https://bugs.python.org/issue14965
UIInputText.text.__set__(self, text) # type: ignore

def on_event(self, event: UIEvent) -> Optional[bool]:
# check if text changed during event handling,
# if so we need to validate the new text
old_text = self.text
pos = self.caret.position

result = super().on_event(event)
if not self.validate(self.text):
self.text = old_text
self.caret.position = pos

return result

def validate(self, text) -> bool:
"""Override this method to add custom validation logic.

Be aware that an empty string should always be valid.
"""
return True


class UIIntInput(UIRestrictedInput):
def validate(self, text) -> bool:
if text == "":
return True

try:
int(text)
return True
except ValueError:
return False


class UIFloatInput(UIRestrictedInput):
def validate(self, text) -> bool:
if text == "":
return True

try:
float(text)
return True
except ValueError:
return False


class UIRegexInput(UIRestrictedInput):
def __init__(self, *args, pattern: str = r".*", **kwargs):
super().__init__()
self.pattern = pattern

def validate(self, text: str) -> bool:
if text == "":
return True

import re

return re.match(self.pattern, text) is not None
51 changes: 51 additions & 0 deletions tests/unit/gui/test_exp_restricted_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from arcade.gui.experimental import UIPasswordInput
from arcade.gui.experimental.restricted_input import UIRestrictedInput, UIIntInput, UIRegexInput


def test_restricted_input_ignore_invalid_input(ui):
class FailingInput(UIRestrictedInput):
def validate(self, text) -> bool:
return text == ""

fi = ui.add(FailingInput())

# WHEN
ui.click(fi.center_x, fi.center_y)
for l in "abcdef-.,1234567890":
ui.type_text(l)

assert fi.text == ""


def test_int_input_accepts_only_digits(ui):
fi = ui.add(UIIntInput())

# WHEN
ui.click(fi.center_x, fi.center_y)
for l in "abcdef-.,1234567890":
ui.type_text(l)

assert fi.text == "1234567890"


def test_float_input_accepts_only_float(ui):
fi = ui.add(UIIntInput())

# WHEN
ui.click(fi.center_x, fi.center_y)
for l in "abcdef-.,1234567890":
ui.type_text(l)

assert fi.text == "1234567890"


def test_regex_input_accepts_only_matching_patterns(ui):
fi = ui.add(UIRegexInput(pattern="^[0-9]+$"))

# WHEN
ui.click(fi.center_x, fi.center_y)
for l in "abcdef-.,1234567890":
ui.type_text(l)

assert fi.text == "1234567890"

Loading