Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Text Sprite #673

Open
wants to merge 5 commits into
base: canon
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ppb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,14 @@
from ppb.systems import Sound
from ppb.systems import Font
from ppb.systems import Text
from ppb.textsprite import TextSprite
from ppb.utils import get_time

__all__ = (
# Shortcuts
'Vector', 'Scene', 'Circle', 'Image', 'Sprite', 'RectangleSprite',
'Square', 'Sound', 'Triangle', 'events', 'Font', 'Text', 'directions',
'Rectangle', 'Ellipse', 'Signal',
'Rectangle', 'Ellipse', 'Signal', 'TextSprite',
# Local stuff
'run', 'make_engine',
)
Expand Down
5 changes: 5 additions & 0 deletions ppb/systems/text.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import io
import threading
from typing import Tuple

from sdl2 import rw_from_object

Expand Down Expand Up @@ -135,3 +136,7 @@ def _background(self):
def free(self, object, _SDL_FreeSurface=SDL_FreeSurface):
# ^^^ is a way to keep required functions during interpreter cleanup
_SDL_FreeSurface(object) # Can't fail

def resolution(self) -> Tuple[int, int]:
asset = self.load()
return asset.contents.w, asset.contents.h
165 changes: 165 additions & 0 deletions ppb/textsprite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
from functools import wraps
from typing import Tuple, Union

from ppb_vector import Vector

from ppb.events import PreRender
from ppb.sprites import BaseSprite, RenderableMixin
from ppb.systems.text import Font, Text


def realized_read(func):

@wraps(func)
def wrapper(self: 'TextSprite'):
if not self.realized:
self.realize()
return func(self)

return wrapper


def realized_write(func):

@wraps(func)
def wrapper(self: 'TextSprite', value):
if self.realized:
return func(self, value)
self._commands.append((func.__name__, value))
return wrapper


class TextSprite(RenderableMixin, BaseSprite):
"""
A sprite for working with text.

TextSprite is more magical than most sprites because it tries to maintain
its aspect ratio to the rendered text associated with it. Otherwise, it
should fit the same API as a Renderable Rectangle Sprite.
"""
_text: str = ""
color: Tuple[int, int, int] = (255, 229, 0)
font: Font = None
realized: bool = False
_rendered_text: str = None
_half_height = 0
_half_width = 0
_image_width_pixels: int = None
_image_height_pixels: int = None
_target_height = 1
_target_width = None
_pixel_ratio = 0
rotation = 0
size = 1

def __init__(self, text="", **kwargs):
self._commands = []
super().__init__(**kwargs)
if self.font is None:
raise ValueError("A Font is required.")
self.text = text

def on_pre_render(self, event: PreRender, signal):
# Attempt to realize sprite.
self.realize()

def render(self):
if self._rendered_text != self.text:
assert self.font is not None
self.image = Text(self.text, font=self.font, color=self.color)
self._rendered_text = self.text
self.realized = False

def set_dimensions(self):
if self._target_height is not None:
self._pixel_ratio = self._target_height / self._image_height_pixels
elif self._target_width is not None:
self._pixel_ratio = self._target_width / self._image_width_pixels

self._half_width = self._image_width_pixels * self._pixel_ratio
self._half_height = self._image_height_pixels * self._pixel_ratio

def realize(self):
if self.realized:
return
self._image_width_pixels, self._image_height_pixels = self.image.resolution()
self.set_dimensions()

self.realized = True

for attr, value in self._commands:
setattr(self, attr, value)

@property
def text(self):
return self._text

@text.setter
def text(self, value: str):
self._text = value
self.render()

@property
@realized_read
def top(self):
return self.position.y + self._half_height

@top.setter
@realized_write
def top(self, value: Union[int, float]):
self.position = Vector(self.position.x, value - self._half_height)


@property
@realized_read
def bottom(self):
return self.position.y - self._half_height

@bottom.setter
@realized_write
def bottom(self, value: Union[int, float]):
self.position = Vector(self.position.x, value + self._half_height)

@property
@realized_read
def left(self):
return self.position.x - self._half_width

@left.setter
@realized_write
def left(self, value: Union[int, float]):
self.position = Vector(value + self._half_width, self.position.y)

@property
@realized_read
def right(self):
return self.position.x + self._half_width

@right.setter
@realized_write
def right(self, value: Union[int, float]):
self.position = Vector(value - self._half_width, self.position.y)

@property
@realized_read
def width(self):
return self._half_width * 2

@width.setter
@realized_write
def width(self, value):
self._target_width = value
self._target_height = None
self.set_dimensions()

@property
@realized_read
def height(self):
return self._half_height * 2

@height.setter
@realized_write
def height(self, value):
self._target_width = None
self._target_height = value
self.set_dimensions()
20 changes: 12 additions & 8 deletions viztests/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@ class TextScene(ppb.Scene):
elapsed = 0

def on_scene_started(self, event, signal):
last = None
for i, font in enumerate(('B', 'BI', 'C', 'L', 'LI', 'M', 'MI', 'R', 'RI', 'Th')):
self.add(ppb.Sprite(
image=ppb.Text(
"Hello, PPB!",
font=ppb.Font(f"resources/ubuntu_font/Ubuntu-{font}.ttf", size=72),
color=hsv2rgb(i / 10, 1.0, 75)
),
position=(0, i-4.5),
))
text = ppb.TextSprite(
text="Hello, PPB!",
font=ppb.Font(f"resources/ubuntu_font/Ubuntu-{font}.ttf", size=72),
color=hsv2rgb(i / 10, 1.0, 75),
position=(0, 6.5),
height=0.75
)
if last is not None:
text.top = last.bottom
last = text
self.add(text)

def on_update(self, event, signal):
self.elapsed += event.time_delta
Expand Down