"""
Global display class that allows for easy screen and window management.
"""
from __future__ import annotations
import sdl2, sdl2.sdlttf, sdl2.ext
from sdl2.sdlgfx import pixelRGBA, thickLineColor
from typing import TYPE_CHECKING
from . import Vector, Defaults
if TYPE_CHECKING:
from . import Color, Font
[docs]class DisplayProperties(type):
"""
Defines static property methods for Display.
Attention:
This is only a metaclass for the class below it, so you wont be able to access this class.
To use the property methods here, simply access them as you would any other ``Display`` property.
"""
@property
def window_size(cls) -> Vector:
"""
The pixel size of the physical window.
Warning:
Using this value to determine the placement of your game objects will
lead to unexpected results. Instead you should use
:func:`Display.res <rubato.utils.display.Display.res>`
"""
return Vector(*cls.window.size)
@window_size.setter
def window_size(cls, new: Vector):
cls.window.size = new.to_int().to_tuple()
@property
def res(cls) -> Vector:
"""
The pixel resolution of the game. This is the number of virtual
pixels on the window.
Example:
The window (:func:`Display.window_size <rubato.utils.display.Display.window_size>`)
could be rendered at 720p while the resolution is still at 1080p.
This means that you can place game objects
at 1000, 1000 and still have them draw despite the window not being
1000 pixels tall.
Warning:
While this value can be changed, it is recommended that you do not
change it as it will scale your entire project in ways you might
not expect.
"""
return Vector(*cls.renderer.logical_size)
@res.setter
def res(cls, new: Vector):
cls.renderer.logical_size = new.to_int().to_tuple()
@property
def window_pos(cls) -> Vector:
"""The current position of the window in terms of screen pixels"""
return Vector(*cls.window.position)
@window_pos.setter
def window_pos(cls, new: Vector):
cls.window.position = new.to_int().to_tuple()
@property
def window_name(cls):
return cls.window.title
@window_name.setter
def window_name(cls, new: str):
cls.window.title = new
@property
def display_ratio(cls) -> Vector:
"""The ratio of the renderer resolution to the window size. This is a read-only property.
Returns:
Vector: The ratio of the renderer resolution to the window size seperated by x and y.
"""
return cls.res / cls.window_size
[docs]class Display(metaclass=DisplayProperties):
"""
A static class that houses all of the display information
Attributes:
window (sdl2.Window): The pysdl2 window element.
renderer (sdl2.Renderer): The pysdl2 renderer element.
format (sdl2.PixelFormat): The pysdl2 pixel format element.
"""
window: sdl2.ext.Window = None
renderer: sdl2.ext.Renderer = None
format = sdl2.SDL_CreateRGBSurfaceWithFormat(0, 1, 1, 32, sdl2.SDL_PIXELFORMAT_RGBA8888).contents.format.contents
[docs] @classmethod
def set_window_icon(cls, path: str):
"""
Set the icon of the window.
Warning:
The image must be less than 128x128
Args:
path: The path to the icon.
"""
image = sdl2.ext.image.load_img(path)
if image.w > 128 or image.h > 128:
raise ValueError("The logo image must be less than 128x128")
sdl2.SDL_SetWindowIcon(
cls.window.window,
image,
)
[docs] @classmethod
def draw_point(cls, pos: Vector, color: Color):
"""
Draw a point onto the renderer.
Args:
pos: The position of the point.
color: The color to use for the pixel. Defaults to black.
"""
pixelRGBA(cls.renderer.renderer, pos.x, pos.y, *color.to_tuple())
[docs] @classmethod
def draw_line(cls, p1: Vector, p2: Vector, color: Color, width: int = 1):
"""
Draw a line onto the renderer.
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 black.
width: The width of the line. Defaults to 1.
"""
thickLineColor(cls.renderer, p1.x, p1.y, p2.x, p2.y, width, color.rgba32)
[docs] @classmethod
def draw_text(
cls,
text: str,
font: Font,
pos: Vector = Vector(),
justify: str = Defaults.text_defaults["justify"],
align: Vector = Defaults.text_defaults["align"],
width: int = Defaults.text_defaults["width"]
):
"""
Draws some text onto the renderer.
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(cls.renderer, font.generate_surface(text, justify, width))
cls.update(tx, pos + (align - 1) * Vector(*tx.size) / 2)
[docs] @classmethod
def update(cls, tx: sdl2.ext.Texture, pos: Vector):
"""
Update the current screen.
Args:
tx: The texture to draw on the screen.
pos: The position to draw the texture on.
"""
try:
w, h = tx.size
except AttributeError:
w, h = tx.contents.size
cls.renderer.copy(
tx,
None,
(
pos.x,
pos.y,
w,
h,
),
)
[docs] @classmethod
def clone_surface(cls, surface: sdl2.SDL_Surface) -> sdl2.SDL_Surface:
"""
Clones an SDL surface.
Args:
surface: The surface to clone.
Returns:
sdl2.SDL_Surface: The cloned surface.
"""
return sdl2.SDL_CreateRGBSurfaceWithFormatFrom(
surface.pixels,
surface.w,
surface.h,
32,
surface.pitch,
surface.format.contents.format,
).contents
@classmethod
@property
def top_left(cls) -> Vector:
"""The position of the top left of the window."""
return Vector(0, 0)
@classmethod
@property
def top_right(cls) -> Vector:
"""The position of the top right of the window."""
return Vector(cls.res.x, 0)
@classmethod
@property
def bottom_left(cls) -> Vector:
"""The position of the bottom left of the window."""
return Vector(0, cls.res.y)
@classmethod
@property
def bottom_right(cls) -> Vector:
"""The position of the bottom right of the window."""
return Vector(cls.res.x, cls.res.y)
@classmethod
@property
def top_center(cls) -> Vector:
"""The position of the top center of the window."""
return Vector(cls.res.x / 2, 0)
@classmethod
@property
def bottom_center(cls) -> Vector:
"""The position of the bottom center of the window."""
return Vector(cls.res.x / 2, cls.res.y)
@classmethod
@property
def center_left(cls) -> Vector:
"""The position of the center left of the window."""
return Vector(0, cls.res.y / 2)
@classmethod
@property
def center_right(cls) -> Vector:
"""The position of the center right of the window."""
return Vector(cls.res.x, cls.res.y / 2)
@classmethod
@property
def center(cls) -> Vector:
"""The position of the center of the window."""
return Vector(cls.res.x / 2, cls.res.y / 2)
@classmethod
@property
def top(cls) -> int:
"""The position of the top of the window."""
return 0
@classmethod
@property
def right(cls) -> int:
"""The position of the right of the window."""
return cls.res.x
@classmethod
@property
def left(cls) -> int:
"""The position of the left of the window."""
return 0
@classmethod
@property
def bottom(cls) -> int:
"""The position of the bottom of the window."""
return cls.res.y