Skip to content

Documentation

Steffen Noa (IT-PTR-CEN2-BDE23) edited this page Jul 21, 2026 · 10 revisions

Documentation

This document will talk about the structure of the package and the design choices that were made. This will include for example the class hierarchy, advanced features like conditional styling and common mistakes.

Structure

The package is divided into multiple subpackages:

pygame_utils_likeablejuniper
├─ core
│  └─ element.py
├─ element
│  ├─ label.py
│  ├─ button.py
│  ├─ container.py
│  ...
├─ style
│  ├─ style.py
│  └─ theme.py
└─ layout
   └─ layout.py

Although this may look complicated to import, you can still import everything you need as usual:

from pygame_utils_likeablejuniper import Label, Button, VerticalLayout

This works because the __init__.py file creates an importable alias for all user-relevant classes.

The rest of this document will focus on the subpackages and the classes contained therein, and also how to write your own subclasses and have them work with the existing classes.

Core

The only significant class contained in the core subpackage is GUIElement. This is the base class for all major classes in the subpackage element. This class just describes some drawable and updatable element that the user can see and/or interact with. Its public API looks like this:

class GUIElement:
    def __init__(self, rect: list[float], style: Style | None, base_style: Style)
    
    def update(self, events: Iterable[pygame.Event])

    def draw(self, screen: pygame.Surface)

    def update_style(self, style: Style)

    def add_conditional_style(self, condition: Callable[[Self], bool], style: Style)

GUIElement also has some protected and private methods. Of these, only the protected method is relevant for understanding how elements work:

def _rerender(self)

Constructor

Constructing a GUIElement requires only one thing: a rect. Rects are a concept from pygame. They are also just a generally convenient and short method of representing rectangle-like areas. Every GUIElement covers a rectangle-like area, there are no truly "round" elements (this is similar to popular rendering implementations, like HTML). The rect contains 4 floats, the elements x and y position and the elements width and height.

You may optionally pass a Style-object, but this will be covered in the corresponding chapter about styles.

update and draw

Every GUIElement has a hook for reacting to pygame events such as button presses or scrolling. Each element also has a method which draws the element to the given pygame surface. Since you create the elements, you are responsible for calling these methods. The methods need to be called every frame and always need to be passed the correct parameters or they will not work.

The update method requires an Iterable of pygame events, which you can obtain simply by calling pg.event.get() every frame and passing the result to the element.

The draw method requires a pygame surface, this should usually be your screen (or similar) variable obtained via pg.display.get_surface() or pg.display.set_mode(). If you know what you're doing, you may also specify any surface to split your application into multiple separately drawable surfaces.

update_style and add_conditional_style

Since these are two style methods, they will be covered in the style chapter.

_rerender

This method reloads all drawing-relevant data for this element. This is one of the core pieces of an element, as it vastly improves performance over simply calculating all data every frame. Unfortunately this also makes elements a bit more difficult to understand.

Whenever something about an element changes that influences the way it is displayed (for example it becomes hovered or the text is updated), this method is called. If you feel like you need to call this method manually for changes to style, text or anything visible to update, you're probably using the API wrong.

Since this method is only protected, it can and should be overriden by extending classes. Since most elements define properties which are unique to them, they also need extra render logic. To keep performance high, it is not recommended to calculate that data every frame, and instead calculate it during _rerender and then save it to instance variables.

If you are interested in writing your own elements, look at the corresponding documentation.

Clone this wiki locally