-
Notifications
You must be signed in to change notification settings - Fork 3
/
pygame_textinput.py
212 lines (166 loc) · 8 KB
/
pygame_textinput.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
"""
Copyright 2017, Silas Gyger, [email protected], All rights reserved.
Borrowed from https://github.com/Nearoo/pygame-text-input under the MIT license.
"""
import os.path
import pygame
import pygame.locals as pl
pygame.font.init()
class TextInput:
"""
This class lets the user input a piece of text, e.g. a name or a message.
This class let's the user input a short, one-lines piece of text at a blinking cursor
that can be moved using the arrow-keys. Delete, home and end work as well.
"""
def __init__(
self,
initial_string="",
font_family="comicsans",
font_size=30,
antialias=True,
text_color=(255, 255, 0),
cursor_color=(0, 0, 1),
repeat_keys_initial_ms=400,
repeat_keys_interval_ms=35,
max_string_length=2,
password=False):
"""
:param initial_string: Initial text to be displayed
:param font_family: name or list of names for font (see pygame.font.match_font for precise format)
:param font_size: Size of font in pixels
:param antialias: Determines if antialias is applied to font (uses more processing power)
:param text_color: Color of text (duh)
:param cursor_color: Color of cursor
:param repeat_keys_initial_ms: Time in ms before keys are repeated when held
:param repeat_keys_interval_ms: Interval between key press repetition when held
:param max_string_length: Allowed length of text
"""
# Text related vars:
self.antialias = antialias
self.text_color = text_color
self.font_size = font_size
self.max_string_length = max_string_length
self.password = password
self.input_string = initial_string # Inputted text
if not os.path.isfile(font_family):
font_family = pygame.font.match_font(font_family)
self.font_object = pygame.font.Font(font_family, font_size)
# Text-surface will be created during the first update call:
self.surface = pygame.Surface((1, 1))
self.surface.set_alpha(0)
# Vars to make keydowns repeat after user pressed a key for some time:
self.keyrepeat_counters = {} # {event.key: (counter_int, event.unicode)} (look for "***")
self.keyrepeat_intial_interval_ms = repeat_keys_initial_ms
self.keyrepeat_interval_ms = repeat_keys_interval_ms
# Things cursor:
self.cursor_surface = pygame.Surface((int(self.font_size / 20 + 1), self.font_size))
self.cursor_surface.fill(cursor_color)
self.cursor_position = len(initial_string) # Inside text
self.cursor_visible = True # Switches every self.cursor_switch_ms ms
self.cursor_switch_ms = 500 # /|\
self.cursor_ms_counter = 0
self.clock = pygame.time.Clock()
def update(self, events):
for event in events:
if event.type == pygame.KEYDOWN:
self.cursor_visible = True # So the user sees where he writes
# If none exist, create counter for that key:
if event.key not in self.keyrepeat_counters:
if not event.key == pl.K_RETURN: # Filters out return key, others can be added as necessary
self.keyrepeat_counters[event.key] = [0, event.unicode]
if event.key == pl.K_BACKSPACE:
self.input_string = (
self.input_string[:max(self.cursor_position - 1, 0)]
+ self.input_string[self.cursor_position:]
)
# Subtract one from cursor_pos, but do not go below zero:
self.cursor_position = max(self.cursor_position - 1, 0)
elif event.key == pl.K_DELETE:
self.input_string = (
self.input_string[:self.cursor_position]
+ self.input_string[self.cursor_position + 1:]
)
elif event.key == pl.K_RETURN:
return True
elif event.key == pl.K_RIGHT:
# Add one to cursor_pos, but do not exceed len(input_string)
self.cursor_position = min(self.cursor_position + 1, len(self.input_string))
elif event.key == pl.K_LEFT:
# Subtract one from cursor_pos, but do not go below zero:
self.cursor_position = max(self.cursor_position - 1, 0)
elif event.key == pl.K_END:
self.cursor_position = len(self.input_string)
elif event.key == pl.K_HOME:
self.cursor_position = 0
elif len(self.input_string) < self.max_string_length or self.max_string_length == -1:
# If no special key is pressed, add unicode of key to input_string
self.input_string = (
self.input_string[:self.cursor_position]
+ event.unicode
+ self.input_string[self.cursor_position:]
)
self.cursor_position += len(event.unicode) # Some are empty, e.g. K_UP
elif event.type == pl.KEYUP:
# *** Because KEYUP doesn't include event.unicode, this dict is stored in such a weird way
if event.key in self.keyrepeat_counters:
del self.keyrepeat_counters[event.key]
# Update key counters:
for key in self.keyrepeat_counters:
self.keyrepeat_counters[key][0] += self.clock.get_time() # Update clock
# Generate new key events if enough time has passed:
if self.keyrepeat_counters[key][0] >= self.keyrepeat_intial_interval_ms:
self.keyrepeat_counters[key][0] = (
self.keyrepeat_intial_interval_ms
- self.keyrepeat_interval_ms
)
event_key, event_unicode = key, self.keyrepeat_counters[key][1]
pygame.event.post(pygame.event.Event(pl.KEYDOWN, key=event_key, unicode=event_unicode))
# Re-render text surface:
string = self.input_string
if self.password:
string = "*" * len(self.input_string)
self.surface = self.font_object.render(string, self.antialias, self.text_color)
# Update self.cursor_visible
self.cursor_ms_counter += self.clock.get_time()
if self.cursor_ms_counter >= self.cursor_switch_ms:
self.cursor_ms_counter %= self.cursor_switch_ms
self.cursor_visible = not self.cursor_visible
if self.cursor_visible:
cursor_y_pos = self.font_object.size(self.input_string[:self.cursor_position])[0]
# Without this, the cursor is invisible when self.cursor_position > 0:
if self.cursor_position > 0:
cursor_y_pos -= self.cursor_surface.get_width()
self.surface.blit(self.cursor_surface, (cursor_y_pos, 0))
self.clock.tick()
return False
def get_surface(self):
return self.surface
def get_text(self):
return self.input_string
def get_cursor_position(self):
return self.cursor_position
def set_text_color(self, color):
self.text_color = color
def set_cursor_color(self, color):
self.cursor_surface.fill(color)
def clear_text(self):
self.input_string = ""
self.cursor_position = 0
if __name__ == "__main__":
pygame.init()
# Create TextInput-object
textinput = TextInput()
screen = pygame.display.set_mode((1000, 200))
clock = pygame.time.Clock()
while True:
screen.fill((225, 225, 225))
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
os._exit(1)
# Feed it with events every frame
textinput.update(events)
# Blit its surface onto the screen
screen.blit(textinput.get_surface(), (10, 10))
pygame.display.update()
clock.tick(30)