Overlay¶
Overview¶
The overlay submodule for PyRend is the largest and most powerful component of the PyRend as it allows control over an invisible overlay on the screen with each creation of shape, text, image or video objects.
Features¶
Easily draw images, shapes or text to the screen
Render videos to the overlay
Rotate or move items
Manage opacity and paint order
Create position or rotation points
Assign children and parents to create a hierachy
Detect collisions and mouse clicks
Introduction¶
This submodule documentation assumes you already know how to create and understand a basic PyRend loop, presumably with a keybind to quit:
import pyrend
def my_update_loop():
if pyrend.input.is_key_down("ALT") and pyrend.input.is_key_down("Q"):
pyrend.close()
pyrend.start(my_update_loop)
Note
The PyRend overlay does not show on the taskbar and will be classified under something like Console Windows Host in the task manager. This is normally shown whether PyRend is running or not, meaning there is no way to tell the overlay is open.
However, if you package a PyRend script into an exe file using PyInstaller (or any alternative), the PyRend overlay will appear in the task manager under the name and icon of that executable.
Items¶
To draw elements onto the overlay in PyRend, you first need to create items. These items are Python objects that store properties defining how and where something appears on the screen. PyRend provides six types of overlay items: ShapeItem, TextItem, ImageItem, VideoItem, PointItem, and a base class BaseItem, which serves as the foundation for all others. This section of the documentation will guide you through how to create and use each item type, as well as explain shared methods and other overlay-related functionality.
Creating items¶
PyRend overlay objects are created by assigning them to variables, which allows you to modify them later. For example, the code below creates a ShapeItem and assigns it to myShape (Ignoring parameters for now).
myShape = pyrend.overlay.shape(...)
With that said, we can begin to create items using the following functions.
Shapes¶
Shapes in PyRend can be rectangular or elliptical. You can create them with pyrend.overlay.shape with default parameters like so:
myShape = pyrend.overlay.shape(
iscircle=False,
pos=(0, 0),
size=(100, 100),
color=(255, 255, 255),
opacity=1.0,
radius=0,
z_index=0
)
Text¶
myText = pyrend.overlay.write(
text,
pos=(0, 0),
size=(48),
color=(255, 255, 255),
font="Arial",
opacity=1,
z_index=0
)
Custom Fonts¶
PyRend allows the addition of custom fonts via TrueType Font files (.ttf). You can load them with the function:
myFont = pyrend.overlay.load_font(path)
path (str): The absoloute or relative path to a true type font file.
Example
Creating and using a custom font:
import pyrend
myFont = pyrend.overlay.load_font("assets/font.ttf")
myText = pyrend.overlay.write("This text uses a custom font", font=myFont)
def update():
pass
pyrend.start(update)
Images¶
myImage = pyrend.overlay.image(
path,
pos=(0, 0),
size=(100, 100),
opacity=1.0,
keep_aspect_ratio=True,
z_index=0
)
PyRend images can be in the format of JPEG, PNG, WEBP, GIF (Animation not supported) or SVG. More images types may work but aren’t fully supported The path parameter can be either a relative or absoloute path, and must include the file extension. The keep_aspect_ratio parameter determines whether to automatically resize the image to remain the file’s aspect ratio. It will take the size tuple and modify it to stay the same aspect ratio, preventing the image from coming out squashed or stretched.
Points¶
PyRend uses points as invisible items that can store just a location and rotation. They are not rendered onto the screen, making them useful for storing a position, adding multiple joints or hitboxes to existing items, and are nessasairy for creating specific roation points. (See custom rotation points)
myPoint = pyrend.overlay.point(base_pos)
base_pos (tuple): The (x, y) position to be stored with the point, in pixels. See: Pixel vs relative coordinates
Debug Mode¶
By enabling debug mode, all points will apear on top of everything as small red dots. This can make it easier to debug errors or visualise things. You can enable debug mode using:
pyrend.overlay.enable_debug(enable=True)
enable (bool): Whether to turn debug mode on or off.
Debug mode also enables the logging of some useful video information, such as displaying when a video has started loading asyncronously and when all frames finish loading.
Videos¶
Videos are one of the more complex features of PyRend, although they aren’t fully supported. When a video is created, it will immediately play (presuming the loop has been started). When the video ends, by default it will loop iteself, but also call a function that you can specify in the parameters. Therefore you could set a function to stop the video when it finishs. Videos also have a few unique methods.
Warning
Videos are not fully supported in PyRend. Videos can lag significantly and cause immense strain on CPU.
It is also not currently possible to sync audio to video.
Videos can be either pre-loaded or played immediately. Pre-loading a video will be less intense on the CPU, so you should use it when you don’t need to play a video immediately. You can pre-load a video like this:
videoPreload = pyrend.overlay.load_video(path)
path (str): Absoloute or relative path to a .mp4, .mov or .webm video file to be loaded under that variable.
That video will be loaded and can be later used to create a video item. You can create one like this:
myVideo = pyrend.overlay.video(
video_data_or_path
base_pos=(0, 0)
size=None
opacity=1.0
on_end=None
on_end_args=None
z_index=0
keep_aspect_ratio=True
smooth=False
)
Video methods¶
myVideo.seek(seconds)
Skip to a certain time length into the video. seconds must be an integer. Using 0 will restart the video.
myVideo.pause()
Pauses the video until played.
myVideo.play(seconds)
Resumes a paused video.
Example¶
This script creates a video that immidiately plays and closes when finished.
import pyrend
def finished():
pyrend.close()
myVideo = pyrend.overlay.video("clip.mp4", (300, 200), (500, 300), on_end=finished)
def update():
pass
pyrend.start(update)
Note
You can use the VideoItem’s proptery frames to get a list of all loaded frames in the video or frame_index to get the current frame.
Example: print(f”The video is on frame {myVideo.frame_index}”)
Demonstration¶
This script uses multiple of the items shown in this section to draw a square image to the screen, and add a rounded border and custom font title to it. Note that the things you can do with PyRend greatly expands with the ability to modify items, documented in the next section.
import pyrend
border = pyrend.overlay.shape(False, (70, 70), (430, 430), (255, 100, 0), radius=10)
image = pyrend.overlay.image("image.png", (100, 100), (370, 370), keep_aspect_ratio=False)
font = pyrend.overlay.load_font("font.ttf")
title = pyrend.overlay.write("Image", (120, 440), 30, font=font)
def my_update_loop():
if pyrend.input.is_key_down("ALT") and pyrend.input.is_key_down("Q"):
pyrend.close()
pyrend.start(my_update_loop)
Modifying Items¶
The possiblities of PyRend greatly expand when you can modify items during the loop. This means you can create games, interactable interfaces or other dynamic effects.
Editing¶
Editing an item is the easiest way to modify it. edit() is a method on all items that allows you to change the properties. As different items have some different properties, the edit method changes slightly between item types. This is an example of the edit method on a ShapeItem:
myShape.edit(
base_pos=None,
size=None,
color=None,
opacity=None,
radius=None,
rotation=None,
)
When you edit, you can specify any properties you would like to change. This documentation will not include the unique edit methods for every item, but it should be pretty self explanatory. To put it simply, this would be how you change the colour of a shape:
myShape.edit(color=(0, 255, 0))
Therefore you could also put things in your update function to change them dynamically. Therefore you could make something like this script which immidiately fades out an image when the script is run:
import pyrend
image = pyrend.overlay.image("image.png", (100, 100), (500, 500))
def my_update_loop(): # The update loop is called 60 times a second
if pyrend.input.is_key_down("ALT") and pyrend.input.is_key_down("Q"):
pyrend.close()
if image.opacity > 0:
image.edit(opacity=image.opacity - 0.01) # Opacity is a decimal value, therefore descreasing at
# 0.01pf at 60fps will take 1.6 seconds to disappear.
else:
pyrend.close() # Finish the program when the image has vanished
pyrend.start(my_update_loop)
Or this script which will create a circle in a random loction on the screen every time the space key is pressed. See: Detecting Keypresses
import pyrend
import random # Use random to create circles in random positions
circles = [] # Store a list of all existing circles
spacedown = False
def my_update_loop():
global spacedown
if pyrend.input.is_key_down("ALT") and pyrend.input.is_key_down("Q"):
pyrend.close()
if pyrend.input.is_key_down("SPACE") and not spacedown: # Only execute once after space is pressed
spacedown = True
circle = pyrend.overlay.shape( # Create a new circle at a random position
True,
(random.randint(0, 1950), random.randint(0, 1020)),
(150, 150)
)
circles.append(circle)
elif not pyrend.input.is_key_down("SPACE"): # Detect if the space key has already been down for a frame
spacedown = False
pyrend.start(my_update_loop)
Note
Technically you can also manually change the properties of an item simply by doing:
myItem.opacity += 0.2
However, it is only reccomended to use this for things like opacity, colour or size. Since rotation and position are cached to save memory, you must only change these with edit(), move() or rotate()
You can also move an item using:
myItem.move(x, y, change=False)
You can also alter the offset of an item using the similar method move_offset(), which works in the exact same way but for the items offset. To learn what an items offset is, see Position and Offset.
Visibility¶
You can choose whether a PyRend object will be visible using the hide() or show() methods.
Note
Hidden items will still have collision, and can be altered while hidden. Hiding an item will only skip it from being drawn onto the screen.
Usage:
myItem.hide() # Make the item invisible
myItem.show() # Make the item visible again
You can also delete an item using:
myItem.delete(soft=False)
The soft parameter is False by default and defines whether the item will be soft or hard deleted. Soft deleted items are still stored in memory but not drawn/processed. This means they are technically recoverable, and your script will be able to handle referencing it after deletion. Hard deletion immediatly erases the item from memory. Hard deleting an item makes it unable to be recovered completely. It is highly reccomended to use hard deletion for deletion of objects in mass, most likely items created in iteration (eg. particles, game enemies).
Note
Deleting a parent also deletes all of its children. To avoid this, use the free() method (inverted) on the Item prior to deletion.
Position and Offset¶
How position, offset, absolute position and base position work is one of the more complex aspects of PyRend to understand.
offset is a tuple property that remains at (0, 0) until changed using the move_offset(x, y) method. It adjusts the visual position of an item without modifying its base coordinates. An item’s offset is taken into account when drawn, so it allows changing where it appears visually without altering its logical or hierarchical location.
Offset is taken into account when detecting collisions, calculating pos, and responding to mouse hover or interaction events.
base_pos is a tuple that defines the original or intended position of the item in screen or world space. It is the starting point of the item, ignoring any visual adjustments (like offset). For items without a parent, base_pos represents their core anchor point. For child items, base_pos is set to the position of the parent at the time the child relationship is formed.
parent_offset is the relative distance between the item’s base_pos and the current position of its parent. It’s automatically calculated when an item becomes a child of another using become_child_of(). This offset is then transformed (rotated if needed) and added to the parent’s absolute position.
absolute position (as returned by get_absolute_pos()) is the calculated position of the item based on its base position, parent hierarchy, and rotation — but not including the visual offset.
pos is a read-only property that returns the final visual position of the item. It combines: - the absolute position of the item - plus the current offset
This is the coordinate used when drawing the item on screen, checking collisions, and tracking mouse interaction.
original_pos is a snapshot style attribute storing the original position when the item was created. Avoid modifying it and generally shouldn’t be used much.
Summary¶
Property |
Meaning |
How to Modify |
|---|---|---|
|
Original anchor position; not affected by offset |
Set via constructor or |
|
Visual adjustment; affects drawing and logic |
Use |
|
Relative positional offset set when parenting |
Automatically via |
|
Computed from |
Read-only (computed dynamically) |
|
Final visual position (absolute + offset) |
Read-only (computed dynamically) |
|
Cached absolute position snapshot (at creation). Not updated. |
Avoid modifying, but can be modified manually |
|
Rotation in degrees (cumulative via parents) |
Use |
Tip: Always use pos and get_absolute_pos() for dynamic logic like collision, drawing, or movement logic.
You can also use offset to align an item to the center of its position. You can easily do this with the build in method:
Item.align_center(x=True, y=True)
This will set it’s offset to half of its width and height, essentially centering it on the screen. You can specify whether just just center it on the x or y plane by setting the variables you want to align to True, which they both are by default. Note that when aligned to the center, all previous offset will be cleared. If you want previous offset to be saved after alignment, you will have to store it in a variable before alignment and apply it afterwards.
Heirachy¶
PyRend has a heirachy that uses children and parents. Children will mimic the parents rotation and position, taking into account parent offset. Children and parents can be assigned in one of two ways:
Item1.become_child_of(Item2)
# Or...
Item2.become_parent_of(Item1)
Parent offset is a property (parent_offset) that is set when you assign a parent to an item. It will store it’s position relative to it’s parent and it’s absoloute position will take this into account every frame. When an item is a child, it cannot be moved as normal, you will only be able to change it’s offset, or it’s parent offset depending on if you want to move it visually or physically. Note that you can only assign one parent to an object, and trying to set a new parent will replace it’s old parent.
To remove a parent or children, you can use free():
Item1.free(inverse=False)
inverse (bool): Whether to inverse the effects of the free() method. When False, the item will become free from any parents, and when True, will free all of it’s children.
Rotating¶
Items can also be rotated using either edit() or rotate. Items rotate around the top left of the item, regardless of offset. Therefore if you were to center an item, it would appear to be rotating around its center. Items can be rotated like this:
myItem.rotate(degrees, change=True)
Custom rotation points¶
Since rotation is based around parent rotation if in a heirachy, this can be taken advantage of to create custom rotation points using PointItems. You can set the PointItem as a parent of a shape and rotate it to have a custom rotation point. It’s rotation point will then be essentially set to it’s parent offset (See: Hierachy). This results in children being able to orbit parents.
This script will create a rectangle with a custom rotation point just left of it’s center, and spins it to demonstrate:
import pyrend
myShape = pyrend.overlay.shape(False, (300, 300), (500, 200), (255, 0, 0))
myRotationPoint = pyrend.overlay.point((425, 400))
myShape.become_child_of(myRotationPoint)
def my_update_loop():
if pyrend.input.is_key_down("ALT") and pyrend.input.is_key_down("Q"):
pyrend.close()
myRotationPoint.rotate(1)
pyrend.start(my_update_loop)
Or alternatively, define the roation point as a propery of the ShapeItem (IDE Autocorrect won’t support):
import pyrend
myShape = pyrend.overlay.shape(False, (300, 300), (500, 200), (255, 0, 0))
myShape.rotationPoint = pyrend.overlay.point((425, 400))
myShape.become_child_of(myShape.rotationPoint)
def my_update_loop():
if pyrend.input.is_key_down("ALT") and pyrend.input.is_key_down("Q"):
pyrend.close()
myShape.rotationPoint.rotate(1)
pyrend.start(my_update_loop)
Other¶
Properties¶
You can return the width and height of an item using:
w = Item.width() -> int
h = Item.height() -> int
You can return the width or height of the screen using the screen_size method:
w, h = pyrend.overlay.screen_size() -> tuple
Both variables in the tuple will be integers.
Collision¶
You can detect a collisions between two objects using:
Item1.get_collision(Item2) -> bool
This will detect if any point of Item1 intersects with Item2. Collision does not take into account rotation. Collision detection for ellipses or text will use a full square hitbox, rather than what is visible.
Note
Points calculate collision in the say way the mouse hover function does. This means it does account for rotation and is very reliable. This method of collision detection will automatically be used whenever you use the get collision method either from or using a PointItem.
Mouse¶
The PyRend overlay has a range of features relating to managing the mouse and interacting with items, however the possibilities of user input expands significantly using the input submodule. The following are mouse related features in the overlay submodule.
pyrend.overlay.get_mouse_pos() -> tuple
Returns a tuple with two integers, x and y, with the current position of the mouse.
You can detect if the mouse is hovering over an object using is_held()
Item.is_mouse_hovering() -> bool
This will return True if the mouse is currently touching the item, accounting for rotation. You can combine this with using input.is_key_down to detect if the mouse is also clicking, to then axecute an action. This could even be used for things such as drag and drop mechanics.
Using this, we could adapt the circle creation script from earlier to allow deleting certain circles. Now, you can press space to create a circle in a random position, and click it to remove it. Note that this code is not optimised.
import pyrend
import random # Use random to create circles in random positions
circles = [] # Store a list of all existing circles
spacedown = False
def my_update_loop():
global spacedown
if pyrend.input.is_key_down("LBUTTON"): # Detect if mouse is clicked
for c in circles:
if c.is_mouse_hovering(): # Detect if the mouse is hovering for each circle
c.delete() # Delete the circle
if pyrend.input.is_key_down("ALT") and pyrend.input.is_key_down("Q"):
pyrend.close()
if pyrend.input.is_key_down("SPACE") and not spacedown: # Only execute once after space is pressed
spacedown = True
circle = pyrend.overlay.shape( # Create a new circle at a random position
True,
(random.randint(0, 1950), random.randint(0, 1020)),
(150, 150)
)
circles.append(circle)
elif not pyrend.input.is_key_down("SPACE"): # Detect if the space key has already been down for a frame
spacedown = False
pyrend.start(my_update_loop)
If you want to learn more about keypresses, read the input submodule.
By default, PyRend overlays are clickthrough, meaning they are ghost-like in the sense that the mouse can click straight through it to whatever application is behind. You can change this with:
pyrend.overlay.set_clickthrough(enabled=False)
Now by setting this with False, the overlay will not be clickthrough, and you will no longer be able to click behind where an item is. You will still be able to click where there is no items. To get around this, you could create an invisible (0.0 opacity) ShapeItem covering the entire screen. Partially or even fully transparent items still detect clicks and cannot be clicked through if clickthrough is disabled.