Skip to content

Commit c73d8dc

Browse files
authored
Merge pull request #441 from ppizarror/keyrepeat-option
Keyrepeat option
2 parents c0a5fd4 + b9f3705 commit c73d8dc

12 files changed

+117
-51
lines changed

pygame_menu/baseimage.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ def get_bitsize(self) -> int:
466466
"""
467467
Return the image bit size.
468468
469-
:return: Image bit size
469+
:return: Image size
470470
"""
471471
return self._surface.get_bitsize()
472472

pygame_menu/examples/other/maze.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ def _sleep(ms: float) -> None:
571571
"""
572572
Sleep time.
573573
574-
:param ms: Sleep in ms
574+
:param ms: Sleep time in milliseconds
575575
"""
576576
time.sleep(ms)
577577

@@ -751,8 +751,8 @@ def _recursive_division(
751751
"""
752752
Performs recursive division.
753753
754-
:param chamber: Limits
755-
:param halving: Divides the recursion area by two
754+
:param chamber: Limit
755+
:param halving: Divide the recursion area by two
756756
"""
757757

758758
def _gaps_to_offset() -> List[int]:

pygame_menu/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ def __str__(self) -> str:
3434
patch = property(lambda self: self[2])
3535

3636

37-
vernum = Version(4, 3, 4)
37+
vernum = Version(4, 3, 5)
3838
ver = str(vernum)
3939
rev = ''

pygame_menu/widgets/selection/arrow_selection.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def _draw_arrow(
9090
:param b: Arrow coord B
9191
:param c: Arrow coord C
9292
"""
93-
SELECTOR_CLOCK.tick(60) # As blink is in ms
93+
SELECTOR_CLOCK.tick(60)
9494
self._blink_time += SELECTOR_CLOCK.get_time()
9595

9696
# Switch the blinking if the time exceeded or the widget has changed

pygame_menu/widgets/widget/button.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ def button(
409409
try:
410410
self._check_kwargs(kwargs)
411411
except ValueError:
412-
if self._verbose:
412+
if self._menu._verbose:
413413
warn('button cannot accept kwargs. If you want to use kwargs '
414414
'options set accept_kwargs=True')
415415
raise

pygame_menu/widgets/widget/colorinput.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class ColorInput(TextInput):
9090
:param onselect: Function when selecting the widget
9191
:param prev_margin: Horizontal margin between the previsualization and the input text in px
9292
:param prev_width_factor: Width of the previsualization box in terms of the height of the widget
93-
:param repeat_keys_initial_ms: Time in ms before keys are repeated when held
93+
:param repeat_keys_initial_ms: Time in milliseconds before keys are repeated when held
9494
:param repeat_keys_interval_ms: Interval between key press repetition when held
9595
:param repeat_mouse_interval_ms: Interval between mouse events when held
9696
:param kwargs: Optional keyword arguments
@@ -635,9 +635,10 @@ def color_input(
635635
- ``previsualization_width`` (int, float) – Pre-visualization width as a factor of the height. Default is ``3``
636636
- ``readonly_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget if readonly mode
637637
- ``readonly_selected_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget if readonly mode and is selected
638-
- ``repeat_keys_initial_ms`` (int, float) - Time in ms before keys are repeated when held in ms. ``400`` by default
639-
- ``repeat_keys_interval_ms`` (int, float) - Interval between key press repetition when held in ms. ``50`` by default
640-
- ``repeat_mouse_interval_ms`` (int, float) - Interval between mouse events when held in ms. ``400`` by default
638+
- ``repeat_keys`` (bool) - Enable key repeat. ``True`` by default
639+
- ``repeat_keys_initial_ms`` (int, float) - Time in milliseconds before keys are repeated when held in milliseconds. ``400`` by default
640+
- ``repeat_keys_interval_ms`` (int, float) - Interval between key press repetition when held in milliseconds. ``50`` by default
641+
- ``repeat_mouse_interval_ms`` (int, float) - Interval between mouse events when held in milliseconds. ``400`` by default
641642
- ``selection_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the selected widget; only affects the font color
642643
- ``selection_effect`` (:py:class:`pygame_menu.widgets.core.Selection`) – Widget selection effect
643644
- ``shadow_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget shadow

pygame_menu/widgets/widget/rangeslider.py

+26-11
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,9 @@ class RangeSlider(Widget):
9393
:param range_text_value_tick_hfactor: Height factor of the range text value tick (factor of the range title height)
9494
:param range_text_value_tick_number: Number of range value text, the values are placed uniformly distributed
9595
:param range_text_value_tick_thick: Thickness of the range text value tick in px
96-
:param repeat_keys_initial_ms: Time in ms before keys are repeated when held in ms
97-
:param repeat_keys_interval_ms: Interval between key press repetition when held in ms
96+
:param repeat_keys: Enable key repeat
97+
:param repeat_keys_initial_ms: Time in milliseconds before keys are repeated when held in milliseconds
98+
:param repeat_keys_interval_ms: Interval between key press repetition when held in milliseconds
9899
:param slider_color: Slider color
99100
:param slider_height_factor: Height of the slider (factor of the range title height)
100101
:param slider_sel_highlight_color: Color of the selected slider highlight box effect
@@ -120,6 +121,7 @@ class RangeSlider(Widget):
120121
_font_slider_value: Optional['pygame.font.Font']
121122
_increment: NumberType
122123
_increment_shift_factor: float
124+
_keyrepeat: bool
123125
_keyrepeat_counters: Dict[int, int]
124126
_keyrepeat_initial_interval_ms: NumberType
125127
_keyrepeat_interval_ms: NumberType
@@ -218,6 +220,7 @@ def __init__(
218220
range_text_value_tick_hfactor: NumberType = 0.35,
219221
range_text_value_tick_number: int = 2,
220222
range_text_value_tick_thick: int = 1,
223+
repeat_keys: bool = True,
221224
repeat_keys_initial_ms: NumberType = 400,
222225
repeat_keys_interval_ms: NumberType = 50,
223226
slider_color: ColorInputType = (120, 120, 120),
@@ -379,6 +382,15 @@ def __init__(
379382
'value_format must be a function that accepts only 1 argument ' \
380383
'(value) and must return a string'
381384

385+
# Check keyrepeat
386+
assert isinstance(repeat_keys, bool)
387+
assert isinstance(repeat_keys_initial_ms, NumberInstance)
388+
assert isinstance(repeat_keys_interval_ms, NumberInstance)
389+
assert repeat_keys_initial_ms > 0, \
390+
'repeat keys initial ms cannot be lower or equal than zero'
391+
assert repeat_keys_interval_ms > 0, \
392+
'repeat keys interval ms cannot be lower or equal than zero'
393+
382394
# Single value
383395
single = isinstance(default_value, NumberInstance)
384396

@@ -393,6 +405,7 @@ def __init__(
393405
self._default_value = tuple(default_value)
394406
self._increment = increment
395407
self._increment_shift_factor = 0.5
408+
self._keyrepeat = repeat_keys
396409
self._keyrepeat_counters = {} # {event.key: (counter_int, event.unicode)} (look for "***")
397410
self._keyrepeat_initial_interval_ms = repeat_keys_initial_ms
398411
self._keyrepeat_interval_ms = repeat_keys_interval_ms
@@ -1076,14 +1089,15 @@ def update(self, events: EventVectorType) -> bool:
10761089
if self._update_value(delta):
10771090
updated = True
10781091

1079-
# Update key counters:
1080-
for key in self._keyrepeat_counters:
1081-
self._keyrepeat_counters[key] += time_clock # Update clock
1092+
# Update key counters
1093+
if self._keyrepeat:
1094+
for key in self._keyrepeat_counters:
1095+
self._keyrepeat_counters[key] += time_clock # Update clock
10821096

1083-
# Generate new key events if enough time has passed:
1084-
if self._keyrepeat_counters[key] >= self._keyrepeat_initial_interval_ms:
1085-
self._keyrepeat_counters[key] = self._keyrepeat_initial_interval_ms - self._keyrepeat_interval_ms
1086-
self._add_event(pygame.event.Event(pygame.KEYDOWN, key=key))
1097+
# Generate new key events if enough time has passed:
1098+
if self._keyrepeat_counters[key] >= self._keyrepeat_initial_interval_ms:
1099+
self._keyrepeat_counters[key] = self._keyrepeat_initial_interval_ms - self._keyrepeat_interval_ms
1100+
self._add_event(pygame.event.Event(pygame.KEYDOWN, key=key))
10871101

10881102
return updated
10891103

@@ -1173,8 +1187,9 @@ def range_slider(
11731187
- ``range_text_value_tick_thick`` (int) - Thickness of the range text value tick in px
11741188
- ``readonly_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget if readonly mode
11751189
- ``readonly_selected_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget if readonly mode and is selected
1176-
- ``repeat_keys_initial_ms`` (int) - Time in ms before keys are repeated when held in ms. ``400`` by default
1177-
- ``repeat_keys_interval_ms`` (int) - Interval between key press repetition when held in ms. ``50`` by default
1190+
- ``repeat_keys`` (bool) - Enable key repeat. ``True`` by default
1191+
- ``repeat_keys_initial_ms`` (int) - Time in milliseconds before keys are repeated when held in milliseconds. ``400`` by default
1192+
- ``repeat_keys_interval_ms`` (int) - Interval between key press repetition when held in milliseconds. ``50`` by default
11781193
- ``selection_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the selected widget; only affects the font color
11791194
- ``selection_effect`` (:py:class:`pygame_menu.widgets.core.Selection`) – Widget selection effect
11801195
- ``shadow_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget shadow

pygame_menu/widgets/widget/textinput.py

+32-25
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,10 @@ class TextInput(Widget):
9191
:param onselect: Function when selecting the widget
9292
:param password: Input string is displayed as a password
9393
:param password_char: Character used by password type
94-
:param repeat_keys_initial_ms: Time in ms before keys are repeated when held in ms
95-
:param repeat_keys_interval_ms: Interval between key press repetition when held in ms
96-
:param repeat_mouse_interval_ms: Interval between mouse events when held in ms
94+
:param repeat_keys: Enable key repeat
95+
:param repeat_keys_initial_ms: Time in milliseconds before keys are repeated when held in milliseconds
96+
:param repeat_keys_interval_ms: Interval between key press repetition when held in milliseconds
97+
:param repeat_mouse_interval_ms: Interval between mouse events when held in milliseconds
9798
:param text_ellipsis: Ellipsis text when overflow occurs (input length exceeds maxwidth)
9899
:param valid_chars: List of chars that are valid, ``None`` if all chars are valid
99100
:param kwargs: Optional keyword arguments
@@ -130,6 +131,7 @@ class TextInput(Widget):
130131
_input_underline_vmargin: int
131132
_key_is_pressed: bool
132133
_keychar_size: Dict[str, NumberType]
134+
_keyrepeat: bool
133135
_keyrepeat_counters: Dict[int, List[int]]
134136
_keyrepeat_initial_interval_ms: NumberType
135137
_keyrepeat_interval_ms: NumberType
@@ -181,6 +183,7 @@ def __init__(
181183
onselect: CallbackType = None,
182184
password: bool = False,
183185
password_char: str = '*',
186+
repeat_keys: bool = True,
184187
repeat_keys_initial_ms: NumberType = 400,
185188
repeat_keys_interval_ms: NumberType = 50,
186189
repeat_mouse_interval_ms: NumberType = 400,
@@ -202,6 +205,7 @@ def __init__(
202205
assert isinstance(maxwidth, int)
203206
assert isinstance(password, bool)
204207
assert isinstance(password_char, str)
208+
assert isinstance(repeat_keys, bool)
205209
assert isinstance(repeat_keys_initial_ms, NumberInstance)
206210
assert isinstance(repeat_keys_interval_ms, NumberInstance)
207211
assert isinstance(repeat_mouse_interval_ms, NumberInstance)
@@ -222,11 +226,11 @@ def __init__(
222226
assert cursor_switch_ms > 0, \
223227
'cursor switch in milliseconds must be greater than zero'
224228
assert repeat_keys_initial_ms > 0, \
225-
'ms cannot be lower or equal than zero'
229+
'repeat keys initial ms cannot be lower or equal than zero'
226230
assert repeat_keys_interval_ms > 0, \
227-
'ms cannot be lower or equal than zero'
231+
'repeat keys interval ms cannot be lower or equal than zero'
228232
assert repeat_mouse_interval_ms > 0, \
229-
'ms cannot be lower or equal than zero'
233+
'repeat mouse interval ms cannot be lower or equal than zero'
230234

231235
cursor_color = assert_color(cursor_color)
232236
cursor_selection_color = assert_color(cursor_selection_color)
@@ -273,6 +277,7 @@ def __init__(
273277
# Vars to make keydown repeat after user pressed a key for some time:
274278
self._block_copy_paste = False # Blocks event
275279
self._key_is_pressed = False
280+
self._keyrepeat = repeat_keys
276281
self._keyrepeat_counters = {} # {event.key: (counter_int, event.unicode)} (look for "***")
277282
self._keyrepeat_initial_interval_ms = repeat_keys_initial_ms
278283
self._keyrepeat_interval_ms = repeat_keys_interval_ms
@@ -1877,22 +1882,23 @@ def update(self, events: EventVectorType) -> bool:
18771882
pos = pygame.mouse.get_pos()
18781883
self._check_mouse_collide_input((pos[0], pos[1]))
18791884

1880-
# Update key counters:
1881-
for key in self._keyrepeat_counters:
1882-
self._keyrepeat_counters[key][0] += time_clock # Update clock
1883-
1884-
# Generate new key events if enough time has passed:
1885-
if self._keyrepeat_counters[key][0] >= self._keyrepeat_initial_interval_ms:
1886-
self._keyrepeat_counters[key][0] = \
1887-
self._keyrepeat_initial_interval_ms - self._keyrepeat_interval_ms
1888-
1889-
event_key, event_unicode = key, self._keyrepeat_counters[key][1]
1890-
self._add_event(
1891-
pygame.event.Event(
1892-
pygame.KEYDOWN,
1893-
key=event_key,
1894-
unicode=event_unicode)
1895-
)
1885+
# Update key counters
1886+
if self._keyrepeat:
1887+
for key in self._keyrepeat_counters:
1888+
self._keyrepeat_counters[key][0] += time_clock # Update clock
1889+
1890+
# Generate new key events if enough time has passed:
1891+
if self._keyrepeat_counters[key][0] >= self._keyrepeat_initial_interval_ms:
1892+
self._keyrepeat_counters[key][0] = \
1893+
self._keyrepeat_initial_interval_ms - self._keyrepeat_interval_ms
1894+
1895+
event_key, event_unicode = key, self._keyrepeat_counters[key][1]
1896+
self._add_event(
1897+
pygame.event.Event(
1898+
pygame.KEYDOWN,
1899+
key=event_key,
1900+
unicode=event_unicode)
1901+
)
18961902

18971903
return updated
18981904

@@ -1968,9 +1974,10 @@ def text_input(
19681974
- ``password_char`` (str) - Character used by password type. ``"*"`` by default
19691975
- ``readonly_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget if readonly mode
19701976
- ``readonly_selected_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget if readonly mode and is selected
1971-
- ``repeat_keys_initial_ms`` (int, float) - Time in ms before keys are repeated when held in ms. ``400`` by default
1972-
- ``repeat_keys_interval_ms`` (int, float) - Interval between key press repetition when held in ms. ``50`` by default
1973-
- ``repeat_mouse_interval_ms`` (int, float) - Interval between mouse events when held in ms. ``400`` by default
1977+
- ``repeat_keys`` (bool) - Enable key repeat. ``True`` by default
1978+
- ``repeat_keys_initial_ms`` (int, float) - Time in milliseconds before keys are repeated when held in milliseconds. ``400`` by default
1979+
- ``repeat_keys_interval_ms`` (int, float) - Interval between key press repetition when held in milliseconds. ``50`` by default
1980+
- ``repeat_mouse_interval_ms`` (int, float) - Interval between mouse events when held in milliseconds. ``400`` by default
19741981
- ``selection_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the selected widget; only affects the font color
19751982
- ``selection_effect`` (:py:class:`pygame_menu.widgets.core.Selection`) – Widget selection effect
19761983
- ``shadow_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget shadow

test/_utils.py

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
# Methods
2121
'reset_widgets_over',
22+
'sleep',
2223
'surface',
2324
'test_reset_surface',
2425

@@ -34,6 +35,8 @@
3435
import sys
3536
import unittest
3637

38+
from time import sleep
39+
3740
import pygame
3841
import pygame_menu
3942

test/test_menu.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from test._utils import BaseRSTest, surface, MenuUtils, PygameEventUtils, \
1212
TEST_THEME, PYGAME_V2, WIDGET_MOUSEOVER, WIDGET_TOP_CURSOR, reset_widgets_over, \
1313
THEME_NON_FIXED_TITLE
14-
from typing import Any, Tuple, List
1514
import copy
1615
import math
1716
import sys
@@ -23,6 +22,8 @@
2322
import pygame_menu.controls as ctrl
2423

2524
from pygame_menu import events
25+
# noinspection PyProtectedMember
26+
from pygame_menu._types import Any, Tuple, List
2627
from pygame_menu.locals import FINGERDOWN, FINGERMOTION
2728
from pygame_menu.utils import set_pygame_cursor, get_cursor
2829
from pygame_menu.widgets import Label, Button

test/test_widget_rangeslider.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
__all__ = ['RangeSliderWidgetTest']
1010

11-
from test._utils import MenuUtils, surface, PygameEventUtils, BaseTest
11+
from test._utils import MenuUtils, surface, PygameEventUtils, BaseTest, sleep
1212

1313
import pygame
1414
import pygame_menu
@@ -477,3 +477,23 @@ def test_value(self) -> None:
477477
r.reset_value()
478478
self.assertEqual(r.get_value(), (0.2, 0.6))
479479
self.assertFalse(r.value_changed())
480+
481+
def test_keyrepeat(self) -> None:
482+
"""
483+
Test keyrepeat.
484+
"""
485+
menu = MenuUtils.generic_menu(keyboard_ignore_nonphysical=False)
486+
487+
e = PygameEventUtils.key(ctrl.KEY_RIGHT, keydown=True)
488+
slider_on = menu.add.range_slider('', 0, [0, 1], increment=0.1)
489+
slider_on.update(e)
490+
slider_off = menu.add.range_slider('', 0, [0, 1], increment=0.1, repeat_keys=False)
491+
slider_off.update(e)
492+
493+
# Test with time
494+
for i in range(5):
495+
sleep(0.5)
496+
slider_on.update([])
497+
slider_off.update([])
498+
self.assertGreater(slider_on.get_value(), 0.1)
499+
self.assertEqual(slider_off.get_value(), 0.1)

test/test_widget_textinput.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
__all__ = ['TextInputWidgetTest']
1010

1111
from test._utils import MenuUtils, surface, PygameEventUtils, TEST_THEME, PYGAME_V2, \
12-
BaseTest
12+
BaseTest, sleep
1313

1414
import pygame
1515
import pygame_menu
@@ -410,7 +410,6 @@ def test_copy_paste(self) -> None:
410410
self.assertEqual(textinput_copy.get_value(), '')
411411
textinput_copy._block_copy_paste = False
412412
textinput_copy._paste()
413-
# self.assertEqual(textinput_copy.get_value(), 'er than the max char')
414413
textinput_copy._cut()
415414
textinput_copy._block_copy_paste = False
416415
# self.assertEqual(textinput_copy.get_value(), '')
@@ -824,3 +823,23 @@ def test_empty_title(self) -> None:
824823
menu = MenuUtils.generic_menu()
825824
text = menu.add.text_input('')
826825
self.assertEqual(text.get_size(), (16, 49))
826+
827+
def test_keyrepeat(self) -> None:
828+
"""
829+
Test keyrepeat.
830+
"""
831+
menu = MenuUtils.generic_menu(keyboard_ignore_nonphysical=False)
832+
833+
e = PygameEventUtils.key(pygame.K_a, keydown=True, char='a')
834+
textinput_on = menu.add.text_input('On', repeat_keys=True)
835+
textinput_on.update(e)
836+
textinput_off = menu.add.text_input('Off', repeat_keys=False)
837+
textinput_off.update(e)
838+
839+
# Test with time
840+
for i in range(5):
841+
sleep(0.5)
842+
textinput_on.update([])
843+
textinput_off.update([])
844+
self.assertGreater(len(textinput_on.get_value()), 1)
845+
self.assertEqual(len(textinput_off.get_value()), 1)

0 commit comments

Comments
 (0)