Repository: ppizarror/pygame-menu

Issue:
Resize all submenus
**Environment information**

- OS: Mac OSX
- python version: v3.7
- pygame version: v2.x
- pygame-menu version: v4.3.8

**Describe the bug**
When resizing menu using `menu.resize(width, height)`, submenu are not resized.

**Expected behavior**
Is there any easy method to pass new size to all submenus?



Discussion:

Patch:
diff --git a/pygame_menu/font.py b/pygame_menu/font.py
index 5378659a..2fe87dff 100644
--- a/pygame_menu/font.py
+++ b/pygame_menu/font.py
@@ -39,7 +39,7 @@ __all__ = [
 ]
 
 from pathlib import Path
-from typing import Union, Optional, Any
+from typing import Union, Optional, Any, Dict, Tuple
 import os.path as path
 
 import pygame.font as __font
@@ -71,12 +71,12 @@ FONT_EXAMPLES = (FONT_8BIT, FONT_BEBAS, FONT_COMIC_NEUE, FONT_DIGITAL, FONT_FRAN
                  FONT_PT_SERIF, FONT_FIRACODE, FONT_FIRACODE_BOLD, FONT_FIRACODE_ITALIC,
                  FONT_FIRACODE_BOLD_ITALIC)
 
-# Stores font cache
-_cache = {}
-
 FontType = Union[str, __font.Font, Path]
 FontInstance = (str, __font.Font, Path)
 
+# Stores font cache
+_cache: Dict[Tuple[FontType, int], '__font.Font'] = {}
+
 
 def assert_font(font: Any) -> None:
     """
diff --git a/pygame_menu/menu.py b/pygame_menu/menu.py
index c40a9dad..2ec416d0 100644
--- a/pygame_menu/menu.py
+++ b/pygame_menu/menu.py
@@ -603,21 +603,29 @@ class Menu(Base):
         width: NumberType,
         height: NumberType,
         screen_dimension: Optional[Vector2IntType] = None,
-        position: Optional[Union[Vector2NumberType, Tuple[NumberType, NumberType, bool]]] = None
+        position: Optional[Union[Vector2NumberType, Tuple[NumberType, NumberType, bool]]] = None,
+        recursive: bool = False
     ) -> 'Menu':
         """
-        Resize the menu to another width/height
+        Resizes the menu to another width/height.
 
         :param width: Menu width (px)
         :param height: Menu height (px)
         :param screen_dimension: List/Tuple representing the dimensions the Menu should reference for sizing/positioning (width, height), if ``None`` pygame is queried for the display mode. This value defines the ``window_size`` of the Menu
         :param position: Position on x-axis and y-axis. If the value is only 2 elements, the position is relative to the window width (thus, values must be 0-100%); else, the third element defines if the position is relative or not. If ``(x, y, False)`` the values of ``(x, y)`` are in px. If ``None`` use the default from the menu constructor
+        :param recursive: If true, resize all submenus in a recursive fashion
         :return: Self reference
         """
         assert isinstance(width, NumberInstance)
         assert isinstance(height, NumberInstance)
         assert width > 0 and height > 0, \
             'menu width and height must be greater than zero'
+        assert isinstance(recursive, bool)
+
+        # Resize recursively
+        if recursive:
+            for menu in self.get_submenus(True):
+                menu.resize(width, height, screen_dimension, position)
 
         # Convert to int
         width, height = int(width), int(height)
diff --git a/pygame_menu/version.py b/pygame_menu/version.py
index b9b93a75..6103afff 100644
--- a/pygame_menu/version.py
+++ b/pygame_menu/version.py
@@ -34,6 +34,6 @@ class Version(tuple):
     patch = property(lambda self: self[2])
 
 
-vernum = Version(4, 3, 8)
+vernum = Version(4, 3, 9)
 ver = str(vernum)
 rev = ''
diff --git a/pygame_menu/widgets/widget/table.py b/pygame_menu/widgets/widget/table.py
index 976e04e1..cb751e15 100644
--- a/pygame_menu/widgets/widget/table.py
+++ b/pygame_menu/widgets/widget/table.py
@@ -265,9 +265,9 @@ class Table(Frame):
 
         # If cells is a previous table row
         if isinstance(cells, Frame) and cells.has_attribute('is_row'):
-            row_cells = list(cells.get_widgets(unpack_subframes=False))
+            _row_cells = list(cells.get_widgets(unpack_subframes=False))
             cells.clear()
-            cells = row_cells
+            cells = _row_cells
         if isinstance(cells, Widget):
             cells = [cells]
 


Test patch:
diff --git a/test/_utils.py b/test/_utils.py
index 72b0350e..46b88295 100644
--- a/test/_utils.py
+++ b/test/_utils.py
@@ -21,11 +21,9 @@ __all__ = [
     'reset_widgets_over',
     'sleep',
     'surface',
-    'test_reset_surface',
 
     # Class utils
     'BaseTest',
-    'BaseRSTest',
     'PygameEventUtils',
     'MenuUtils'
 
@@ -94,18 +92,18 @@ class BaseTest(unittest.TestCase):
     Base test class.
     """
 
-
-class BaseRSTest(unittest.TestCase):
-    """
-    Test class that Reset the Surface (RS) each time a test runs.
-    """
-
     def setUp(self) -> None:
         """
         Reset the surface.
         """
         test_reset_surface()
 
+    def tearDown(self) -> None:
+        """
+        Reset the surface.
+        """
+        test_reset_surface()
+
 
 class PygameEventUtils(object):
     """
diff --git a/test/test_examples.py b/test/test_examples.py
index 53952eac..6834394d 100644
--- a/test/test_examples.py
+++ b/test/test_examples.py
@@ -8,7 +8,7 @@ Test example files.
 
 __all__ = ['ExamplesTest']
 
-from test._utils import BaseRSTest, MenuUtils, PygameEventUtils, test_reset_surface
+from test._utils import BaseTest, MenuUtils, PygameEventUtils
 
 import pygame
 import pygame_menu
@@ -30,11 +30,8 @@ import pygame_menu.examples.other.scrollbar_area as scrollbar_area
 import pygame_menu.examples.other.ui_solar_system as ui_solarsystem
 import pygame_menu.examples.other.widget_positioning as widget_positioning
 
-# Reset the surface as some example could have changed it
-test_reset_surface()
 
-
-class ExamplesTest(BaseRSTest):
+class ExamplesTest(BaseTest):
 
     def test_example_game_selector(self) -> None:
         """
diff --git a/test/test_menu.py b/test/test_menu.py
index 4a390646..e95f83f8 100644
--- a/test/test_menu.py
+++ b/test/test_menu.py
@@ -8,7 +8,7 @@ Menu object tests.
 
 __all__ = ['MenuTest']
 
-from test._utils import BaseRSTest, surface, MenuUtils, PygameEventUtils, \
+from test._utils import BaseTest, surface, MenuUtils, PygameEventUtils, \
     TEST_THEME, PYGAME_V2, WIDGET_MOUSEOVER, WIDGET_TOP_CURSOR, reset_widgets_over, \
     THEME_NON_FIXED_TITLE
 import copy
@@ -40,7 +40,7 @@ def dummy_function() -> None:
     return
 
 
-class MenuTest(BaseRSTest):
+class MenuTest(BaseTest):
 
     def test_mainloop_disabled(self) -> None:
         """
@@ -2378,7 +2378,7 @@ class MenuTest(BaseRSTest):
         self.assertEqual(menu._column_max_width, [300])
         self.assertEqual(menu._menubar._width, 300)
 
-        # render
+        # Render
         self.assertIsNone(menu._widgets_surface)
         menu.render()
         self.assertIsNotNone(menu._widgets_surface)
@@ -2435,6 +2435,19 @@ class MenuTest(BaseRSTest):
         sa.show_scrollbars(pygame_menu.locals.ORIENTATION_VERTICAL, force=False)
         self.assertEqual(sa.get_size(inner=True), (580, 400))
 
+        # Test submenu recursive resizing
+        menu = MenuUtils.generic_menu(theme=theme)
+        menu2 = MenuUtils.generic_menu(theme=theme)
+        menu3 = MenuUtils.generic_menu(theme=theme)
+        menu.add.button('btn', menu2)
+        menu2.add.button('btn', menu3)
+        self.assertEqual(menu.get_submenus(True), (menu2, menu3))
+        for m in (menu, menu2, menu3):
+            self.assertEqual(m.get_size(), (600, 400))
+        menu.resize(300, 300, recursive=True)  # Now, resize
+        for m in (menu, menu2, menu3):
+            self.assertEqual(m.get_size(), (300, 300))
+
     def test_get_size(self) -> None:
         """
         Test get menu size.
diff --git a/test/test_scrollarea.py b/test/test_scrollarea.py
index acf22bf1..30eda782 100644
--- a/test/test_scrollarea.py
+++ b/test/test_scrollarea.py
@@ -150,6 +150,10 @@ class ScrollAreaTest(BaseTest):
         s1.hide()
         self.assertFalse(s1.is_visible())
 
+        # Check scrollbar render
+        s1._slider_rect = None
+        self.assertIsNone(s1._render())
+
     def test_size(self) -> None:
         """
         Test size.
diff --git a/test/test_widget_frame.py b/test/test_widget_frame.py
index e7ea673f..f6921b1d 100644
--- a/test/test_widget_frame.py
+++ b/test/test_widget_frame.py
@@ -9,9 +9,8 @@ its layout and contains other widgets.
 
 __all__ = ['FrameWidgetTest']
 
-from test._utils import MenuUtils, surface, PygameEventUtils, test_reset_surface, \
+from test._utils import BaseTest, MenuUtils, surface, PygameEventUtils, \
     TEST_THEME, PYGAME_V2, WIDGET_MOUSEOVER, reset_widgets_over, THEME_NON_FIXED_TITLE
-import unittest
 
 import pygame
 import pygame_menu
@@ -30,13 +29,7 @@ from pygame_menu._scrollarea import get_scrollbars_from_position
 from pygame_menu.widgets.widget.frame import _FrameDoNotAcceptScrollarea
 
 
-class FrameWidgetTest(unittest.TestCase):
-
-    def setUp(self) -> None:
-        """
-        Setup frame widget test.
-        """
-        test_reset_surface()
+class FrameWidgetTest(BaseTest):
 
     def test_general(self) -> None:
         """