Title: Graphical Cursor Class

Author: Frank Raiser (crashchaos at gmx.net) and Pete Shinners (pete at shinners.org)
Submission date: December 14, 2001

Description: This class replaces the standard cursor with a new version that gets its appearance from any surface (such as one returned by image.load()). The graphical cursor responds to mousemotion events just like the normal cursor.

Download: gfxcursor.py

pygame version required: Any
SDL version required: Any
Python version required: Any

Comments: The example script included with this class is pretty neat - it allows you to draw as in a paint program, over a multi-coloured background. Note that the graphical cursor (a simple green circle in this case) reverts to the standard pygame cursor whenever drawing is taking place. The idea of using a software-generated artificial cursor, rather than pygame's standard cursor, is an especially good one if you're considering using hardware acceleration (pygame.HWSURFACE) - the standard cursor may flicker or disappear altogether on a hardware surface. It would be pretty trivial to subclass gfxcursor to make an animated cursor - just create an "animate()" method, and call that from update(). Also note that the cursor's constructor function takes a simple surface as an argument, so the image could be generated with pygame.draw functions as it is here, loaded from an image file, or even created with pygame.font.

Messages: 0


"""
This is a nice little GfxCursor class that gives you arbitrary mousecursor
loadable from all SDL_image supported filetypes. 

Author: Raiser, Frank aka CrashChaos (crashchaos at gmx.net)
Author: Shinners, Pete aka ShredWheat
Version: 2001-12-15

Usage:
Instantiate the GfxCursor class. Either pass the correct parameters to
the constructor or use setCursor, setHotspot and enable lateron.

The blitting is pretty optimized, the testing code at the bottom of
this script does a pretty thorough test of all the drawing cases.
It enables and disables the cursor, as well as uses a changing background.

In your mainloop, the cursor.show() should be what you draw last
(unless you want objects on top of the cursor?). Then before drawing
anything, be sure to call the hide(). You can likely call hide() immediately
after the display.flip() or display.update().

The show() method also returns a list of rectangles of what needs to be
updated. You can also move the cursor with pygame.mouse.set_pos()


That's it. Have fun with your new funky cursors.
"""

import pygame

class GfxCursor:
    """
    Replaces the normal pygame cursor with any bitmap cursor
    """

    def __init__(self,surface,cursor=None,hotspot=(0,0)):
        """
        surface = Global surface to draw on
        cursor  = surface of cursor (needs to be specified when enabled!)
        hotspot = the hotspot for your cursor
        """
        self.surface = surface
        self.enabled = 0
        self.cursor  = None
        self.hotspot = hotspot
        self.bg      = None
        self.offset  = 0,0
        self.old_pos = 0,0
        
        if cursor:
            self.setCursor(cursor,hotspot)
            self.enable()

    def enable(self):
        """
        Enable the GfxCursor (disable normal pygame cursor)
        """
        if not self.cursor or self.enabled: return
        pygame.mouse.set_visible(0)
        self.enabled = 1

    def disable(self):
        """
        Disable the GfxCursor (enable normal pygame cursor)
        """
        if self.enabled:
            self.hide()
            pygame.mouse.set_visible(1)
            self.enabled = 0

    def setCursor(self,cursor,hotspot=(0,0)):
        """
        Set a new cursor surface
        """
        if not cursor: return
        self.cursor = cursor
        self.hide()
        self.show()
        self.offset = 0,0
        self.bg = pygame.Surface(self.cursor.get_size())
        pos = self.old_pos[0]-self.offset[0],self.old_pos[1]-self.offset[1]
        self.bg.blit(self.surface,(0,0),
            (pos[0],pos[1],self.cursor.get_width(),self.cursor.get_height()))

        self.offset = hotspot

    def setHotspot(self,pos):
        """
        Set a new hotspot for the cursor
        """
        self.hide()
        self.offset = pos

    def hide(self):
        """
        Hide the cursor (useful for redraws)
        """
        if self.bg and self.enabled:
            return self.surface.blit(self.bg,
                (self.old_pos[0]-self.offset[0],self.old_pos[1]-self.offset[1]))

    def show(self):
        """
        Show the cursor again
        """
        if self.bg and self.enabled:
            pos = self.old_pos[0]-self.offset[0],self.old_pos[1]-self.offset[1]
            self.bg.blit(self.surface,(0,0),
                (pos[0],pos[1],self.cursor.get_width(),self.cursor.get_height()))
            return self.surface.blit(self.cursor,pos)

    def update(self,event):
        """
        Update the cursor with a MOUSEMOTION event
        """
        self.old_pos = event.pos

if __name__ == '__main__': #test it out
    import pygame.draw
    pygame.init()
    screen = pygame.display.set_mode((400, 300))
    screen.fill((50, 50, 111), (0, 0, 400, 150))
    pygame.display.flip()
    pygame.display.set_caption('Test the GfxCursor (and paint)')
    
    image = pygame.Surface((20, 20))
    pygame.draw.circle(image, (50, 220, 100), (10, 10), 8, 0)
    pygame.draw.circle(image, (220, 200, 50), (10, 10), 8, 2)
    image.set_at((9, 9), (255,255,255))
    image.set_colorkey(0, pygame.RLEACCEL)
    
    magicbox = pygame.Rect(10, 10, 100, 90)
    magiccolor = 0
    
    cursor = GfxCursor(screen, image, (10, 10))
    finished = 0
    downpos = None
    while not finished:
        dirtyrects = []
        dirtyrects.extend([cursor.hide()])
        for e in pygame.event.get():
            if e.type in (pygame.QUIT, pygame.KEYDOWN):
                finished = 1
                break
            elif e.type == pygame.MOUSEBUTTONDOWN:
                cursor.disable()
                downpos = e.pos
            elif e.type == pygame.MOUSEBUTTONUP:
                cursor.enable()
                downpos = None
            elif downpos and e.type == pygame.MOUSEMOTION:
                r = pygame.draw.line(screen, (100,100,100), downpos, e.pos, 2)
                dirtyrects.append(r)
                downpos = e.pos
                cursor.update(e)
            elif not downpos and e.type == pygame.MOUSEMOTION:
                cursor.update(e)
        
        magiccolor = (magiccolor + 2) % 255
        r = screen.fill((0, 0, magiccolor), magicbox)
        dirtyrects.append(r)
        
        #here's how we sandwich the flip/update with cursor show and hide
        dirtyrects.extend([cursor.show()])
        pygame.display.update(dirtyrects)
        
        pygame.time.delay(5) #should be time.wait(5) with pygame-1.3 :]

Main - Repository - Submit - News

Feedback