Repository: MyreMylar/pygame_gui (python)

Issue:
Tabs in windows/containers
**Is your feature request related to a problem? Please describe.**
Several related sets of content need to be shown in separate windows/sections, or hacked to mimic the position.

**Describe the solution you'd like**
The option to group a collection of windows/containers in a single window/container and select a button (tab) to navigate between them. 

**Additional context**
Your comment about tabs, from #75 :
> Yeah, I've thought about doing 'proper' tabs for windows, the main thing I'm waiting on a bit there is the eventual proper release of Pygame 2 as I have hopes it'll allow me to speed up and simplify my existing drawing code and make things like the rounded corners more viable to have everywhere (if you haven't noticed if you add a bunch of rounded corners to everything in a complicated UI thing it eventually can take about half a second to draw things which feels unresponsive). With rounded corners on a tab you want a surprisingly tricky shape to dynamically draw where the 'exposed' edge is rounded in the normal fashion and the 'connecting' edge is has curves that go the other way, all of which means it will take longer to put together on the fly.


Pull request:
Add tab containers
**Desired features**
A tab container that can switch between different windows by clicking on the tab.

**Sample code**
```python
import pygame

from pygame_gui import UIManager, UI_BUTTON_PRESSED
from pygame_gui.elements import UIButton, UITabContainer

pygame.init()


pygame.display.set_caption('Quick Start')
window_surface = pygame.display.set_mode((800, 600))
manager = UIManager((800, 600))

background = pygame.Surface((800, 600))
background.fill(manager.ui_theme.get_colour('dark_bg'))

tab_container = UITabContainer(relative_rect=pygame.Rect(50, 50, 700, 500), manager=manager)

container_1 = tab_container.get_container(tab_container.add_tab("Tab1"))
container_2 = tab_container.get_container(tab_container.add_tab("Tab2"))

hello_button_1 = UIButton((250, 180), 'Hello1', container=container_1, command=lambda: print("Hello1!"))
hello_button_2 = UIButton((350, 280), 'Hello2', container=container_2, command=lambda: print("Hello2!"))

clock = pygame.time.Clock()
is_running = True

while is_running:
    time_delta = clock.tick(60)/1000.0
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            is_running = False
        manager.process_events(event)

    manager.update(time_delta)

    window_surface.blit(background, (0, 0))
    manager.draw_ui(window_surface)

    pygame.display.update()

```

**Screenshot**
![7QDW1CH1SH4B@9WFWIYZ`XI](https://github.com/MyreMylar/pygame_gui/assets/43668206/cd6a8c65-222f-4b4d-9e49-7cc6a00caef5)


I still don't quite understand what features I want and what interfaces should be opened. So I send the PR and see what everyone thinks.

Fixes #102 

Patch:
diff --git a/pygame_gui/core/drawable_shapes/drawable_shape.py b/pygame_gui/core/drawable_shapes/drawable_shape.py
index 75c0bce..c7d5a4f 100644
--- a/pygame_gui/core/drawable_shapes/drawable_shape.py
+++ b/pygame_gui/core/drawable_shapes/drawable_shape.py
@@ -540,10 +540,10 @@ class DrawableShape:
             # gather any override parameters for text_width and text_height now
             # as we need to feed them into max_dimensions
             max_dimensions = [self.containing_rect.width, self.containing_rect.height]
-            if 'text_width' in self.theming:
-                max_dimensions[0] = self.theming['text_width']
-            if 'text_height' in self.theming:
-                max_dimensions[1] = self.theming['text_height']
+            if 'max_text_width' in self.theming:
+                max_dimensions[0] = self.theming['max_text_width']
+            if 'max_text_height' in self.theming:
+                max_dimensions[1] = self.theming['max_text_height']
             text_chunk = TextLineChunkFTFont(self.theming['text'],
                                              self.theming['font'],
                                              underlined=False,
diff --git a/pygame_gui/core/ui_container.py b/pygame_gui/core/ui_container.py
index 1f6f254..7c464b9 100644
--- a/pygame_gui/core/ui_container.py
+++ b/pygame_gui/core/ui_container.py
@@ -30,7 +30,7 @@ class UIContainer(UIElement, IUIContainerInterface, IContainerLikeInterface):
                     Warning - it's parent container visibility may override this.
     """
     def __init__(self,
-                 relative_rect: pygame.Rect,
+                 relative_rect: Union[pygame.Rect, Tuple[int, int, int, int]],
                  manager: IUIManagerInterface,
                  *,
                  starting_height: int = 1,
diff --git a/pygame_gui/core/ui_element.py b/pygame_gui/core/ui_element.py
index 84dfffb..407caa6 100644
--- a/pygame_gui/core/ui_element.py
+++ b/pygame_gui/core/ui_element.py
@@ -45,11 +45,11 @@ class UIElement(GUISprite, IUIElementInterface):
                  *,
                  starting_height: int,
                  layer_thickness: int,
-                 anchors: Dict[str, Union[str, IUIElementInterface]] = None,
+                 anchors: Optional[Dict[str, Union[str, IUIElementInterface]]] = None,
                  visible: int = 1,
-                 parent_element: Union[None, IUIElementInterface] = None,
+                 parent_element: Optional[IUIElementInterface] = None,
                  object_id: Union[ObjectID, str, None] = None,
-                 element_id: List[str] = None):
+                 element_id: Optional[List[str]] = None):
 
         self._layer = 0
         self.ui_manager = manager
diff --git a/pygame_gui/data/default_theme.json b/pygame_gui/data/default_theme.json
index ed75ae2..32abf3c 100644
--- a/pygame_gui/data/default_theme.json
+++ b/pygame_gui/data/default_theme.json
@@ -533,5 +533,36 @@
             "border_width": "1",
             "shadow_width": "0"
         }
+    },
+    "tab_container.@tab_title_button":
+    {
+        "colours":
+        {
+            "normal_bg": "#4c5052",
+            "hovered_bg": "#63686b",
+            "disabled_bg": "#25292e",
+            "selected_bg": "#5c6062",
+            "active_bg": "#5c6062",
+            "normal_border": "#3c4042",
+            "selected_border": "#5c6062",
+            "hovered_border": "#5c6062",
+            "disabled_border": "#35393e",
+            "active_border": "#5c6062"
+        },
+        "misc":
+        {
+            "shape": "rounded_rectangle",
+            "shape_corner_radius": "5,5,0,0",
+            "border_width": "1",
+            "shadow_width": "0"
+        }
+    },
+    "tab_container.panel":
+    {
+        "misc":
+        {
+            "border_width": "1",
+            "shadow_width": "0"
+        }
     }
 }
\ No newline at end of file
diff --git a/pygame_gui/elements/__init__.py b/pygame_gui/elements/__init__.py
index b9a3c3a..da82a1f 100644
--- a/pygame_gui/elements/__init__.py
+++ b/pygame_gui/elements/__init__.py
@@ -20,6 +20,7 @@ from pygame_gui.elements.ui_scrolling_container import UIScrollingContainer
 from pygame_gui.elements.ui_text_entry_box import UITextEntryBox
 from pygame_gui.elements.ui_auto_resizing_container import UIAutoResizingContainer
 from pygame_gui.elements.ui_auto_scrolling_container import UIAutoScrollingContainer
+from pygame_gui.elements.ui_tab_container import UITabContainer
 
 
 __all__ = ['UIImage',
@@ -44,5 +45,6 @@ __all__ = ['UIImage',
            'UIScrollingContainer',
            'UITextEntryBox',
            'UIAutoResizingContainer',
-           'UIAutoScrollingContainer'
+           'UIAutoScrollingContainer',
+           'UITabContainer' 
            ]
diff --git a/pygame_gui/elements/ui_button.py b/pygame_gui/elements/ui_button.py
index 056551b..d1b32d6 100644
--- a/pygame_gui/elements/ui_button.py
+++ b/pygame_gui/elements/ui_button.py
@@ -65,6 +65,7 @@ class UIButton(UIElement):
                  tool_tip_object_id: Optional[ObjectID] = None,
                  text_kwargs: Optional[Dict[str, str]] = None,
                  tool_tip_text_kwargs: Optional[Dict[str, str]] = None,
+                 max_dynamic_width: Optional[int] = None
                  ):
 
         rel_rect = (relative_rect if isinstance(relative_rect, pygame.Rect)
@@ -130,6 +131,7 @@ class UIButton(UIElement):
         self.shape = 'rectangle'
         self.text_shadow_size = 0
         self.text_shadow_offset = (0, 0)
+        self.max_dynamic_width = max_dynamic_width
 
         self.state_transitions = {}
         
@@ -749,6 +751,8 @@ class UIButton(UIElement):
         drawable_shape_rect = self.rect.copy()
         if self.dynamic_width:
             drawable_shape_rect.width = -1
+            if self.max_dynamic_width is not None:
+                theming_parameters["max_text_width"] = self.max_dynamic_width
         if self.dynamic_height:
             drawable_shape_rect.height = -1
 
diff --git a/pygame_gui/elements/ui_tab_container.py b/pygame_gui/elements/ui_tab_container.py
new file mode 100644
index 0000000..b3932ba
--- /dev/null
+++ b/pygame_gui/elements/ui_tab_container.py
@@ -0,0 +1,259 @@
+from typing import List, Dict, Optional, Union, Tuple
+
+import pygame
+
+from pygame_gui.core.interfaces import (Coordinate, IContainerLikeInterface,
+                                        IUIManagerInterface, IUIElementInterface)
+from pygame_gui._constants import UI_BUTTON_PRESSED
+from pygame_gui.core import UIElement, UIContainer, ObjectID
+from pygame_gui.elements import UIButton, UIPanel
+
+
+class UITabContainer(UIElement):
+    """
+    ** EXPERIMENTAL ** A tab container. The displayed panel can be switched by clicking on the tab.
+
+    :param relative_rect: Normally a rectangle describing the position (relative to its container) and
+                          dimensions. Also accepts a position Tuple, or Vector2 where the dimensions
+                          will be dynamic depending on the button's contents. Dynamic dimensions can
+                          be requested by setting the required dimension to -1.
+    :param manager: The UIManager that manages this element. If not provided or set to None,
+                    it will try to use the first UIManager that was created by your application.
+    :param container: The container that this element is within. If not provided or set to None
+                      will be the root window's container.
+    :param starting_height: The height in layers above its container that this element will be
+                            placed.
+    :param parent_element: The element this element 'belongs to' in the theming hierarchy.
+    :param object_id: A custom defined ID for fine-tuning of theming.
+    :param anchors: A dictionary describing what this element's relative_rect is relative to.
+    :param visible: Whether the element is visible by default. Warning - container visibility may
+                    override this.
+    """
+
+    def __init__(self, relative_rect: Union[pygame.Rect, Tuple[int, int, int, int]],
+                 manager: Optional[IUIManagerInterface] = None,
+                 container: Optional[IContainerLikeInterface] = None,
+                 *,
+                 starting_height: int = 1,
+                 parent_element: Optional[IUIElementInterface] = None,
+                 object_id: Union[ObjectID, str, None] = None,
+                 anchors: Optional[Dict[str, Union[str, IUIElementInterface]]] = None,
+                 visible: int = 1):
+
+        super().__init__(relative_rect, manager, container,
+                         starting_height=starting_height,
+                         layer_thickness=1,
+                         anchors=anchors,
+                         visible=visible,
+                         parent_element=parent_element,
+                         object_id=object_id,
+                         element_id=['tab_container'])
+
+        self._root_container = UIContainer(relative_rect=relative_rect,
+                                           manager=manager,
+                                           starting_height=starting_height,
+                                           container=container,
+                                           anchors=anchors,
+                                           visible=self.visible)
+
+        self.tabs: List[Dict] = []
+        self.current_container_index: Optional[int] = None
+
+        self.button_height: int = 30
+        self.max_button_width: int = self.rect.width
+
+    @property
+    def tab_count(self) -> int:
+        return len(self.tabs)
+
+    def switch_current_container(self, index: int):
+        current_container = self.get_tab_container()
+        if current_container is not None:
+            current_container.hide()
+        self.current_container_index = index
+        self.get_tab_container().show()
+
+    def add_tab(self, title_text: str, title_object_id: str) -> int:
+        """
+        Create a new tab.
+
+        :return : the integer id of the newly created tab
+        """
+        self.rebuild(self.tab_count + 1)
+        max_button_width = self._calculate_max_button_width(self.tab_count + 1)
+        furthest_right = 0
+        if len(self.tabs) > 0:
+            for tab in self.tabs:
+                furthest_right += tab["button"].rect.width
+        button_rect = pygame.Rect(furthest_right, 0, -1, self.button_height)
+        button = UIButton(button_rect, title_text, manager=self.ui_manager, container=self._root_container,
+                          parent_element=self, object_id=ObjectID(title_object_id, '@tab_title_button'),
+                          max_dynamic_width=max_button_width)
+        container_rect = self._calculate_container_rect_by_layout()
+        container = UIPanel(container_rect, manager=self.ui_manager, container=self._root_container, parent_element=self)
+        self.tabs.append({"text": title_text, "button": button, "container": container})
+        tab_id = self.tab_count - 1
+        if self.current_container_index is None:
+            self.current_container_index = tab_id
+            button.select()
+        else:
+            container.hide()
+        return tab_id
+
+    def get_tab(self, tab_id: Optional[int] = None) -> Optional[Dict]:
+        if tab_id is None:
+            if self.current_container_index is None:
+                return None
+            else:
+                tab_id = self.current_container_index
+
+        return self.tabs[tab_id]
+
+    def get_title_text(self, tab_id: Optional[int] = None):
+        tab = self.get_tab(tab_id)
+        if tab is None:
+            return None
+        else:
+            return tab["text"]
+
+    def get_title_button(self, tab_id: Optional[int] = None):
+        tab = self.get_tab(tab_id)
+        if tab is None:
+            return None
+        else:
+            return tab["button"]
+
+    def get_tab_container(self, tab_id=None) -> Optional[UIPanel]:
+        tab = self.get_tab(tab_id)
+        if tab is None:
+            return None
+        else:
+            return tab["container"]
+
+    def delete_tab(self, tab_id):
+        self.tabs[tab_id]["button"].kill()
+        self.tabs[tab_id]["container"].kill()
+        del self.tabs[tab_id]
+        self.rebuild()
+
+    def _calculate_max_button_width(self, count: Optional[int] = None):
+        width = self._root_container.rect.width
+        if count is None:
+            count = self.tab_count
+
+        if count == 0:
+            button_width = self.max_button_width
+        else:
+            button_width = width // count
+            button_width = min(button_width, self.max_button_width)
+
+        return button_width
+
+    def _calculate_container_rect_by_layout(self) -> pygame.Rect:
+        return pygame.Rect(0, self.button_height, self._root_container.rect.width,
+                           self.rect.height - self.button_height)
+
+    def disable(self):
+        """
+        Disables the window and it's contents so it is no longer interactive.
+        """
+        super().disable()
+        if self.is_enabled:
+            self._root_container.disable()
+
+    def enable(self):
+        """
+        Enables the window and it's contents so it is interactive again.
+        """
+        super().enable()
+        if not self.is_enabled:
+            self._root_container.enable()
+
+    def show(self):
+        """
+        In addition to the base UIElement.show() - show the _window_root_container which will
+        propagate and show all the children.
+        """
+        super().show()
+        self._root_container.show()
+
+    def hide(self):
+        """
+        In addition to the base UIElement.hide() - hide the _window_root_container which will
+        propagate and hide all the children.
+        """
+        super().hide()
+        if self._root_container is not None:
+            self._root_container.hide()
+
+    def set_dimensions(self, dimensions: Coordinate, clamp_to_container: bool = False):
+        """
+        Set the size of this tab container.
+
+        :param dimensions: The new dimensions to set.
+        :param clamp_to_container: Whether we should clamp the dimensions to the
+                                    dimensions of the container or not.
+
+        """
+        super().set_dimensions(dimensions)
+        self._root_container.set_dimensions(dimensions)
+        self.rebuild()
+
+    def rebuild(self, count: Optional[int] = None):
+        """
+        Rebuilds the tab container.
+
+        """
+        super().rebuild()
+        if count is None:
+            count = self.tab_count
+        max_button_width = self._calculate_max_button_width(count)
+        current_right = 0
+        for i, tab in enumerate(self.tabs, 0):
+            button: UIButton = tab["button"]
+            if i >= count:
+                continue
+            button.max_dynamic_width = max_button_width
+            button.rebuild()
+            button.set_relative_position((current_right, 0))
+            current_right += button.rect.width
+
+            container = tab["container"]
+            if i == self.current_container_index:
+                container.show()
+                button.select()
+            else:
+                container.hide()
+
+    def process_event(self, event: pygame.Event):
+        """
+        Handles various interactions with the tab container.
+
+        :param event: The event to process.
+
+        :return: Return True if we want to consume this event, so it is not passed on to the
+                 rest of the UI.
+
+        """
+        consumed_event = False
+
+        if event.type == UI_BUTTON_PRESSED:
+            for i, tab in enumerate(self.tabs, 0):
+                tab_title_button: UIButton = tab["button"]
+                if event.ui_element == tab_title_button:
+                    self.switch_current_container(i)
+                    consumed_event = True
+                    tab_title_button.select()
+                elif tab_title_button.is_selected:
+                    tab_title_button.unselect()
+
+        return consumed_event
+
+    def kill(self):
+        """
+        Overrides the basic kill() method of a pygame sprite so that we also kill all the UI
+        elements in this panel.
+
+        """
+        self._root_container.kill()
+        super().kill()
diff --git a/pygame_gui/elements/ui_text_entry_line.py b/pygame_gui/elements/ui_text_entry_line.py
index b472027..b92d6f9 100644
--- a/pygame_gui/elements/ui_text_entry_line.py
+++ b/pygame_gui/elements/ui_text_entry_line.py
@@ -228,6 +228,7 @@ class UITextEntryLine(UIElement):
                               'font': display_font,
                               'text': display_text if len(display_text) > 0 else translate(self.placeholder_text),
                               'text_width': -1,
+                              'max_text_width': -1,
                               'text_horiz_alignment': text_horiz_alignment,
                               'text_vert_alignment': 'centre',
                               'text_horiz_alignment_padding': self.padding[0],


Test patch:
diff --git a/tests/test_elements/test_ui_tab_container.py b/tests/test_elements/test_ui_tab_container.py
new file mode 100644
index 0000000..767d820
--- /dev/null
+++ b/tests/test_elements/test_ui_tab_container.py
@@ -0,0 +1,205 @@
+import pytest
+import pygame
+
+
+from pygame_gui.core.interfaces import IUIManagerInterface
+from pygame_gui.elements.ui_tab_container import UITabContainer
+from pygame_gui.elements.ui_button import UIButton
+from pygame_gui.core.ui_container import UIContainer
+from pygame_gui import UIManager
+from pygame_gui import UI_BUTTON_PRESSED
+
+
+class TestUITabContainer:
+
+    def test_creation(self, _init_pygame, _display_surface_return_none, default_ui_manager):
+        UITabContainer(relative_rect=pygame.Rect(50, 50, 150, 400),
+                       manager=default_ui_manager)
+
+    def test_creation_with_tab(self, _init_pygame, _display_surface_return_none, default_ui_manager):
+        tab_container = UITabContainer(relative_rect=pygame.Rect(50, 50, 150, 400),
+                                       manager=default_ui_manager)
+
+        tab_container.add_tab("Tab 1", "tab_1")
+
+    def test_add_button(self, _init_pygame, _display_surface_return_none, default_ui_manager):
+        tab_container = UITabContainer(relative_rect=pygame.Rect(50, 50, 150, 400),
+                                       manager=default_ui_manager)
+
+        tab_1_container = tab_container.get_tab_container(tab_container.add_tab("Tab 1", "tab_1"))
+
+        button = UIButton(relative_rect=pygame.Rect(100, 100, 150, 30),
+                          text="Test Button",
+                          tool_tip_text="This is a test of the button's tool tip functionality.",
+                          manager=default_ui_manager,
+                          container=tab_1_container)
+
+        assert button.image is not None
+
+    def test_process_event(self, _init_pygame, _display_surface_return_none, default_ui_manager):
+        tab_container = UITabContainer(relative_rect=pygame.Rect(50, 50, 150, 400),
+                                       manager=default_ui_manager)
+        tab_1_id = tab_container.add_tab("Tab 1", "tab_1")
+        tab_1_container = tab_container.get_tab_container(tab_1_id)
+        tab_1_title_button = tab_container.get_title_button(tab_1_id)
+
+        tab_2_id = tab_container.add_tab("Tab 2", "tab_2")
+        tab_2_container = tab_container.get_tab_container(tab_2_id)
+        tab_2_title_button = tab_container.get_title_button(tab_2_id)
+
+        button = UIButton(relative_rect=pygame.Rect(100, 100, 150, 30),
+                          text="Test Button",
+                          tool_tip_text="This is a test of the button's tool tip functionality.",
+                          manager=default_ui_manager,
+                          container=tab_1_container)
+
+        assert button.visible
+
+        consumed_event = tab_container.process_event(
+            pygame.event.Event(UI_BUTTON_PRESSED, {'ui_element': tab_2_title_button}))
+
+        assert consumed_event and not button.visible
+
+    def test_kill(self, _init_pygame, _display_surface_return_none, default_ui_manager: IUIManagerInterface):
+        tab_container = UITabContainer(relative_rect=pygame.Rect(50, 50, 150, 400),
+                                       manager=default_ui_manager)
+        tab_1_id = tab_container.add_tab("Tab 1", "tab_1")
+        tab_1_container = tab_container.get_tab_container(tab_1_id)
+        tab_1_title_button = tab_container.get_title_button(tab_1_id)
+
+        button = UIButton(relative_rect=pygame.Rect(100, 100, 150, 30),
+                          text="Test Button",
+                          tool_tip_text="This is a test of the button's tool tip functionality.",
+                          manager=default_ui_manager,
+                          container=tab_1_container)
+
+        assert len(default_ui_manager.get_root_container().elements) == 2
+        assert len(default_ui_manager.get_sprite_group().sprites()) == 7
+        assert (default_ui_manager.get_sprite_group().sprites() ==
+                [default_ui_manager.get_root_container(),
+                 tab_container,
+                 tab_container._root_container,
+                 tab_1_title_button,
+                 tab_1_container,
+                 tab_1_container.get_container(),
+                 button])
+        tab_container.kill()
+        assert len(default_ui_manager.get_root_container().elements) == 0
+        assert len(default_ui_manager.get_sprite_group().sprites()) == 1
+        assert (default_ui_manager.get_sprite_group().sprites() ==
+                [default_ui_manager.get_root_container()])
+
+    def test_set_dimensions(self, _init_pygame, _display_surface_return_none, default_ui_manager):
+        test_container = UIContainer(relative_rect=pygame.Rect(10, 10, 700, 500),
+                                     manager=default_ui_manager)
+
+        tab_container_1 = UITabContainer(relative_rect=pygame.Rect(50, 50, 150, 350),
+                                         manager=default_ui_manager,
+                                         container=test_container,
+                                         anchors={'left': 'left',
+                                                  'right': 'left',
+                                                  'top': 'top',
+                                                  'bottom': 'top'}
+                                         )
+        tab_1_id = tab_container_1.add_tab("Tab 1", "tab_1")
+        tab_1_container = tab_container_1.get_tab_container(tab_1_id)
+        tab_1_title_button = tab_container_1.get_title_button(tab_1_id)
+
+        button = UIButton(relative_rect=pygame.Rect(100, 100, 150, 30),
+                          text="Test Button",
+                          tool_tip_text="This is a test of the button's tool tip functionality.",
+                          manager=default_ui_manager,
+                          container=tab_1_container)
+
+        assert tab_container_1.relative_right_margin is None
+        assert tab_container_1.relative_bottom_margin is None
+
+        tab_container_1.set_dimensions((200, 200))
+        assert tab_container_1.relative_rect.topleft == (50, 50)
+        assert tab_container_1.relative_rect.size == (200, 200)
+        assert tab_container_1.relative_rect.bottomright == (250, 250)
+        assert tab_container_1.rect.topleft == (60, 60)
+        assert tab_container_1.rect.size == (200, 200)
+        assert tab_container_1.rect.bottomright == (260, 260)
+        assert tab_container_1.relative_right_margin is None
+        assert tab_container_1.relative_bottom_margin is None
+
+    def test_enable(self, _init_pygame: None, default_ui_manager: UIManager,
+                    _display_surface_return_none: None):
+        tab_container = UITabContainer(relative_rect=pygame.Rect(50, 50, 150, 400),
+                                       manager=default_ui_manager)
+        tab_1_id = tab_container.add_tab("Tab 1", "tab_1")
+        tab_1_container = tab_container.get_tab_container(tab_1_id)
+        tab_1_title_button = tab_container.get_title_button(tab_1_id)
+
+        button = UIButton(relative_rect=pygame.Rect(100, 100, 150, 30),
+                          text="Test Button",
+                          tool_tip_text="This is a test of the button's tool tip functionality.",
+                          manager=default_ui_manager,
+                          container=tab_1_container)
+
+        tab_container.disable()
+        tab_container.enable()
+
+        assert tab_container.is_enabled is True
+        assert button.is_enabled is True
+        assert tab_1_title_button.is_enabled is True
+
+    def test_panel_children_inheriting_hidden_status(self, _init_pygame, default_ui_manager: IUIManagerInterface,
+                                                     _display_surface_return_none):
+        tab_container = UITabContainer(relative_rect=pygame.Rect(50, 50, 150, 400),
+                                       manager=default_ui_manager,
+                                       visible=False)
+        tab_1_id = tab_container.add_tab("Tab 1", "tab_1")
+        tab_1_container = tab_container.get_tab_container(tab_1_id)
+        tab_1_title_button = tab_container.get_title_button(tab_1_id)
+
+        button = UIButton(relative_rect=pygame.Rect(100, 100, 150, 30),
+                          text="Test Button",
+                          tool_tip_text="This is a test of the button's tool tip functionality.",
+                          manager=default_ui_manager,
+                          container=tab_1_container)
+        assert not tab_container.visible
+        assert not button.visible
+        assert not tab_1_title_button.visible
+
+    def test_hidden_panel_children_behaviour_on_show(self, _init_pygame, default_ui_manager: IUIManagerInterface,
+                                                     _display_surface_return_none):
+        tab_container = UITabContainer(relative_rect=pygame.Rect(50, 50, 150, 400),
+                                       manager=default_ui_manager,
+                                       visible=False)
+        tab_1_id = tab_container.add_tab("Tab 1", "tab_1")
+        tab_1_container = tab_container.get_tab_container(tab_1_id)
+        tab_1_title_button = tab_container.get_title_button(tab_1_id)
+
+        button = UIButton(relative_rect=pygame.Rect(100, 100, 150, 30),
+                          text="Test Button",
+                          tool_tip_text="This is a test of the button's tool tip functionality.",
+                          manager=default_ui_manager,
+                          container=tab_1_container)
+
+        assert not tab_container.visible
+        assert not button.visible
+        assert not tab_1_title_button.visible
+        tab_container.show()
+        assert tab_container.visible
+        assert button.visible
+        assert tab_1_title_button.visible
+        tab_container.hide()
+        assert not tab_container.visible
+        assert not button.visible
+        assert not tab_1_title_button.visible
+
+    def test_get_title_text(self, _init_pygame: None, default_ui_manager: UIManager,
+                            _display_surface_return_none: None):
+        tab_container = UITabContainer(relative_rect=pygame.Rect(50, 50, 150, 400),
+                                       manager=default_ui_manager)
+        tab_1_id = tab_container.add_tab("Tab 1", "tab_1")
+        tab_1_container = tab_container.get_tab_container(tab_1_id)
+        tab_1_title_button = tab_container.get_title_button(tab_1_id)
+
+        assert tab_container.get_title_text(tab_1_id) == "Tab 1"
+
+
+if __name__ == '__main__':
+    pytest.console_main()