Ok, because you are not updating the screen every frame, it's necessary to save the bitmap of what will be "underneath" the pop-up, then use this to erase it later.
Python does not have a Surface.copy_area()
, but it's reasonably uncomplicated to make a Surface the size of the required area to copy, then use the third parameter to Surface.blit()
- which is the area of the source surface to copy:
def copyArea( screen, x, y, width, height ):
""" Copy a region of the given Surface, returning a new Surface """
# create surface to save the background into
copy_to = pygame.Surface( ( width, height ) )
# copy the background
copy_to.blit( screen, ( 0, 0 ), ( x, y, width, height ) )
return copy_to
The complicated part is rendering the textual elements into a pop-up box!
I don't want to go into detail, but basically you convert each line of text into a bitmap using the given font. Each of these has its own height and width. The code needs to sum the heights and find the maximum width to know the size of the target pop-up box. These can then be drawn in-turn to the target bitmap, applying a margin and line-spacing for each.
Then there's the hovering. The code needs to first detect the hover. This is fairly easy, it's just whether the mouse-cursor is within a rectangle. When it enters draw the popup, when it leaves un-draw it again.

Reference Code:
import pygame
# Window size
WINDOW_WIDTH = 500
WINDOW_HEIGHT = 500
WINDOW_SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
RED = ( 200, 0, 0 )
class ErasablePopup:
FOREGROUND_COLOUR = ( 86, 54, 8 ) # dark chocolate brown
BACKGROUND_COLOUR = ( 255, 228, 157 ) # sepia yellowish white
SIDE_MARGIN = 7 # size of corners and margin
LINE_SPACING = 1 # pixels between lines
def __init__( self, font, message ):
# First render the text to an image, line by line
self.image = self._textToBitmap( font, message )
self.rect = self.image.get_rect()
self.under = None # The underneath image when drawn
def drawAt( self, screen, position ):
""" Draw the popup at the given location, saving the underneath """
x, y = position
self.rect.topleft = ( x, y )
self.under = pygame.Surface( ( self.rect.width, self.rect.height ) ) # create surface to save
self.under.blit( screen, ( 0, 0 ), ( x, y, self.rect.width, self.rect.height ) ) # copy the background
screen.blit( self.image, self.rect ) # draw the rendered-text
def isShown( self ):
""" Is this popup drawn to the screen? """
return ( self.under != None ) # if we're on-screen there's an under
def unDraw( self, screen ):
""" Erase the pop-up by re-drawing the previous background """
# Only erase if we're drawn
if ( self.under != None ):
screen.blit( self.under, self.rect ) # restore the background
self.under = None # release the RAM
def _textToBitmap( self, font, message ):
""" Given a (possibly) multiline text message
convert it into a bitmap represenation with the
given font """
height_tally = 2 * self.SIDE_MARGIN # height-sum of message lines
maximum_width = 0 # maximum message width
message_lines = [] # the text-rendered image
message_rects = [] # where it's painted to
# cleanup messages, remove blank lines, et.al
for line in message.split( '\n' ): # for each line
if ( len( line ) == 0 ):
line = ' ' # make empty lines non-empty
# Make each line into a bitmap
message_line = font.render( line, True, self.FOREGROUND_COLOUR, self.BACKGROUND_COLOUR )
message_lines.append( message_line )
# do the statistics to determine the bounding-box
maximum_width = max( maximum_width, message_line.get_width() )
height_tally += self.LINE_SPACING + message_line.get_height()
# remember where to draw it later
position_rect = message_line.get_rect()
if ( len( message_rects ) == 0 ):
position_rect.move_ip( self.SIDE_MARGIN, self.SIDE_MARGIN )
else:
y_cursor = message_rects[-1].bottom + self.LINE_SPACING + 1
position_rect.move_ip( self.SIDE_MARGIN, y_cursor )
message_rects.append( position_rect )
# Render the underlying text-box
maximum_width += 2 * self.SIDE_MARGIN # add the margin
image = pygame.Surface( ( maximum_width, height_tally ), pygame.SRCALPHA ) # transparent bitmap
image.fill( self.BACKGROUND_COLOUR )
# draw the lines of text
for i in range( len ( message_lines ) ):
image.blit( message_lines[i], message_rects[i] )
return image
### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption("text player")
### Message Text For Displaying
popup_font = pygame.font.Font( None, 18 )
popups = [] # list of popup objects
hover_rects= [] # list of hover locations
popups.append( ErasablePopup( popup_font, "The Owl and the Pussy-Cat went to sea\n In a beautiful pea-green boat:\nThey took some honey,\n and plenty of money\nWrapped up in a five-pound note." ) )
popups.append( ErasablePopup( popup_font, "I smell a Wumpus!" ) )
hover_rects.append( pygame.Rect( 150, 150, 70, 70 ) ) # hot-spot1
hover_rects.append( pygame.Rect( 300, 300, 70, 70 ) ) # hot-spot2
### Background image
grassy_background = pygame.image.load( "big_grass_texture.jpg" ) # ref: https://jooinn.com/images/grass-texture-10.jpg
grassy_background = pygame.transform.smoothscale( grassy_background, ( WINDOW_WIDTH, WINDOW_HEIGHT ) )
### Main Loop
do_once = True
clock = pygame.time.Clock()
done = False
while not done:
# Paint the background, but just once
if ( do_once ):
do_once = False
window.blit( grassy_background, ( 0, 0 ) )
for i in range( len ( hover_rects ) ):
pygame.draw.rect( window, RED, hover_rects[i], 2 )
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
# Do the hover and popup
mouse_pos = pygame.mouse.get_pos()
for i in range( len ( hover_rects ) ):
if ( hover_rects[i].collidepoint( mouse_pos ) ): # mouse inside the rect?
if ( not popups[i].isShown() ):
popups[i].drawAt( window, mouse_pos )
else:
# not inside the rect
if ( popups[i].isShown() ):
popups[i].unDraw( window )
# Update the window, but not more than 60fps
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop(60)
pygame.quit()