155 lines
6.6 KiB
Python
155 lines
6.6 KiB
Python
|
|
"""
|
|
Copyright 2017, Silas Gyger, silasgyger@gmail.com, All rights reserved.
|
|
"""
|
|
|
|
import pygame
|
|
import pygame.locals as pl
|
|
import os.path
|
|
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, font_family = "",
|
|
font_size = 35,
|
|
antialias=True,
|
|
text_color=(0, 0, 0),
|
|
cursor_color=(0, 0, 1),
|
|
repeat_keys_initial_ms=400,
|
|
repeat_keys_interval_ms=35):
|
|
"""
|
|
Args:
|
|
font_family: Name or path of the font that should be used. Default is pygame-font
|
|
font_size: Size of the font in pixels
|
|
antialias: (bool) Determines if antialias is used on fonts or not
|
|
text_color: Color of the text
|
|
repeat_keys_initial_ms: ms until the keydowns get repeated when a key is not released
|
|
repeat_keys_interval_ms: ms between to keydown-repeats if key is not released
|
|
"""
|
|
|
|
# Text related vars:
|
|
self.antialias = antialias
|
|
self.text_color = text_color
|
|
self.font_size = font_size
|
|
self.input_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 = 0 # 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 not event.key in self.keyrepeat_counters:
|
|
self.keyrepeat_counters[event.key] = [0, event.unicode]
|
|
|
|
if event.key == pl.K_BACKSPACE: # FIXME: Delete at beginning of line?
|
|
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
|
|
|
|
else:
|
|
# 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))
|
|
|
|
# Rerender text surface:
|
|
self.surface = self.font_object.render(self.input_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)
|