Skip to content

Commit

Permalink
Merge pull request #426 from MyreMylar/text-input-changes
Browse files Browse the repository at this point in the history
Switch to processing text input with TEXTINPUT events
  • Loading branch information
MyreMylar authored Apr 6, 2023
2 parents cf5d69d + ac98586 commit d18bb6d
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 104 deletions.
87 changes: 48 additions & 39 deletions pygame_gui/elements/ui_text_entry_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from pygame_gui.core.utility import clipboard_paste, clipboard_copy

from pygame import Rect, MOUSEBUTTONDOWN, MOUSEBUTTONUP, BUTTON_LEFT, KEYDOWN
from pygame import Rect, MOUSEBUTTONDOWN, MOUSEBUTTONUP, BUTTON_LEFT, KEYDOWN, TEXTINPUT
from pygame import KMOD_META, KMOD_CTRL, K_a, K_x, K_c, K_v
from pygame import K_LEFT, K_RIGHT, K_UP, K_DOWN, K_HOME, K_END, K_BACKSPACE, K_DELETE, K_RETURN
from pygame import key
Expand Down Expand Up @@ -239,8 +239,13 @@ def process_event(self, event: Event) -> bool:
consumed_event = True
elif self._process_action_key_event(event):
consumed_event = True
elif self._process_text_entry_key(event):
consumed_event = True

if self.is_enabled and self.is_focused and event.type == TEXTINPUT:
processed_any_char = False
for char in event.text:
if self._process_entered_character(char):
processed_any_char = True
consumed_event = processed_any_char

if self.html_text != initial_text_state:
# new event
Expand All @@ -262,8 +267,16 @@ def _process_action_key_event(self, event: Event) -> bool:
"""
consumed_event = False

if event.key == K_BACKSPACE:
if event.key == K_RETURN:
start_str = self.html_text[:self.edit_position]
end_str = self.html_text[self.edit_position:]
self.html_text = start_str + '\n' + end_str
self.text_box_layout.insert_line_break(self.edit_position, self.parser)
self.edit_position += 1
self.redraw_from_text_block()
self.cursor_has_moved_recently = True
consumed_event = True
elif event.key == K_BACKSPACE:
if abs(self.select_range[0] - self.select_range[1]) > 0:
self.text_box_layout.delete_selected_text()
low_end = min(self.select_range[0], self.select_range[1])
Expand Down Expand Up @@ -432,44 +445,40 @@ def _process_text_entry_key(self, event: Event) -> bool:
:return: True if consumed.
"""
consumed_event = False
if event.key == K_RETURN:
start_str = self.html_text[:self.edit_position]
end_str = self.html_text[self.edit_position:]
self.html_text = start_str + '\n' + end_str
self.text_box_layout.insert_line_break(self.edit_position, self.parser)
self.edit_position += 1
self.redraw_from_text_block()
self.cursor_has_moved_recently = True
consumed_event = True

elif hasattr(event, 'unicode'):
if hasattr(event, 'unicode'):
character = event.unicode
# here we really want to get the font metrics for the current active style where the edit cursor is
# instead we will make do for now
font = self.ui_theme.get_font(self.combined_element_ids)
char_metrics = font.get_metrics(character)
if len(char_metrics) > 0 and char_metrics[0] is not None:
valid_character = True
if valid_character:
if abs(self.select_range[0] - self.select_range[1]) > 0:
low_end = min(self.select_range[0], self.select_range[1])
high_end = max(self.select_range[0], self.select_range[1])
self.html_text = self.html_text[:low_end] + character + self.html_text[high_end:]
self.text_box_layout.delete_selected_text()
self.text_box_layout.insert_text(character, low_end, self.parser)
self.edit_position = low_end + 1
self.select_range = [0, 0]
else:
start_str = self.html_text[:self.edit_position]
end_str = self.html_text[self.edit_position:]
self.html_text = start_str + character + end_str
self.text_box_layout.insert_text(character, self.edit_position, self.parser)
self.edit_position += 1
self.redraw_from_text_block()
self.cursor_has_moved_recently = True
consumed_event = True
consumed_event = self._process_entered_character(character)
return consumed_event

def _process_entered_character(self, character: str) -> bool:
processed_char = False
# here we really want to get the font metrics for the current active style where the edit cursor is
# instead we will make do for now
font = self.ui_theme.get_font(self.combined_element_ids)
char_metrics = font.get_metrics(character)
if len(char_metrics) > 0 and char_metrics[0] is not None:
valid_character = True
if valid_character:
if abs(self.select_range[0] - self.select_range[1]) > 0:
low_end = min(self.select_range[0], self.select_range[1])
high_end = max(self.select_range[0], self.select_range[1])
self.html_text = self.html_text[:low_end] + character + self.html_text[high_end:]
self.text_box_layout.delete_selected_text()
self.text_box_layout.insert_text(character, low_end, self.parser)
self.edit_position = low_end + 1
self.select_range = [0, 0]
else:
start_str = self.html_text[:self.edit_position]
end_str = self.html_text[self.edit_position:]
self.html_text = start_str + character + end_str
self.text_box_layout.insert_text(character, self.edit_position, self.parser)
self.edit_position += 1
self.redraw_from_text_block()
self.cursor_has_moved_recently = True
processed_char = True
return processed_char

def _process_keyboard_shortcut_event(self, event: Event) -> bool:
"""
Check if event is one of the CTRL key keyboard shortcuts.
Expand Down
24 changes: 17 additions & 7 deletions pygame_gui/elements/ui_text_entry_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,13 @@ def process_event(self, event: pygame.event.Event) -> bool:
consumed_event = True
elif self._process_action_key_event(event):
consumed_event = True
elif self._process_text_entry_key(event):
consumed_event = True

if self.is_enabled and self.is_focused and event.type == pygame.TEXTINPUT:
processed_any_char = False
for char in event.text:
if self._process_entered_character(char):
processed_any_char = True
consumed_event = processed_any_char

if self.is_enabled and self.is_focused and event.type == pygame.KEYUP:
if event.key == pygame.K_RETURN or event.key == pygame.K_KP_ENTER:
Expand All @@ -429,22 +434,27 @@ def process_event(self, event: pygame.event.Event) -> bool:
return consumed_event

def _process_text_entry_key(self, event: pygame.event.Event) -> bool:
consumed_event = False
if hasattr(event, 'unicode'):
consumed_event = self._process_entered_character(event.unicode)
return consumed_event

def _process_entered_character(self, character: str) -> bool:
"""
Process key input that can be added to the text entry text.
:param event: The event to process.
:return: True if consumed.
"""
consumed_event = False
processed_character = False
within_length_limit = True
if (self.length_limit is not None
and (len(self.text) -
abs(self.select_range[0] -
self.select_range[1])) >= self.length_limit):
within_length_limit = False
if within_length_limit and hasattr(event, 'unicode') and self.font is not None:
character = event.unicode
if within_length_limit and self.font is not None:
char_metrics = self.font.get_metrics(character)
if len(char_metrics) > 0 and char_metrics[0] is not None:
valid_character = True
Expand Down Expand Up @@ -476,8 +486,8 @@ def _process_text_entry_key(self, event: pygame.event.Event) -> bool:

self.edit_position += 1
self.cursor_has_moved_recently = True
consumed_event = True
return consumed_event
processed_character = True
return processed_character

def _process_action_key_event(self, event: pygame.event.Event) -> bool:
"""
Expand Down
35 changes: 11 additions & 24 deletions tests/test_elements/test_ui_text_entry_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,17 +714,12 @@ def test_enable(self, _init_pygame: None, default_ui_manager: UIManager,
assert text_entry.is_enabled is True
text_entry.focus()
# process a mouse button down event
processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
{'key': pygame.K_d,
'mod': 0,
'unicode': 'd'}))
processed_text_input_event = text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'd'}))

text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_a, 'mod': 0,
'unicode': 'a'}))
text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_n, 'mod': 0,
'unicode': 'n'}))
text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'a'}))
text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'n'}))

assert processed_key_event is True and text_entry.get_text() == 'dan'
assert processed_text_input_event is True and text_entry.get_text() == 'dan'

def test_on_locale_changed(self, _init_pygame, default_ui_manager, _display_surface_return_none):
text_box = UITextEntryBox(initial_text='la la LA LA LAL LAL ALALA'
Expand Down Expand Up @@ -862,17 +857,12 @@ def test_process_event_text_entered_success(self, _init_pygame: None,

text_entry.focus()

processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
{'key': pygame.K_d,
'mod': 0,
'unicode': 'd'}))
processed_text_input_event = text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'd'}))

text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_a, 'mod': 0,
'unicode': 'a'}))
text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_n, 'mod': 0,
'unicode': 'n'}))
text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'a'}))
text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'n'}))

assert processed_key_event and text_entry.get_text() == 'dan'
assert processed_text_input_event and text_entry.get_text() == 'dan'

def test_process_event_text_entered_with_select_range(self, _init_pygame: None,
default_ui_manager: UIManager,
Expand All @@ -884,13 +874,10 @@ def test_process_event_text_entered_with_select_range(self, _init_pygame: None,
text_entry.focus()
text_entry.select_range = [1, 9]

# process a mouse button down event
processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
{'key': pygame.K_o,
'mod': 0,
'unicode': 'o'}))
# process a text input event
processed_text_input_event = text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'o'}))

assert (processed_key_event is True and
assert (processed_text_input_event is True and
text_entry.get_text() == 'Ho hours of fun writing tests')

def test_process_event_text_ctrl_c(self, _init_pygame: None,
Expand Down
50 changes: 16 additions & 34 deletions tests/test_elements/test_ui_text_entry_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,15 +262,10 @@ def test_process_event_text_entered_success(self, _init_pygame: None,

text_entry.focus()

processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
{'key': pygame.K_d,
'mod': 0,
'unicode': 'd'}))
processed_key_event = text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'd'}))

text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_a, 'mod': 0,
'unicode': 'a'}))
text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_n, 'mod': 0,
'unicode': 'n'}))
text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'a'}))
text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'n'}))

assert processed_key_event and text_entry.get_text() == 'dan'

Expand Down Expand Up @@ -318,13 +313,10 @@ def test_process_event_text_entered_with_select_range(self, _init_pygame: None,
text_entry.focus()
text_entry.select_range = [1, 9]

# process a mouse button down event
processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
{'key': pygame.K_o,
'mod': 0,
'unicode': 'o'}))
# process a text input event
processed_text_input_event = text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'o'}))

assert (processed_key_event is True and
assert (processed_text_input_event is True and
text_entry.get_text() == 'Ho hours of fun writing tests')

def test_process_event_text_entered_too_long(self, _init_pygame: None,
Expand All @@ -336,18 +328,13 @@ def test_process_event_text_entered_too_long(self, _init_pygame: None,
text_entry.set_text_length_limit(3)
text_entry.focus()

text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_t, 'mod': 0,
'unicode': 't'}))
text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_e, 'mod': 0,
'unicode': 'e'}))
text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_s, 'mod': 0,
'unicode': 's'}))
processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
{'key': pygame.K_s,
'mod': 0,
'unicode': 't'}))
text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 't'}))
text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'e'}))
text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 's'}))

assert processed_key_event is False and text_entry.get_text() == 'tes'
processed_text_input_event = text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 't'}))

assert processed_text_input_event is False and text_entry.get_text() == 'tes'

def test_process_event_text_ctrl_c(self, _init_pygame: None,
_display_surface_return_none: None):
Expand Down Expand Up @@ -1105,17 +1092,12 @@ def test_enable(self, _init_pygame: None, default_ui_manager: UIManager,
assert text_entry.is_enabled is True
text_entry.focus()
# process a mouse button down event
processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
{'key': pygame.K_d,
'mod': 0,
'unicode': 'd'}))
processed_text_input_event = text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'd'}))

text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_a, 'mod': 0,
'unicode': 'a'}))
text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_n, 'mod': 0,
'unicode': 'n'}))
text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'a'}))
text_entry.process_event(pygame.event.Event(pygame.TEXTINPUT, {'text': 'n'}))

assert processed_key_event is True and text_entry.get_text() == 'dan'
assert processed_text_input_event is True and text_entry.get_text() == 'dan'

def test_show(self, _init_pygame, default_ui_manager, _display_surface_return_none):
text_entry = UITextEntryLine(relative_rect=pygame.Rect(100, 100, 200, 30),
Expand Down

0 comments on commit d18bb6d

Please sign in to comment.