Repository: MyreMylar/pygame_gui
Pull request #540 (2024-03-11 19:48:50)

Add UI2DSlider element
This is the first major splitting from #494 - bringing over the 2D Slider so it can be tested & reviewed in isolation.

Step 5 of splitting mega-PR #494 into smaller easier to review PRs


Patch:
diff --git a/pygame_gui/__init__.py b/pygame_gui/__init__.py
index fee3647..867a55d 100644
--- a/pygame_gui/__init__.py
+++ b/pygame_gui/__init__.py
@@ -13,7 +13,7 @@ from pygame_gui._constants import UI_BUTTON_PRESSED, UI_BUTTON_DOUBLE_CLICKED, U
 from pygame_gui._constants import UI_BUTTON_ON_HOVERED, UI_BUTTON_ON_UNHOVERED
 from pygame_gui._constants import UI_TEXT_ENTRY_FINISHED, UI_TEXT_ENTRY_CHANGED
 from pygame_gui._constants import UI_TEXT_BOX_LINK_CLICKED
-from pygame_gui._constants import UI_DROP_DOWN_MENU_CHANGED, UI_HORIZONTAL_SLIDER_MOVED
+from pygame_gui._constants import UI_DROP_DOWN_MENU_CHANGED, UI_HORIZONTAL_SLIDER_MOVED, UI_2D_SLIDER_MOVED
 from pygame_gui._constants import UI_SELECTION_LIST_NEW_SELECTION
 from pygame_gui._constants import UI_SELECTION_LIST_DROPPED_SELECTION
 from pygame_gui._constants import UI_SELECTION_LIST_DOUBLE_CLICKED_SELECTION
@@ -46,6 +46,7 @@ __all__ = ['UIManager',
            'UI_TEXT_ENTRY_FINISHED',
            'UI_DROP_DOWN_MENU_CHANGED',
            'UI_HORIZONTAL_SLIDER_MOVED',
+           'UI_2D_SLIDER_MOVED',
            'UI_SELECTION_LIST_NEW_SELECTION',
            'UI_SELECTION_LIST_DROPPED_SELECTION',
            'UI_SELECTION_LIST_DOUBLE_CLICKED_SELECTION',
diff --git a/pygame_gui/_constants.py b/pygame_gui/_constants.py
index 0600af8..11cc186 100644
--- a/pygame_gui/_constants.py
+++ b/pygame_gui/_constants.py
@@ -62,6 +62,7 @@ UI_TEXT_ENTRY_CHANGED = custom_type()
 UI_TEXT_ENTRY_FINISHED = custom_type()
 UI_DROP_DOWN_MENU_CHANGED = custom_type()
 UI_HORIZONTAL_SLIDER_MOVED = custom_type()
+UI_2D_SLIDER_MOVED = custom_type()
 UI_SELECTION_LIST_NEW_SELECTION = custom_type()
 UI_SELECTION_LIST_DROPPED_SELECTION = custom_type()
 UI_SELECTION_LIST_DOUBLE_CLICKED_SELECTION = custom_type()
@@ -244,6 +245,7 @@ __all__ = ['UI_BUTTON_PRESSED',
            'UI_TEXT_ENTRY_FINISHED',
            'UI_DROP_DOWN_MENU_CHANGED',
            'UI_HORIZONTAL_SLIDER_MOVED',
+           'UI_2D_SLIDER_MOVED',
            'UI_SELECTION_LIST_NEW_SELECTION',
            'UI_SELECTION_LIST_DROPPED_SELECTION',
            'UI_SELECTION_LIST_DOUBLE_CLICKED_SELECTION',
diff --git a/pygame_gui/data/default_theme.json b/pygame_gui/data/default_theme.json
index 2ec33eb..8a26374 100644
--- a/pygame_gui/data/default_theme.json
+++ b/pygame_gui/data/default_theme.json
@@ -131,6 +131,27 @@
             "shadow_width": "1"
         }
     },
+    "2d_slider":
+    {
+        "prototype": "#default_shape_style",
+        "misc":
+        {
+            "sliding_button_width": "20"
+        }
+    },
+    "2d_slider.button":
+    {
+        "prototype": "#no_frills_button"
+    },
+    "2d_slider.#sliding_button":
+    {
+        "misc":
+        {
+            "shape": "ellipse",
+            "border_width": "1",
+            "shadow_width": "1"
+        }
+    },
     "label":
     {
         "misc":
diff --git a/pygame_gui/elements/__init__.py b/pygame_gui/elements/__init__.py
index be4aa1d..97c535a 100644
--- a/pygame_gui/elements/__init__.py
+++ b/pygame_gui/elements/__init__.py
@@ -1,6 +1,7 @@
 from pygame_gui.elements.ui_image import UIImage
 from pygame_gui.elements.ui_button import UIButton
 from pygame_gui.elements.ui_horizontal_slider import UIHorizontalSlider
+from pygame_gui.elements.ui_2d_slider import UI2DSlider
 from pygame_gui.elements.ui_vertical_scroll_bar import UIVerticalScrollBar
 from pygame_gui.elements.ui_horizontal_scroll_bar import UIHorizontalScrollBar
 from pygame_gui.elements.ui_label import UILabel
@@ -22,6 +23,7 @@ from pygame_gui.elements.ui_text_entry_box import UITextEntryBox
 __all__ = ['UIImage',
            'UIButton',
            'UIHorizontalSlider',
+           'UI2DSlider',
            'UIVerticalScrollBar',
            'UIHorizontalScrollBar',
            'UILabel',
diff --git a/pygame_gui/elements/ui_2d_slider.py b/pygame_gui/elements/ui_2d_slider.py
new file mode 100644
index 0000000..0033fc1
--- /dev/null
+++ b/pygame_gui/elements/ui_2d_slider.py
@@ -0,0 +1,493 @@
+import pygame
+from pygame_gui._constants import UI_2D_SLIDER_MOVED
+from pygame_gui.core import UIElement, UIContainer, ObjectID
+from pygame_gui.core.interfaces import IUIManagerInterface, IContainerLikeInterface, Coordinate
+from pygame_gui.core.drawable_shapes import RectDrawableShape, RoundedRectangleShape
+from pygame_gui.elements import UIButton
+
+import warnings
+from typing import *
+
+
+class UI2DSlider(UIElement):
+    """
+    A 2d slider is intended to help users adjust values within a range, for example a
+    volume control.
+
+    :param relative_rect: A rectangle describing the position and dimensions of the element.
+    :param start_value_x: The x value to start the slider at.
+    :param value_range_x: The full range of x values.
+    :param start_value_y: The y value to start the slider at.
+    :param value_range_y: The full range of y values.
+    :param invert_y: Should the y increase from bottom to top instead of the other way round?
+    :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 set to None will be the root
+                      window's container.
+    :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: pygame.Rect,
+                 start_value_x: Union[float, int],
+                 value_range_x: Union[Tuple[float, float], Tuple[int, int]],
+                 start_value_y: Union[float, int],
+                 value_range_y: Union[Tuple[float, float], Tuple[int, int]],
+                 invert_y: bool = False,
+                 manager: Optional[IUIManagerInterface] = None,
+                 container: Optional[IContainerLikeInterface] = None,
+                 starting_height: int = 1,
+                 parent_element: Optional[UIElement] = None,
+                 object_id: Optional[Union[ObjectID, str]] = None,
+                 anchors: Optional[Dict[str, Union[str, UIElement]]] = None,
+                 visible: int = 1
+                 ):
+
+        # Need to move some declarations early as they are indirectly referenced via the ui element
+        # constructor
+        self.sliding_button = None
+        self.button_container = None
+        super().__init__(relative_rect, manager, container,
+                         layer_thickness=2,
+                         starting_height=starting_height,
+                         anchors=anchors,
+                         visible=visible,
+                         parent_element=parent_element,
+                         object_id=object_id,
+                         element_id=["2d_slider"])
+
+        self.default_button_width = 20
+        self.sliding_button_width = self.default_button_width
+        self.current_x_percentage = 0.5
+        self.current_y_percentage = 0.5
+        self.left_limit_position = 0.0
+        self.top_limit_position = 0.0
+        self.starting_grab_difference = 0
+
+        self.invert_y = invert_y
+
+        if all([isinstance(n, int) for n in [start_value_x, *value_range_x, start_value_y, *value_range_y]]):
+            self.use_integers_for_value = True
+        else:
+            self.use_integers_for_value = False
+
+        self.value_range_x = value_range_x
+        value_range_x_length = self.value_range_x[1] - self.value_range_x[0]
+        self.current_x_value = self.value_range_x[0] + (self.current_x_percentage * value_range_x_length)
+
+        self.value_range_y = value_range_y
+        value_range_y_length = self.value_range_y[1] - self.value_range_y[0]
+        self.current_y_value = self.value_range_y[0] + (self.current_y_percentage * value_range_y_length)
+
+        if self.use_integers_for_value:
+            self.current_x_value = int(self.current_x_value)
+            self.current_y_value = int(self.current_y_value)
+
+        self.grabbed_slider = False
+        self.has_moved_recently = False
+        self.has_been_moved_by_user_recently = False
+
+        self.background_colour = None
+        self.border_colour = None
+        self.disabled_border_colour = None
+        self.disabled_background_colour = None
+
+        self.border_width = None
+        self.shadow_width = None
+
+        self.drawable_shape = None
+        self.shape = "rectangle"
+        self.shape_corner_radius = None
+
+        self.background_rect: Optional[pygame.Rect] = None
+
+        self.scrollable_width = None
+        self.scrollable_height = None
+        self.right_limit_position = None
+        self.bottom_limit_position = None
+        self.scroll_position: Optional[pygame.Vector2] = None
+
+        self.sliding_button = None
+
+        self.button_container = None
+
+        self.rebuild_from_changed_theme_data()
+
+        sliding_x_pos = int(self.background_rect.width / 2 - self.sliding_button_width / 2)
+        sliding_y_pos = int(self.background_rect.height / 2 - self.sliding_button_width / 2)
+        self.sliding_button = UIButton(pygame.Rect((sliding_x_pos, sliding_y_pos),
+                                                   (self.sliding_button_width,
+                                                    self.sliding_button_width)),
+                                       "", self.ui_manager,
+                                       container=self.button_container,
+                                       starting_height=1,
+                                       parent_element=self,
+                                       object_id=ObjectID(object_id="#sliding_button",
+                                                          class_id=None),
+                                       anchors={"left": "left",
+                                                "right": "left",
+                                                "top": "top",
+                                                "bottom": "top"},
+                                       visible=self.visible
+                                       )
+
+        self.sliding_button.set_hold_range((self.background_rect.width, self.background_rect.height))
+
+        self.set_current_value(start_value_x, start_value_y)
+
+    def rebuild(self):
+        """
+        Rebuild anything that might need rebuilding.
+
+        """
+        border_and_shadow = self.border_width + self.shadow_width
+        self.background_rect = pygame.Rect((border_and_shadow + self.relative_rect.x,
+                                            border_and_shadow + self.relative_rect.y),
+                                           (self.relative_rect.width - (2 * border_and_shadow),
+                                            self.relative_rect.height - (2 * border_and_shadow)))
+
+        theming_parameters = {"normal_bg": self.background_colour,
+                              "normal_border": self.border_colour,
+                              "disabled_bg": self.disabled_background_colour,
+                              "disabled_border": self.disabled_border_colour,
+                              "border_width": self.border_width,
+                              "shadow_width": self.shadow_width,
+                              "shape_corner_radius": self.shape_corner_radius}
+
+        if self.shape == "rectangle":
+            self.drawable_shape = RectDrawableShape(self.rect, theming_parameters,
+                                                    ["normal", "disabled"], self.ui_manager)
+        elif self.shape == "rounded_rectangle":
+            self.drawable_shape = RoundedRectangleShape(self.rect, theming_parameters,
+                                                        ["normal", "disabled"], self.ui_manager)
+
+        self._set_image(self.drawable_shape.get_fresh_surface())
+
+        if self.button_container is None:
+            self.button_container = UIContainer(self.background_rect,
+                                                manager=self.ui_manager,
+                                                container=self.ui_container,
+                                                anchors=self.anchors,
+                                                object_id="#2d_slider_buttons_container",
+                                                visible=self.visible)
+        else:
+            self.button_container.set_dimensions(self.background_rect.size)
+            self.button_container.set_relative_position(self.background_rect.topleft)
+
+        self.scrollable_width = (self.background_rect.width - self.sliding_button_width)
+        self.scrollable_height = (self.background_rect.height - self.sliding_button_width)
+        self.right_limit_position = self.scrollable_width
+        self.bottom_limit_position = self.scrollable_height
+        self.scroll_position = pygame.Vector2(self.scrollable_width / 2, self.scrollable_height / 2)
+
+        if self.sliding_button is not None:
+            self.sliding_button.set_dimensions((self.sliding_button_width,
+                                                self.sliding_button_width))
+            self.sliding_button.set_hold_range((self.background_rect.width, self.background_rect.height))
+            self.set_current_value(self.current_x_value, self.current_y_value)
+
+    def kill(self):
+        """
+        Overrides the normal sprite kill() method to also kill the button elements that help make
+        up the slider.
+
+        """
+        self.button_container.kill()
+        super().kill()
+
+    def update(self, time_delta: float):
+        """
+        Takes care of actually moving the slider based on interactions reported by the buttons or
+        based on movement of the mouse if we are gripping the slider itself.
+
+        :param time_delta: the time in seconds between calls to update.
+
+        """
+        super().update(time_delta)
+
+        if not (self.alive() and self.is_enabled):
+            return
+        moved_this_frame = False
+
+        mouse_pos = pygame.Vector2(self.ui_manager.get_mouse_position())
+        if self.sliding_button.held and self.sliding_button.in_hold_range(mouse_pos):
+            if not self.grabbed_slider:
+                self.grabbed_slider = True
+                real_scroll_pos = pygame.Vector2(self.sliding_button.rect.topleft)
+                self.starting_grab_difference = mouse_pos - real_scroll_pos
+
+            real_scroll_pos = pygame.Vector2(self.sliding_button.rect.topleft)
+            current_grab_difference = mouse_pos - real_scroll_pos
+            adjustment_required = current_grab_difference - self.starting_grab_difference
+            self.scroll_position = self.scroll_position + adjustment_required
+
+            self.scroll_position = pygame.Vector2(min(max(self.scroll_position.x, self.left_limit_position),
+                                                      self.right_limit_position),
+                                                  min(max(self.scroll_position.y, self.top_limit_position),
+                                                      self.bottom_limit_position))
+
+            self.sliding_button.set_relative_position(self.scroll_position)
+
+            moved_this_frame = True
+        elif not self.sliding_button.held:
+            self.grabbed_slider = False
+
+        if moved_this_frame:
+            self.current_x_percentage = self.scroll_position.x / self.scrollable_width
+            self.current_x_value = self.value_range_x[0] + (self.current_x_percentage *
+                                                            (self.value_range_x[1] - self.value_range_x[0]))
+
+            if self.invert_y:
+                # Scroll position is inverted, so invert it again to get actual percentage
+                self.current_y_percentage = (self.scrollable_height - self.scroll_position.y) / self.scrollable_height
+            else:
+                self.current_y_percentage = self.scroll_position.y / self.scrollable_height
+            self.current_y_value = self.value_range_y[0] + (self.current_y_percentage *
+                                                            (self.value_range_y[1] - self.value_range_y[0]))
+            if self.use_integers_for_value:
+                self.current_x_value = int(self.current_x_value)
+                self.current_y_value = int(self.current_y_value)
+
+            if not self.has_moved_recently:
+                self.has_moved_recently = True
+
+            if not self.has_been_moved_by_user_recently:
+                self.has_been_moved_by_user_recently = True
+
+            # new event
+            event_data = {"value": self.current_x_value,
+                          "ui_element": self,
+                          "ui_object_id": self.most_specific_combined_id}
+            pygame.event.post(pygame.event.Event(UI_2D_SLIDER_MOVED, event_data))
+
+    def get_current_value(self) -> Tuple[Union[float, float], Union[int, int]]:
+        """
+        Gets the current value the slider is set to.
+
+        :return: The current value recorded by the slider.
+
+        """
+        self.has_moved_recently = False
+        self.has_been_moved_by_user_recently = False
+        return self.current_x_value, self.current_y_value
+
+    def set_current_value(self, value_x: Union[float, int], value_y: Union[float, int], warn: bool = True) -> None:
+        """
+        Sets the value of the slider, which will move the position of the slider to match. Will
+        issue a warning if the value set is not in the value range.
+
+        :param value_x: The x value to set.
+        :param value_y: The y value to set.
+        :param warn: set to false in order to suppress the default warning,
+                     instead the value will be clamped.
+        :param: invert_y: Should the y value be inverted? If not passed then will use self.invert_value for this info.
+        :return: None
+        """
+        if self.use_integers_for_value:
+            value_x = int(value_x)
+            value_y = int(value_y)
+
+        min_x_value = min(self.value_range_x[0], self.value_range_x[1])
+        max_x_value = max(self.value_range_x[0], self.value_range_x[1])
+
+        if value_x < min_x_value or value_x > max_x_value:
+            if warn:
+                warnings.warn("x value not in range", UserWarning)
+                return
+            else:
+                self.current_x_value = max(min(value_x, max_x_value), min_x_value)
+        else:
+            self.current_x_value = value_x
+
+        min_y_value = min(self.value_range_y[0], self.value_range_y[1])
+        max_y_value = max(self.value_range_y[0], self.value_range_y[1])
+
+        if value_y < min_y_value or value_y > max_y_value:
+            if warn:
+                warnings.warn("y value not in range", UserWarning)
+                return
+            else:
+                self.current_y_value = max(min(value_y, max_y_value), min_y_value)
+        else:
+            self.current_y_value = value_y
+
+        value_x_range_size = (self.value_range_x[1] - self.value_range_x[0])
+        value_y_range_size = (self.value_range_y[1] - self.value_range_y[0])
+
+        if value_x_range_size != 0 and value_y_range_size != 0:
+            self.current_x_percentage = (float(self.current_x_value) -
+                                         self.value_range_x[0]) / value_x_range_size
+            self.current_y_percentage = (float(self.current_y_value) -
+                                         self.value_range_y[0]) / value_y_range_size
+
+            # Invert the position at which the slider should be
+            if self.invert_y:
+                height = self.scrollable_height * (1 - self.current_y_percentage)
+            else:
+                height = self.scrollable_height * self.current_y_percentage
+
+            self.scroll_position = (self.scrollable_width * self.current_x_percentage,
+                                    height)
+
+            self.sliding_button.set_relative_position(self.scroll_position)
+            self.has_moved_recently = True
+
+    def rebuild_from_changed_theme_data(self) -> None:
+        """
+        Called by the UIManager to check the theming data and rebuild whatever needs rebuilding for
+        this element when the theme data has changed.
+
+        :return: None
+        """
+        super().rebuild_from_changed_theme_data()
+        has_any_changed = False
+
+        if self._check_misc_theme_data_changed(attribute_name="shape",
+                                               default_value="rectangle",
+                                               casting_func=str,
+                                               allowed_values=["rectangle",
+                                                               "rounded_rectangle"]):
+            has_any_changed = True
+
+        if self._check_shape_theming_changed(defaults={"border_width": 1,
+                                                       "shadow_width": 2,
+                                                       "shape_corner_radius": 2}):
+            has_any_changed = True
+
+        background_colour = self.ui_theme.get_colour_or_gradient("dark_bg",
+                                                                 self.combined_element_ids)
+        if background_colour != self.background_colour:
+            self.background_colour = background_colour
+            has_any_changed = True
+
+        border_colour = self.ui_theme.get_colour_or_gradient("normal_border",
+                                                             self.combined_element_ids)
+        if border_colour != self.border_colour:
+            self.border_colour = border_colour
+            has_any_changed = True
+
+        disabled_background_colour = self.ui_theme.get_colour_or_gradient("disabled_dark_bg",
+                                                                          self.combined_element_ids)
+        if disabled_background_colour != self.disabled_background_colour:
+            self.disabled_background_colour = disabled_background_colour
+            has_any_changed = True
+
+        disabled_border_colour = self.ui_theme.get_colour_or_gradient("disabled_border",
+                                                                      self.combined_element_ids)
+        if disabled_border_colour != self.disabled_border_colour:
+            self.disabled_border_colour = disabled_border_colour
+            has_any_changed = True
+
+        if self._check_misc_theme_data_changed(attribute_name="sliding_button_width",
+                                               default_value=self.default_button_width,
+                                               casting_func=int):
+            has_any_changed = True
+
+        if has_any_changed:
+            self.rebuild()
+
+    def set_position(self, position: Coordinate) -> None:
+        """
+        Sets the absolute screen position of this slider, updating all subordinate button elements
+        at the same time.
+
+        :param position: The absolute screen position to set.
+        :return: None
+        """
+        super().set_position(position)
+
+        border_and_shadow = self.border_width + self.shadow_width
+        self.background_rect.x = border_and_shadow + self.relative_rect.x
+        self.background_rect.y = border_and_shadow + self.relative_rect.y
+
+        self.button_container.set_relative_position(self.background_rect.topleft)
+
+    def set_relative_position(self, position: Coordinate) -> None:
+        """
+        Sets the relative screen position of this slider, updating all subordinate button elements
+        at the same time.
+
+        :param position: The relative screen position to set.
+        :return: None
+        """
+        super().set_relative_position(position)
+
+        border_and_shadow = self.border_width + self.shadow_width
+        self.background_rect.x = border_and_shadow + self.relative_rect.x
+        self.background_rect.y = border_and_shadow + self.relative_rect.y
+
+        self.button_container.set_relative_position(self.background_rect.topleft)
+
+    def set_dimensions(self, dimensions: Coordinate,
+                       clamp_to_container: bool = False):
+        """
+        Method to directly set the dimensions of an element.
+
+        :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, clamp_to_container)
+
+        border_and_shadow = self.border_width + self.shadow_width
+        self.background_rect.width = self.relative_rect.width - (2 * border_and_shadow)
+        self.background_rect.height = self.relative_rect.height - (2 * border_and_shadow)
+
+        self.button_container.set_dimensions(self.background_rect.size)
+
+        # sort out sliding button parameters
+        self.scrollable_width = (self.background_rect.width - self.sliding_button_width)
+        self.scrollable_height = (self.background_rect.height - self.sliding_button_width)
+        self.right_limit_position = self.scrollable_width
+        self.bottom_limit_position = self.scrollable_height
+        self.scroll_position = (self.scrollable_width * self.current_x_percentage,
+                                self.scrollable_height * self.current_y_percentage)
+
+        self.sliding_button.set_dimensions((self.sliding_button_width, self.sliding_button_width))
+        self.sliding_button.set_relative_position(self.scroll_position)
+
+    def disable(self):
+        """
+        Disable the slider. It should not be interactive and will use the disabled theme colours.
+        """
+        if self.is_enabled:
+            self.is_enabled = False
+            self.sliding_button.disable()
+            self.drawable_shape.set_active_state("disabled")
+
+    def enable(self):
+        """
+        Enable the slider. It should become interactive and will use the normal theme colours.
+        """
+        if not self.is_enabled:
+            self.is_enabled = True
+            self.sliding_button.enable()
+            self.drawable_shape.set_active_state("normal")
+
+    def show(self):
+        """
+        In addition to the base UIElement.show() - show the sliding button and show
+        the button_container which will propagate and show the left and right buttons.
+        """
+        super().show()
+
+        if self.sliding_button is not None:
+            self.sliding_button.show()
+        if self.button_container is not None:
+            self.button_container.show()
+
+    def hide(self):
+        """
+        In addition to the base UIElement.hide() - hide the sliding button and hide
+        the button_container which will propagate and hide the left and right buttons.
+        """
+        super().hide()
+
+        if self.sliding_button is not None:
+            self.sliding_button.hide()
+        if self.button_container is not None:
+            self.button_container.hide()
diff --git a/pygame_gui/elements/ui_scrolling_container.py b/pygame_gui/elements/ui_scrolling_container.py
index 7ff254e..b61de40 100644
--- a/pygame_gui/elements/ui_scrolling_container.py
+++ b/pygame_gui/elements/ui_scrolling_container.py
@@ -40,10 +40,7 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
                  parent_element: Optional[UIElement] = None,
                  object_id: Optional[Union[ObjectID, str]] = None,
                  anchors: Optional[Dict[str, Union[str, UIElement]]] = None,
-                 visible: int = 1,
-                 allow_scroll_x: bool = True,
-                 allow_scroll_y: bool = True,
-                 ):
+                 visible: int = 1):
         # Need to move some declarations early as they are indirectly referenced via the ui element
         # constructor
         self._root_container = None
@@ -58,14 +55,11 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
                          object_id=object_id,
                          element_id=['scrolling_container'])
 
+        # self.parent_element = parent_element
         self.scroll_bar_width = 0
         self.scroll_bar_height = 0
 
         self.need_to_sort_out_scrollbars = False
-
-        self.allow_scroll_x = allow_scroll_x
-        self.allow_scroll_y = allow_scroll_y
-
         self.vert_scroll_bar: Optional[UIVerticalScrollBar] = None
         self.horiz_scroll_bar: Optional[UIHorizontalScrollBar] = None
 
@@ -277,11 +271,7 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
         bar has been moved. Instead, it tries to keep the scrollbars in the same approximate position
         they were in before resizing
         """
-
-        # First call to see if scrolling container size on its own necessitates scroll bars
         self._check_scroll_bars_and_adjust()
-        # second call to see if the view space contraction produced by any scroll bars created
-        # in the first call, require an additional scroll bar
         need_horiz_scroll_bar, need_vert_scroll_bar = self._check_scroll_bars_and_adjust()
 
         if need_vert_scroll_bar:
@@ -354,11 +344,11 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
         need_horiz_scroll_bar = False
         need_vert_scroll_bar = False
         if (self.scrolling_height > self._view_container.rect.height or
-                self.scrollable_container.relative_rect.top != 0) and self.allow_scroll_y:
+                self.scrollable_container.relative_rect.top != 0):
             need_vert_scroll_bar = True
             self.scroll_bar_width = 20
         if (self.scrolling_width > self._view_container.rect.width or
-                self.scrollable_container.relative_rect.left != 0) and self.allow_scroll_x:
+                self.scrollable_container.relative_rect.left != 0):
             need_horiz_scroll_bar = True
             self.scroll_bar_height = 20
         if need_vert_scroll_bar or need_horiz_scroll_bar:
@@ -366,16 +356,6 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
             new_height = (self._root_container.rect.height - self.scroll_bar_height)
             new_dimensions = (new_width, new_height)
             self._view_container.set_dimensions(new_dimensions)
-
-            if not self.allow_scroll_x:
-                # horizontal scrolling is banned, lets shrink the scrollable width
-                # to account for any scroll bar as well
-                self.scrollable_container.set_dimensions((new_width,
-                                                          self.scrollable_container.rect.height))
-            if not self.allow_scroll_y:
-                self.scrollable_container.set_dimensions((self.scrollable_container.rect.width,
-                                                          new_height))
-
         self._calculate_scrolling_dimensions()
         return need_horiz_scroll_bar, need_vert_scroll_bar
 


Test patch:
diff --git a/tests/data/themes/ui_2d_slider_bad_values.json b/tests/data/themes/ui_2d_slider_bad_values.json
new file mode 100644
index 0000000..ee0ff3e
--- /dev/null
+++ b/tests/data/themes/ui_2d_slider_bad_values.json
@@ -0,0 +1,17 @@
+{
+    "2d_slider":
+    {
+        "colours":
+         {
+             "normal_border": "horse",
+             "dark_bg": "patrol"
+         },
+         "misc":
+         {
+             "shape": "grounded_drecktangle",
+             "shape_corner_radius": "cow",
+             "border_width": "0.0",
+             "shadow_width": "3.0"
+         }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/themes/ui_2d_slider_non_default.json b/tests/data/themes/ui_2d_slider_non_default.json
new file mode 100644
index 0000000..ee869ab
--- /dev/null
+++ b/tests/data/themes/ui_2d_slider_non_default.json
@@ -0,0 +1,18 @@
+{
+    "2d_slider":
+    {
+        "colours":
+         {
+             "normal_border": "#AAAAAA,#FFFFFF,0",
+             "dark_bg": "#f4251b,#FF0000AA,90"
+         },
+         "misc":
+         {
+             "shape": "rounded_rectangle",
+             "shape_corner_radius": "6",
+             "border_width": "0",
+             "shadow_width": "3",
+             "sliding_button_width": "15"
+         }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_elements/test_ui_2d_slider.py b/tests/test_elements/test_ui_2d_slider.py
new file mode 100644
index 0000000..3640d7c
--- /dev/null
+++ b/tests/test_elements/test_ui_2d_slider.py
@@ -0,0 +1,447 @@
+import os
+
+import pygame
+import pytest
+
+from pygame_gui.core.interfaces import IUIManagerInterface
+from pygame_gui.core.ui_container import UIContainer
+from pygame_gui.elements.ui_2d_slider import UI2DSlider
+from pygame_gui.ui_manager import UIManager
+from tests.shared_comparators import compare_surfaces
+
+
+class TestUI2DSlider:
+
+    def test_creation(self, _init_pygame, default_ui_manager: IUIManagerInterface,
+                      _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(100, 100, 100, 100),
+                            start_value_x=50,
+                            value_range_x=(0, 100),
+                            start_value_y=50,
+                            value_range_y=(0, 100),
+                            manager=default_ui_manager)
+        assert slider.image is not None
+
+    def test_rebuild(self, _init_pygame, default_ui_manager: IUIManagerInterface,
+                     _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(100, 100, 100, 100),
+                            start_value_x=50,
+                            value_range_x=(0, 100),
+                            start_value_y=50,
+                            value_range_y=(0, 100),
+                            manager=default_ui_manager)
+        slider.rebuild()
+
+        assert slider.image is not None
+
+    def test_kill(self, _init_pygame, default_ui_manager: IUIManagerInterface,
+                  _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(100, 100, 100, 100),
+                            start_value_x=50,
+                            value_range_x=(0, 100),
+                            start_value_y=50,
+                            value_range_y=(0, 100),
+                            manager=default_ui_manager)
+
+        assert len(default_ui_manager.get_root_container().elements) == 2
+        assert len(default_ui_manager.get_sprite_group().sprites()) == 4
+        assert default_ui_manager.get_sprite_group().sprites() == [default_ui_manager.get_root_container(),
+                                                                   slider,
+                                                                   slider.button_container,
+                                                                   slider.sliding_button]
+        slider.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_check_has_moved_recently(self, _init_pygame, default_ui_manager,
+                                      _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(100, 100, 100, 100),
+                            start_value_x=50,
+                            value_range_x=(0, 100),
+                            start_value_y=50,
+                            value_range_y=(0, 100),
+                            manager=default_ui_manager)
+
+        # move the scroll bar a bit
+        slider.sliding_button.held = True
+        pygame.mouse.set_pos((slider.sliding_button.get_abs_rect().centerx + 20,
+                              slider.sliding_button.get_abs_rect().centery))
+        slider.update(0.2)
+        assert slider.has_moved_recently is True
+
+    def test_check_update_sliding_bar(self, _init_pygame, default_ui_manager,
+                                      _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(0, 0, 200, 30),
+                            start_value_x=50,
+                            value_range_x=(0, 100),
+                            start_value_y=50,
+                            value_range_y=(0, 100),
+                            manager=default_ui_manager)
+
+        # scroll down a bit then up again to exercise update
+        default_ui_manager.mouse_position = (100, 15)
+        slider.sliding_button.held = True
+        slider.update(0.3)
+
+        assert slider.grabbed_slider is True
+
+        slider.sliding_button.held = False
+        slider.update(0.3)
+
+        assert slider.grabbed_slider is False
+
+        non_y_invert_slider = UI2DSlider(relative_rect=pygame.Rect(0, 0, 200, 30),
+                                         start_value_x=50,
+                                         value_range_x=(0, 100),
+                                         start_value_y=50,
+                                         value_range_y=(0, 100),
+                                         invert_y=True,
+                                         manager=default_ui_manager)
+
+        # scroll down a bit then up again to exercise update
+        default_ui_manager.mouse_position = (100, 15)
+        non_y_invert_slider.sliding_button.held = True
+        non_y_invert_slider.update(0.3)
+
+        assert non_y_invert_slider.grabbed_slider is True
+
+        non_y_invert_slider.sliding_button.held = False
+        non_y_invert_slider.update(0.3)
+
+        assert non_y_invert_slider.grabbed_slider is False
+
+    def test_get_current_value(self, _init_pygame, default_ui_manager,
+                               _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(100, 100, 100, 100),
+                            start_value_x=50,
+                            value_range_x=(0, 100),
+                            start_value_y=50,
+                            value_range_y=(0, 100),
+                            manager=default_ui_manager)
+
+        assert slider.get_current_value() == (50, 50)
+
+    def test_set_current_value_in_range(self, _init_pygame, default_ui_manager,
+                                        _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(100, 100, 100, 100),
+                            start_value_x=50,
+                            value_range_x=(0, 100),
+                            start_value_y=50,
+                            value_range_y=(0, 100),
+                            manager=default_ui_manager)
+
+        slider.set_current_value(75, 75)
+        assert slider.get_current_value() == (75, 75)
+
+        slider = UI2DSlider(relative_rect=pygame.Rect(100, 100, 100, 100),
+                            start_value_x=50,
+                            value_range_x=(0, 100),
+                            start_value_y=50,
+                            value_range_y=(0, 100),
+                            invert_y=True,
+                            manager=default_ui_manager)
+
+        slider.set_current_value(75, 75)
+        assert slider.get_current_value() == (75, 75)
+
+    def test_set_current_value_out_of_range(self, _init_pygame, default_ui_manager,
+                                            _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(100, 100, 100, 100),
+                            start_value_x=50,
+                            value_range_x=(0, 100),
+                            start_value_y=50,
+                            value_range_y=(0, 100),
+                            manager=default_ui_manager)
+
+        with pytest.warns(UserWarning, match='x value not in range'):
+            slider.set_current_value(200, 50)
+
+        slider.set_current_value(200, 50, warn=False)
+
+        assert slider.get_current_value() == (100, 50)
+
+        with pytest.warns(UserWarning, match='y value not in range'):
+            slider.set_current_value(50, 250)
+
+        slider.set_current_value(50, 250, warn=False)
+
+        assert slider.get_current_value() == (50, 100)
+
+    def test_rebuild_from_theme_data_non_default(self, _init_pygame,
+                                                 _display_surface_return_none):
+        manager = UIManager((800, 600), os.path.join("tests", "data",
+                                                     "themes",
+                                                     "ui_2d_slider_non_default.json"))
+
+        slider = UI2DSlider(relative_rect=pygame.Rect(100, 100, 100, 100),
+                            start_value_x=50,
+                            value_range_x=(0, 100),
+                            start_value_y=50,
+                            value_range_y=(0, 100),
+                            manager=manager)
+        assert slider.image is not None
+
+    @pytest.mark.filterwarnings("ignore:Invalid value")
+    @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
+    def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
+                                                _display_surface_return_none):
+        manager = UIManager((800, 600), os.path.join("tests",
+                                                     "data",
+                                                     "themes",
+                                                     "ui_2d_slider_bad_values.json"))
+
+        slider = UI2DSlider(relative_rect=pygame.Rect(100, 100, 100, 100),
+                            start_value_x=51,
+                            value_range_x=(0, 100),
+                            start_value_y=50,
+                            value_range_y=(0, 100),
+                            manager=manager)
+        assert slider.image is not None
+
+    def test_set_position(self, _init_pygame, default_ui_manager, _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(300, 400, 200, 200),
+                            start_value_x=100,
+                            value_range_x=(0, 200),
+                            start_value_y=100,
+                            value_range_y=(0, 200),
+                            manager=default_ui_manager)
+
+        slider.set_position((200, 200))
+
+        # try to click on the sliding button
+        default_ui_manager.process_events(pygame.event.Event(pygame.MOUSEBUTTONDOWN,
+                                                             {'button': 1,
+                                                              'pos': (305, 305)}))
+        # if we successfully clicked on the moved slider then this button should be True
+        assert slider.sliding_button.held is True
+
+    def test_set_relative_position(self, _init_pygame, default_ui_manager,
+                                   _display_surface_return_none):
+        test_container = UIContainer(relative_rect=pygame.Rect(100, 100, 300, 300),
+                                     manager=default_ui_manager)
+        slider = UI2DSlider(relative_rect=pygame.Rect(300, 400, 150, 150),
+                            start_value_x=75,
+                            value_range_x=(0, 150),
+                            start_value_y=75,
+                            value_range_y=(0, 150),
+                            container=test_container,
+                            manager=default_ui_manager)
+
+        slider.set_relative_position((150.0, 150.0))
+
+        # try to click on the slider
+        default_ui_manager.process_events(pygame.event.Event(pygame.MOUSEBUTTONDOWN,
+                                                             {'button': 1,
+                                                              'pos': (330, 330)}))
+
+        assert slider.rect.topleft == (250, 250) and slider.sliding_button.held is True
+
+    def test_set_dimensions(self, _init_pygame, default_ui_manager, _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(0, 0, 150, 150),
+                            start_value_x=100,
+                            value_range_x=(0, 200),
+                            start_value_y=100,
+                            value_range_y=(0, 200),
+                            manager=default_ui_manager)
+
+        slider.set_dimensions((200, 200))
+
+        # try to click on the slider
+        default_ui_manager.process_events(pygame.event.Event(pygame.MOUSEBUTTONDOWN,
+                                                             {'button': 1,
+                                                              'pos': (105, 105)}))
+        # if we successfully clicked on the moved slider then this button should be True
+        assert slider.sliding_button.held is True
+
+        slider.set_dimensions((100, 100))
+
+        # try to click on the slider
+        default_ui_manager.process_events(pygame.event.Event(pygame.MOUSEBUTTONDOWN,
+                                                             {'button': 1,
+                                                              'pos': (55, 55)}))
+        # if we successfully clicked on the moved slider then this button should be True
+        assert slider.sliding_button.held is True
+
+    def test_disable(self, _init_pygame: None, default_ui_manager: UIManager,
+                     _display_surface_return_none: None):
+        slider = UI2DSlider(relative_rect=pygame.Rect(0, 0, 150, 150),
+                            start_value_x=50,
+                            value_range_x=(0, 200),
+                            start_value_y=50,
+                            value_range_y=(0, 200),
+                            manager=default_ui_manager)
+
+        slider.disable()
+
+        # process a mouse button down event
+        slider.sliding_button.process_event(
+            pygame.event.Event(pygame.MOUSEBUTTONDOWN, {'button': 1,
+                                                        'pos': slider.sliding_button.rect.center}))
+        slider.update(0.1)
+
+        pygame.mouse.set_pos((slider.sliding_button.get_abs_rect().centerx + 20,
+                              slider.sliding_button.get_abs_rect().centery))
+        slider.update(0.1)
+
+        # process a mouse button up event
+        slider.sliding_button.process_event(
+            pygame.event.Event(pygame.MOUSEBUTTONUP, {'button': 1,
+                                                      'pos': slider.sliding_button.rect.center}))
+
+        assert slider.get_current_value() == (50, 50) and slider.is_enabled is False
+
+    def test_enable(self, _init_pygame: None, default_ui_manager: UIManager,
+                    _display_surface_return_none: None):
+        slider = UI2DSlider(relative_rect=pygame.Rect(0, 0, 200, 200),
+                            start_value_x=100,
+                            value_range_x=(0, 200),
+                            start_value_y=100,
+                            value_range_y=(0, 200),
+                            manager=default_ui_manager)
+
+        slider.disable()
+
+        slider.enable()
+
+        assert slider.is_enabled is True
+
+    def test_ints_in_ints_out(self, _init_pygame: None, default_ui_manager: UIManager,
+                              _display_surface_return_none: None):
+        slider = UI2DSlider(relative_rect=pygame.Rect(0, 0, 150, 150),
+                            start_value_x=50,
+                            value_range_x=(0, 200),
+                            start_value_y=50,
+                            value_range_y=(0, 200),
+                            manager=default_ui_manager)
+
+        current_value = slider.get_current_value()
+        assert isinstance(current_value, tuple)
+        assert isinstance(current_value[0], int)
+        assert isinstance(current_value[1], int)
+
+        # process a mouse button down event
+        slider.sliding_button.process_event(
+            pygame.event.Event(pygame.MOUSEBUTTONDOWN, {'button': 1,
+                                                        'pos': slider.sliding_button.rect.center}))
+
+        pygame.mouse.set_pos((slider.sliding_button.get_abs_rect().centerx + 20,
+                              slider.sliding_button.get_abs_rect().centery))
+        slider.update(0.1)
+
+        # process a mouse button up event
+        slider.sliding_button.process_event(
+            pygame.event.Event(pygame.MOUSEBUTTONUP, {'button': 1,
+                                                      'pos': slider.sliding_button.rect.center}))
+
+        current_value = slider.get_current_value()
+        assert isinstance(current_value, tuple)
+        assert isinstance(current_value[0], int)
+        assert isinstance(current_value[1], int)
+
+    def test_floats_in_floats_out(self, _init_pygame: None, default_ui_manager: UIManager,
+                                  _display_surface_return_none: None):
+        slider = UI2DSlider(relative_rect=pygame.Rect(0, 0, 150, 150),
+                            start_value_x=25.0,
+                            value_range_x=(0.0, 200.0),
+                            start_value_y=25.0,
+                            value_range_y=(0.0, 200.0),
+                            manager=default_ui_manager)
+
+        current_value = slider.get_current_value()
+        assert isinstance(current_value, tuple)
+        assert isinstance(current_value[0], float)
+        assert isinstance(current_value[1], float)
+
+        # process a mouse button down event
+        slider.sliding_button.process_event(
+            pygame.event.Event(pygame.MOUSEBUTTONDOWN, {'button': 1,
+                                                        'pos': slider.sliding_button.rect.center}))
+
+        pygame.mouse.set_pos((slider.sliding_button.get_abs_rect().centerx + 20,
+                              slider.sliding_button.get_abs_rect().centery))
+        slider.update(0.1)
+
+        # process a mouse button up event
+        slider.sliding_button.process_event(
+            pygame.event.Event(pygame.MOUSEBUTTONUP, {'button': 1,
+                                                      'pos': slider.sliding_button.rect.center}))
+
+        current_value = slider.get_current_value()
+        assert isinstance(current_value, tuple)
+        assert isinstance(current_value[0], float)
+        assert isinstance(current_value[1], float)
+
+    def test_show(self, _init_pygame, default_ui_manager, _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(0, 0, 150, 40),
+                            start_value_x=50,
+                            value_range_x=(0, 200),
+                            start_value_y=50,
+                            value_range_y=(0, 200),
+                            manager=default_ui_manager, visible=0)
+
+        assert slider.visible == 0
+
+        assert slider.sliding_button.visible == 0
+        assert slider.button_container.visible == 0
+
+        slider.show()
+
+        assert slider.visible == 1
+
+        assert slider.sliding_button.visible == 1
+        assert slider.button_container.visible == 1
+
+    def test_hide(self, _init_pygame, default_ui_manager, _display_surface_return_none):
+        slider = UI2DSlider(relative_rect=pygame.Rect(0, 0, 150, 40),
+                            start_value_x=50,
+                            value_range_x=(0, 200),
+                            start_value_y=50,
+                            value_range_y=(0, 200),
+                            manager=default_ui_manager)
+
+        assert slider.visible == 1
+
+        assert slider.sliding_button.visible == 1
+        assert slider.button_container.visible == 1
+
+        slider.hide()
+
+        assert slider.visible == 0
+
+        assert slider.sliding_button.visible == 0
+        assert slider.button_container.visible == 0
+
+    def test_show_hide_rendering(self, _init_pygame, default_ui_manager, _display_surface_return_none):
+        resolution = (400, 400)
+        empty_surface = pygame.Surface(resolution)
+        empty_surface.fill(pygame.Color(0, 0, 0))
+
+        surface = empty_surface.copy()
+        manager = UIManager(resolution)
+        slider = UI2DSlider(relative_rect=pygame.Rect(25, 25, 375, 375),
+                            start_value_x=50,
+                            value_range_x=(0, 200),
+                            start_value_y=50,
+                            value_range_y=(0, 200),
+                            manager=manager, visible=0)
+        manager.update(0.01)
+        manager.draw_ui(surface)
+        assert compare_surfaces(empty_surface, surface)
+
+        surface.fill(pygame.Color(0, 0, 0))
+        slider.show()
+        manager.update(0.01)
+        manager.draw_ui(surface)
+        assert not compare_surfaces(empty_surface, surface)
+
+        surface.fill(pygame.Color(0, 0, 0))
+        slider.hide()
+        manager.update(0.01)
+        manager.draw_ui(surface)
+        assert compare_surfaces(empty_surface, surface)
+
+
+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 b68e11a..0f07096 100644
--- a/tests/test_elements/test_ui_scrolling_container.py
+++ b/tests/test_elements/test_ui_scrolling_container.py
@@ -116,43 +116,6 @@ class TestUIScrollingContainer:
         assert container._view_container.rect.size == (200-container.vert_scroll_bar.rect.width,
                                                        200-container.horiz_scroll_bar.rect.height)
 
-    def test_set_scrollable_area_dimensions_constrained_x_axis(self, _init_pygame, default_ui_manager,
-                                                               _display_surface_return_none):
-        container = UIScrollingContainer(pygame.Rect(100, 100, 200, 200),
-                                         manager=default_ui_manager,
-                                         allow_scroll_x=False)
-
-        assert container.vert_scroll_bar is None
-        assert container.horiz_scroll_bar is None
-        assert container.scrollable_container.rect.size == (200, 200)
-
-        container.set_scrollable_area_dimensions((200, 600))
-
-        assert container.vert_scroll_bar is not None
-        assert container.horiz_scroll_bar is None
-        assert container._view_container.rect.size == (200 - container.vert_scroll_bar.rect.width,
-                                                       200)
-        assert container.scrollable_container.rect.size == (200 - container.vert_scroll_bar.rect.width, 600)
-
-    def test_set_scrollable_area_dimensions_constrained_y_axis(self, _init_pygame, default_ui_manager,
-                                                               _display_surface_return_none):
-        container = UIScrollingContainer(pygame.Rect(100, 100, 200, 200),
-                                         manager=default_ui_manager,
-                                         allow_scroll_y=False)
-
-        assert container.vert_scroll_bar is None
-        assert container.horiz_scroll_bar is None
-        assert container.scrollable_container.rect.size == (200, 200)
-
-        container.set_scrollable_area_dimensions((600, 200))
-
-        assert container.vert_scroll_bar is None
-        assert container.horiz_scroll_bar is not None
-        assert container._view_container.rect.size == (200,
-                                                       200 - container.horiz_scroll_bar.rect.height)
-        assert container.scrollable_container.rect.size == (600,
-                                                            200 - container.horiz_scroll_bar.rect.height)
-
     def test_update(self, _init_pygame, default_ui_manager,
                     _display_surface_return_none):
         container = UIScrollingContainer(pygame.Rect(100, 100, 200, 200),
@@ -165,7 +128,7 @@ class TestUIScrollingContainer:
 
         container.update(0.02)
 
-        assert container.get_container().get_relative_rect().x == -37
+        assert container.get_container().relative_rect.x == -37
 
         container.vert_scroll_bar.scroll_wheel_moved = True
         container.vert_scroll_bar.scroll_wheel_amount = -1.0
@@ -173,7 +136,7 @@ class TestUIScrollingContainer:
 
         container.update(0.02)
 
-        assert container.get_container().get_relative_rect().y == -67
+        assert container.get_container().relative_rect.y == -67
 
         container.horiz_scroll_bar.scroll_wheel_right = True
         container.horiz_scroll_bar.update(0.02)