Blogroll

Forums

python

TextWidget – A simple text class for PyGame

Introduction

All right, this is just a little tutorial about working with text in pygame. Now, this isn’t the only way to work with text, there are many other methods to do this, in fact much of the time you’ll probably end up using images for interactive text. So this is mainly meant to serve as a informative guide to using the text features in PyGame, and how you might want to implement them.

The TextWidget object discussed in this tutorial can be used to make something that looks like this (but you’d probably want to use better looking colours):

The full source and necessary files for this tutorial can be downloaded here.

So, in order to make this easy to use and very reusable we’re going to create a class called TextWidget in a file called TextWidget.py. The top of the file is full of the standard python initialization:

[code lang=”python”]
#! /usr/bin/env python

import pygame

TEXT_WIDGET_CLICK = pygame.locals.USEREVENT + 1
[/code]

We import pygame and then we set a define TEXT_WIDGET_CLICK , which will be used later on as the event type when the TextWidget is clicked on.

The next thing to do is define the actual class:

[code lang=”python”]
class TextWidget(object):
[/code]

Now the astute among you will recognize that this is a “new-style” python class (i.e. it’s base class is the object class) rather then a classic class. I did this for two reasons:

I wanted to be able to control what happens when people set values in the class. So, for example, if you set the size of the font to be something, I wanted the display to automatically adjust to reflect the new size.

I haven’t really used new-style classes before so I thought I’d try them out.

Note: If you are unfamiliar with properties or new-style classes you might want to give this a read:

highlight – A boolean, whether the font is highlighted or not. (i.e. whether the mouse is over the TextWidget.

highlight_cursor – A boolean that controls whether or not to show a hand cursor when the TextWidget is in highlight mode. If the TextWidget is not supposed to be interactive you should set this to False.

There are also a few other important members of the class, but since they are not python properties I have listed them separately:

__hand – A hand cursor, which can optionally by displayed when the cursor hovers over the text.

dirty – A boolean, that lets the TextWidget know whether or not it needs to draw. If dirty = False it will not draw, if dirty = True it will draw.

bold_rect – pygame.Rect – A rectangle that is equal to the size of the highlighted text. It is used so that when we move from highlighted to non-highlighted text all of the text is erased. Otherwise if we just drew updated the normal rectangle, all of the highlighted text that was outside of it would not be redrawn.

rect Ã¢Â€Â“ pygame.Rect Ã¢Â€Â“ The Rectangle where the text will be drawn. Use this to position your text, but its height and width will always be controlled by the text and the font.

highlight_increase – Boolean whether or not the text should increase in size when the cursor is over it. If the text is “non-interactive” then this should be False.

tracking – Boolean whether or not the TextWidget is tracking a mouse click. Used so that we can tell whether or not a true “click” has occurred. A true click is one where both the mouse-down and mouse-up events occur on the TextWidget.

To hopefully make the functionality of the TextWidget a bit clearer here is some non-highlighted text and then some highlighted text:

The Code

Now we get to the heart of the actual python\PyGame code, where we actually create a working TextWidget object.

So we’ll start off at the top of the file and then work out way downwards in a linear manner.

The above code is where we create the hand cursor that we will use if the person turns on the highlight_cursor. We first define what the cursor will look like as explained in the PyGame cursor documentation.

The next step is to create all of our properties, now this code may seem a bit complicated, especially since it deals with functions that we have not created yet, but as I explain it, it could become clearer and clearer.

The above code creates the TextWidget.text property. The __get_text() function is used to get the text property which is represented by the __m_text variable, and the __set_text() function is the function that is called when you want to set the value of the text property.

What you will notice about the __set_text() function is that it first tests to see if the text that we are setting (text) is any different then the current text (__m_text) if it isn’t any different we don’t do anything.

However if it is different we set __m_text and we call update_surface(). update_surface() is a member function that I will explain in more detail later, but for now all you need to know is that it updates what the widget will display. So since we changed the text we want the widget to update and display the new text.

You can probably see why I wanted to use new-style classes and properties now.

The next property is the size property, which controls the size of the font. Now this is slightly different in that it calls the create_font() function rather then the update_surface() function that the text and the colour property did.

The reason for this is that the size property is used in the creation of the actual font object that is used to update the surface which is eventually drawn to the screen. As with the update_surface() function I will explain the create_font() function later. Properties that call create_font() do not have to call update_surface() because create_font() calls update_surface().

Next we have the highlight property, the highlight property is a boolean property that controls whether or not the TextWidget should display “highlighted” or not. When a TextWidget is highlighted the font will be displayed in bold, have its size increased by a specific amount, and (if enabled) the hand cursor will be displayed.

So now since the __get_highlight() function is the same as all of the others, I’ll just explain the __set_highlight() function.

First we check to see that the new value is different then the old value. We do this so that we don’t waste time with useless font creations or extra draws.

Then we check to see if the current state of the TextWidget is highlighted, because if it is, we know that it is about to go back to normal (because of the first if.) If that is the case we save the current rect as bold_rect so that the next time we draw we will want bold_rect to be drawn so that all of the previous highlighted text is overwritten.

Then we set the internal highlight value and call the update_cursor() function which will decide whether or not the cursor should be a hand or the default arrow.

Then we set the size of the font depending on whether or not the highlight state has been turned on. If it is we increase the size, and if it is not we decrease the size back down to normal.

Since increasing or decreasing the size will call create_font() we only need to call create_font() if self.highlight_increase is equal to zero, since in that case the value of the size property will not change and the font will not be created.

Lastly we have the highlight_cursor property, which is a boolean property that controls whether or not the highlight (hand) cursor should be displayed. If you have made it this far and have understood the rest of the properties then understanding this property shouldn’t be too difficult. Basically it simply checks to see if we should update the property, and if so it updates it and then calls update_cursor() just in case we need the cursor to update right now.

[code lang=”python”]
def __init__(self, text=””, colour=(0,0,0), size=32
, highlight_increase = 20, font_filename=None
, show_highlight_cursor = True):
“””Initialize the TextWidget
@param text = “” – string – The text for the text widget
@param colour = (0,0,0) – The colour of the text
@param size = 32 – number – The size of the text
@param highlight_increate – number – How large do we want the
text to grow when it is highlighted?
@param font_filename = None – string the patht to the font file
to use, None to use the default pygame font.
@param show_highlight_cursor = True – boolean – Whether or not to change
the cursor when the text is highlighted. The cursor will turn into
a hand if this is true.
“””

Now we actually start using the class the first thing we do is code the __init__() function in which we initialize all of our data members and properties, and then create the font at the end. This may result in the font being create twice sometimes but it will only happen once when the Widget is created, and it makes sure that the font is always created.

Here is the update_cursor() function, it’s pretty straight forward. First we check whether or not we are supposed to change the cursor at all. If we are then we check to see if we are highlighted or not. If we are highlighted then we use the hand cursor that we created above, and if we are not highlighted we use the built-in pygame arrow cursor.

Drawing and Fonts

Now we finally get to drawing and the font creation that we talked about earlier. The first thing to do is to create our font:

As you can see this function really is pretty simple, all we need to make sure that we have is a size for the font. Then we create the font using pygame.font.Font() We don’t care if self.font_filename is None since if it is None, then the default font will be loaded.

In update_surfacte() we first make sure that our font (__m_font) exists, since if it does not then there is no point in creating any surface. Then we set the font’s bold setting Font.set_bold() function and controlled by the highlight property.

Then we render the font onto a pygame.surface using the Font.render() function and our current text and colour. We also make sure that the font is anti-aliased by passing True as the second parameter.

Then we set self.Dirty to True to make sure that the next time through the draw loop the new surface gets displayed.

The last thing we do in the function is move our text into the correct location. If self.rect has been specified then we move the image into that position and set our rect, otherwise we initialize self.rect to be be surfaces rectangle.

self.rect must eventually be move into some position because Surface.get_rect() always returns a rectangle that starts at 0,0.

[code lang=”python”]
def draw(self, screen):
“””Draw yourself text widget
@param screen – pygame.Surface – The surface that we will draw to
@returns – pygame.rect – If drawing has occurred this is the
rect that we drew to. None if no drawing has occurred.”””

rect_return = None
if ((self.image) and (self.rect) and (self.dirty)):
if (self.bold_rect):
“””We may need to overwrite the bold text size
This gets rid of leftover text when moving from
bold text to non-bold text.
“””
rect_return = pygame.Rect(self.bold_rect)
“””Set to None, since we only need to do this
once.”””
self.bold_rect = None
else:
rect_return = self.rect
#Draw the text
screen.blit(self.image, self.rect)
#Dirty no more
self.dirty = False

return rect_return
[/code]

Finally we get to the draw code, and thankfully it’s pretty simple. The draw() function take a pygame.Surface as the only parameter, this is the surface that we will be drawing to, probably the main surface in your game or application. The draw function will also return a pygame.Rect object (or None) which represents the area of the surface that needs to be updated (or not).

The first thing we do in the function is make sure that we actually have something to draw. We check our rectangle (self.tect) and our surface (self.image) to make sure that everything has been initialized, and then we check self.dirty to see if we should draw anything.

Then we check to see if self.bold_rect is valid, if it is we return it as the rectangle to update and invalidate the bold_rect, since it only needed once, when moving from highlighted to non-highlighted). If self.bold_rect is not valid then we will return self.rect as the rectangle to update.

Then we blit our text to the screen (which is stored in self.image as a pygame.Surface object) and set self.dirty to False so that we do not needlessly draw in the future.

Events

If you remember way back at the beginning of this tutorial we create a define for a future event that would be fired when the TextWidget was clicked on:

First we have the on_mouse_button_down() function, this should be called by your main application during the event loop. If you come across a MOUSEBUTTONDOWN event, then you should pass it to all of your TextWidget objects, or at least the TextWidgets that you want to be interactive.

All this function does is check to see if the mouse button down event occurred within the TextWidgets rect, if it did, self.Tracking is set to True to let the TextWidget know that it is Ã¢Â€ÂœtrackingÃ¢Â€Â or listening for the MOUSEBUTTONUP event to complete the click.

Next we have the on_mouse_button_up function which called by your main application for all of your interactive TextWidget objects when the MOUSEBUTTONDOWN event is found.

This function is very simple, it first checks to see if we are currently tracking, if we are then we check to see if the event has occurred within the TextWidget’s rect. If it has we call self.one_mouse_click() and pass it the event.

Then we set self.tracking to False, since whatever happened we are no longer tracking. Now if you don’t fully understand what the self.tracking boolean does, it simply makes sure that the TextWidget behaves like a standard button, which means that it only registers MOUSEBUTTONUP events as a “click” if the MOUSEBUTTONDOWN also occurred within the buttons rectangle.

Finally in our event handling we have the on_mouse_click() function. By default this function will post a TEXT_WIDGET_CLICK event to the PyGame event queue which you can handle in whichever manner you like.

The event has the following attributes:

button – The button of the mouse that was used for the click. This is the same as the MOUSEBUTTONUP event’s button attribute.

pos (x,y) – The position of the mouse cursor when the click occurred. The is the same as the MOUSEBUTTONUP event’s pos attribute.

text_widget – The TextWidget object that was clicked on, so you know how to handle the event.

That is the default way that the on_mouse_click() function functions, in general it is probably easier to override the on_mouse_click() function in all of the TextWidget instances that you want to be interactive.

As an example, in your main application you might use the following for an Exit TextWidget:

Using the TextWidget

In order to make it easy to see how to use the TextWidget object (if it is not already) I whipped up a quick sample application, the short flash capture is to show you what it looks like without having to run it:

In general this code is pretty simple, it mostly involves creating our TextWidget objects, settings some of their properties and then adding them to a list. It really should be pretty self explanatory how this code works, one thing that I will point out is how the in the exit_text TextWidget the pygame.font.match_font() function is used to get the path to the font, and the on_mouse_click() function is overridden.

The first thing you should notice is that we respond to the ACTIVEEVENT, we do this so that when the PyGame window gets focus back (after loosing it) we redraw all of the text widgets. We force the TextWidgets to redraw by setting the dirty member to True.

Next we set the TextWidget.highlight property by checking for a collision with each TextWidget rect when we get the MOUSEMOTION event.

If you want your TextWidgets to be responsive to a click whenever you get a MOUSEBUTTONDOWN or MOUSEBUTTONUP event you can simply pass it forward to the on_mouse_button_down() and on_mouse_button_up() functions in each widget, the TextWidget will take care of the rest of the processing.

The draw loop is actually pretty simple. I use a list to keep track of rectangles that I want to update at the end of the draw using pygame.display.update() function.

In order to draw the TextWidget’s I simply loop through the list of them and call their draw() function. If the draw() function returns a rectangle, then I know that the TextWdiget has drawn so I add it to the list of rectangles that need to be updated.

If None is returned then I know that the TextWidget has not drawn and that it does not need to be drawn.

Conclusion

The full source and necessary files for this tutorial can be downloaded here.

So that’s it for this tutorial, as I said early on I’m not saying that this is the best or only way to work with text, I’m simply presenting this as a possible way to work with text, or as an example that might help get you started with your own text processing.

If you have any suggestions, problems, or notice anything that is incorrect, as always, add a comment!

Feel free to use this in whatever manner you want (it’s licensed under the LGPL), but if you do end up using it, I would appreciate it if you sent me an email and let me know.

17 thoughts on “TextWidget – A simple text class for PyGame”

I’m using your TextWidget class for a little project of mine- it’s proving very useful!

However, I’m getting a strange error. I’m developing on Ubuntu and the code seems to work fine- visually at least- it looks like it’s working. However after a moment or two (5->15 sec) it crashes and says:

I downloaded the new version and gave it a spin- unfortunately it seems to still throw the same error. The update delays the time before the error is thrown, but unfortunately on my system it still throws it. I’ve created an issue on the Google Code group with more detailed information.

I haven’t heard it being discussed but I’m sure otehrs have used it. I also don’t think it would be applicable to any kind of form. The reason I like it goes something like: it is much easier for a user to see ONLY the fields they made mistakes in, with ONLY the text explaining what went wrong and how to fix it, than a full page with additional error info where they have to try to find the fields that have problems. I haven’t given it too much thought though

Drupal is one of these effective software packages that helps people and businesses publish content on their websites.
One should keep in mind that communication lines for live support are kept open for
paid accounts, who are given top priority. The amount of space
required by a website should also be considered while choosing a web host.

The South? Of what? South Korea? South Africa?If you are going to the Southern US, they do speak English there, although a Northerner may have to get used to the accent.If you are going to Latin America or Spain, you might want to learn "Habla usted ingles?" (Pronounced "A-bla oo-STEAD EEN-gless) which means "Do you speak English". Be aware that much of Latin America doesn’t speak Spanish (Brazil, Guyana, Surinam, Haiti)References :

As much as I say that I am NEVER getting a dog, I would have brought her home too. How sad that someone just left her. I'm so glad that God sent her to you guys. :)And also, I read your Christmas post. It looks like you all had a wonderful day. I loved all the pictures. You all looked wonderful. And if you don't mind me saying so, I didn't realize your husband was such a hottie. ;)Happy New Year, friend!