Repository: ppizarror/pygame-menu

Issue:
Center menu items when scrollbar is shown
**Environment information**
Describe your environment information, such as:
- OS: win/linux
- python version: v3.9
- pygame version: v1.9.6
- pygame-menu version: v4.3.1

**Describe the bug**
I would like to center the menu items even when there is a scrollbar. (I added the red line in paint to visualize the center)
![image](https://user-images.githubusercontent.com/9664767/218218397-514de10c-05fb-41c7-8762-1bcae17fa688.png)


**To Reproduce**
```py3
import pygame_menu
from pygame_menu.examples import create_example_window


surface = create_example_window('Example - Simple', (320, 240))

menu = pygame_menu.Menu(height=240, theme=pygame_menu.themes.THEME_BLUE, title='Main menu', width=320)

menu.add.button('Option1')
menu.add.button('Option2')
menu.add.button('Option3')
menu.add.button('Option4')
menu.add.button('Option5')
menu.add.button('Quit', pygame_menu.events.EXIT)

if __name__ == '__main__':
    menu.mainloop(surface)
```

**Expected behavior**
Center the items, or at least give me an option to toggle this behaviour.

**Additional context**
The reason is, I'm creating an application to be run on a Raspberry Pi on a 320x240 tft screen, with a few physical buttons below it, so you clearly see where is the center of the screen, and that the menu items are off-center. But even if you don't have a tiny screen, if you have a main menu with a few items, so without a scrollbar -> centered, then a submenu with more items so with a scrollbar -> off-center, and you switch between them, the jump is noticable.


Discussion:

Patch:
diff --git a/pygame_menu/menu.py b/pygame_menu/menu.py
index d92287cc..012d98b0 100644
--- a/pygame_menu/menu.py
+++ b/pygame_menu/menu.py
@@ -1551,9 +1551,6 @@ class Menu(Base):
                     y=menubar_height + padding[0] + d_border)
                 continue
 
-            # Update the position of the widget
-            widget.set_position(x_coord, y_coord)
-
             # Add the widget translation to the widget for computing the min/max position. This
             # feature does not work as intended as there's edge cases not covered, and centering makes
             # the translation more difficult
@@ -1567,6 +1564,13 @@ class Menu(Base):
             min_x = min(min_x, x_coord - padding[3] - sm_left)
             min_y = min(min_y, y_coord - padding[0])
 
+            # Restore the discounted scrollbar thickness
+            if self._theme.widget_alignment_ignore_scrollbar_thickness:
+                x_coord += self._get_scrollbar_thickness()[1] / 2
+
+            # Update the position of the widget
+            widget.set_position(x_coord, y_coord)
+
         # Update position
         if min_max_updated:
             self._widget_max_position = (max_x, max_y)
diff --git a/pygame_menu/themes.py b/pygame_menu/themes.py
index 680df55c..0f689f4f 100644
--- a/pygame_menu/themes.py
+++ b/pygame_menu/themes.py
@@ -167,6 +167,8 @@ class Theme(object):
     :type title_updates_pygame_display: bool
     :param widget_alignment: Widget default `alignment <https://pygame-menu.readthedocs.io/en/latest/_source/themes.html#alignment>`_. See :py:mod:`pygame_menu.locals`
     :type widget_alignment: str
+    :param widget_alignment_ignore_scrollbar_thickness: Widget positioning ignores the scrollbar thickness. If ``True``, the widgets only consider the menu size, ignoring the thickness of the visible scrollbars
+    :type widget_alignment_ignore_scrollbar_thickness: bool
     :param widget_background_color: Background color of a widget, it can be a color, ``None`` (transparent), or a BaseImage object. Background fills the entire widget + the padding
     :type widget_background_color: tuple, list, str, int, :py:class:`pygame.Color`, :py:class:`pygame_menu.baseimage.BaseImage`, None
     :param widget_background_inflate: Inflate background on x-axis and y-axis (x, y) in px. By default, it uses the highlight margin. This parameter is visual only. For modifying widget size use padding instead
@@ -284,6 +286,7 @@ class Theme(object):
     title_offset: Tuple2NumberType
     title_updates_pygame_display: bool
     widget_alignment: str
+    widget_alignment_ignore_scrollbar_thickness: bool
     widget_background_color: Optional[Union[ColorType, 'BaseImage']]
     widget_background_inflate: Tuple2IntType
     widget_background_inflate_to_selection: bool
@@ -378,6 +381,7 @@ class Theme(object):
 
         # Generic widget themes
         self.widget_alignment = self._get(kwargs, 'widget_alignment', 'alignment', ALIGN_CENTER)
+        self.widget_alignment_ignore_scrollbar_thickness = self._get(kwargs, 'widget_alignment_ignore_scrollbar_thickness', bool, False)
         self.widget_background_color = self._get(kwargs, 'widget_background_color', 'color_image_none')
         self.widget_background_inflate = self._get(kwargs, 'background_inflate', 'tuple2int', (0, 0))
         self.widget_background_inflate_to_selection = self._get(kwargs, 'widget_background_inflate_to_selection', bool,


Test patch:
diff --git a/test/test_widgets.py b/test/test_widgets.py
index 0d29af5b..a51cc972 100644
--- a/test/test_widgets.py
+++ b/test/test_widgets.py
@@ -542,3 +542,25 @@ class WidgetsTest(BaseTest):
         image_widget.translate(-50, 0)
         menu.render()
         self.assertEqual(image_widget.get_position(), (-42, 60))
+
+    def test_widget_center_overflow_ignore_scrollbar_thickness(self) -> None:
+        """
+        Test widget centering if overflow ignores scrollbar thickness.
+        """
+        theme = TEST_THEME.copy()
+
+        menu = MenuUtils.generic_menu(width=320, theme=theme)
+        for i in range(5):
+            menu.add.button(f'Option{i + 1}')
+            menu.add.button('Quit', pygame_menu.events.EXIT)
+
+        pos_before = menu.get_selected_widget().get_position()
+        theme.widget_alignment_ignore_scrollbar_thickness = True
+        menu.render()
+        pos_after = menu.get_selected_widget().get_position()
+
+        # If we ignore scrollbar thickess in position, the difference
+        # should be equal to the half of the scrollbar thickness (because
+        # we have centered alignment)
+        self.assertEqual(pos_after[0] - pos_before[0], menu._get_scrollbar_thickness()[1] / 2)  # x
+        self.assertEqual(pos_after[1], pos_before[1])  # y