How do I detect if the mouse is hovering over a button? PyGame button class is not displaying the text or changing colour on hover
The condition
if self.x < x < self.width and self.y < y < self.height:
is wrong. You have to evaluate if x < self.x + self.width
and y < self.y + self.height
:
if self.x < x < self.x + self.width and self.y < y < self.y + self.height:
# [...]
Anyway, I recommend to pygame.Rect
/ collidepoint()
for the collision test:
button_rect = pygame.Rect(self.x, self.y, self.width, self.height)
if button_rect.collidepoint((x, y):
# [...]
button_rect
can be used further for drawing the rectangle:
pygame.draw.rect(screen, (211, 211, 211), button_rect)
The code can be simplified a lot by the use of an attribute rect
rather than the separate attributes x
, y
, width
, height
:
class Button:
def __init__(self, color, x, y, width, height, text, fontsize):
self.color = color
self.rect = pygame.Rect(x, y, width, height)
self.text = text
self.fontsize = fontsize
self.courier_button_font = pygame.font.SysFont("Courier", self.fontsize)
def draw(self, pos):
button_color = (211, 211, 211) if self.rect.collidepoint(pos) else self.color
pygame.draw.rect(screen, button_color, self.rect)
text_width, text_height = self.courier_button_font.size(self.text)
textpos = (self.rect.centerx - text_width // 2, self.rect.centery - text_height // 2)
buttontext = Fonts(courier_font, True, self.text, BLACK, textpos)
buttontext.draw(screen)
def isOver(self, pos):
return self.rect.collidepoint(pos)
Why won't my button change color when i hover over it pygame?
Your main problem is that you have a nested event loop inside your event loop:
while run: # outer loop
redrawWindow()
pygame.display.update()
for event in pygame.event.get():
pos = pygame.mouse.get_pos()
Exit = False
while not Exit: # inner loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
print(event)
pygame.quit()
quit()
When execution reaches this inner loop, neither redrawWindow()
or GrnBut.MouseOver(pos)
is ever called again.
Just get rid of it:
while run:
redrawWindow()
pygame.display.update()
for event in pygame.event.get():
pos = pygame.mouse.get_pos()
if event.type == pygame.QUIT:
print(event)
pygame.quit()
quit()
Your code can be improved by using some of pygame's features, such as the Sprite
and Rect
classes.
Here's an example of how you could create a more "pygamy" version of your Button
class that supports multiple, different buttons:
import pygame
pygame.init()
display_width = 1200
display_height = 600
# use python style variable names (lowercase)
screen = pygame.display.set_mode((display_width, display_height))
pygame.display.set_caption('Log In')
clock = pygame.time.Clock()
# load the font only once instead of every frame
font = pygame.font.SysFont('comicsans', 20)
# class name should be singular
class Button(pygame.sprite.Sprite):
# 1) no need to have 4 parameters for position and size, use pygame.Rect instead
# 2) let the Button itself handle which color it is
# 3) give a callback function to the button so it can handle the click itself
def __init__(self, color, color_hover, rect, callback, text='', outline=None):
super().__init__()
self.text = text
# a temporary Rect to store the size of the button
tmp_rect = pygame.Rect(0, 0, *rect.size)
# create two Surfaces here, one the normal state, and one for the hovering state
# we create the Surfaces here once, so we can simple blit them and dont have
# to render the text and outline again every frame
self.org = self._create_image(color, outline, text, tmp_rect)
self.hov = self._create_image(color_hover, outline, text, tmp_rect)
# in Sprites, the image attribute holds the Surface to be displayed...
self.image = self.org
# ...and the rect holds the Rect that defines it position
self.rect = rect
self.callback = callback
def _create_image(self, color, outline, text, rect):
# function to create the actual surface
# see how we can make use of Rect's virtual attributes like 'size'
img = pygame.Surface(rect.size)
if outline:
# here we can make good use of Rect's functions again
# first, fill the Surface in the outline color
# then fill a rectangular area in the actual color
# 'inflate' is used to 'shrink' the rect
img.fill(outline)
img.fill(color, rect.inflate(-4, -4))
else:
img.fill(color)
# render the text once here instead of every frame
if text != '':
text_surf = font.render(text, 1, pygame.Color('black'))
# again, see how easy it is to center stuff using Rect's attributes like 'center'
text_rect = text_surf.get_rect(center=rect.center)
img.blit(text_surf, text_rect)
return img
def update(self, events):
# here we handle all the logic of the Button
pos = pygame.mouse.get_pos()
hit = self.rect.collidepoint(pos)
# if the mouse in inside the Rect (again, see how the Rect class
# does all the calculation for use), use the 'hov' image instead of 'org'
self.image = self.hov if hit else self.org
for event in events:
# the Button checks for events itself.
# if this Button is clicked, it runs the callback function
if event.type == pygame.MOUSEBUTTONDOWN and hit:
self.callback(self)
run = True
# we store all Sprites in a Group, so we can easily
# call the 'update' and 'draw' functions of the Buttons
# in the main loop
sprites = pygame.sprite.Group()
sprites.add(Button(pygame.Color('green'),
pygame.Color('red'),
pygame.Rect(150, 200, 90, 100),
lambda b: print(f"Button '{b.text}' was clicked"),
'Press',
pygame.Color('black')))
sprites.add(Button(pygame.Color('dodgerblue'),
pygame.Color('lightgreen'),
pygame.Rect(300, 200, 90, 100),
lambda b: print(f"Click me again!"),
'Another'))
while run:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
quit()
# update all sprites
# it now doesn't matter if we have one or 200 Buttons
sprites.update(events)
# clear the screen
screen.fill(pygame.Color('white'))
# draw all sprites/Buttons
sprites.draw(screen)
pygame.display.update()
# limit framerate to 60 FPS
clock.tick(60)
new with pygame. My button doesnt work. why?
The condition 600 <= mouse[1] <= 470
is always False
, because 600 is never less than 470. It has to be 470 <= mouse[1] <= 600
:
if (300 <= mouse[0] <= 1150) and (600 <= mouse[1] <= 470):
if (300 <= mouse[0] <= 1150) and (470 <= mouse[1] <= 600):
However, I recommend using pygame.Rect
and collidepoint()
. See the example:
import pygame
pygame.init()
size=(1440,810)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("POCKER SFAHOT")
welcomeimage = 'welcome.png'
imgwelcome = pygame.image.load(welcomeimage).convert_alpha()
welcome = True
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
rect = pygame.Rect(300, 370, 850, 130)
if welcome and rect.collidepoint(event.pos):
welcome = False
if welcome:
screen.blit(imgwelcome, (0,0))
else:
screen.fill(0)
AmountOfPlayers()
pygame.display.flip()
pygame.quit()
exit()
Changing a class so that when the mouse hovers over it, it changes colour - Pygame
It's not enough to change the colour
attribute of the sprite, you also have to change the image
because pygame blits the image onto the screen when you call buttonsGroup.draw(screen)
.
I'd create the images in the __init__
method or pass them as arguments and then swap them by assigning the current image to self.image
if the mouse hovers over the button.
class Button(pg.sprite.Sprite):
def __init__(self, text, x, y, width, height, enabled):
super().__init__()
self.colour = buttonGray2
self.image = pg.Surface((width, height))
self.image.fill(buttonGray2)
self.image_normal = self.image # Store a reference to the original image.
# Create a separate hover image.
self.image_hover = pg.Surface((width, height))
self.image_hover.fill(buttonGray1)
self.rect = self.image.get_rect()
txt = buttonFont.render(text, True, textColour)
txtRect = txt.get_rect(center=self.rect.center)
self.image.blit(txt, txtRect)
self.image_hover.blit(txt, txtRect) # Blit the text onto the hover image.
self.rect.topleft = x, y
self.enabled = enabled
def isPressed(self, event):
if self.enabled == True:
if event.type == pg.MOUSEBUTTONDOWN:
# MOUSE... events have an event.pos attribute (the mouse position)
# which you can pass to the collidepoint method of the rect.
if self.rect.collidepoint(event.pos):
return True
return False
def HoveredOver(self, event):
if event.type == pg.MOUSEMOTION:
if self.rect.collidepoint(event.pos):
print("Hoveredover")
# Swap the image.
self.image = self.image_hover
else:
# Swap the image.
self.image = self.image_normal
How do i make a text box pop up when i hover over a button in pygame
Add another text attribute to your class. e.g.:
class Button():
def __init__(self, x, y, sx, sy, bcolour, fbcolour, font, fontsize, fcolour, text, tiptext):
# [...]
self.tiptextsurface = self.buttonf.render(tiptext, False, (0, 0, 0), (255, 255, 0))
Draw the text in a Button.showTip
method, when self.current
is set:
class Button():
# [...]
def showTip(self, display):
if self.current:
mouse_pos = pygame.mouse.get_pos()
display.blit(self.tiptextsurface, (mouse_pos[0]+16, mouse_pos[1]))
Minimal example based on your code:
I've simplified the Button
class, but it behaves exactly like the class in your question.
import pygame
pygame.init()
class Button():
def __init__(self, x, y, sx, sy, bcolour, fbcolour, font, fontsize, fcolour, text, tiptext):
self.rect = pygame.Rect(x, y, sx, sy)
self.bcolour = bcolour
self.fbcolour = fbcolour
self.fcolour = fcolour
self.fontsize = fontsize
self.current = False
self.buttonf = pygame.font.SysFont(font, fontsize)
self.textsurface = self.buttonf.render(text, False, self.fcolour)
self.tiptextsurface = self.buttonf.render(tiptext, False, (0, 0, 0), (255, 255, 0))
def showButton(self, display):
color = self.fbcolour if self.current else self.bcolour
pygame.draw.rect(display, color, self.rect)
display.blit(self.textsurface, self.textsurface.get_rect(center = self.rect.center))
def showTip(self, display):
if self.current:
mouse_pos = pygame.mouse.get_pos()
display.blit(self.tiptextsurface, (mouse_pos[0]+16, mouse_pos[1]))
def focusCheck(self, mousepos, mouseclick):
self.current = self.rect.collidepoint(mousepos)
return mouseclick if self.current else True
screen = pygame.display.set_mode((400, 600))
clock = pygame.time.Clock()
font = pygame.font.Font('freesansbold.ttf', 32)
shopButton = Button(125, 500, 150, 50, "black", "red", "arial", 20, "white", "Shop", "Enter the shop")
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if(event.type == pygame.QUIT):
done = True
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_click = True
mouse_pos = pygame.mouse.get_pos()
mouse_click = False
screen2button = shopButton.focusCheck(mouse_pos, mouse_click)
screen.fill((255, 255, 255))
shopButton.showButton(screen)
shopButton.showTip(screen)
pygame.display.update()
pygame.quit()
Related Topics
How to Make a 4D Plot with Matplotlib Using Arbitrary Data
Asyncio.Sleep() VS Time.Sleep()
Python Regular Expressions - How to Capture Multiple Groups from a Wildcard Expression
Pyplot Common Axes Labels for Subplots
How to Specify Your Own Distance Function Using Scikit-Learn K-Means Clustering
Basic Python Client Socket Example
How to Tell If Numpy Creates a View or a Copy
Get Previous Row's Value and Calculate New Column Pandas Python
Calculate Weighted Average Using a Pandas/Dataframe
Typeerror: Can Only Concatenate Str (Not "Float") to Str
How to Add a Custom Ca Root Certificate to the Ca Store Used by Pip in Windows
Sorting by a Custom List in Pandas
Algorithm to Find Which Number in a List Sum Up to a Certain Number
How to Import Members of All Modules Within a Package
Is There Any Difference Between "Foo Is None" and "Foo == None"