Repository: ppizarror/pygame-menu

Issue:
update_font() in ToggleSwitch does not update state_text_font_size
I hope I'm not missing something obvious..
I'm changing the size of the widget font when the size of the window changes, by calling update_font() for each widget in the menu.
It works well for all the widgets, except for the state text inside the toggle switch, which stays the same size it was initialized.
I could not find a function that updates the state text...


Discussion:

Patch:
diff --git a/pygame_menu/_base.py b/pygame_menu/_base.py
index cd8858bc..c123de9c 100644
--- a/pygame_menu/_base.py
+++ b/pygame_menu/_base.py
@@ -21,6 +21,7 @@ class Base(object):
     _class_id__repr__: bool
     _id: str
     _id__repr__: bool
+    _verbose: bool
 
     def __init__(self, object_id: str) -> None:
         """
@@ -35,6 +36,7 @@ class Base(object):
         self._class_id__repr__ = False  # If True, repr/str of the object is class id
         self._id = object_id
         self._id__repr__ = False  # If True, repr/str of the object adds object id
+        self._verbose = True  # Enable verbose mode (errors/warnings)
 
     def __repr__(self) -> str:
         """
diff --git a/pygame_menu/_decorator.py b/pygame_menu/_decorator.py
index 3e57b65e..fa17e4a8 100644
--- a/pygame_menu/_decorator.py
+++ b/pygame_menu/_decorator.py
@@ -58,8 +58,7 @@ class Decorator(Base):
     :param obj: Object to decorate
     :param decorator_id: ID of the decorator
     """
-    _coord_cache: Dict[
-        str, Tuple[int, int, Union[Tuple[Tuple2NumberType, ...], Tuple2NumberType]]]  # centerx, centery, coords
+    _coord_cache: Dict[str, Tuple[int, int, Union[Tuple[Tuple2NumberType, ...], Tuple2NumberType]]]  # centerx, centery, coords
     _cache_last_status: Dict[str, Tuple[int, int, int, int, int, int]]
     _cache_needs_update: Dict[str, bool]
     _cache_surface: Dict[str, Optional['pygame.Surface']]
@@ -153,7 +152,8 @@ class Decorator(Base):
 
         # Check sizes
         if self._total_decor() >= 300 and not self.cache:
-            warn('cache is recommended if the total number of decorations exceeds 300')
+            if self._verbose:
+                warn('cache is recommended if the total number of decorations exceeds 300')
 
         # Set automatically as enabled
         self._decor_enabled[decor_id] = True
diff --git a/pygame_menu/_widgetmanager.py b/pygame_menu/_widgetmanager.py
index 49a09ada..2c2df22f 100644
--- a/pygame_menu/_widgetmanager.py
+++ b/pygame_menu/_widgetmanager.py
@@ -251,6 +251,7 @@ class WidgetManager(
 
     def _configure_widget(self, widget: 'Widget', **kwargs) -> None:
         assert isinstance(widget, Widget)
+        widget._verbose = self._verbose
 
         widget.set_alignment(
             align=kwargs['align']
@@ -422,10 +423,11 @@ class WidgetManager(
 
         # Raise warning if adding button with Menu
         if isinstance(widget, pygame_menu.widgets.Button) and widget.to_menu:
-            warn(
-                'prefer adding submenus using add_button method instead, '
-                'unintended behaviours may occur'
-            )
+            if self._verbose:
+                warn(
+                    'prefer adding submenus using add_button method instead, '
+                    'unintended behaviours may occur'
+                )
 
         # Configure widget
         if configure_defaults:
diff --git a/pygame_menu/menu.py b/pygame_menu/menu.py
index b201ed8f..884e6cf1 100644
--- a/pygame_menu/menu.py
+++ b/pygame_menu/menu.py
@@ -100,7 +100,7 @@ class Menu(Base):
     :param theme: Menu theme
     :param touchscreen: Enable/disable touch action inside the Menu. Only available on pygame 2
     :param touchscreen_motion_selection: Select widgets using touchscreen motion. If ``True`` menu draws a ``focus`` on the selected widget
-    :param verbose: Enable/disable verbose mode (warnings/errors)
+    :param verbose: Enable/disable verbose mode (warnings/errors). Propagates to all widgets
     """
     _auto_centering: bool
     _background_function: Tuple[bool, Optional[Union[Callable[['Menu'], Any], CallableNoArgsType]]]
@@ -209,6 +209,7 @@ class Menu(Base):
             verbose: bool = True
     ) -> None:
         super(Menu, self).__init__(object_id=menu_id)
+        self._verbose = verbose
 
         assert isinstance(center_content, bool)
         assert isinstance(column_max_width, (VectorInstance, type(None), NumberInstance))
@@ -226,6 +227,7 @@ class Menu(Base):
             'theme bust be a pygame_menu.themes.Theme object instance'
         assert isinstance(touchscreen, bool)
         assert isinstance(touchscreen_motion_selection, bool)
+        assert isinstance(verbose, bool)
 
         # Assert theme
         theme.validate()
@@ -278,12 +280,13 @@ class Menu(Base):
                 'column_min_width must be equal or greater than zero'
             if columns != 1:
                 if column_min_width > 0:  # Ignore the default value
-                    warn(
-                        f'column_min_width can be a single number if there is only '
-                        f'1 column, but there is {columns} columns. Thus, column_min_width '
-                        f'should be a vector of {columns} items. By default a vector has '
-                        f'been created using the same value for each column'
-                    )
+                    if self._verbose:
+                        warn(
+                            f'column_min_width can be a single number if there is only '
+                            f'1 column, but there is {columns} columns. Thus, column_min_width '
+                            f'should be a vector of {columns} items. By default a vector has '
+                            f'been created using the same value for each column'
+                        )
                 column_min_width = [column_min_width for _ in range(columns)]
             else:
                 column_min_width = [column_min_width]
@@ -504,7 +507,6 @@ class Menu(Base):
             offsety=theme.title_offset[1],
             onreturn=self._back,
             title=title,
-            verbose=verbose,
             width=self._width,
         )
         self._menubar.set_menu(self)
@@ -587,6 +589,14 @@ class Menu(Base):
         self._disable_update = False
         self._validate_frame_widgetmove = True
 
+        # Configure verbose
+        self.add._verbose = verbose
+        self._decorator._verbose = verbose
+        self._menubar._verbose = verbose
+        self._scrollarea._verbose = verbose
+        self._scrollarea._verbose = verbose
+        self._sound._verbose = verbose
+
     def resize(
             self,
             width: NumberType,
@@ -649,11 +659,12 @@ class Menu(Base):
         # If centering is enabled, but widget offset in the vertical is different
         # from zero a warning is raised
         if self._auto_centering and self._widget_offset[1] != 0:
-            warn(
-                f'menu is vertically centered (center_content=True), but widget '
-                f'offset (from theme) is different than zero ({self._widget_offset[1]}px). '
-                f'Auto-centering has been disabled'
-            )
+            if self._verbose:
+                warn(
+                    f'menu is vertically centered (center_content=True), but widget '
+                    f'offset (from theme) is different than zero ({self._widget_offset[1]}px). '
+                    f'Auto-centering has been disabled'
+                )
             self._auto_centering = False
 
         # Scroll area outer margin
@@ -668,11 +679,12 @@ class Menu(Base):
         # If centering is enabled, but ScrollArea margin in the vertical is
         # different from zero a warning is raised
         if self._auto_centering and self._scrollarea_margin[1] != 0:
-            warn(
-                f'menu is vertically centered (center_content=True), but '
-                f'ScrollArea outer margin (from theme) is different than zero '
-                f'({round(self._scrollarea_margin[1], 3)}px). Auto-centering has been disabled'
-            )
+            if self._verbose:
+                warn(
+                    f'menu is vertically centered (center_content=True), but '
+                    f'ScrollArea outer margin (from theme) is different than zero '
+                    f'({round(self._scrollarea_margin[1], 3)}px). Auto-centering has been disabled'
+                )
             self._auto_centering = False
 
         # Configure menubar
@@ -1262,7 +1274,8 @@ class Menu(Base):
                 try:
                     widget.update_position()
                 except:
-                    warn(f'{widget.get_class_id()} failed to update')
+                    if self._verbose:
+                        warn(f'{widget.get_class_id()} failed to update')
                     raise
                 has_frame = True
 
@@ -3321,7 +3334,8 @@ class Menu(Base):
 
         widget_scroll = widget.get_scrollarea()
         if widget_scroll is None:
-            warn(f'{widget.get_class_id()} scrollarea is None, thus, scroll to widget cannot be performed')
+            if self._verbose:
+                warn(f'{widget.get_class_id()} scrollarea is None, thus, scroll to widget cannot be performed')
             return self
 
         # Scroll to rect
diff --git a/pygame_menu/sound.py b/pygame_menu/sound.py
index f54ed4bc..dc9718a9 100644
--- a/pygame_menu/sound.py
+++ b/pygame_menu/sound.py
@@ -163,8 +163,9 @@ class Sound(Base):
         # Check if mixer is init
         mixer_missing = 'MissingModule' in str(type(mixer))
         if mixer_missing:
-            warn('pygame mixer module could not be found, NotImplementedError'
-                 'has been raised. Sound support is disabled')
+            if self._verbose:
+                warn('pygame mixer module could not be found, NotImplementedError'
+                     'has been raised. Sound support is disabled')
             SOUND_INITIALIZED[1] = False
 
         # Initialize sounds if not initialized
@@ -200,9 +201,11 @@ class Sound(Base):
                 mixer.init(**mixer_kwargs)
 
             except Exception as e:
-                warn('sound error: ' + str(e))
+                if self._verbose:
+                    warn('sound error: ' + str(e))
             except pygame_error as e:
-                warn('sound engine could not be initialized, pygame error: ' + str(e))
+                if self._verbose:
+                    warn('sound engine could not be initialized, pygame error: ' + str(e))
 
         # Store mixer configs
         self._mixer_configs = {
@@ -332,7 +335,8 @@ class Sound(Base):
             # noinspection PyTypeChecker
             sound_data = mixer.Sound(file=sound_file)
         except pygame_error:
-            warn(f'the sound file "{sound_file}" could not be loaded, it has been disabled')
+            if self._verbose:
+                warn(f'the sound file "{sound_file}" could not be loaded, it has been disabled')
             self._sound[sound_type] = {}
             return False
 
diff --git a/pygame_menu/version.py b/pygame_menu/version.py
index 5bfc109e..f15b3a90 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, 1)
+vernum = Version(4, 3, 2)
 ver = str(vernum)
 rev = ''
diff --git a/pygame_menu/widgets/core/widget.py b/pygame_menu/widgets/core/widget.py
index a251c0c4..523ac2da 100644
--- a/pygame_menu/widgets/core/widget.py
+++ b/pygame_menu/widgets/core/widget.py
@@ -140,7 +140,7 @@ def _check_widget_mouseleave(
             WIDGET_MOUSEOVER[0] = None
             WIDGET_MOUSEOVER[1] = []
             if prev_cursor != WIDGET_TOP_CURSOR[0]:
-                if WIDGET_TOP_CURSOR_WARNING:
+                if WIDGET_TOP_CURSOR_WARNING and current._verbose:
                     warn(
                         f'expected {WIDGET_TOP_CURSOR[0]} to be the top cursor '
                         f'(WIDGET_TOP_CURSOR), but {prev_cursor} is the current '
@@ -1342,8 +1342,9 @@ class Widget(Base):
                 WIDGET_SHADOW_GENERATOR.clear_short_term_caches()
                 self._shadow['surface'] = s
             if not self._shadow['surface']:
-                warn(f'{self.get_class_id()} shadow computation failed, check if'
-                     f' the radius is smaller than the width and height of the menu')
+                if self._verbose:
+                    warn(f'{self.get_class_id()} shadow computation failed, check if'
+                         f' the radius is smaller than the width and height of the menu')
                 self._shadow['enabled'] = False
                 return
             w = self._shadow['properties'][1]
@@ -1802,7 +1803,8 @@ class Widget(Base):
             # Font background color must be opaque, otherwise the results are quite bad
             if len(background_color) == 4 and background_color[3] != 255:
                 background_color = None
-                warn('font background color must be opaque, alpha channel must be 255')
+                if self._verbose:
+                    warn('font background color must be opaque, alpha channel must be 255')
 
         font_size = int(font_size)
 
@@ -2126,6 +2128,8 @@ class Widget(Base):
         :param maxwidth: Warn bout maxwidth
         :param maxheight: Warn about maxheight
         """
+        if not self._verbose:
+            return
         if self._scale[0] and scale:
             warn('widget already has a scaling factor applied. Scaling has '
                  'been disabled')
@@ -2353,7 +2357,8 @@ class Widget(Base):
         """
         self._disable_scale()
         if width == 1 and height == 1:
-            warn('did you mean widget.scale(1,1) instead of widget.resize(1,1)?')
+            if self._verbose:
+                warn('did you mean widget.scale(1,1) instead of widget.resize(1,1)?')
         self.scale(float(width) / self.get_width(),
                    float(height) / self.get_height(), smooth)
         return self
diff --git a/pygame_menu/widgets/widget/button.py b/pygame_menu/widgets/widget/button.py
index 60c353b7..349f4367 100644
--- a/pygame_menu/widgets/widget/button.py
+++ b/pygame_menu/widgets/widget/button.py
@@ -409,8 +409,9 @@ class ButtonManager(AbstractWidgetManager, ABC):
             try:
                 self._check_kwargs(kwargs)
             except ValueError:
-                warn('button cannot accept kwargs. If you want to use kwargs '
-                     'options set accept_kwargs=True')
+                if self._verbose:
+                    warn('button cannot accept kwargs. If you want to use kwargs '
+                         'options set accept_kwargs=True')
                 raise
 
         self._configure_widget(widget=widget, **attributes)
diff --git a/pygame_menu/widgets/widget/frame.py b/pygame_menu/widgets/widget/frame.py
index aca2f992..51d1c2a7 100644
--- a/pygame_menu/widgets/widget/frame.py
+++ b/pygame_menu/widgets/widget/frame.py
@@ -749,7 +749,8 @@ class Frame(Widget):
 
         # If it has title
         if self._has_title:
-            warn(f'previous {self.get_class_id()} title has been removed')
+            if self._verbose:
+                warn(f'previous {self.get_class_id()} title has been removed')
             self.remove_title()
 
         return self
@@ -1522,12 +1523,13 @@ class Frame(Widget):
             f'{widget.get_class_id()} must be configured before packing'
 
         if widget.get_margin() != (0, 0) and self._pack_margin_warning:
-            warn(
-                f'{widget.get_class_id()} margin should be (0, 0) if packed, but'
-                f' received {widget.get_margin()}; {self.get_class_id()}.pack() '
-                f'does not consider previous widget margin. Set '
-                f'frame._pack_margin_warning=False to hide this warning'
-            )
+            if self._verbose:
+                warn(
+                    f'{widget.get_class_id()} margin should be (0, 0) if packed, but'
+                    f' received {widget.get_margin()}; {self.get_class_id()}.pack() '
+                    f'does not consider previous widget margin. Set '
+                    f'frame._pack_margin_warning=False to hide this warning'
+                )
 
         if isinstance(widget, Frame):
             widget.update_indices()
diff --git a/pygame_menu/widgets/widget/label.py b/pygame_menu/widgets/widget/label.py
index 55a585a7..f3755e21 100644
--- a/pygame_menu/widgets/widget/label.py
+++ b/pygame_menu/widgets/widget/label.py
@@ -146,10 +146,11 @@ class Label(Widget):
     def set_title(self, title: str) -> 'Label':
         super(Label, self).set_title(title)
         if self._title_generator is not None:
-            warn(
-                f'{self.get_class_id()} title generator is not None, thus, the new'
-                f' title "{title}" will be overridden after next update'
-            )
+            if self._verbose:
+                warn(
+                    f'{self.get_class_id()} title generator is not None, thus, the new'
+                    f' title "{title}" will be overridden after next update'
+                )
         return self
 
     def _get_leading(self) -> int:
diff --git a/pygame_menu/widgets/widget/menubar.py b/pygame_menu/widgets/widget/menubar.py
index 21f48e5e..408de891 100644
--- a/pygame_menu/widgets/widget/menubar.py
+++ b/pygame_menu/widgets/widget/menubar.py
@@ -52,11 +52,14 @@ _MODE_CLOSE = 1020
 _MODE_BACK = 1021
 
 # Custom types
-MenuBarStyleModeType = Literal[MENUBAR_STYLE_ADAPTIVE, MENUBAR_STYLE_SIMPLE,
-MENUBAR_STYLE_TITLE_ONLY,
-MENUBAR_STYLE_TITLE_ONLY_DIAGONAL,
-MENUBAR_STYLE_NONE, MENUBAR_STYLE_UNDERLINE,
-MENUBAR_STYLE_UNDERLINE_TITLE]
+MenuBarStyleModeType = Literal[
+    MENUBAR_STYLE_ADAPTIVE,
+    MENUBAR_STYLE_SIMPLE,
+    MENUBAR_STYLE_TITLE_ONLY,
+    MENUBAR_STYLE_TITLE_ONLY_DIAGONAL,
+    MENUBAR_STYLE_NONE,
+    MENUBAR_STYLE_UNDERLINE,
+    MENUBAR_STYLE_UNDERLINE_TITLE]
 
 
 # noinspection PyMissingOrEmptyDocstring
@@ -79,7 +82,6 @@ class MenuBar(Widget):
     :param offsetx: Offset x-position of title in px
     :param offsety: Offset y-position of title in px
     :param onreturn: Callback when pressing the back-box button
-    :param verbose: Enable verbose mode (errors/warnings)
     :param args: Optional arguments for callbacks
     :param kwargs: Optional keyword arguments for callbacks
     """
@@ -97,7 +99,6 @@ class MenuBar(Widget):
     _style: int
     _width: int
     fixed: bool
-    verbose: bool
 
     def __init__(
             self,
@@ -112,7 +113,6 @@ class MenuBar(Widget):
             offsetx: NumberType = 0,
             offsety: NumberType = 0,
             onreturn: CallbackType = None,
-            verbose: bool = False,
             *args,
             **kwargs
     ) -> None:
@@ -150,7 +150,6 @@ class MenuBar(Widget):
         self._style = mode
         self._title = ''
         self._width = int(width)
-        self._verbose = verbose
 
         self.set_title(title, offsetx, offsety)
 
diff --git a/pygame_menu/widgets/widget/table.py b/pygame_menu/widgets/widget/table.py
index 4e10376d..66ee17c7 100644
--- a/pygame_menu/widgets/widget/table.py
+++ b/pygame_menu/widgets/widget/table.py
@@ -363,13 +363,14 @@ class Table(Frame):
                 cell = c
                 if c._accept_events:
                     cell_widget_type = True
-                    warn(
-                        f'{self.get_class_id()} does not accept events in current'
-                        f' pygame-menu v{ver}; thus appended cell row widget '
-                        f'{c.get_class_id()} (pos {j}) would not work properly, '
-                        f'as it will ignore all inputs. Also, widgets within Tables'
-                        f' cannot be selected. Consider Tables as visual-only'
-                    )
+                    if self._verbose:
+                        warn(
+                            f'{self.get_class_id()} does not accept events in current'
+                            f' pygame-menu v{ver}; thus appended cell row widget '
+                            f'{c.get_class_id()} (pos {j}) would not work properly, '
+                            f'as it will ignore all inputs. Also, widgets within Tables'
+                            f' cannot be selected. Consider Tables as visual-only'
+                        )
                 # self._append_menu_update_frame(self)
 
             # Configure cell
diff --git a/pygame_menu/widgets/widget/textinput.py b/pygame_menu/widgets/widget/textinput.py
index d57216d3..0decdca9 100644
--- a/pygame_menu/widgets/widget/textinput.py
+++ b/pygame_menu/widgets/widget/textinput.py
@@ -1812,11 +1812,12 @@ class TextInput(Widget):
                 # Any other key, add as input
                 elif event.key not in self._ignore_keys and hasattr(event, 'unicode'):
                     if event.unicode == ' ' and event.key != 32:
-                        warn(
-                            f'{self.get_class_id()} received "{event.unicode}" '
-                            f'unicode but key is different than 32 ({event.key}), '
-                            f'check if event has defined the proper unicode char'
-                        )
+                        if self._verbose:
+                            warn(
+                                f'{self.get_class_id()} received "{event.unicode}" '
+                                f'unicode but key is different than 32 ({event.key}), '
+                                f'check if event has defined the proper unicode char'
+                            )
                         break
 
                     # Error in char, not valid or string limit exceeds
diff --git a/pygame_menu/widgets/widget/toggleswitch.py b/pygame_menu/widgets/widget/toggleswitch.py
index 0de4d789..864f2176 100644
--- a/pygame_menu/widgets/widget/toggleswitch.py
+++ b/pygame_menu/widgets/widget/toggleswitch.py
@@ -277,12 +277,9 @@ class ToggleSwitch(Widget):
         return self._state_values[self._state]
 
     def _apply_font(self) -> None:
-        if self._state_text_font is None:
-            self._state_text_font = self._font_name
-        if self._state_text_font_size is None:
-            self._state_text_font_size = self._font_size
         self._state_font = pygame_menu.font.get_font(
-            self._state_text_font, self._state_text_font_size
+            self._font_name if self._state_text_font is None else self._state_text_font,
+            self._font_size if self._state_text_font_size is None else self._state_text_font_size
         )
 
         # Compute the height
@@ -291,6 +288,7 @@ class ToggleSwitch(Widget):
         self._slider_height = int(self._switch_height * self._slider_height_factor) - 2 * self._switch_border_width
 
         # Render the state texts
+        self._switch_font_rendered = []
         for t in range(self._total_states):
             f_render = self._state_font.render(self._state_text[t], True, self._state_text_font_color[t])
             self._switch_font_rendered.append(f_render)


Test patch:
diff --git a/test/test_widget_toggleswitch.py b/test/test_widget_toggleswitch.py
index 7a414714..f6305721 100644
--- a/test/test_widget_toggleswitch.py
+++ b/test/test_widget_toggleswitch.py
@@ -185,3 +185,15 @@ class ToggleSwitchWidgetTest(BaseTest):
         menu = MenuUtils.generic_menu()
         switch = menu.add.toggle_switch('')
         self.assertEqual(switch.get_size(), (191, 49))
+
+    def test_update_font(self) -> None:
+        """
+        Test update font.
+        """
+        menu = MenuUtils.generic_menu()
+        switch = menu.add.toggle_switch('abc')
+        self.assertEqual(switch.get_size(), (240, 49))
+        self.assertEqual(switch._state_font.get_height(), 41)
+        switch.update_font(dict(size=100))
+        self.assertEqual(switch._state_font.get_height(), 137)
+        self.assertEqual(switch.get_size(), (356, 145))