Repository: MyreMylar/pygame_gui
Pull request #284 (2022-07-23 09:15:41)

Add capability to add & remove items/options to already existing menus & lists
Of course, we don't have any removal options yet. Still easier usually to just recreate the list or dropdown, most of the same functions get called in either case.


Patch:
diff --git a/pygame_gui/elements/ui_drop_down_menu.py b/pygame_gui/elements/ui_drop_down_menu.py
index f6ffe0b..a4e0ad4 100644
--- a/pygame_gui/elements/ui_drop_down_menu.py
+++ b/pygame_gui/elements/ui_drop_down_menu.py
@@ -694,6 +694,39 @@ class UIDropDownMenu(UIContainer):
         self.current_state = self.menu_states['closed']
         self.current_state.start(should_rebuild=True)
 
+    def add_options(self, new_options: List[str]) -> None:
+        """
+        Add new options to the drop down. Will close the drop down if it is currently open.
+
+        In many cases it may be easier just to recreate the drop down with whatever the new options list is.
+
+        :param new_options: The list of new options to add.
+        """
+        self.options_list.extend(new_options)
+        self.menu_states['expanded'].options_list = self.options_list
+
+        # if we have the dropdown open, close it so it can be reopened with the new options in place
+        self._close_dropdown_if_open()
+
+    def remove_options(self, options_to_remove: List[str]) -> None:
+        """
+        Will remove all instances of the options provided.
+
+        :param options_to_remove: The list of new options to remove.
+        """
+        self.options_list = [option for option in self.options_list if option not in options_to_remove]
+        self.menu_states['expanded'].options_list = self.options_list
+
+        # if we have the dropdown open, close it so it can be reopened with the new options in place
+        self._close_dropdown_if_open()
+
+    def _close_dropdown_if_open(self):
+        if self.current_state == self.menu_states['expanded']:
+            self.current_state.finish()
+            self.current_state = self.menu_states['closed']
+            self.current_state.selected_option = self.selected_option
+            self.current_state.start()
+
     def kill(self):
         """
         Overrides the standard sprite kill to also properly kill/finish the current state of the
diff --git a/pygame_gui/elements/ui_selection_list.py b/pygame_gui/elements/ui_selection_list.py
index 20c07df..f47d9dd 100644
--- a/pygame_gui/elements/ui_selection_list.py
+++ b/pygame_gui/elements/ui_selection_list.py
@@ -108,6 +108,26 @@ class UISelectionList(UIElement):
         if self._default_selection is not None:
             self.set_default_selection()
 
+    def add_items(self, new_items: Union[List[str], List[Tuple[str, str]]]) -> None:
+        """
+        Add any number of new items to the selection list. Uses the same format
+        as when the list is first created.
+
+        :param new_items: the list of new items to add
+        """
+        self._raw_item_list.extend(new_items)
+        self.set_item_list(self._raw_item_list)
+
+    def remove_items(self, items_to_remove: Union[List[str], List[Tuple[str, str]]]) -> None:
+        """
+        Will remove all instances of the items provided. The full tuple is required for items with a
+        display name and an object ID.
+
+        :param items_to_remove: The list of new options to remove.
+        """
+        self._raw_item_list = [item for item in self._raw_item_list if item not in items_to_remove]
+        self.set_item_list(self._raw_item_list)
+
     def get_single_selection(self) -> Union[str, None]:
         """
         Get the selected item in a list, if any. Only works if this is a single-selection list.
@@ -326,7 +346,7 @@ class UISelectionList(UIElement):
         :raise ValueError: Throw an exception if a list is used for the default for a
                            single-selection list, or if the default value(s) requested is/are not
                            present in the options list.
-        
+
         :raise TypeError: Throw an exception if anything other than a string or a (str, str) tuple
                           is encountered in the requested defaults.
 


Test patch:
diff --git a/tests/test_elements/test_ui_drop_down_menu.py b/tests/test_elements/test_ui_drop_down_menu.py
index 2f9eb79..162a5ef 100644
--- a/tests/test_elements/test_ui_drop_down_menu.py
+++ b/tests/test_elements/test_ui_drop_down_menu.py
@@ -20,6 +20,48 @@ class TestUIDropDownMenu:
                               manager=default_ui_manager)
         assert menu.image is not None
 
+    def test_addition(self, _init_pygame, default_ui_manager,
+                      _display_surface_return_none):
+        menu = UIDropDownMenu(options_list=['eggs', 'flour', 'sugar'],
+                              starting_option='eggs',
+                              relative_rect=pygame.Rect(100, 100, 200, 30),
+                              manager=default_ui_manager)
+
+        menu.add_options(['spam', 'bad spam'])
+
+        assert menu.options_list[-1] == 'bad spam'
+
+        menu.current_state.should_transition = True
+        menu.update(0.01)
+
+        assert menu.current_state == menu.menu_states['expanded']
+
+        menu.add_options(['steak'])
+
+        assert menu.options_list[-1] == 'steak'
+        assert menu.current_state == menu.menu_states['closed']
+
+    def test_removal(self, _init_pygame, default_ui_manager,
+                     _display_surface_return_none):
+        menu = UIDropDownMenu(options_list=['cheese', 'eggs', 'flour', 'sugar'],
+                              starting_option='eggs',
+                              relative_rect=pygame.Rect(100, 100, 200, 30),
+                              manager=default_ui_manager)
+
+        menu.remove_options(['flour', 'sugar'])
+
+        assert menu.options_list[-1] == 'eggs'
+
+        menu.current_state.should_transition = True
+        menu.update(0.01)
+
+        assert menu.current_state == menu.menu_states['expanded']
+
+        menu.remove_options(['eggs'])
+
+        assert menu.options_list[-1] == 'cheese'
+        assert menu.current_state == menu.menu_states['closed']
+
     def test_kill(self, _init_pygame, default_ui_manager, _display_surface_return_none):
         menu = UIDropDownMenu(options_list=['eggs', 'flour', 'sugar'],
                               starting_option='eggs',
diff --git a/tests/test_elements/test_ui_selection_list.py b/tests/test_elements/test_ui_selection_list.py
index bcf05e3..b597f66 100644
--- a/tests/test_elements/test_ui_selection_list.py
+++ b/tests/test_elements/test_ui_selection_list.py
@@ -19,6 +19,26 @@ class TestUISelectionList:
                         item_list=['green', 'eggs', 'and', 'ham'],
                         manager=default_ui_manager)
 
+    def test_addition(self, _init_pygame, default_ui_manager,
+                      _display_surface_return_none):
+        selection_list = UISelectionList(relative_rect=pygame.Rect(50, 50, 150, 400),
+                                         item_list=['green', 'eggs', 'and', 'ham'],
+                                         manager=default_ui_manager)
+
+        selection_list.add_items(['spam', ('bad spam', 'bad_spam')])
+
+        assert selection_list.item_list[-1]['text'] == 'bad spam'
+
+    def test_removal(self, _init_pygame, default_ui_manager,
+                     _display_surface_return_none):
+        selection_list = UISelectionList(relative_rect=pygame.Rect(50, 50, 150, 400),
+                                         item_list=['green', 'eggs', 'and', 'ham', ('bad spam', 'bad_spam')],
+                                         manager=default_ui_manager)
+
+        selection_list.remove_items(['ham', ('bad spam', 'bad_spam')])
+
+        assert selection_list.item_list[-1]['text'] == 'and'
+
     def test_get_single_selection(self, _init_pygame, default_ui_manager,
                                   _display_surface_return_none):
         selection_list = UISelectionList(relative_rect=pygame.Rect(50, 50, 150, 400),