Repository: MyreMylar/pygame_gui
Pull request #577 (2024-04-12 18:16:37)

Fix are_contents_hovered() method
To cover more nested scenarios correctly.


Patch:
diff --git a/pygame_gui/core/ui_container.py b/pygame_gui/core/ui_container.py
index 3081229..9b4a251 100644
--- a/pygame_gui/core/ui_container.py
+++ b/pygame_gui/core/ui_container.py
@@ -419,7 +419,11 @@ class UIContainer(UIElement, IUIContainerInterface, IContainerLikeInterface):
         """
         any_hovered = False
         for element in self:
-            any_hovered = any(sub_element.hovered for sub_element in element.get_focus_set())
+            if any(sub_element.hovered for sub_element in element.get_focus_set()):
+                any_hovered = True
+            elif isinstance(element, IContainerLikeInterface):
+                any_hovered = element.are_contents_hovered()
+
             if any_hovered:
                 break
         return any_hovered
diff --git a/pygame_gui/elements/ui_panel.py b/pygame_gui/elements/ui_panel.py
index a0c5cfd..4938c3d 100644
--- a/pygame_gui/elements/ui_panel.py
+++ b/pygame_gui/elements/ui_panel.py
@@ -331,7 +331,11 @@ class UIPanel(UIElement, IContainerLikeInterface):
         """
         any_hovered = False
         for element in self:
-            any_hovered = any(sub_element.hovered for sub_element in element.get_focus_set())
+            if any(sub_element.hovered for sub_element in element.get_focus_set()):
+                any_hovered = True
+            elif isinstance(element, IContainerLikeInterface):
+                any_hovered = element.are_contents_hovered()
+
             if any_hovered:
                 break
         return any_hovered
diff --git a/pygame_gui/elements/ui_scrolling_container.py b/pygame_gui/elements/ui_scrolling_container.py
index c33e576..c5c1893 100644
--- a/pygame_gui/elements/ui_scrolling_container.py
+++ b/pygame_gui/elements/ui_scrolling_container.py
@@ -218,6 +218,7 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
         self.scrolling_bottom = 0
         self.scrolling_right = 0
         self._calculate_scrolling_dimensions()
+        self._sort_out_element_container_scroll_bars()
 
     def get_container(self) -> IUIContainerInterface:
         """
@@ -402,6 +403,7 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
         need_horiz_scroll_bar, need_vert_scroll_bar = self._check_scroll_bars()
 
         if need_vert_scroll_bar:
+            self.vert_scroll_bar.enable()
             # It is important to subtract scroll_bar_height as horiz scroll bar's dimensions are evaluated after.
             # If horizontal scrollbar is not needed, then scroll bar height is 0.
             vis_percent = (self._view_container.rect.height - self.scroll_bar_height) / self.scrolling_height
@@ -421,6 +423,7 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
             self._remove_vert_scrollbar()
 
         if need_horiz_scroll_bar:
+            self.horiz_scroll_bar.enable()
             vis_percent = self._view_container.rect.width / self.scrolling_width
             start_percent = ((self._view_container.rect.left -
                               self.scrollable_container.rect.left)
@@ -471,6 +474,7 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
             self.scroll_bar_width = 0
             self.vert_scroll_bar.set_dimensions((0, self._view_container.rect.height))
             self.vert_scroll_bar.set_relative_position((0, 0))
+            self.vert_scroll_bar.disable()
             self._calculate_scrolling_dimensions()
 
             if self.horiz_scroll_bar is not None:
@@ -485,6 +489,7 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
             self.scroll_bar_height = 0
             self.horiz_scroll_bar.set_dimensions((self._view_container.rect.width, 0))
             self.horiz_scroll_bar.set_relative_position((0, 0))
+            self.horiz_scroll_bar.disable()
             self._calculate_scrolling_dimensions()
 
             if self.vert_scroll_bar is not None:
@@ -554,7 +559,11 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
         """
         any_hovered = False
         for element in self:
-            any_hovered = any(sub_element.hovered for sub_element in element.get_focus_set())
+            if any(sub_element.hovered for sub_element in element.get_focus_set()):
+                any_hovered = True
+            elif isinstance(element, IContainerLikeInterface):
+                any_hovered = element.are_contents_hovered()
+
             if any_hovered:
                 break
         return any_hovered
diff --git a/pygame_gui/elements/ui_window.py b/pygame_gui/elements/ui_window.py
index d98c8c5..6a2d344 100644
--- a/pygame_gui/elements/ui_window.py
+++ b/pygame_gui/elements/ui_window.py
@@ -774,7 +774,11 @@ class UIWindow(UIElement, IContainerLikeInterface, IWindowInterface):
         """
         any_hovered = False
         for element in self:
-            any_hovered = any(sub_element.hovered for sub_element in element.get_focus_set())
+            if any(sub_element.hovered for sub_element in element.get_focus_set()):
+                any_hovered = True
+            elif isinstance(element, IContainerLikeInterface):
+                any_hovered = element.are_contents_hovered()
+
             if any_hovered:
                 break
         return any_hovered


Test patch:
diff --git a/tests/test_core/test_ui_container.py b/tests/test_core/test_ui_container.py
index f54ad5a..3075a3c 100644
--- a/tests/test_core/test_ui_container.py
+++ b/tests/test_core/test_ui_container.py
@@ -295,6 +295,20 @@ class TestUIContainer:
 
         assert container.are_contents_hovered()
 
+    def test_are_nested_contents_hovered(self, _init_pygame, default_ui_manager: IUIManagerInterface,
+                                         _display_surface_return_none):
+        manager = UIManager((800, 600))
+        container = UIContainer(pygame.Rect(100, 100, 250, 300), manager=manager)
+
+        container_2 = UIContainer(pygame.Rect(10, 10, 230, 280), manager=manager, container=container)
+
+        button = UIButton(relative_rect=pygame.Rect(50, 50, 50, 50), text="1",
+                          manager=manager, container=container_2)
+        manager.mouse_position = button.rect.center
+        button.check_hover(0.1, False)
+
+        assert container.are_contents_hovered()
+
 
 if __name__ == '__main__':
     pytest.console_main()
diff --git a/tests/test_elements/test_ui_panel.py b/tests/test_elements/test_ui_panel.py
index ac58929..09da2e0 100644
--- a/tests/test_elements/test_ui_panel.py
+++ b/tests/test_elements/test_ui_panel.py
@@ -7,7 +7,9 @@ from tests.shared_comparators import compare_surfaces
 from pygame_gui.core.interfaces import IUIManagerInterface
 from pygame_gui.core.ui_container import UIContainer
 from pygame_gui.elements.ui_panel import UIPanel
+from pygame_gui.elements.ui_scrolling_container import UIScrollingContainer
 from pygame_gui.elements.ui_button import UIButton
+from pygame_gui.elements.ui_text_box import UITextBox
 from pygame_gui.ui_manager import UIManager
 
 
@@ -630,6 +632,24 @@ class TestUIPanel:
 
         assert container.are_contents_hovered()
 
+    def test_are_nested_contents_hovered(self, _init_pygame, default_ui_manager: IUIManagerInterface,
+                                         _display_surface_return_none):
+        manager = UIManager((800, 600))
+        panel = UIPanel(pygame.Rect(100, 100, 250, 300), manager=manager)
+
+        container_2 = UIScrollingContainer(pygame.Rect(10, 10, 230, 280), manager=manager, container=panel)
+
+        nested_text_box = UITextBox(html_text="Some text inside a scrolling text box, itself"
+                                              " inside a container that scrolls inside "
+                                              " another container. ",
+                                    relative_rect=pygame.Rect(10, 10, 180, 200),
+                                    container=container_2,
+                                    manager=manager)
+        manager.mouse_position = nested_text_box.rect.center
+        nested_text_box.check_hover(0.1, False)
+
+        assert panel.are_contents_hovered()
+
 
 if __name__ == '__main__':
     pytest.console_main()
diff --git a/tests/test_elements/test_ui_scrolling_container.py b/tests/test_elements/test_ui_scrolling_container.py
index b669fe8..1fe3dca 100644
--- a/tests/test_elements/test_ui_scrolling_container.py
+++ b/tests/test_elements/test_ui_scrolling_container.py
@@ -490,6 +490,29 @@ class TestUIScrollingContainer:
 
         assert not container.vert_scroll_bar.scroll_wheel_moved
 
+    def test_scroll_while_hovering_nested_contents(self, _init_pygame, default_ui_manager: IUIManagerInterface,
+                                                   _display_surface_return_none):
+        manager = UIManager((800, 600))
+        container = UIScrollingContainer(pygame.Rect(100, 100, 250, 300), manager=manager)
+        container.set_scrollable_area_dimensions((250, 600))
+
+        container_2 = UIScrollingContainer(pygame.Rect(10, 10, 230, 280), manager=manager, container=container,
+                                           should_grow_automatically=False)
+
+        text_box_inside_two_scrolling_containers = UITextBox(html_text="Some text",
+                                                             relative_rect=pygame.Rect(10, 10, 210, 260),
+                                                             container=container_2,
+                                                             manager=manager)
+        manager.mouse_position = text_box_inside_two_scrolling_containers.rect.center
+        text_box_inside_two_scrolling_containers.check_hover(0.1, False)
+
+        assert container.are_contents_hovered()
+        assert container.vert_scroll_bar is not None
+
+        manager.process_events(pygame.event.Event(pygame.MOUSEWHEEL, {'y': -0.5}))
+
+        assert container.vert_scroll_bar.scroll_wheel_moved
+
 
 if __name__ == '__main__':
     pytest.console_main()
diff --git a/tests/test_elements/test_ui_window.py b/tests/test_elements/test_ui_window.py
index 7d2c96d..4dcefeb 100644
--- a/tests/test_elements/test_ui_window.py
+++ b/tests/test_elements/test_ui_window.py
@@ -6,7 +6,9 @@ import pygame_gui
 from tests.shared_comparators import compare_surfaces
 
 from pygame_gui.elements.ui_window import UIWindow
+from pygame_gui.elements.ui_scrolling_container import UIScrollingContainer
 from pygame_gui.elements.ui_button import UIButton
+from pygame_gui.elements.ui_text_box import UITextBox
 from pygame_gui.core.interfaces import IUIManagerInterface
 from pygame_gui import UIManager
 
@@ -702,16 +704,34 @@ class TestUIWindow:
     def test_are_contents_hovered(self,  _init_pygame, default_ui_manager: IUIManagerInterface,
                                   _display_surface_return_none):
         manager = UIManager((800, 600))
-        container = UIWindow(pygame.Rect(100, 100, 200, 200), manager=manager)
+        window = UIWindow(pygame.Rect(100, 100, 200, 200), manager=manager)
         button_1 = UIButton(relative_rect=pygame.Rect(50, 50, 50, 50), text="1",
-                            manager=manager, container=container)
+                            manager=manager, container=window)
         button_2 = UIButton(relative_rect=pygame.Rect(125, 50, 50, 50), text="2",
-                            manager=manager, container=container)
+                            manager=manager, container=window)
         manager.mouse_position = button_1.rect.center
         button_1.check_hover(0.1, False)
         button_2.check_hover(0.1, False)
 
-        assert container.are_contents_hovered()
+        assert window.are_contents_hovered()
+
+    def test_are_nested_contents_hovered(self, _init_pygame, default_ui_manager: IUIManagerInterface,
+                                         _display_surface_return_none):
+        manager = UIManager((800, 600))
+        window = UIWindow(pygame.Rect(100, 100, 250, 300), manager=manager)
+
+        container_2 = UIScrollingContainer(pygame.Rect(10, 10, 230, 280), manager=manager, container=window)
+
+        nested_text_box = UITextBox(html_text="Some text inside a scrolling text box, itself"
+                                              " inside a container that scrolls inside "
+                                              " another container. ",
+                                    relative_rect=pygame.Rect(10, 10, 180, 200),
+                                    container=container_2,
+                                    manager=manager)
+        manager.mouse_position = nested_text_box.rect.center
+        nested_text_box.check_hover(0.1, False)
+
+        assert window.are_contents_hovered()
 
 
 if __name__ == '__main__':