Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] User Interface
#2
TextBox
You can simply make a white rectangle and input text over top of it. That is by far the simplest approach. However TextBox's have expected features such as a blinking prompt for location, text wrap behind the textbox, border, visible background, text, Return Key integration on command, backspace hold deleting all text, etc. All of this requires handling.

The following example does this all. It is similar to the structure of the Button class as before. The line at the endif __name__ == '__main__': means the code in it block will only be executed if it ran directly, and not imported as a module. This code is the example of running the TextBox class. The only difference in its use compared to the Button class, is it requires an extra update call.
the problem is you are expecting your function to return the value, but you also have the main loop in the function that is expected to return it. Its not going to work in any sensible method and i would suggest to redo it.

Here is an example of a textbox class.
import pygame as pg
import string

class TextBox(object):
    def __init__(self,rect,**kwargs):
        '''
        Optional kwargs and their defaults:
            "id" : None,
            "command" : None,
                function to execute upon enter key
                Callback for command takes 2 args, id and final (the string in the textbox)
            "active" : True,
                textbox active on opening of window
            "color" : pg.Color("white"),
                background color
            "font_color" : pg.Color("black"),
            "outline_color" : pg.Color("black"),
            "outline_width" : 2,
            "active_color" : pg.Color("blue"),
            "font" : pg.font.Font(None, self.rect.height+4),
            "clear_on_enter" : False,
                remove text upon enter
            "inactive_on_enter" : True
            "blink_speed": 500
                prompt blink time in milliseconds
            "delete_speed": 500
                backspace held clear speed in milliseconds
            
        Values:
            self.rect = pg.Rect(rect)
            self.buffer = []
            self.final = None
            self.rendered = None
            self.render_rect = None
            self.render_area = None
            self.blink = True
            self.blink_timer = 0.0
            self.delete_timer = 0.0
            self.accepted = string.ascii_letters+string.digits+string.punctuation+" "
        '''
        self.rect = pg.Rect(rect)
        self.buffer = []
        self.final = None
        self.rendered = None
        self.render_rect = None
        self.render_area = None
        self.blink = True
        self.blink_timer = 0.0
        self.delete_timer = 0.0
        self.accepted = string.ascii_letters+string.digits+string.punctuation+" "
        self.process_kwargs(kwargs)

    def process_kwargs(self,kwargs):
        defaults = {"id" : None,
                    "command" : None,
                    "active" : True,
                    "color" : pg.Color("white"),
                    "font_color" : pg.Color("black"),
                    "outline_color" : pg.Color("black"),
                    "outline_width" : 2,
                    "active_color" : pg.Color("blue"),
                    "font" : pg.font.Font(None, self.rect.height+4),
                    "clear_on_enter" : False,
                    "inactive_on_enter" : True,
                    "blink_speed": 500,
                    "delete_speed": 75}
        for kwarg in kwargs:
            if kwarg in defaults:
                defaults[kwarg] = kwargs[kwarg]
            else:
                raise KeyError("TextBox accepts no keyword {}.".format(kwarg))
        self.__dict__.update(defaults)

    def get_event(self,event, mouse_pos=None):
        ''' Call this on your event loop
        
            for event in pg.event.get():
                TextBox.get_event(event)
        '''
        if event.type == pg.KEYDOWN and self.active:
            if event.key in (pg.K_RETURN,pg.K_KP_ENTER):
                self.execute()
            elif event.key == pg.K_BACKSPACE:
                if self.buffer:
                    self.buffer.pop()
            elif event.unicode in self.accepted:
                self.buffer.append(event.unicode)
        elif event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
            if not mouse_pos:
                mouse_pos = pg.mouse.get_pos()
            self.active = self.rect.collidepoint(mouse_pos)

    def execute(self):
        if self.command:
            self.command(self.id,self.final)
        self.active = not self.inactive_on_enter
        if self.clear_on_enter:
            self.buffer = []
            
    def switch_blink(self):
        if pg.time.get_ticks()-self.blink_timer > self.blink_speed:
            self.blink = not self.blink
            self.blink_timer = pg.time.get_ticks()

    def update(self):
        '''
        Call once on your main game loop
        '''
        new = "".join(self.buffer)
        if new != self.final:
            self.final = new
            self.rendered = self.font.render(self.final, True, self.font_color)
            self.render_rect = self.rendered.get_rect(x=self.rect.x+2,
                                                      centery=self.rect.centery)
            if self.render_rect.width > self.rect.width-6:
                offset = self.render_rect.width-(self.rect.width-6)
                self.render_area = pg.Rect(offset,0,self.rect.width-6,
                                           self.render_rect.height)
            else:
                self.render_area = self.rendered.get_rect(topleft=(0,0))
        self.switch_blink()
        self.handle_held_backspace()
        
    def handle_held_backspace(self):
        if pg.time.get_ticks()-self.delete_timer > self.delete_speed:
            self.delete_timer = pg.time.get_ticks()
            keys = pg.key.get_pressed()
            if keys[pg.K_BACKSPACE]:
                if self.buffer:
                    self.buffer.pop()

    def draw(self,surface):
        '''
        Call once on your main game loop
        '''
        outline_color = self.active_color if self.active else self.outline_color
        outline = self.rect.inflate(self.outline_width*2,self.outline_width*2)
        surface.fill(outline_color,outline)
        surface.fill(self.color,self.rect)
        if self.rendered:
            surface.blit(self.rendered,self.render_rect,self.render_area)
        if self.blink and self.active:
            curse = self.render_area.copy()
            curse.topleft = self.render_rect.topleft
            surface.fill(self.font_color,(curse.right+1,curse.y,2,curse.h))
and here is an example of that class executed to have a username and password input boxes.
pg.init()
screen = pg.display.set_mode((600,400))
done = False

def name_on_enter(id, final):
    print('enter pressed, username is "{}"'.format(final))
    
def pass_on_enter(id, final):
    print('enter pressed, password is "{}"'.format(final))

username_settings = {
    "command" : name_on_enter,
    "inactive_on_enter" : False,
}
password_settings = {
    "command" : pass_on_enter,
    "inactive_on_enter" : False,
}

name_entry = TextBox(rect=(70,100,150,30), **username_settings)
pass_entry = TextBox(rect=(70,200,150,30), **password_settings)

while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True
        name_entry.get_event(event)
        pass_entry.get_event(event)
    name_entry.update()
    pass_entry.update()
    name_entry.draw(screen)
    pass_entry.draw(screen)
    pg.display.update()
It functions and acts like a text box. Each textbox can take focus. Each textbox can retrieve its content. When you hit enter, it executes the function assigned via the command and passes the content of the textbox at that time to that function. So the callback function will have an ID and content as args like:
Quote:
def print_on_enter(id, final):
    print('enter pressed, textbox contains "{}"'.format(final))

If you had more than one textbox requirement i would loop all the textbox objects to obtain their current values, and save them in a dict to be compared to the database. This would be handled by a callback function of a button. TO BE CONTINUED
Recommended Tutorials:
Reply


Messages In This Thread
User Interface - by metulburr - Nov-13-2017, 01:05 PM
RE: User Interface - by metulburr - Nov-03-2018, 04:08 PM
RE: User Interface - by metulburr - Nov-03-2018, 04:30 PM

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020