Repository: MyreMylar/pygame_gui
Pull request #528 (2024-03-03 20:03:43)

Improve the text box entry keyboard controls
also fix a couple of other minor bugs noticed in testing


Patch:
diff --git a/pygame_gui/core/text/text_box_layout.py b/pygame_gui/core/text/text_box_layout.py
index a07ad02..5ed5046 100644
--- a/pygame_gui/core/text/text_box_layout.py
+++ b/pygame_gui/core/text/text_box_layout.py
@@ -96,7 +96,7 @@ class TextBoxLayout:
             self._add_row_to_layout(current_row, True)
 
         self.edit_buffer = 2
-        self.cursor_text_row = None
+        self.cursor_text_row: Optional[TextBoxLayoutRow] = None
         self.last_horiz_cursor_row_pos = 0
 
         self.selection_colour = pygame.Color(128, 128, 200, 255)
@@ -585,6 +585,29 @@ class TextBoxLayout:
             row.vert_align_items_to_row()
             new_y -= row.height
 
+    def set_cursor_to_end_of_current_row(self):
+        """
+        Set the edit cursor position in the text layout to the end of the current row and returns the overall
+        position in the text
+
+        :return: the overall position of the cursor in the text layout, after setting it to the end of the current row
+        """
+        if self.cursor_text_row is not None:
+            is_last_row = self.cursor_text_row == self.layout_rows[-1]
+            self.cursor_text_row.set_cursor_to_end(is_last_row)
+        return self.get_cursor_index()
+
+    def set_cursor_to_start_of_current_row(self):
+        """
+        Set the edit cursor position in the text layout to the end of the current row and returns the overall
+        position in the text
+
+        :return: the overall position of the cursor in the text layout, after setting it to the end of the current row
+        """
+        if self.cursor_text_row is not None:
+            self.cursor_text_row.set_cursor_to_start()
+        return self.get_cursor_index()
+
     def set_cursor_position(self, cursor_pos):
         """
         Set the edit cursor position in the text layout.
diff --git a/pygame_gui/core/text/text_box_layout_row.py b/pygame_gui/core/text/text_box_layout_row.py
index 10590d4..bfb452c 100644
--- a/pygame_gui/core/text/text_box_layout_row.py
+++ b/pygame_gui/core/text/text_box_layout_row.py
@@ -470,6 +470,23 @@ class TextBoxLayoutRow(pygame.Rect):
 
         self._setup_offset_position_from_edit_cursor()
 
+    def set_cursor_to_end(self, is_last_row):
+        end_pos = self.letter_count
+        # we need to ignore the trailing space on line-wrapped rows,
+        # or we will spill over to the row below
+        if not is_last_row and len(self.items) > 0:
+            last_chunk = self.items[-1]
+            if (isinstance(last_chunk, TextLineChunkFTFont) and
+                    (len(last_chunk.text) > 0 and last_chunk.text[-1] == " ")):
+                end_pos -= 1
+            if isinstance(last_chunk, LineBreakLayoutRect):
+                end_pos -= 1
+        end_pos = max(0, end_pos)
+        self.set_cursor_position(end_pos)
+
+    def set_cursor_to_start(self):
+        self.set_cursor_position(0)
+
     def get_cursor_index(self) -> int:
         """
         Get the current character index of the cursor
diff --git a/pygame_gui/core/ui_element.py b/pygame_gui/core/ui_element.py
index 5eef478..98c7151 100644
--- a/pygame_gui/core/ui_element.py
+++ b/pygame_gui/core/ui_element.py
@@ -556,11 +556,19 @@ class UIElement(GUISprite, IUIElementInterface):
 
         self.rect.left = new_left
         self.rect.top = new_top
-        new_height = new_bottom - new_top
-        new_width = new_right - new_left
+        if self.dynamic_height:
+            new_height = new_bottom - new_top
+        else:
+            new_height = max(0, new_bottom - new_top)
+
+        if self.dynamic_width:
+            new_width = new_right - new_left
+        else:
+            new_width = max(0, new_right - new_left)
+
         new_width, new_height = self._get_clamped_to_minimum_dimensions((new_width, new_height))
         if (new_height != self.relative_rect.height) or (new_width != self.relative_rect.width):
-            self._set_dimensions((new_width, new_height))
+            self.set_dimensions((new_width, new_height))
 
     def _update_relative_rect_position_from_anchors(self, recalculate_margins=False):
         """
@@ -764,7 +772,7 @@ class UIElement(GUISprite, IUIElementInterface):
                 (self.rect.height < self.minimum_dimensions[1])):
             new_width = max(self.minimum_dimensions[0], self.rect.width)
             new_height = max(self.minimum_dimensions[1], self.rect.height)
-            self._set_dimensions((new_width, new_height))
+            self.set_dimensions((new_width, new_height))
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
@@ -800,9 +808,9 @@ class UIElement(GUISprite, IUIElementInterface):
             self._set_dimensions(dimensions, clamp_to_container)
 
     def _set_dimensions(self, dimensions: Union[pygame.math.Vector2,
-                                               Tuple[int, int],
-                                               Tuple[float, float]],
-                       clamp_to_container: bool = False):
+                                                Tuple[int, int],
+                                                Tuple[float, float]],
+                        clamp_to_container: bool = False):
         """
         Method to directly set the dimensions of an element.
         Dimensions must be positive values.
diff --git a/pygame_gui/data/default_theme.json b/pygame_gui/data/default_theme.json
index 5b5843f..2ec33eb 100644
--- a/pygame_gui/data/default_theme.json
+++ b/pygame_gui/data/default_theme.json
@@ -231,6 +231,13 @@
             "rect_width": "170"
         }
     },
+    "file_dialog.button.tool_tip":
+    {
+        "misc":
+        {
+            "rect_width": "-1"
+        }
+    },
     "vertical_scroll_bar":
     {
         "prototype": "#default_shape_style"
diff --git a/pygame_gui/elements/ui_text_box.py b/pygame_gui/elements/ui_text_box.py
index 61d493f..aa35f0c 100644
--- a/pygame_gui/elements/ui_text_box.py
+++ b/pygame_gui/elements/ui_text_box.py
@@ -588,7 +588,7 @@ class UITextBox(UIElement, IUITextOwnerInterface):
                                              text_direction=self.parser.default_style['direction'])
         self.text_box_layout.set_cursor_colour(self.text_cursor_colour)
         self.parser.empty_layout_queue()
-        if not self.dynamic_height:
+        if self.dynamic_height:
             self.text_box_layout.view_rect.height = self.text_box_layout.layout_rect.height
 
         self._align_all_text_rows()
diff --git a/pygame_gui/elements/ui_text_entry_box.py b/pygame_gui/elements/ui_text_entry_box.py
index c9c8f4a..35fd268 100644
--- a/pygame_gui/elements/ui_text_entry_box.py
+++ b/pygame_gui/elements/ui_text_entry_box.py
@@ -132,6 +132,9 @@ class UITextEntryBox(UITextBox):
         super().focus()
         key.set_repeat(500, 25)
 
+        self.text_box_layout.set_cursor_position(self.edit_position)
+        self.cursor_has_moved_recently = True
+
     def update(self, time_delta: float):
         """
         Called every update loop of our UI Manager. Largely handles text drag selection and
@@ -338,84 +341,49 @@ class UITextEntryBox(UITextBox):
 
         """
         consumed_event = False
-        # modded versions of LEFT and RIGHT must go first otherwise the simple
-        # cases will absorb the events
-        if (event.key == K_HOME or
-                (event.key == K_LEFT and
-                 (event.mod & KMOD_CTRL or event.mod & KMOD_META))):
-            try:
-                next_pos = self.edit_position - self.html_text[:self.edit_position][::-1].index("\n")
-            except ValueError:
-                next_pos = 0
-            # include case when shift held down to select everything to start
-            # of current line.
-            if abs(self.select_range[0] - self.select_range[1]) > 0:
-                if event.mod & KMOD_SHIFT:
-                    if self.edit_position == self.select_range[1]:
-                        # undo selection to right, create to left
-                        self.select_range = [next_pos, self.select_range[0]]
-                    else:
-                        # extend left
-                        self.select_range = [next_pos, self.select_range[1]]
-                else:
-                    self.select_range = [0, 0]
+        # 4 left/right cases:
+        # 1. jump left or right one character (LEFT or RIGHT on their own)
+        # 2. jump to end/start of word (CTRL + LEFT or RIGHT)
+        # 3. jump to end/start of line (HOME or END)
+        # 4. jump to end/start of document (CTRL + HOME or END)
+        # plus 1 up or down a line/text row case (UP & DOWN arrows, CTRL makes no difference here in notepad,
+        # but in IDE it scrolls if possible)
+        # All cases can have selection attached with shift held
+
+        if event.key == K_LEFT:
+            if event.mod & KMOD_CTRL or event.mod & KMOD_META:
+                self._jump_edit_pos_to_start_of_word(should_select=(event.mod & KMOD_SHIFT))
             else:
-                if event.mod & KMOD_SHIFT:
-                    self.select_range = [next_pos, self.edit_position]
-                else:
-                    self.select_range = [0, 0]
-            self.edit_position = next_pos
-            self.cursor_has_moved_recently = True
+                self._jump_edit_pos_one_character_left(should_select=(event.mod & KMOD_SHIFT))
             consumed_event = True
-        elif (event.key == K_END or
-              (event.key == K_RIGHT and
-               (event.mod & KMOD_CTRL or event.mod & KMOD_META))):
-            try:
-                next_pos = self.edit_position + self.html_text[self.edit_position:].index("\n")
-            except ValueError:
-                next_pos = len(self.html_text)
-            # include case when shift held down to select everything to end
-            # of current line.
-            if abs(self.select_range[0] - self.select_range[1]) > 0:
-                if event.mod & KMOD_SHIFT:
-                    if self.edit_position == self.select_range[0]:
-                        # undo selection to left, create to right
-                        self.select_range = [self.select_range[1], next_pos]
-                    else:
-                        # extend right
-                        self.select_range = [self.select_range[0], next_pos]
-                else:
-                    self.select_range = [0, 0]
+        elif event.key == K_RIGHT:
+            if event.mod & KMOD_CTRL or event.mod & KMOD_META:
+                self._jump_edit_pos_to_end_of_word(should_select=(event.mod & KMOD_SHIFT))
             else:
-                if event.mod & KMOD_SHIFT:
-                    self.select_range = [self.edit_position, next_pos]
-                else:
-                    self.select_range = [0, 0]
-            self.edit_position = next_pos
-            self.cursor_has_moved_recently = True
+                self._jump_edit_pos_one_character_right(should_select=(event.mod & KMOD_SHIFT))
             consumed_event = True
-        elif event.key == K_LEFT and event.mod & KMOD_SHIFT:
-            # keyboard-based selection
-            if abs(self.select_range[0] - self.select_range[1]) > 0:
-                # existing selection, so edit_position should correspond to one
-                # end of it
-                if self.edit_position == self.select_range[0]:
-                    # try to extend to the left
-                    self.select_range = [max(0, self.edit_position - 1),
-                                         self.select_range[1]]
-                elif self.edit_position == self.select_range[1]:
-                    # reduce to the left
-                    self.select_range = [self.select_range[0],
-                                         max(0, self.edit_position - 1)]
+        elif event.key == K_UP:
+            self._jump_edit_pos_one_row_up(should_select=(event.mod & KMOD_SHIFT))
+            consumed_event = True
+        elif event.key == K_DOWN:
+            self._jump_edit_pos_one_row_down(should_select=(event.mod & KMOD_SHIFT))
+            consumed_event = True
+        elif event.key == K_HOME:
+            if event.mod & KMOD_CTRL or event.mod & KMOD_META:
+                self._jump_edit_pos_to_start_of_all_text(should_select=(event.mod & KMOD_SHIFT))
             else:
-                self.select_range = [max(0, self.edit_position - 1),
-                                     self.edit_position]
-            if self.edit_position > 0:
-                self.edit_position -= 1
-                self.cursor_has_moved_recently = True
+                self._jump_edit_pos_to_start_of_line(should_select=(event.mod & KMOD_SHIFT))
+            consumed_event = True
+        elif event.key == K_END:
+            if event.mod & KMOD_CTRL or event.mod & KMOD_META:
+                self._jump_edit_pos_to_end_of_all_text(should_select=(event.mod & KMOD_SHIFT))
+            else:
+                self._jump_edit_pos_to_end_of_line(should_select=(event.mod & KMOD_SHIFT))
             consumed_event = True
-        elif event.key == K_RIGHT and event.mod & KMOD_SHIFT:
-            # keyboard-based selection
+        return consumed_event
+
+    def _jump_edit_pos_one_character_right(self, should_select=False):
+        if should_select:
             if abs(self.select_range[0] - self.select_range[1]) > 0:
                 if self.edit_position == self.select_range[1]:
                     # try to extend to the right
@@ -431,29 +399,45 @@ class UITextEntryBox(UITextBox):
             if self.edit_position < len(self.html_text):
                 self.edit_position += 1
                 self.cursor_has_moved_recently = True
-            consumed_event = True
-        elif event.key == K_UP and event.mod & KMOD_SHIFT:
-            # keyboard-based selection
-            new_pos = self.text_box_layout.get_cursor_pos_move_up_one_row(self.last_horiz_cursor_index)
+        else:
+            if abs(self.select_range[0] - self.select_range[1]) > 0:
+                self.edit_position = max(self.select_range[0], self.select_range[1])
+                self.select_range = [0, 0]
+                self.cursor_has_moved_recently = True
+            elif self.edit_position < len(self.html_text):
+                self.edit_position += 1
+                self.cursor_has_moved_recently = True
+
+    def _jump_edit_pos_one_character_left(self, should_select=False):
+        if should_select:
             if abs(self.select_range[0] - self.select_range[1]) > 0:
                 # existing selection, so edit_position should correspond to one
                 # end of it
                 if self.edit_position == self.select_range[0]:
                     # try to extend to the left
-                    self.select_range = [max(0, new_pos),
+                    self.select_range = [max(0, self.edit_position - 1),
                                          self.select_range[1]]
                 elif self.edit_position == self.select_range[1]:
                     # reduce to the left
                     self.select_range = [self.select_range[0],
-                                         max(0, new_pos)]
+                                         max(0, self.edit_position - 1)]
             else:
-                self.select_range = [max(0, new_pos),
+                self.select_range = [max(0, self.edit_position - 1),
                                      self.edit_position]
             if self.edit_position > 0:
-                self.edit_position = new_pos
+                self.edit_position -= 1
                 self.cursor_has_moved_recently = True
-        elif event.key == K_DOWN and event.mod & KMOD_SHIFT:
-            # keyboard-based selection
+        else:
+            if abs(self.select_range[0] - self.select_range[1]) > 0:
+                self.edit_position = min(self.select_range[0], self.select_range[1])
+                self.select_range = [0, 0]
+                self.cursor_has_moved_recently = True
+            elif self.edit_position > 0:
+                self.edit_position -= 1
+                self.cursor_has_moved_recently = True
+
+    def _jump_edit_pos_one_row_down(self, should_select=False):
+        if should_select:
             new_pos = self.text_box_layout.get_cursor_pos_move_down_one_row(self.last_horiz_cursor_index)
             if abs(self.select_range[0] - self.select_range[1]) > 0:
                 if self.edit_position == self.select_range[1]:
@@ -470,26 +454,37 @@ class UITextEntryBox(UITextBox):
             if self.edit_position < len(self.html_text):
                 self.edit_position = new_pos
                 self.cursor_has_moved_recently = True
-            consumed_event = True
-        elif event.key == K_LEFT:
+        else:
             if abs(self.select_range[0] - self.select_range[1]) > 0:
-                self.edit_position = min(self.select_range[0], self.select_range[1])
+                self.edit_position = self.text_box_layout.get_cursor_pos_move_down_one_row(self.last_horiz_cursor_index)
                 self.select_range = [0, 0]
                 self.cursor_has_moved_recently = True
-            elif self.edit_position > 0:
-                self.edit_position -= 1
+            else:
+                self.edit_position = self.text_box_layout.get_cursor_pos_move_down_one_row(self.last_horiz_cursor_index)
                 self.cursor_has_moved_recently = True
-            consumed_event = True
-        elif event.key == K_RIGHT:
+            self.vertical_cursor_movement = True
+
+    def _jump_edit_pos_one_row_up(self, should_select=False):
+        if should_select:
+            new_pos = self.text_box_layout.get_cursor_pos_move_up_one_row(self.last_horiz_cursor_index)
             if abs(self.select_range[0] - self.select_range[1]) > 0:
-                self.edit_position = max(self.select_range[0], self.select_range[1])
-                self.select_range = [0, 0]
-                self.cursor_has_moved_recently = True
-            elif self.edit_position < len(self.html_text):
-                self.edit_position += 1
+                # existing selection, so edit_position should correspond to one
+                # end of it
+                if self.edit_position == self.select_range[0]:
+                    # try to extend to the left
+                    self.select_range = [max(0, new_pos),
+                                         self.select_range[1]]
+                elif self.edit_position == self.select_range[1]:
+                    # reduce to the left
+                    self.select_range = [self.select_range[0],
+                                         max(0, new_pos)]
+            else:
+                self.select_range = [max(0, new_pos),
+                                     self.edit_position]
+            if self.edit_position > 0:
+                self.edit_position = new_pos
                 self.cursor_has_moved_recently = True
-            consumed_event = True
-        elif event.key == K_UP:
+        else:
             if abs(self.select_range[0] - self.select_range[1]) > 0:
                 self.edit_position = self.text_box_layout.get_cursor_pos_move_up_one_row(self.last_horiz_cursor_index)
                 self.select_range = [0, 0]
@@ -498,30 +493,147 @@ class UITextEntryBox(UITextBox):
                 self.edit_position = self.text_box_layout.get_cursor_pos_move_up_one_row(self.last_horiz_cursor_index)
                 self.cursor_has_moved_recently = True
             self.vertical_cursor_movement = True
-            consumed_event = True
-        elif event.key == K_DOWN:
-            if abs(self.select_range[0] - self.select_range[1]) > 0:
-                self.edit_position = self.text_box_layout.get_cursor_pos_move_down_one_row(self.last_horiz_cursor_index)
+
+    def _jump_edit_pos_to_start_of_word(self, should_select=False):
+        try:
+            text_to_search = self.html_text[max(0, self.edit_position-1)::-1]
+            match_result = re.search(r'\b\w+\b', text_to_search)
+            if match_result is not None:
+                next_pos = max(self.edit_position - match_result.regs[-1][1], 0)
+            else:
+                next_pos = self.edit_position
+        except ValueError:
+            next_pos = 0
+        # include case when shift held down to select everything to start
+        # of current line.
+        if abs(self.select_range[0] - self.select_range[1]) > 0:
+            if should_select:
+                if self.edit_position == self.select_range[1]:
+                    # undo selection to right, create to left
+                    self.select_range = [next_pos, self.select_range[0]]
+                else:
+                    # extend left
+                    self.select_range = [next_pos, self.select_range[1]]
+            else:
+                next_pos = self.select_range[0]
                 self.select_range = [0, 0]
-                self.cursor_has_moved_recently = True
+        else:
+            if should_select:
+                self.select_range = [next_pos, self.edit_position]
             else:
-                self.edit_position = self.text_box_layout.get_cursor_pos_move_down_one_row(self.last_horiz_cursor_index)
-                self.cursor_has_moved_recently = True
-            self.vertical_cursor_movement = True
-            consumed_event = True
-        elif event.key == K_HOME:
+                self.select_range = [0, 0]
+        self.edit_position = next_pos
+        self.cursor_has_moved_recently = True
+
+    def _jump_edit_pos_to_end_of_word(self, should_select=False):
+        try:
+            match_result = re.search(r'\b\w+\b', self.html_text[self.edit_position:])
+            if match_result is not None:
+                next_pos = min(self.edit_position + match_result.regs[0][1], len(self.html_text))
+            else:
+                next_pos = self.edit_position
+        except ValueError:
+            next_pos = len(self.html_text)
+        # include case when shift held down to select everything to end
+        # of current line.
+        if abs(self.select_range[0] - self.select_range[1]) > 0:
+            if should_select:
+                if self.edit_position == self.select_range[0]:
+                    # undo selection to left, create to right
+                    self.select_range = [self.select_range[1], next_pos]
+                else:
+                    # extend right
+                    self.select_range = [self.select_range[0], next_pos]
+            else:
+                self.select_range = [0, 0]
+        else:
+            if should_select:
+                self.select_range = [self.edit_position, next_pos]
+            else:
+                self.select_range = [0, 0]
+        self.edit_position = next_pos
+        self.cursor_has_moved_recently = True
+
+    def _jump_edit_pos_to_start_of_line(self, should_select=False):
+        try:
+            next_pos = self.text_box_layout.set_cursor_to_start_of_current_row()
+        except ValueError:
+            next_pos = 0
+        # include case when shift held down to select everything to start
+        # of current line.
+        if abs(self.select_range[0] - self.select_range[1]) > 0:
+            if should_select:
+                if self.edit_position == self.select_range[1]:
+                    # undo selection to right, create to left
+                    self.select_range = [next_pos, self.select_range[0]]
+                else:
+                    # extend left
+                    self.select_range = [next_pos, self.select_range[1]]
+            else:
+                self.select_range = [0, 0]
+        else:
+            if should_select:
+                self.select_range = [next_pos, self.edit_position]
+            else:
+                self.select_range = [0, 0]
+        self.edit_position = next_pos
+        self.cursor_has_moved_recently = True
+
+    def _jump_edit_pos_to_end_of_line(self, should_select=False):
+        try:
+            next_pos = self.text_box_layout.set_cursor_to_end_of_current_row()
+        except ValueError:
+            next_pos = len(self.html_text)
+        # include case when shift held down to select everything to end
+        # of current line.
+        if abs(self.select_range[0] - self.select_range[1]) > 0:
+            if should_select:
+                if self.edit_position == self.select_range[0]:
+                    # undo selection to left, create to right
+                    self.select_range = [self.select_range[1], next_pos]
+                else:
+                    # extend right
+                    self.select_range = [self.select_range[0], next_pos]
+            else:
+                self.select_range = [0, 0]
+        else:
+            if should_select:
+                self.select_range = [self.edit_position, next_pos]
+            else:
+                self.select_range = [0, 0]
+        self.edit_position = next_pos
+        self.cursor_has_moved_recently = True
+
+    def _jump_edit_pos_to_start_of_all_text(self, should_select=False):
+        if should_select:
+            if abs(self.select_range[0] - self.select_range[1]) > 0:
+                if self.edit_position == self.select_range[0]:
+                    self.select_range = [0, self.select_range[1]]
+                else:
+                    self.select_range = [0, self.select_range[0]]
+            else:
+                self.select_range = [0, self.edit_position]
+        else:
             if abs(self.select_range[0] - self.select_range[1]) > 0:
                 self.select_range = [0, 0]
-            self.edit_position = 0
-            self.cursor_has_moved_recently = True
-            consumed_event = True
-        elif event.key == K_END:
+        self.edit_position = 0
+        self.cursor_has_moved_recently = True
+
+    def _jump_edit_pos_to_end_of_all_text(self, should_select=False):
+        end_of_all_pos = len(self.html_text)
+        if should_select:
+            if abs(self.select_range[0] - self.select_range[1]) > 0:
+                if self.edit_position == self.select_range[0]:
+                    self.select_range = [self.select_range[1], end_of_all_pos]
+                else:
+                    self.select_range = [self.select_range[0], end_of_all_pos]
+            else:
+                self.select_range = [self.edit_position, end_of_all_pos]
+        else:
             if abs(self.select_range[0] - self.select_range[1]) > 0:
                 self.select_range = [0, 0]
-            self.edit_position = len(self.html_text)
-            self.cursor_has_moved_recently = True
-            consumed_event = True
-        return consumed_event
+        self.edit_position = end_of_all_pos
+        self.cursor_has_moved_recently = True
 
     def _process_mouse_button_event(self, event: Event) -> bool:
         """
diff --git a/pygame_gui/windows/ui_file_dialog.py b/pygame_gui/windows/ui_file_dialog.py
index 8ac1a43..7764439 100644
--- a/pygame_gui/windows/ui_file_dialog.py
+++ b/pygame_gui/windows/ui_file_dialog.py
@@ -70,6 +70,8 @@ class UIFileDialog(UIWindow):
         self.current_file_path = None  # type: Union[Path, None]
         if allowed_suffixes is None:
             self.allowed_suffixes = {""}
+        else:
+            self.allowed_suffixes = allowed_suffixes
         if initial_file_path is not None:
             pathed_initial_file_path = Path(initial_file_path)
             if pathed_initial_file_path.exists() and not pathed_initial_file_path.is_file():


Test patch:
diff --git a/tests/test_elements/test_ui_text_entry_box.py b/tests/test_elements/test_ui_text_entry_box.py
index a5c5acb..aa88e90 100644
--- a/tests/test_elements/test_ui_text_entry_box.py
+++ b/tests/test_elements/test_ui_text_entry_box.py
@@ -1170,6 +1170,87 @@ class TestUITextEntryBox:
                                                                            'mod': 0}))
 
         assert processed_key_event
+        assert text_entry.edit_position == 3
+
+        text_entry.edit_position = 1
+        text_entry.focus()
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_RIGHT,
+                                                                           'mod': pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [1, 2]
+        assert text_entry.edit_position == 2
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_RIGHT,
+                                                                           'mod': pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [1, 3]
+        assert text_entry.edit_position == 3
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_LEFT,
+                                                                           'mod': pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [1, 2]
+        assert text_entry.edit_position == 2
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_RIGHT,
+                                                                           'mod': 0}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 0]
+        assert text_entry.edit_position == 2
+
+        text_entry.edit_position = 0
+        text_entry.focus()
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_RIGHT,
+                                                                           'mod': pygame.KMOD_CTRL}))
+
+        assert processed_key_event
+        assert text_entry.edit_position == 3
+
+        text_entry.edit_position = 0
+        text_entry.focus()
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_RIGHT,
+                                                                           'mod': pygame.KMOD_CTRL | pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 3]
+        assert text_entry.edit_position == 3
+
+        text_entry.set_text('dan dan')
+        text_entry.edit_position = 0
+        text_entry.select_range = [0, 0]
+        text_entry.focus()
+
+        text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_RIGHT,
+                                                                     'mod': pygame.KMOD_CTRL | pygame.KMOD_SHIFT}))
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_RIGHT,
+                                                                           'mod': pygame.KMOD_CTRL | pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 7]
+        assert text_entry.edit_position == 7
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_RIGHT,
+                                                                           'mod': pygame.KMOD_CTRL}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 0]
+        assert text_entry.edit_position == 7
 
     def test_process_event_text_left(self, _init_pygame: None, default_ui_manager: UIManager,
                                      _display_surface_return_none: None):
@@ -1187,6 +1268,153 @@ class TestUITextEntryBox:
         assert processed_key_event
         assert text_entry.edit_position == 2
 
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_LEFT,
+                                                                           'mod': pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [1, 2]
+        assert text_entry.edit_position == 1
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_LEFT,
+                                                                           'mod': pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 2]
+        assert text_entry.edit_position == 0
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_RIGHT,
+                                                                           'mod': pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [1, 2]
+        assert text_entry.edit_position == 1
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_LEFT,
+                                                                           'mod': 0}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 0]
+        assert text_entry.edit_position == 1
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_LEFT,
+                                                                           'mod': pygame.KMOD_CTRL}))
+
+        assert processed_key_event
+        assert text_entry.edit_position == 0
+
+        text_entry.edit_position = 3
+        text_entry.focus()
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_LEFT,
+                                                                           'mod': pygame.KMOD_CTRL | pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 3]
+        assert text_entry.edit_position == 0
+
+        text_entry.set_text('dan dan')
+        text_entry.edit_position = 7
+        text_entry.select_range = [0, 0]
+        text_entry.focus()
+
+        text_entry.process_event(pygame.event.Event(pygame.KEYDOWN, {'key': pygame.K_LEFT,
+                                                                     'mod': pygame.KMOD_CTRL | pygame.KMOD_SHIFT}))
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_LEFT,
+                                                                           'mod': pygame.KMOD_CTRL | pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 7]
+        assert text_entry.edit_position == 0
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_LEFT,
+                                                                           'mod': pygame.KMOD_CTRL}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 0]
+        assert text_entry.edit_position == 0
+
+    def test_process_event_text_down(self, _init_pygame: None, default_ui_manager: UIManager,
+                                     _display_surface_return_none: None):
+        text_entry = UITextEntryBox(relative_rect=pygame.Rect(100, 100, 200, 30),
+                                    manager=default_ui_manager)
+
+        text_entry.set_text('daniel\nmulti-line\ntext')
+        text_entry.edit_position = 3
+        text_entry.focus()
+        text_entry.update(0.1)
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_DOWN,
+                                                                           'mod': 0}))
+
+        assert processed_key_event
+        assert text_entry.edit_position == 10
+
+        text_entry.edit_position = 3
+        text_entry.focus()
+        text_entry.update(0.1)
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_DOWN,
+                                                                           'mod': pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [3, 10]
+        assert text_entry.edit_position == 10
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_DOWN,
+                                                                           'mod': 0}))
+        assert text_entry.select_range == [0, 0]
+        assert processed_key_event
+        assert text_entry.edit_position == 10
+
+    def test_process_event_text_up(self, _init_pygame: None, default_ui_manager: UIManager,
+                                   _display_surface_return_none: None):
+        text_entry = UITextEntryBox(relative_rect=pygame.Rect(100, 100, 200, 30),
+                                    manager=default_ui_manager)
+
+        text_entry.set_text('daniel\nmulti-line\ntext')
+        text_entry.edit_position = 10
+        text_entry.focus()
+        text_entry.update(0.1)
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_UP,
+                                                                           'mod': 0}))
+
+        assert processed_key_event
+        assert text_entry.edit_position == 3
+
+        text_entry.edit_position = 10
+        text_entry.focus()
+        text_entry.update(0.1)
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_UP,
+                                                                           'mod': pygame.KMOD_SHIFT}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [3, 10]
+        assert text_entry.edit_position == 3
+
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_UP,
+                                                                           'mod': 0}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 0]
+        assert text_entry.edit_position == 3
+
     def test_process_event_home(self, _init_pygame: None, default_ui_manager: UIManager,
                                 _display_surface_return_none: None):
         text_entry = UITextEntryBox(relative_rect=pygame.Rect(100, 100, 200, 30),
@@ -1204,6 +1432,14 @@ class TestUITextEntryBox:
         assert text_entry.select_range == [0, 0]
         assert text_entry.edit_position == 0
 
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_HOME,
+                                                                           'mod': pygame.KMOD_CTRL}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 0]
+        assert text_entry.edit_position == 0
+
         text_entry.set_text('daniel\nmulti-line\ntext')
         text_entry.edit_position = 3
         text_entry.focus()
@@ -1217,6 +1453,14 @@ class TestUITextEntryBox:
         assert text_entry.select_range == [0, 1]
         assert text_entry.edit_position == 0
 
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_HOME,
+                                                                           'mod': pygame.KMOD_SHIFT | pygame.KMOD_CTRL}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 1]
+        assert text_entry.edit_position == 0
+
     def test_process_event_end(self, _init_pygame: None, default_ui_manager: UIManager,
                                _display_surface_return_none: None):
         text_entry = UITextEntryBox(relative_rect=pygame.Rect(100, 100, 200, 30),
@@ -1234,6 +1478,14 @@ class TestUITextEntryBox:
         assert text_entry.select_range == [0, 0]
         assert text_entry.edit_position == 3
 
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_END,
+                                                                           'mod': pygame.KMOD_CTRL}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [0, 0]
+        assert text_entry.edit_position == 3
+
         text_entry.set_text('daniel\nmulti-line\ntext')
         text_entry.edit_position = 3
         text_entry.focus()
@@ -1247,6 +1499,14 @@ class TestUITextEntryBox:
         assert text_entry.select_range == [1, len("daniel")]
         assert text_entry.edit_position == len("daniel")
 
+        processed_key_event = text_entry.process_event(pygame.event.Event(pygame.KEYDOWN,
+                                                                          {'key': pygame.K_END,
+                                                                           'mod': pygame.KMOD_SHIFT | pygame.KMOD_CTRL}))
+
+        assert processed_key_event
+        assert text_entry.select_range == [1, len("daniel\nmulti-line\ntext")]
+        assert text_entry.edit_position == len("daniel\nmulti-line\ntext")
+
     def test_process_event_text_right_select_range(self, _init_pygame: None,
                                                    default_ui_manager: UIManager,
                                                    _display_surface_return_none: None):