"""A static draw class for drawing things directly to the renderer."""
from __future__ import annotations
from ctypes import c_int16
from typing import TYPE_CHECKING, Optional, Callable
import cython
import sdl2, sdl2.sdlgfx, sdl2.ext
from . import Vector, Color, Font, Display, Math, InitError
if TYPE_CHECKING:
from .. import Surf, Camera
@cython.cclass
class DrawTask:
priority: cython.int = cython.declare(cython.int, visibility="public")
func: Callable = cython.declare(object, visibility="public")
def __init__(self, priority: cython.int, func: Callable):
self.priority = priority
self.func = func
# THIS IS A STATIC CLASS
[docs]class Draw:
"""Draws things to the renderer. Don't instantiate, instead use it as a static class."""
_queue: list[DrawTask] = []
def __init__(self) -> None:
raise InitError(self)
[docs] @classmethod
def clear(cls, background_color: Color = Color.white, border_color: Color = Color.black):
"""Clears the renderer and draws the background of the frame.
Args:
background_color (Color): The background color. Defaults to white.
border_color (Color): The border color. Defaults to black.
Shown when the aspect ratio of the game does not match the aspect ratio of the window.
"""
Display.renderer.clear(border_color.to_tuple())
Display.renderer.fill(
(0, 0, *Display.renderer.logical_size),
background_color.to_tuple(),
)
[docs] @classmethod
def push(cls, z_index: int, callback: Callable):
"""
Add a custom draw function to the frame queue.
Args:
z_index (int): The z_index to call at (lower z_indexes get called first).
callback (Callable): The function to call.
"""
cls._queue.append(DrawTask(z_index, callback))
[docs] @classmethod
def dump(cls):
"""Draws all queued items. Is called automatically at the end of every frame."""
if not cls._queue:
return
cls._queue.sort(key=lambda x: x.priority)
for task in cls._queue:
task.func()
cls._queue.clear()
[docs] @classmethod
def queue_point(cls, pos: Vector, color: Color = Color.cyan, z_index: int = Math.INF):
"""
Draw a point onto the renderer at the end of the frame.
Args:
pos (Vector): The position of the point.
color (Color): The color to use for the pixel. Defaults to Color.cyan.
z_index (int): Where to draw it in the drawing order. Defaults to Math.INF.
"""
cls.push(z_index, lambda: cls.point(pos, color))
[docs] @staticmethod
def point(pos: Vector, color: Color = Color.cyan):
"""
Draw a point onto the renderer immediately.
Args:
pos (Vector): The position of the point.
color (Color): The color to use for the pixel. Defaults to Color.cyan.
"""
sdl2.sdlgfx.pixelRGBA(Display.renderer.sdlrenderer, round(pos.x), round(pos.y), *color.to_tuple())
[docs] @classmethod
def queue_line(
cls, p1: Vector, p2: Vector, color: Color = Color.cyan, width: int | float = 1, z_index: int = Math.INF
):
"""
Draw a line onto the renderer at the end of the frame.
Args:
p1: The first point of the line.
p2: The second point of the line.
color: The color to use for the line. Defaults to Color.cyan.
width: The width of the line. Defaults to 1.
z_index: Where to draw it in the drawing order. Defaults to Math.INF.
"""
cls.push(z_index, lambda: cls.line(p1, p2, color, width))
[docs] @staticmethod
def line(p1: Vector, p2: Vector, color: Color = Color.cyan, width: int | float = 1):
"""
Draw a line onto the renderer immediately.
Args:
p1: The first point of the line.
p2: The second point of the line.
color: The color to use for the line. Defaults to Color.cyan.
width: The width of the line. Defaults to 1.
"""
sdl2.sdlgfx.thickLineRGBA(
Display.renderer.sdlrenderer, round(p1.x), round(p1.y), round(p2.x), round(p2.y), round(width), color.r,
color.g, color.b, color.a
)
[docs] @classmethod
def queue_rect(
cls,
center: Vector,
width: int | float,
height: int | float,
border: Color = Color.clear,
border_thickness: int | float = 1,
fill: Optional[Color] = None,
angle: float = 0,
z_index: int = Math.INF
):
"""
Draws a rectangle onto the renderer at the end of the frame.
Args:
center: The center of the rectangle.
width: The width of the rectangle.
height: The height of the rectangle.
border: The border color. Defaults to Color.clear.
border_thickness: The border thickness. Defaults to 1.
fill: The fill color. Defaults to None.
angle: The angle in degrees. Defaults to 0.
z_index: Where to draw it in the drawing order. Defaults to Math.INF.
"""
cls.push(z_index, lambda: cls.rect(center, width, height, border, border_thickness, fill, angle))
[docs] @staticmethod
def rect(
center: Vector,
width: int | float,
height: int | float,
border: Color = Color.clear,
border_thickness: int | float = 1,
fill: Optional[Color] = None,
angle: float = 0
):
"""
Draws a rectangle onto the renderer immediately.
Args:
center: The center of the rectangle.
width: The width of the rectangle.
height: The height of the rectangle.
border: The border color. Defaults to Color.clear.
border_thickness: The border thickness. Defaults to 1.
fill: The fill color. Defaults to None.
angle: The angle in degrees. Defaults to 0.
"""
x, y = width // 2, height // 2
verts = (Vector(-x, -y), Vector(x, -y), Vector(x, y), Vector(-x, y))
Draw.poly([center + v.rotate(angle) for v in verts], border, border_thickness, fill)
[docs] @classmethod
def queue_circle(
cls,
center: Vector,
radius: int = 4,
border: Color = Color.clear,
border_thickness: int | float = 1,
fill: Optional[Color] = None,
z_index: int = Math.INF
):
"""
Draws a circle onto the renderer at the end of the frame.
Args:
center: The center.
radius: The radius. Defaults to 4.
border: The border color. Defaults to green.
border_thickness: The border thickness. Defaults to 1.
fill: The fill color. Defaults to None.
z_index: Where to draw it in the drawing order. Defaults to Math.INF.
"""
cls.push(z_index, lambda: cls.circle(center, radius, border, border_thickness, fill))
[docs] @staticmethod
def circle(
center: Vector,
radius: int | float = 4,
border: Color = Color.clear,
border_thickness: int | float = 1,
fill: Optional[Color] = None
):
"""
Draws a circle onto the renderer immediately.
Args:
center: The center.
radius: The radius. Defaults to 4.
border: The border color. Defaults to green.
border_thickness: The border thickness. Defaults to 1.
fill: The fill color. Defaults to None.
"""
if fill:
sdl2.sdlgfx.filledCircleRGBA(
Display.renderer.sdlrenderer,
int(center.x),
int(center.y),
int(radius),
fill.r,
fill.g,
fill.b,
fill.a,
)
for i in range(int(border_thickness)):
sdl2.sdlgfx.aacircleRGBA(
Display.renderer.sdlrenderer,
int(center.x),
int(center.y),
int(radius) + i,
border.r,
border.g,
border.b,
border.a,
)
[docs] @classmethod
def queue_poly(
cls,
points: list[Vector],
border: Color = Color.clear,
border_thickness: int | float = 1,
fill: Optional[Color] = None,
z_index: int = Math.INF
):
"""
Draws a polygon onto the renderer at the end of the frame.
Args:
points: The list of points to draw.
border: The border color. Defaults to green.
border_thickness: The border thickness. Defaults to 1.
fill: The fill color. Defaults to None.
z_index: Where to draw it in the drawing order. Defaults to Math.INF.
"""
cls.push(z_index, lambda: cls.poly(points, border, border_thickness, fill))
[docs] @staticmethod
def poly(
points: list[Vector],
border: Color = Color.clear,
border_thickness: int | float = 1,
fill: Optional[Color] = None
):
"""
Draws a polygon onto the renderer immediately.
Args:
points: The list of points to draw.
border: The border color. Defaults to green.
border_thickness: The border thickness. Defaults to 1.
fill: The fill color. Defaults to None.
"""
x_coords, y_coords = zip(*(coord.tuple_int() for coord in points))
vx = (c_int16 * len(x_coords))(*x_coords)
vy = (c_int16 * len(y_coords))(*y_coords)
if fill:
sdl2.sdlgfx.filledPolygonRGBA(
Display.renderer.sdlrenderer,
vx,
vy,
len(points),
fill.r,
fill.g,
fill.b,
fill.a,
)
if border_thickness <= 0:
return
elif border_thickness == 1:
sdl2.sdlgfx.aapolygonRGBA(
Display.renderer.sdlrenderer,
vx,
vy,
len(points),
border.r,
border.g,
border.b,
border.a,
)
else:
for i in range(len(points)):
Draw.line(
Vector(
points[i].x,
points[i].y,
),
Vector(
points[(i + 1) % len(points)].x,
points[(i + 1) % len(points)].y,
),
border,
border_thickness,
)
[docs] @classmethod
def queue_text(
cls,
text: str,
font: Font,
pos: Vector = Vector(),
justify: str = "left",
align: Vector = Vector(),
width: int | float = 0,
z_index: int = Math.INF
):
"""
Draws some text onto the renderer at the end of the frame.
Args:
text: The text to draw.
font: The Font object to use.
pos: The position of the text. Defaults to Vector(0, 0).
justify: The justification of the text. (left, center, right). Defaults to "left".
align: The alignment of the text. Defaults to Vector(0, 0).
width: The maximum width of the text. Will automatically wrap the text. Defaults to -1.
z_index: Where to draw it in the drawing order. Defaults to Math.INF.
"""
cls.push(z_index, lambda: cls.text(text, font, pos, justify, align, width))
[docs] @staticmethod
def text(
text: str,
font: Font,
pos: Vector = Vector(),
justify: str = "left",
align: Vector = Vector(),
width: int | float = 0
):
"""
Draws some text onto the renderer immediately.
Args:
text: The text to draw.
font: The Font object to use.
pos: The position of the text. Defaults to Vector(0, 0).
justify: The justification of the text. (left, center, right). Defaults to "left".
align: The alignment of the text. Defaults to Vector(0, 0).
width: The maximum width of the text. Will automatically wrap the text. Defaults to -1.
"""
tx = sdl2.ext.Texture(Display.renderer, font.generate_surface(text, justify, width))
Display.update(tx, pos + (align - 1) * Vector(*tx.size) / 2)
[docs] @classmethod
def queue_texture(
cls,
texture: sdl2.ext.Texture,
pos: Vector = Vector(),
z_index: int = Math.INF,
scale: Vector = Vector(1, 1),
angle: float = 0,
):
"""
Draws an texture onto the renderer at the end of the frame.
Args:
texture: The texture to draw.
pos: The position of the texture. Defaults to Vector(0, 0).
z_index: Where to draw it in the drawing order. Defaults to Math.INF.
scale: The scale of the texture. Defaults to Vector(1, 1).
angle: The clockwise rotation of the texture. Defaults to 0.
"""
cls.push(z_index, lambda: cls.texture(texture, pos, scale, angle))
[docs] @staticmethod
def texture(
texture: sdl2.ext.Texture,
pos: Vector = Vector(),
scale: Vector = Vector(1, 1),
angle: float = 0,
):
"""
Draws an SDL Texture onto the renderer immediately.
Args:
texture: The texture to draw.
pos: The position to draw the texture at. Defaults to Vector().
scale: The scale of the texture. Defaults to Vector(1, 1).
angle: The clockwise rotation of the texture. Defaults to 0.
"""
Display.update(texture, pos, scale, angle)
[docs] @classmethod
def queue_surf(cls, surf: Surf, pos: Vector = Vector(), z_index: int = Math.INF, camera: Camera | None = None):
"""
Draws an surf onto the renderer at the end of the frame.
Args:
surf: The surf to draw.
pos: The position to draw the surf at. Defaults to Vector(0, 0).
z_index: The z-index of the surf. Defaults to 0.
camera: The camera to use. Set to None to ignore the camera. Defaults to None.
"""
cls.push(z_index, lambda: cls.surf(surf, pos, camera))
[docs] @staticmethod
def surf(surf: Surf, pos: Vector = Vector(), camera: Camera | None = None):
"""
Draws an surf onto the renderer immediately.
Args:
surf: The surf to draw.
pos: The position to draw the surf at. Defaults to Vector().
camera: The camera to use. Set to None to ignore the camera. Defaults to None.
"""
if not surf.surf:
return
if not surf.uptodate:
surf.generate_tx()
if camera is None:
pos -= surf.get_size() / 2
else:
pos = camera.transform(pos - (surf.get_size() / 2))
Draw.texture(surf.tx, pos, surf.scale, surf.rotation)