[PyGame] Create multiple buttons in a loop? - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: Game Development (https://python-forum.io/forum-11.html) +--- Thread: [PyGame] Create multiple buttons in a loop? (/thread-14116.html) |
Create multiple buttons in a loop? - mzmingle - Nov-15-2018 Hi, I'm trying to create a screen which will create buttons with each letter of the alphabet and then, when clicked, show a list of buttons with people with names beginning with that letter (from the database). I got the letters screen to run and show the different letters, but I haven't actually made them buttons yet - will this be possible without me creating a buttons class? What's the simplest way I can do this in? def letterButton(): runlettersButton = True screen.fill(WHITE) title = lettersTitle.render('What letter does your name begin with?', False, BLACK) screen.blit(title, [0, 0]) lettersDict = {'a': [5, 55], 'b': [90, 55], 'c': [175, 55], 'd': [260, 55], 'e': [345, 55], 'f': [430, 55], 'g': [515, 55], 'h': [5, 150], 'i': [90, 150], 'j': [175, 150], 'k': [260, 150], 'l': [345, 150], 'm': [430, 150], 'n': [515, 150], 'o': [5, 250], 'p': [90, 250], 'q': [175, 250], 'r': [260, 250], 's': [345, 250], 't': [430, 250], 'u': [515, 250], 'v': [5, 350], 'w': [90, 350], 'x': [175, 350], 'y': [260, 350], 'z': [345, 350]} lettersList = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] letterX = 5 letterY = 55 for i in range(0, len(lettersList)): currentLetter = lettersList[i] if i % 2 == 1: colour = YELLOW elif i % 2 == 0: colour = BLUE x = lettersDict[currentLetter][0] y = lettersDict[currentLetter][1] currentLetter = pygame.draw.rect(screen, colour, [x, y, 80, 95]) letterSurface = letters.render(lettersList[i], False, BLACK) screen.blit(letterSurface, currentLetter) RE: Create multiple buttons in a loop? - metulburr - Nov-15-2018 A button class is ideal. Once you have a button class setup for one button you just have to loop the letters to insert the text to each button, and position each button. If you want them in odd weird positions, your going to have to set a dictionary with each position. Its much easier to increment them the same. I modified our tutorial button class to include something like this. This code creates a button and sets the y axis (top) of each button. enumerate allows you to increment a number for each button which can be used as a multiplier for the y axis. lambda l=letter:print_on_press(l) passes the letter to the callback function so you can do something with each button uniquely. It requires lambda because you are passing something to the callback function. In addition it also requires letter to be passed into the lambda function as a local variable l . If you only did lambda:print_on_press(letter) then z would be the only thing that shows up because that is the last letter in the loop. letter would then be changed to what the next loop letter value is, instead of retaining it for each iteration. Quote:for position, letter in enumerate(string.ascii_lowercase): btn_height = 15 spacer = 5 top = position*btn_height + spacer b = Button(rect=(10,top,105,btn_height), command=lambda l=letter:print_on_press(l), text=letter, **settings) btns.append(b) Here is the full code including the button class import pygame as pg class Button(object): def __init__(self,rect,command,**kwargs): self.rect = pg.Rect(rect) self.command = command self.clicked = False self.hovered = False self.hover_text = None self.clicked_text = None self.process_kwargs(kwargs) self.render_text() def process_kwargs(self,kwargs): settings = { "color" : pg.Color('red'), "text" : None, "font" : None, #pg.font.Font(None,16), "call_on_release" : True, "hover_color" : None, "clicked_color" : None, "font_color" : pg.Color("white"), "hover_font_color" : None, "clicked_font_color": None, "click_sound" : None, "hover_sound" : None, 'border_color' : pg.Color('black'), 'border_hover_color': pg.Color('yellow'), 'disabled' : False, 'disabled_color' : pg.Color('grey'), 'radius' : 3, } for kwarg in kwargs: if kwarg in settings: settings[kwarg] = kwargs[kwarg] else: raise AttributeError("{} has no keyword: {}".format(self.__class__.__name__, kwarg)) self.__dict__.update(settings) def render_text(self): if self.text: if self.hover_font_color: color = self.hover_font_color self.hover_text = self.font.render(self.text,True,color) if self.clicked_font_color: color = self.clicked_font_color self.clicked_text = self.font.render(self.text,True,color) self.text = self.font.render(self.text,True,self.font_color) def get_event(self,event): if event.type == pg.MOUSEBUTTONDOWN and event.button == 1: self.on_click(event) elif event.type == pg.MOUSEBUTTONUP and event.button == 1: self.on_release(event) def on_click(self,event): if self.rect.collidepoint(event.pos): self.clicked = True if not self.call_on_release: self.function() def on_release(self,event): if self.clicked and self.call_on_release: #if user is still within button rect upon mouse release if self.rect.collidepoint(pg.mouse.get_pos()): self.command() self.clicked = False def check_hover(self): if self.rect.collidepoint(pg.mouse.get_pos()): if not self.hovered: self.hovered = True if self.hover_sound: self.hover_sound.play() else: self.hovered = False def draw(self,surface): color = self.color text = self.text border = self.border_color self.check_hover() if not self.disabled: if self.clicked and self.clicked_color: color = self.clicked_color if self.clicked_font_color: text = self.clicked_text elif self.hovered and self.hover_color: color = self.hover_color if self.hover_font_color: text = self.hover_text if self.hovered and not self.clicked: border = self.border_hover_color else: color = self.disabled_color #if not self.rounded: # surface.fill(border,self.rect) # surface.fill(color,self.rect.inflate(-4,-4)) #else: if self.radius: rad = self.radius else: rad = 0 self.round_rect(surface, self.rect , border, rad, 1, color) if self.text: text_rect = text.get_rect(center=self.rect.center) surface.blit(text,text_rect) def round_rect(self, surface, rect, color, rad=20, border=0, inside=(0,0,0,0)): rect = pg.Rect(rect) zeroed_rect = rect.copy() zeroed_rect.topleft = 0,0 image = pg.Surface(rect.size).convert_alpha() image.fill((0,0,0,0)) self._render_region(image, zeroed_rect, color, rad) if border: zeroed_rect.inflate_ip(-2*border, -2*border) self._render_region(image, zeroed_rect, inside, rad) surface.blit(image, rect) def _render_region(self, image, rect, color, rad): corners = rect.inflate(-2*rad, -2*rad) for attribute in ("topleft", "topright", "bottomleft", "bottomright"): pg.draw.circle(image, color, getattr(corners,attribute), rad) image.fill(color, rect.inflate(-2*rad,0)) image.fill(color, rect.inflate(0,-2*rad)) def update(self): #for completeness pass if __name__ == '__main__': #code pertaining to the main program not in the button module import string pg.init() screen = pg.display.set_mode((600,400)) screen_rect = screen.get_rect() done = False def print_on_press(letter): print('{} pressed'.format(letter)) settings = { "clicked_font_color" : (0,0,0), "hover_font_color" : (205,195, 100), 'font' : pg.font.Font(None,16), 'font_color' : (255,255,255), 'border_color' : (0,0,0), } btns = [] for position, letter in enumerate(string.ascii_lowercase): btn_height = 15 spacer = 5 top = position*btn_height + spacer b = Button(rect=(10,top,105,btn_height), command=lambda l=letter:print_on_press(l), text=letter, **settings) btns.append(b) while not done: mouse = pg.mouse.get_pos() for event in pg.event.get(): if event.type == pg.QUIT: done = True for btn in btns: btn.get_event(event) for btn in btns: btn.draw(screen) pg.display.update()and here is a modified version of the callback function comparing with a database that only has a . You would just need to modify it accordingly to how your database is. if __name__ == '__main__': #code pertaining to the main program not in the button module import string pg.init() screen = pg.display.set_mode((600,400)) screen_rect = screen.get_rect() done = False NAMES = { 'a':['Alex','Andy'] } def print_on_press(letter): print('{} pressed'.format(letter)) try: print(NAMES[letter]) except KeyError: print('There are no names in the database starting with {}'.format(letter)) settings = { "clicked_font_color" : (0,0,0), "hover_font_color" : (205,195, 100), 'font' : pg.font.Font(None,16), 'font_color' : (255,255,255), 'border_color' : (0,0,0), } btns = [] for position, letter in enumerate(string.ascii_lowercase): btn_height = 15 spacer = 5 top = position*btn_height + spacer b = Button(rect=(10,top,105,btn_height), command=lambda l=letter:print_on_press(l), text=letter, **settings) btns.append(b) while not done: mouse = pg.mouse.get_pos() for event in pg.event.get(): if event.type == pg.QUIT: done = True for btn in btns: btn.get_event(event) for btn in btns: btn.draw(screen) pg.display.update() RE: Create multiple buttons in a loop? - Windspar - Nov-15-2018 @mzmingle What I see so far. This would be easier done in tkinter, gtk, wxpython, or qt. pygame is not a widget toolkit. I wish there was a widget toolkit for SDL that pygame could inherit from. PyGizmo is my 10th attempt. It's a work in progress. example. import pygame import string class SimpleButton: def __init__(self, caption, position, callback, font, color, bg_color, hover_color): self.image = font.render(caption, 1, color) self.rect = self.image.get_rect() self.rect.topleft = position self.bg_color = bg_color self.hover_color = hover_color self.mouse_hovering = False self.callback = callback self.caption = caption def draw(self, surface): if self.mouse_hovering: surface.fill(self.hover_color, self.rect) else: surface.fill(self.bg_color, self.rect) surface.blit(self.image, self.rect) def on_mousemotion(self, event): self.mouse_hovering = self.rect.collidepoint(event.pos) def on_click(self, event): if self.mouse_hovering: self.callback(self) def button_callback(button): print(button.caption) def main(): pygame.init() pygame.display.set_caption('Simple Button Example') surface = pygame.display.set_mode((800, 600)) clock = pygame.time.Clock() buttons = [] button_layout = (pygame.font.Font(None, 24), pygame.Color('white'), pygame.Color('navy'), pygame.Color('dodgerblue')) for enum, letter in enumerate(string.ascii_lowercase): x = enum % 13 pos = x * 20 + 100, enum // 13 * 30 + 40 buttons.append(SimpleButton(letter, pos, button_callback, *button_layout)) running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.MOUSEMOTION: for button in buttons: button.on_mousemotion(event) elif event.type == pygame.MOUSEBUTTONUP: if event.button == 1: for button in buttons: button.on_click(event) surface.fill(pygame.Color('black')) for button in buttons: button.draw(surface) pygame.display.flip() clock.tick(20) pygame.quit() main() |