How can I create a text input box with Pygame?

Question:

I want to get some text input from the user in Python and display what they are typing in a text box, and when they press enter, it gets stored in a string.

I’ve looked everywhere, but I just can’t find anything. I’m using Pygame.

Asked By: Deps

||

Answers:

You can define a rect as the area of the input box. If a pygame.MOUSEBUTTONDOWN event occurs, use the colliderect method of the input_box rect to check if it collides with the event.pos and then activate it by setting a active variable to True.

If the box is active you can type something and Pygame will generate pygame.KEYDOWN events which have a unicode attribute that you can simply add to a string, e.g. text += event.unicode. If the user presses enter, you can do something with the text string (in the example I just print it) and reset it to ''.

import pygame as pg


def main():
    screen = pg.display.set_mode((640, 480))
    font = pg.font.Font(None, 32)
    clock = pg.time.Clock()
    input_box = pg.Rect(100, 100, 140, 32)
    color_inactive = pg.Color('lightskyblue3')
    color_active = pg.Color('dodgerblue2')
    color = color_inactive
    active = False
    text = ''
    done = False

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            if event.type == pg.MOUSEBUTTONDOWN:
                # If the user clicked on the input_box rect.
                if input_box.collidepoint(event.pos):
                    # Toggle the active variable.
                    active = not active
                else:
                    active = False
                # Change the current color of the input box.
                color = color_active if active else color_inactive
            if event.type == pg.KEYDOWN:
                if active:
                    if event.key == pg.K_RETURN:
                        print(text)
                        text = ''
                    elif event.key == pg.K_BACKSPACE:
                        text = text[:-1]
                    else:
                        text += event.unicode

        screen.fill((30, 30, 30))
        # Render the current text.
        txt_surface = font.render(text, True, color)
        # Resize the box if the text is too long.
        width = max(200, txt_surface.get_width()+10)
        input_box.w = width
        # Blit the text.
        screen.blit(txt_surface, (input_box.x+5, input_box.y+5))
        # Blit the input_box rect.
        pg.draw.rect(screen, color, input_box, 2)

        pg.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()

Here’s an object-oriented variant that allows you to easily create multiple input boxes:

import pygame as pg


pg.init()
screen = pg.display.set_mode((640, 480))
COLOR_INACTIVE = pg.Color('lightskyblue3')
COLOR_ACTIVE = pg.Color('dodgerblue2')
FONT = pg.font.Font(None, 32)


class InputBox:

    def __init__(self, x, y, w, h, text=''):
        self.rect = pg.Rect(x, y, w, h)
        self.color = COLOR_INACTIVE
        self.text = text
        self.txt_surface = FONT.render(text, True, self.color)
        self.active = False

    def handle_event(self, event):
        if event.type == pg.MOUSEBUTTONDOWN:
            # If the user clicked on the input_box rect.
            if self.rect.collidepoint(event.pos):
                # Toggle the active variable.
                self.active = not self.active
            else:
                self.active = False
            # Change the current color of the input box.
            self.color = COLOR_ACTIVE if self.active else COLOR_INACTIVE
        if event.type == pg.KEYDOWN:
            if self.active:
                if event.key == pg.K_RETURN:
                    print(self.text)
                    self.text = ''
                elif event.key == pg.K_BACKSPACE:
                    self.text = self.text[:-1]
                else:
                    self.text += event.unicode
                # Re-render the text.
                self.txt_surface = FONT.render(self.text, True, self.color)

    def update(self):
        # Resize the box if the text is too long.
        width = max(200, self.txt_surface.get_width()+10)
        self.rect.w = width

    def draw(self, screen):
        # Blit the text.
        screen.blit(self.txt_surface, (self.rect.x+5, self.rect.y+5))
        # Blit the rect.
        pg.draw.rect(screen, self.color, self.rect, 2)



def main():
    clock = pg.time.Clock()
    input_box1 = InputBox(100, 100, 140, 32)
    input_box2 = InputBox(100, 300, 140, 32)
    input_boxes = [input_box1, input_box2]
    done = False

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            for box in input_boxes:
                box.handle_event(event)

        for box in input_boxes:
            box.update()

        screen.fill((30, 30, 30))
        for box in input_boxes:
            box.draw(screen)

        pg.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    main()
    pg.quit()

There are also third party modules available like pygame_textinput.

Answered By: skrx

You can find a great module for Pygame text input here.

I have been using it for a while and I really like it. A tutorial how to use it is included in the description.

However, I have added the possibility to draw a (coloured) rectangle around the text, by adding a rect and a rect_color parameter to the *_init_() function and adding

if self.rect != None:
    pygame.draw.rect(screen, self.rect_color, self.rect)    #screen is my pygame display surface

to the update(self, events) function.

Answered By: Jonas De Schouwer

Use the KEYDOWN event to get the input from the keyboard (see pygame.event). The key that was pressed can be obtained from the key attribute of the pygame.event.Event object. unicode contains a single character string that is the fully translated character. Add the character to the text when a key is pressed.
Two special keys need to be dealt with. If RETURN is pressed, the input is finished. If BACKSPACE is pressed, the last character of the input text must be removed:

repl.it/@Rabbid76/PyGame-TextInput

import pygame
pygame.init()
window = pygame.display.set_mode((500, 200))
clock = pygame.time.Clock()

font = pygame.font.SysFont(None, 100)
text = ""
input_active = True

run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            input_active = True
            text = ""
        elif event.type == pygame.KEYDOWN and input_active:
            if event.key == pygame.K_RETURN:
                input_active = False
            elif event.key == pygame.K_BACKSPACE:
                text =  text[:-1]
            else:
                text += event.unicode

        window.fill(0)
        text_surf = font.render(text, True, (255, 0, 0))
        window.blit(text_surf, text_surf.get_rect(center = window.get_rect().center))
        pygame.display.flip()

pygame.quit()
exit()

Use the algorithm in a pygame.sprite.Sprite class. Handle the event in the update method.Determine whether the mouse clicks in the text entry field with collidepoint (see How to detect when a rectangular object, image or sprite is clicked) and activate the text input box:

class TextInputBox(pygame.sprite.Sprite):
    # [...]

    def update(self, event_list):
        for event in event_list:
            if event.type == pygame.MOUSEBUTTONDOWN and not self.active:
                self.active = self.rect.collidepoint(event.pos)
            if event.type == pygame.KEYDOWN and self.active:
                if event.key == pygame.K_RETURN:
                    self.active = False
                elif event.key == pygame.K_BACKSPACE:
                    self.text = self.text[:-1]
                else:
                    self.text += event.unicode
                self.render_text()

Pass the list of events to the update method of the Group that contains the Sprite:

event_list = pygame.event.get()
for event in event_list:
    if event.type == pygame.QUIT:
        run = False
group.update(event_list)

Minimal example: repl.it/@Rabbid76/PyGame-SpriteTextInput

import pygame

class TextInputBox(pygame.sprite.Sprite):
    def __init__(self, x, y, w, font):
        super().__init__()
        self.color = (255, 255, 255)
        self.backcolor = None
        self.pos = (x, y) 
        self.width = w
        self.font = font
        self.active = False
        self.text = ""
        self.render_text()

    def render_text(self):
        t_surf = self.font.render(self.text, True, self.color, self.backcolor)
        self.image = pygame.Surface((max(self.width, t_surf.get_width()+10), t_surf.get_height()+10), pygame.SRCALPHA)
        if self.backcolor:
            self.image.fill(self.backcolor)
        self.image.blit(t_surf, (5, 5))
        pygame.draw.rect(self.image, self.color, self.image.get_rect().inflate(-2, -2), 2)
        self.rect = self.image.get_rect(topleft = self.pos)

    def update(self, event_list):
        for event in event_list:
            if event.type == pygame.MOUSEBUTTONDOWN and not self.active:
                self.active = self.rect.collidepoint(event.pos)
            if event.type == pygame.KEYDOWN and self.active:
                if event.key == pygame.K_RETURN:
                    self.active = False
                elif event.key == pygame.K_BACKSPACE:
                    self.text = self.text[:-1]
                else:
                    self.text += event.unicode
                self.render_text()

pygame.init()
window = pygame.display.set_mode((500, 200))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)

text_input_box = TextInputBox(50, 50, 400, font)
group = pygame.sprite.Group(text_input_box)

run = True
while run:
    clock.tick(60)
    event_list = pygame.event.get()
    for event in event_list:
        if event.type == pygame.QUIT:
            run = False
    group.update(event_list)

    window.fill(0)
    group.draw(window)
    pygame.display.flip()

pygame.quit()
exit()
Answered By: Rabbid76

The pygame_gui module allows you to create a text_input box from the user by creating a UITextEntryLine instance. You’ll need to set up an instance as in the quick start guide.

Create the text_input:

from pygame.rect import Rect
from pygame_gui.elements.ui_text_entry_line import UITextEntryLine
text_input = UITextEntryLine(relative_rect=Rect(0, 0, 100, 100), manager=manager)

Get the text if enter is clicked:

for event in pygame.event.get():
    if event.type == pygame.USEREVENT:
        if event.user_type == pygame_gui.UI_TEXT_ENTRY_FINISHED:
            if event.ui_element == text_input:
                entered_text = event.text
Answered By: Umbral Reaper

I have written a class that can handle text input

minimal example:

import pygame as pg
from pgtextbox import pgtextbox
pg.init()

screen=pg.display.set_mode((1000,500))
textbox=pgtextbox(200,20)
textbox.insertAtCurser('Hallo')

while True:
    e = pg.event.wait(30000)
    if e.type == pg.QUIT:
        raise StopIteration

    textbox.addPgEvent(e)#uses keydown events
    
    print(textbox.text)

    screen.fill((0,0,0))
    screen.blit(textbox.render(),(10,0))
    pg.display.flip()
pg.display.quit()

pgtextbox class:

import pygame as pg

class pgtextbox:#By K1521
    def __init__(self,width=100,height=10,fontname=None):
        self.surface=pg.Surface((width,height))
        self.text=""
        self.width=width
        self.height=height
        self.font=pg.font.Font(fontname,pgtextbox.getMaxFontSize(fontname,lineheight=height))
        self.curserindex=0
        self.cursersurface=pg.Surface((self.font.size("|")[0]//2,self.font.size("|")[1]))
        self.cursersurface.fill((255,255,255))
        #self.cursersurface=self.font.render("|",False,(255,255,255),(0,0,0))
        self.offsety=int((height-self.font.get_linesize())/2)
        self.offsetx=0


    def curserpos(self):
        return self.font.size(self.text[:self.curserindex])[0]

    def addPgEvent(self,event):
        if event.type==pg.KEYDOWN:
            if event.key==pg.K_BACKSPACE:
                self.deleteAtCurser()
            elif event.key==pg.K_RIGHT:
                self.offsetCurser(1)
            elif event.key==pg.K_LEFT:
                self.offsetCurser(-1)
            else:
                self.insertAtCurser(event.unicode)

    def render(self):
        self.surface.fill((0,0,0))

        width=self.width-self.cursersurface.get_width()
        text=self.font.render(self.text,False,(255,255,255),(0,0,0))


        if self.curserindex>=0:
            curserpos=self.curserpos()+self.offsetx

            curserposnew=max(0,min(curserpos,width))
            self.offsetx+=curserposnew-curserpos
            curserpos=curserposnew
            #if curserpos<0:
                #self.offsetx-=curserpos
                #curserpos=0
            #if curserpos>width:
                #curserpos=curserpos-width
                #self.offsetx-=curserpos
        else:
            #self.offsetx=min(width-text.get_width(),0)
            self.offsetx=0

        self.surface.blit(text,(self.offsetx,self.offsety))
        if self.curserindex>=0:
            self.surface.blit(self.cursersurface,(curserpos,self.offsety))
            #print((curserpos,self.offsety))
        return self.surface

    def insertAtCurser(self,t):
        if self.curserindex<0:
            self.curserindex=len(self.text)
        self.text=self.text[:self.curserindex]+t+self.text[self.curserindex:]
        self.curserindex+=len(t)

    def deleteAtCurser(self,length=1):
        if self.curserindex<0:
            self.curserindex=len(self.text)

        newcurserindex=max(0,self.curserindex-length)
        self.text=self.text[:newcurserindex]+self.text[self.curserindex:]
        self.curserindex=newcurserindex

    def offsetCurser(self,i):
        self.curserindex=max(min(self.curserindex+i,len(self.text)),0)


    @staticmethod
    def longestline(self,fontname,lines):
        size=pg.font.Font(fontname,1000)
        return max(lines,key=lambda t:size(t)[0])

    @staticmethod
    def getMaxFontSize(fontname,width=None,lineheight=None,line=None):
        def font(size):
            return pg.font.Font(fontname,size)
        fontsize=float("inf")# inf

        if width:
            aproxsize=width*1000//font(1000).size(line)[0]
            while font(aproxsize).size(line)[0]<width:
                aproxsize+=1
            while font(aproxsize).size(line)[0]>width:
                aproxsize-=1
            fontsize=min(aproxsize,fontsize)

        if lineheight:
            aproxsize=lineheight*4//3
            while font(aproxsize).get_linesize()<lineheight:
                aproxsize+=1
            while font(aproxsize).get_linesize()>lineheight:
                aproxsize-=1
            fontsize=min(aproxsize,fontsize)
        return fontsize

    @staticmethod
    def rendermultilinetext(text,width=None,height=10,fontname=None,antialias=False,color=(255,255,255),background=None):
        if(len(text)-text.count("n")==0):
            return pg.Surface((0,0))
        def font(size):
            return pg.font.Font(fontname,size)

        text=text.split("n")
        fontsize=1000000000# inf

        longestline=None
        if height:
            longestline=pgtextbox.longestline(fontname,lines)
        fontsize=pgtextbox.getMaxFontSize(fontname,width,lineheight,longestline)

        font=font(fontsize)
        width=font.size(longestline)[0]
        lineheight=font.get_linesize()
        heigth=len(text)*lineheight
        textsurface=pg.Surface((width,heigth))
        if background:
            textsurface.fill(background)
        for i,line in enumerate(text):
            textsurface.blit(font.render(line,antialias,color,background),(0,i*lineheight))
        return textsurface
Answered By: K1521

This can be solved with pygame.TEXTINPUT event

#   MASHIUR RAHMAN MAHID 2022
#   https://github.com/01one/Pygame-Examples
import pygame,sys,time
from pygame.locals import*
pygame.init()
clock=pygame.time.Clock()

w=1000
h=600

screen=pygame.display.set_mode((w,h),RESIZABLE)
then=time.time()
txt=""
pulse='|'
font=pygame.font.Font(pygame.font.get_default_font(),22)


class TextView():
    def __init__(self,screen,text='',t_x=0,t_y=0,t_w=200,t_h=400,text_color="#666666",pulse=''):
        self.screen=screen
        self.t_x=t_x
        self.t_y=t_y
        self.t_w=t_w
        self.t_h=t_h
        self.text_color=text_color 
        self.text=text+pulse
        self.text_font=font
        self.text_lines=[]
        self.splitted_lines=self.text.splitlines()
        for splitted_line in self.splitted_lines:
            if self.text_font.size(splitted_line)[0] > self.t_w:
                words = splitted_line.split(' ')
                fitted_line=""
                for word in words:
                    test_line = fitted_line + word + " "
                    if self.text_font.size(test_line)[0] < self.t_w:
                        fitted_line = test_line
                    else:
                        self.text_lines.append(fitted_line)
                        fitted_line = word + " "
                self.text_lines.append(fitted_line)
            else:
                self.text_lines.append(splitted_line)
        
        text_row=self.t_y

        for line in self.text_lines:
            if line != "":
                text_surface = self.text_font.render(line, 1, self.text_color)
                if all_select==True:
                    first_line=(self.text_font.render(self.text_lines[0], 1, self.text_color)).get_rect()
                    txt_rect=text_surface.get_rect()
                    txt_rect=(self.t_x,self.t_y,txt_rect[2],txt_rect[3])
                    pygame.draw.rect(screen,"#ccffcc",txt_rect)
                self.screen.blit(text_surface, (self.t_x, self.t_y))
            self.t_y +=self.text_font.size(line)[1]

            
all_select=False
game_running=True
Next_line=False
while game_running:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type==QUIT:
            pygame.quit()
            sys.exit()
        if event.type==pygame.VIDEORESIZE:
            w,h=event.size
        if event.type==pygame.TEXTINPUT:
            txt+=event.text
            all_select=False
        if event.type==pygame.KEYDOWN:
            if event.key==pygame.K_BACKSPACE:
                pygame.key.set_repeat(200,5)
                if len(txt)==0:
                    pass
                else:
                    txt=txt[:-1]
                if all_select==True:
                    txt=''
                    all_select=False
                    
                    
            if event.key==pygame.K_a:
                if event.mod == pygame.KMOD_NONE:
                    continue
                else:
                    if event.mod & pygame.KMOD_LCTRL:
                        all_select=True
                        
            if event.key==pygame.K_RETURN:
                Next_line=True
            
                
    now=time.time()
    d=now-then
    if d>=1 and d<=2:
        then=now
        pulse=''
    else:
        pulse='|'
    if Next_line==True:
        txt=txt+'n'
        Next_line=False
    screen.fill('#F0FFFF')
    TextView(screen,text=txt,t_x=20,t_y=10,t_w=w-50,t_h=h,pulse=pulse)
    pygame.display.update()

Text Input in pygame

Answered By: 01one
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.