Repository: ppizarror/pygame-menu

Issue:
I can't input Latin Extended characters
Hello,
I have this code:

`nick = menu.add_text_input('Name: ', default='John')`

In the game menu, it is impossible to input from keyboard characters like:

> ą, ę, ś, ć, ż, ź, ó, ł, ń

Is it possible to fix by adding some code or is it not supported by pygame-menu?

Discussion:

Patch:
diff --git a/docs/_source/themes.rst b/docs/_source/themes.rst
index 3cb80b04..0a147d3d 100644
--- a/docs/_source/themes.rst
+++ b/docs/_source/themes.rst
@@ -70,7 +70,7 @@ Background Color/Images
 -----------------------
 
 Theme background can be both a color or an image. All colors can be defined
-using a tuple or an list of 3 or 4 numbers between 0 and 255. The format of
+using a tuple or a list of 3 or 4 numbers between 0 and 255. The format of
 the numbers are:
 
 .. code-block:: python
diff --git a/pygame_menu/__init__.py b/pygame_menu/__init__.py
index 58731f89..9c0137db 100644
--- a/pygame_menu/__init__.py
+++ b/pygame_menu/__init__.py
@@ -51,7 +51,7 @@ except (ModuleNotFoundError, ImportError):
 # Import modules that require pygame
 if __pygame_version__ is not None:
     """
-    BaseImage: Provides basic image loading an manipulation with pygame
+    BaseImage: Provides basic image loading and manipulation with pygame
     """
     import pygame_menu.baseimage  # lgtm [py/import-and-import-from]
     from pygame_menu.baseimage import BaseImage
diff --git a/pygame_menu/_widgetmanager.py b/pygame_menu/_widgetmanager.py
index dfc75fad..96494107 100644
--- a/pygame_menu/_widgetmanager.py
+++ b/pygame_menu/_widgetmanager.py
@@ -2057,6 +2057,12 @@ class WidgetManager(Base):
             Widget floating is also considered within frames. If a widget is floating,
             it does not add any size to the respective positioning.
 
+        .. note::
+
+            The Frame size created with this method does consider the padding. Thus,
+            if Frame is created with ``width=100``, ``height=200`` and ``padding=25`` the
+            final internal size is ``width=50`` and ``height=150``.
+
         .. note::
 
             This is applied only to the base Menu (not the currently displayed,
@@ -2141,6 +2147,12 @@ class WidgetManager(Base):
             Widget floating is also considered within frames. If a widget is floating,
             it does not add any size to the respective positioning.
 
+        .. note::
+
+            The Frame size created with this method does consider the padding. Thus,
+            if Frame is created with ``width=100``, ``height=200`` and ``padding=25`` the
+            final internal size is ``width=50`` and ``height=150``.
+
         .. note::
 
             This is applied only to the base Menu (not the currently displayed,
diff --git a/pygame_menu/baseimage.py b/pygame_menu/baseimage.py
index fa820185..52872e1d 100644
--- a/pygame_menu/baseimage.py
+++ b/pygame_menu/baseimage.py
@@ -3,7 +3,7 @@ pygame-menu
 https://github.com/ppizarror/pygame-menu
 
 BASEIMAGE
-Provides a class to perform basic image loading an manipulation with pygame.
+Provides a class to perform basic image loading and manipulation with pygame.
 
 License:
 -------------------------------------------------------------------------------
@@ -101,7 +101,7 @@ ColorChannelType = Literal['r', 'g', 'b']
 class BaseImage(Base):
     """
     Object that loads an image, stores as a surface, transform it and
-    let write the image to an surface.
+    let write the image to a surface.
 
     :param image_path: Path of the image to be loaded. It can be a string (path, base64), :py:class:`pathlib.Path`, or :py:class:`io.BytesIO`
     :param drawing_mode: Drawing mode of the image
@@ -237,7 +237,7 @@ class BaseImage(Base):
         For pygame 2.0: per-surface alpha can be combined with per-pixel alpha.
 
         The optional flags argument can be set to pygame.RLEACCEL to provide better
-        performance on non accelerated displays. An RLEACCEL Surface will be slower
+        performance on non accelerated displays. A RLEACCEL Surface will be slower
         to modify, but quicker to blit as a source.
 
         :param value: Transparency value from ``0`` to ``255``
diff --git a/pygame_menu/menu.py b/pygame_menu/menu.py
index a3f7d7f0..c6b608e1 100644
--- a/pygame_menu/menu.py
+++ b/pygame_menu/menu.py
@@ -246,7 +246,7 @@ class Menu(Base):
         assert isinstance(mouse_visible, bool)
         assert isinstance(overflow, (tuple, list, bool))
         assert isinstance(rows, (int, type(None), VectorInstance))
-        assert isinstance(theme, Theme), 'theme bust be an pygame_menu.themes.Theme object instance'
+        assert isinstance(theme, Theme), 'theme bust be a pygame_menu.themes.Theme object instance'
         assert isinstance(touchscreen, bool)
         assert isinstance(touchscreen_motion_selection, bool)
 
@@ -1481,7 +1481,7 @@ class Menu(Base):
         sx = self._scrollarea.get_scrollbar_thickness(ORIENTATION_HORIZONTAL, real=True)
         sy = self._scrollarea.get_scrollbar_thickness(ORIENTATION_VERTICAL, real=True)
 
-        # Remove the thick of the scrollbar to avoid displaying an horizontal one
+        # Remove the thick of the scrollbar to avoid displaying a horizontal one
         # If overflow on both axis
         if max_x > self._width and max_y > self._height - menubar_height:
             width, height = max_x + sy * 0.5, max_y
diff --git a/pygame_menu/themes.py b/pygame_menu/themes.py
index 5934e9b6..76d52b62 100644
--- a/pygame_menu/themes.py
+++ b/pygame_menu/themes.py
@@ -111,7 +111,7 @@ class Theme(object):
     :type readonly_color: tuple, list, str, int, :py:class:`pygame.Color`
     :param readonly_selected_color: Color of the selected widget in readonly mode
     :type readonly_selected_color: tuple, list, str, int, :py:class:`pygame.Color`
-    :param scrollarea_outer_margin: Outer ScrollArea margin (px); the tuple is added to computed ScrollArea width/height, it can add an margin to bottom/right scrolls after widgets. If value less than ``1`` use percentage of width/height. It cannot be a negative value
+    :param scrollarea_outer_margin: Outer ScrollArea margin (px); the tuple is added to computed ScrollArea width/height, it can add a margin to bottom/right scrolls after widgets. If value less than ``1`` use percentage of width/height. It cannot be a negative value
     :type scrollarea_outer_margin: tuple, list
     :param scrollarea_position: Position of ScrollArea scrollbars. See :py:mod:`pygame_menu.locals`
     :type scrollarea_position: str
diff --git a/pygame_menu/widgets/core/widget.py b/pygame_menu/widgets/core/widget.py
index e40959ec..38b0d3d0 100644
--- a/pygame_menu/widgets/core/widget.py
+++ b/pygame_menu/widgets/core/widget.py
@@ -1772,7 +1772,7 @@ class Widget(Base):
     def set_max_width(self, width: Optional[NumberType], scale_height: NumberType = False,
                       smooth: bool = True) -> 'Widget':
         """
-        Transformation: Set the Widget max width, it applies an scaling factor
+        Transformation: Set the Widget max width, it applies a scaling factor
         if the widget width is greater than the limit.
 
         .. note::
@@ -1830,7 +1830,7 @@ class Widget(Base):
     def set_max_height(self, height: NumberType, scale_width: NumberType = False,
                        smooth: bool = True) -> 'Widget':
         """
-        Transformation: Set the Widget max height, it applies an scaling factor
+        Transformation: Set the Widget max height, it applies a scaling factor
         if the widget height is greater than the limit.
 
         .. note::
diff --git a/pygame_menu/widgets/widget/dropselect.py b/pygame_menu/widgets/widget/dropselect.py
index 699b2485..dae13393 100644
--- a/pygame_menu/widgets/widget/dropselect.py
+++ b/pygame_menu/widgets/widget/dropselect.py
@@ -905,7 +905,7 @@ class DropSelect(Widget):
         :param item: Item to select, can be a string or an integer
         :return: None
         """
-        assert isinstance(item, (str, int)), 'item must be an string or an integer'
+        assert isinstance(item, (str, int)), 'item must be a string or an integer'
 
         if isinstance(item, str):
             found = False
diff --git a/pygame_menu/widgets/widget/dropselect_multiple.py b/pygame_menu/widgets/widget/dropselect_multiple.py
index 75f832e8..cf3519c2 100644
--- a/pygame_menu/widgets/widget/dropselect_multiple.py
+++ b/pygame_menu/widgets/widget/dropselect_multiple.py
@@ -375,7 +375,7 @@ class DropSelectMultiple(DropSelect):
         :param process_index: Adds/Removes the index from the selected indices list
         :return: None
         """
-        assert isinstance(item, (str, int)), 'item must be an string or an integer'
+        assert isinstance(item, (str, int)), 'item must be a string or an integer'
 
         if isinstance(item, str):
             found = False
diff --git a/pygame_menu/widgets/widget/frame.py b/pygame_menu/widgets/widget/frame.py
index cffaf7de..7226b65f 100644
--- a/pygame_menu/widgets/widget/frame.py
+++ b/pygame_menu/widgets/widget/frame.py
@@ -95,7 +95,7 @@ class Frame(Widget):
 
     .. note::
 
-        Frame only implements translation.
+        Frame only implements translation and resize transformations.
 
     .. note::
 
@@ -217,6 +217,9 @@ class Frame(Widget):
         # If has previous title
         self.remove_title()
 
+        assert isinstance(draggable, bool)
+        self._draggable = draggable
+
         # Format title font properties
         if title_font is None:
             title_font = self._font_name
@@ -274,6 +277,23 @@ class Frame(Widget):
         )
         self._frame_title._update__repr___(self)
 
+        # Store constructor
+        self._frame_title.set_attribute(
+            'constructor', {
+                'title': title,
+                'cursor': cursor,
+                'background_color': background_color,
+                'draggable': draggable,
+                'padding_inner': padding_inner,
+                'padding_outer': padding_outer,
+                'title_alignment': title_alignment,
+                'title_buttons_alignment': title_buttons_alignment,
+                'title_font': title_font,
+                'title_font_color': title_font_color,
+                'title_font_size': title_font_size
+            }
+        )
+
         # Create frame title background rect
         title_bg = make_surface(self.get_width(), title_label.get_height() + pad_outer[0] + pad_outer[2] +
                                 pad_inner[0] + pad_inner[2])
@@ -338,6 +358,7 @@ class Frame(Widget):
             self._has_title = False
         if not self.is_scrollable and self in self._menu._update_frames:
             self._menu._update_frames.remove(self)
+        self._draggable = False
         self._render()
         self.force_menu_surface_update()
         return self
@@ -381,6 +402,8 @@ class Frame(Widget):
 
         # Pack
         align = self._frame_title.get_attribute('buttons_alignment')
+        button.set_attribute('align', align)
+        button.set_attribute('margin', margin)
         self._frame_title.pack(button, alignment=align, margin=margin)
         self._frame_title.update_position()
 
@@ -592,7 +615,7 @@ class Frame(Widget):
             scrollbar_slider_color: ColorInputType,
             scrollbar_slider_pad: NumberType,
             scrollbar_thick: NumberType,
-            scrollbars: Union[str, Tuple[str, ...]],
+            scrollbars: Union[str, Tuple[str, ...]]
     ) -> 'Frame':
         """
         Make the scrollarea of the frame.
@@ -666,6 +689,24 @@ class Frame(Widget):
             shadow_position=scrollbar_shadow_position
         )
 
+        # Store constructor data
+        self._frame_scrollarea.set_attribute(
+            'constructor',
+            {
+                'scrollarea_color': scrollarea_color,
+                'scrollbar_color': scrollbar_color,
+                'scrollbar_cursor': scrollbar_cursor,
+                'scrollbar_shadow': scrollbar_shadow,
+                'scrollbar_shadow_color': scrollbar_shadow_color,
+                'scrollbar_shadow_offset': scrollbar_shadow_offset,
+                'scrollbar_shadow_position': scrollbar_shadow_position,
+                'scrollbar_slider_color': scrollbar_slider_color,
+                'scrollbar_slider_pad': scrollbar_slider_pad,
+                'scrollbar_thick': scrollbar_thick,
+                'scrollbars': scrollbars
+            }
+        )
+
         if self._width == max_width:
             self._frame_scrollarea.hide_scrollbars(ORIENTATION_HORIZONTAL)
         if self._height == max_height:
@@ -728,9 +769,6 @@ class Frame(Widget):
     def scale(self, *args, **kwargs) -> 'Frame':
         return self
 
-    def resize(self, *args, **kwargs) -> 'Frame':
-        return self
-
     def set_max_width(self, *args, **kwargs) -> 'Frame':
         return self
 
@@ -1063,6 +1101,118 @@ class Frame(Widget):
             unpackd.append(w)
         return tuple(unpackd)
 
+    def resize(
+            self,
+            width: NumberType,
+            height: NumberType,
+            max_width: Optional[NumberType] = None,
+            max_height: Optional[NumberType] = None
+    ) -> 'Frame':
+        """
+        Resize the Frame.
+
+        :param width: New width (px). Horizontal padding will be subtracted
+        :param height: New height (px). Vertical padding will be subtracted
+        :param max_width: Max frame width if the Frame is scrollable. If ``None`` the same width will be used
+        :param max_height: Max frame height if the Frame is scrollable. If ``None`` the same height will be used
+        :return: Self reference
+        """
+        assert isinstance(width, NumberInstance)
+        assert isinstance(height, NumberInstance)
+
+        pad_h = self._padding[1] + self._padding[3]
+        pad_v = self._padding[0] + self._padding[2]
+
+        # Subtract padding
+        width -= pad_h
+        height -= pad_v
+
+        # Check size
+        assert width > 0 and height > 0, 'new width and height must be greater than zero'
+
+        # Update width/height
+        if width < self._width or height < self._height:
+            self.relax()
+        self._frame_size = (width, height)  # Size of the frame, set in make_scrollarea
+        self._height = int(height)
+        self._real_rect = pygame.Rect(0, 0, width, height)
+        self._width = int(width)
+
+        # Get previous buttons if has title
+        prev_has_title = self._has_title
+        prev_title_frame = self._frame_title
+        prev_title_buttons = list(self._frame_title.get_widgets()) if self._has_title else []
+        if len(prev_title_buttons) >= 1:  # Pop label
+            prev_title_buttons.pop(0)
+
+        # Make scrollable if scrollable
+        if self.is_scrollable:
+            assert self._frame_scrollarea.has_attribute('constructor'), \
+                'frame scrollarea does not have the "constructor" attribute. Make sure the scrollarea ' \
+                'has been created using make_scrollarea() method'
+            kwargs: Dict[str, Any] = self._frame_scrollarea.get_attribute('constructor')
+            if max_width is None:
+                max_width = width
+            if max_height is None:
+                max_height = height
+            self.make_scrollarea(
+                max_height=max_height,
+                max_width=max_width,
+                scrollarea_color=kwargs['scrollarea_color'],
+                scrollbar_color=kwargs['scrollbar_color'],
+                scrollbar_cursor=kwargs['scrollbar_cursor'],
+                scrollbar_shadow=kwargs['scrollbar_shadow'],
+                scrollbar_shadow_color=kwargs['scrollbar_shadow_color'],
+                scrollbar_shadow_offset=kwargs['scrollbar_shadow_offset'],
+                scrollbar_shadow_position=kwargs['scrollbar_shadow_position'],
+                scrollbar_slider_color=kwargs['scrollbar_slider_color'],
+                scrollbar_slider_pad=kwargs['scrollbar_slider_pad'],
+                scrollbar_thick=kwargs['scrollbar_thick'],
+                scrollbars=kwargs['scrollbars']
+            )
+        else:
+            assert max_width is None and max_height is None, \
+                'if previous Frame is not scrollable (make_scrollarea has been ' \
+                'called) max_width and max_height must be None'
+
+        # If had title, remove and create a new one
+        self.remove_title()
+        if prev_has_title:
+            for btn in prev_title_buttons:
+                prev_title_frame.unpack(btn)
+                btn.set_margin(0, 0)
+                btn.set_float(False)
+            assert prev_title_frame.has_attribute('constructor'), \
+                'frame title does not have the attribute "constructor". Make sure the frame title has ' \
+                'been created through set_title() method.'
+            kwargs = prev_title_frame.get_attribute('constructor')
+            new_title_frame = self.set_title(
+                title=kwargs['title'],
+                cursor=kwargs['cursor'],
+                background_color=kwargs['background_color'],
+                draggable=kwargs['draggable'],
+                padding_inner=kwargs['padding_inner'],
+                padding_outer=kwargs['padding_outer'],
+                title_alignment=kwargs['title_alignment'],
+                title_buttons_alignment=kwargs['title_buttons_alignment'],
+                title_font=kwargs['title_font'],
+                title_font_color=kwargs['title_font_color'],
+                title_font_size=kwargs['title_font_size']
+            )
+
+            # Pack previous buttons
+            # prev_title_buttons.reverse()
+            for btn in prev_title_buttons:
+                align = btn.get_attribute('align', kwargs['title_buttons_alignment'])
+                margin = btn.get_attribute('margin', (0, 0))
+                new_title_frame.pack(btn, alignment=align, margin=margin)
+
+        # Force render
+        self._render()
+        self.force_menu_surface_update()
+
+        return self
+
     def unfloat(self) -> 'Frame':
         """
         Disable float status for each subwidget.
@@ -1494,8 +1644,8 @@ class Frame(Widget):
                 self._frame_title._check_mouseover(event)
 
                 # If clicked in title
-                if event.type == pygame.MOUSEBUTTONDOWN and self._mouse_enabled and event.button in (1, 2, 3) or \
-                        event.type == FINGERDOWN and self._touchscreen_enabled:
+                if (event.type == pygame.MOUSEBUTTONDOWN and self._mouse_enabled and event.button in (
+                        1, 2, 3) or event.type == FINGERDOWN and self._touchscreen_enabled) and self._draggable:
                     if self._frame_title.get_rect(to_real_position=True).collidepoint(*event.pos):
                         if not self._frame_title.get_attribute('drag', False):
                             self._frame_title.set_attribute('drag', True)
@@ -1515,7 +1665,7 @@ class Frame(Widget):
                 # User moves the mouse while drag
                 elif event.type == pygame.MOUSEMOTION and hasattr(event, 'rel') or \
                         event.type == FINGERMOTION and self._touchscreen_enabled:
-                    if self._frame_title.get_attribute('drag', False):
+                    if self._frame_title.get_attribute('drag', False) and self._draggable:
                         # Get relative movement
                         rx = event.rel[0]
                         ry = event.rel[1]
diff --git a/pygame_menu/widgets/widget/selector.py b/pygame_menu/widgets/widget/selector.py
index 45791705..81180a13 100644
--- a/pygame_menu/widgets/widget/selector.py
+++ b/pygame_menu/widgets/widget/selector.py
@@ -213,7 +213,7 @@ class Selector(Widget):
 
     def set_sformat(self, sformat: str) -> 'Selector':
         """
-        Set sformat for classic style. This receives an string which is later
+        Set sformat for classic style. This receives a string which is later
         formatted with {0}: title and {1}: the current selected item.
 
         :param sformat: String. Must contain {0} and {1}
@@ -375,7 +375,7 @@ class Selector(Widget):
         :param item: Item to select, can be a string or an integer
         :return: None
         """
-        assert isinstance(item, (str, int)), 'item must be an string or an integer'
+        assert isinstance(item, (str, int)), 'item must be a string or an integer'
         if isinstance(item, str):
             found = False
             for i in self._items:
diff --git a/pygame_menu/widgets/widget/textinput.py b/pygame_menu/widgets/widget/textinput.py
index 3cae6292..23b6a88d 100644
--- a/pygame_menu/widgets/widget/textinput.py
+++ b/pygame_menu/widgets/widget/textinput.py
@@ -92,6 +92,12 @@ class TextInput(Widget):
 
         This widget only accepts vertical flip and translation transformations.
 
+    .. note::
+
+        TextInput text input is sensitive to the widget font, some fonts do not support
+        some characters or languages (for example Chinese). Be careful about which font
+        use.
+
     :param title: Text input title
     :param textinput_id: ID of the text input
     :param copy_paste_enable: Enables copy, paste, and cut
@@ -1522,7 +1528,7 @@ class TextInput(Widget):
                     self._keyrepeat_counters[event.key] = [0, event.unicode]
 
                 # User press ctrl+something
-                if pygame.key.get_mods() & pygame.KMOD_CTRL:
+                if pygame.key.get_mods() in (pygame.KMOD_CTRL, pygame.KMOD_RCTRL, pygame.KMOD_LCTRL):
 
                     # If test, disable CTRL
                     if 'test' in event.dict and event.dict['test']:


Test patch:
diff --git a/test/test_frame.py b/test/test_frame.py
index 2a893826..3c3f892d 100644
--- a/test/test_frame.py
+++ b/test/test_frame.py
@@ -1893,4 +1893,49 @@ class FrameWidgetTest(unittest.TestCase):
         menu.update(PygameEventUtils.middle_rect_mouse_motion(frame2._frame_title, rel=(0, 100)))
         self.assertEqual(frame2.get_translate(), (10, 115))
 
-        # menu.mainloop(surface)
+    def test_resize(self) -> None:
+        """
+        Test resize.
+        """
+        menu = MenuUtils.generic_menu()
+
+        # No title, no scrollable
+        frame = menu.add.frame_v(300, 200, background_color=(170, 170, 170), frame_id='f1')
+        self.assertEqual(frame.get_size(), (300, 200))
+
+        # Resize
+        frame.resize(400, 400)
+        self.assertEqual(frame.get_size(), (400, 400))
+
+        # No title, scrollable to non-scrollable
+        frame2 = menu.add.frame_v(300, 200, background_color=(170, 170, 170),
+                                  frame_id='f2', max_width=200, max_height=100)
+        self.assertEqual(frame2.get_size(), (204, 112))
+        frame2.resize(400, 400)  # Now it's not scrollable
+        self.assertFalse(frame2.is_scrollable)
+        self.assertEqual(frame2.get_size(), (400, 400))
+        self.assertRaises(AssertionError, lambda: frame2.resize(400, 400, max_width=300, max_height=200))
+
+        # No title, scrollable to scrollable
+        frame3 = menu.add.frame_v(300, 200, background_color=(170, 170, 170),
+                                  frame_id='f3', max_width=200, max_height=100)
+        self.assertTrue(frame3.is_scrollable)
+        frame3.resize(400, 400, max_width=300, max_height=300)
+        self.assertEqual(frame3.get_size(), (320, 320))
+        self.assertTrue(frame3.is_scrollable)
+
+        # Title, no scrollable
+        frame4 = menu.add.frame_v(300, 200, background_color=(170, 170, 170), frame_id='f4')
+        frame4title = frame4.set_title('generic title')
+        btn1 = frame4.add_title_button(pygame_menu.widgets.FRAME_TITLE_BUTTON_CLOSE, lambda: print(''))
+        btn2 = frame4.add_title_button(pygame_menu.widgets.FRAME_TITLE_BUTTON_MINIMIZE, lambda: print(''))
+        frame4title_widgets = frame4._frame_title.get_widgets()
+        label = frame4title_widgets[0]
+        self.assertEqual(frame4title_widgets, (label, btn1, btn2))
+
+        frame4.resize(600, 600)
+        new_frame4title_widgets = frame4._frame_title.get_widgets()
+        label = new_frame4title_widgets[0]
+        self.assertEqual(frame4.get_size(), (600, 641 if PYGAME_V2 else 642))  # Plus title height
+        self.assertNotEqual(frame4title, frame4._frame_title)
+        self.assertEqual(new_frame4title_widgets, (label, btn1, btn2))
diff --git a/test/test_widgets.py b/test/test_widgets.py
index 70e12284..b086529f 100644
--- a/test/test_widgets.py
+++ b/test/test_widgets.py
@@ -156,8 +156,7 @@ class WidgetsTest(unittest.TestCase):
         vmargin.select(update_menu=True)
         self.assertEqual(test[0], text)
 
-    @staticmethod
-    def test_non_ascii() -> None:
+    def test_non_ascii(self) -> None:
         """
         Test non-ascii.
         """
@@ -168,12 +167,19 @@ class WidgetsTest(unittest.TestCase):
         menu.add.button('Test', pygame_menu.events.NONE)
         menu.add.button(u'Menú', pygame_menu.events.NONE)
         menu.add.color_input(u'Cólor', 'rgb')
-        menu.add.text_input(u'Téxt')
+        text = menu.add.text_input(u'Téxt')
         menu.add.label(u'Téxt')
         menu.add.selector(u'Sélect'.encode('latin1'), [('a', 'a')])
         menu.enable()
         menu.draw(surface)
 
+        # Text text input
+        text.set_value('ą, ę, ś, ć, ż, ź, ó, ł, ń')
+        self.assertEqual(text.get_value(), 'ą, ę, ś, ć, ż, ź, ó, ł, ń')
+        text.set_value('')
+        text.update(PygameEventUtils.key(pygame.K_l, char='ł', keydown=True))
+        self.assertEqual(text.get_value(), 'ł')
+
     def test_background(self) -> None:
         """
         Test widget background.