Skip to content

Commit

Permalink
Merge #443
Browse files Browse the repository at this point in the history
443: Text r=pathunstrom a=astronouth7303

Implements basic text rending. No wrapping or rich text supported.

Depends on #432, #436 

Closes #146 

Co-authored-by: Jamie Bliss <jamie@ivyleav.es>
  • Loading branch information
bors[bot] and AstraLuma authored Apr 26, 2020
2 parents 710f571 + e818ece commit 92a2a20
Show file tree
Hide file tree
Showing 31 changed files with 889 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ task:
fi
- >-
apt-get update || true;
apt-get install -qq -y libsdl2-2.0-0 libsdl2-mixer-2.0-0 libsdl2-image-2.0-0 libsdl2-gfx-1.0-0
apt-get install -qq -y libsdl2-2.0-0 libsdl2-mixer-2.0-0 libsdl2-image-2.0-0 libsdl2-gfx-1.0-0 libsdl2-ttf-2.0-0
script:
- command -v pypy3 >/dev/null && export PY=pypy3
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Additionally, on Linux only you must install the SDL library:

Debian, Ubuntu::

sudo apt install libsdl2-2.0-0 libsdl2-mixer-2.0-0 libsdl2-image-2.0-0 libsdl2-gfx-1.0-0
sudo apt install libsdl2-2.0-0 libsdl2-mixer-2.0-0 libsdl2-image-2.0-0 libsdl2-gfx-1.0-0 libsdl2-ttf-2.0-0

You should see a few libraries get put together in your terminal, and when
you have a prompt again, we're ready to go!
Expand Down
4 changes: 3 additions & 1 deletion ppb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
from ppb.sprites import Sprite
from ppb.systems import Image
from ppb.systems import Sound
from ppb.systems import Font
from ppb.systems import Text

__all__ = (
# Shortcuts
'Vector', 'BaseScene', 'BaseSprite', 'Circle', 'Image', 'Sprite',
'Square', 'Sound', 'Triangle', 'events',
'Square', 'Sound', 'Triangle', 'events', 'Font', 'Text',
# Local stuff
'run', 'make_engine',
)
Expand Down
2 changes: 2 additions & 0 deletions ppb/systems/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from ppb.systems.renderer import Renderer, Image
from ppb.systems.clocks import Updater
from ppb.systems.sound import SoundController, Sound
from ppb.systems.text import Font, Text

__all__ = (
'EventPoller', 'Renderer', 'Image', 'Updater', 'SoundController', 'Sound',
'Font', 'Text',
)
86 changes: 86 additions & 0 deletions ppb/systems/_sdl_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@
SDL_Quit, # https://wiki.libsdl.org/SDL_Quit
)

from sdl2.sdlmixer import (
# Errors, https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_7.html#SEC7
Mix_GetError, Mix_SetError,
)

from sdl2.sdlimage import (
IMG_GetError, IMG_SetError, # https://www.libsdl.org/projects/SDL_image/docs/SDL_image_43.html#SEC43
)

from sdl2.sdlttf import (
# https://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf_6.html#SEC6
TTF_GetError, TTF_SetError,
)


from ppb.systemslib import System


Expand Down Expand Up @@ -54,3 +69,74 @@ def __enter__(self):
def __exit__(self, exc_type, exc_val, exc_tb):
sdl_call(SDL_QuitSubSystem, self._sdl_subsystems)
super().__exit__(exc_type, exc_val, exc_tb)


class SdlMixerError(SdlError):
"""
SDL_mixer raised an error
"""


def mix_call(func, *pargs, _check_error=None, **kwargs):
"""
Wrapper for calling SDL_mixer functions for handling errors.
If _check_error is given, called with the return value to check for errors.
If _check_error returns truthy, an error occurred.
If _check_error is not given, it is assumed that a non-empty error from
Mix_GetError indicates error.
"""
Mix_SetError(b"")
rv = func(*pargs, **kwargs)
err = Mix_GetError()
if (_check_error(rv) if _check_error else err):
raise SdlMixerError(f"Error calling {func.__name__}: {err.decode('utf-8')}")
else:
return rv


class ImgError(SdlError):
pass


def img_call(func, *pargs, _check_error=None, **kwargs):
"""
Wrapper for calling SDL functions for handling errors.
If _check_error is given, called with the return value to check for errors.
If _check_error returns truthy, an error occurred.
If _check_error is not given, it is assumed that a non-empty error from
Mix_GetError indicates error.
"""
IMG_SetError(b"")
rv = func(*pargs, **kwargs)
err = IMG_GetError()
if (_check_error(rv) if _check_error else err):
raise SdlError(f"Error calling {func.__name__}: {err.decode('utf-8')}")
else:
return rv


class TtfError(SdlError):
pass


def ttf_call(func, *pargs, _check_error=None, **kwargs):
"""
Wrapper for calling SDL functions for handling errors.
If _check_error is given, called with the return value to check for errors.
If _check_error returns truthy, an error occurred.
If _check_error is not given, it is assumed that a non-empty error from
Mix_GetError indicates error.
"""
TTF_SetError(b"")
rv = func(*pargs, **kwargs)
err = TTF_GetError()
if (_check_error(rv) if _check_error else err):
raise SdlError(f"Error calling {func.__name__}: {err.decode('utf-8')}")
else:
return rv
33 changes: 8 additions & 25 deletions ppb/systems/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,20 @@
)

from sdl2.sdlimage import (
IMG_GetError, IMG_SetError, # https://www.libsdl.org/projects/SDL_image/docs/SDL_image_43.html#SEC43
IMG_Load_RW, # https://www.libsdl.org/projects/SDL_image/docs/SDL_image_12.html#SEC12
IMG_Init, IMG_Quit, # https://www.libsdl.org/projects/SDL_image/docs/SDL_image_6.html#SEC6
IMG_INIT_JPG, IMG_INIT_PNG, IMG_INIT_TIF,
)

from sdl2.sdlttf import (
TTF_Init, TTF_Quit, # https://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf_6.html#SEC6
)


import ppb.assetlib as assets
import ppb.events as events
import ppb.flags as flags
from ppb.systems._sdl_utils import SdlSubSystem, sdl_call, SdlError
from ppb.systems._sdl_utils import SdlSubSystem, sdl_call, img_call, ttf_call
from ppb.systems._utils import ObjectSideData

logger = logging.getLogger(__name__)
Expand All @@ -47,29 +51,6 @@
DEFAULT_RESOLUTION = 800, 600


class ImgError(SdlError):
pass


def img_call(func, *pargs, _check_error=None, **kwargs):
"""
Wrapper for calling SDL functions for handling errors.
If _check_error is given, called with the return value to check for errors.
If _check_error returns truthy, an error occurred.
If _check_error is not given, it is assumed that a non-empty error from
Mix_GetError indicates error.
"""
IMG_SetError(b"")
rv = func(*pargs, **kwargs)
err = IMG_GetError()
if (_check_error(rv) if _check_error else err):
raise SdlError(f"Error calling {func.__name__}: {err.decode('utf-8')}")
else:
return rv


# TODO: Move Image out of the renderer so sprites can type hint appropriately.
class Image(assets.Asset):
# Wraps POINTER(SDL_Surface)
Expand Down Expand Up @@ -145,6 +126,7 @@ def __init__(
def __enter__(self):
super().__enter__()
img_call(IMG_Init, IMG_INIT_JPG | IMG_INIT_PNG | IMG_INIT_TIF)
ttf_call(TTF_Init, _check_error=lambda rv: rv == -1)
self.window = ctypes.POINTER(SDL_Window)()
self.renderer = ctypes.POINTER(SDL_Renderer)()
sdl_call(
Expand All @@ -163,6 +145,7 @@ def __enter__(self):
def __exit__(self, *exc):
sdl_call(SDL_DestroyRenderer, self.renderer)
sdl_call(SDL_DestroyWindow, self.window)
ttf_call(TTF_Quit)
img_call(IMG_Quit)
super().__exit__(*exc)

Expand Down
55 changes: 14 additions & 41 deletions ppb/systems/sound.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
)

from sdl2.sdlmixer import (
# Errors, https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_7.html#SEC7
Mix_GetError, Mix_SetError,
# Support library loading https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_7.html#SEC7
Mix_Init, Mix_Quit, MIX_INIT_FLAC, MIX_INIT_MOD, MIX_INIT_MP3, MIX_INIT_OGG,
# Mixer init https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_7.html#SEC7
Expand All @@ -20,35 +18,10 @@
)

from ppb import assetlib
from ppb.systems._sdl_utils import SdlError, SdlSubSystem
from ppb.systems._sdl_utils import SdlSubSystem, mix_call, SdlMixerError
from ppb.utils import LoggingMixin

__all__ = ('SoundController', 'Sound', 'SdlMixerError')


class SdlMixerError(SdlError):
"""
SDL_mixer raised an error
"""


def _call(func, *pargs, _check_error=None, **kwargs):
"""
Wrapper for calling SDL_mixer functions for handling errors.
If _check_error is given, called with the return value to check for errors.
If _check_error returns truthy, an error occurred.
If _check_error is not given, it is assumed that a non-empty error from
Mix_GetError indicates error.
"""
Mix_SetError(b"")
rv = func(*pargs, **kwargs)
err = Mix_GetError()
if (_check_error(rv) if _check_error else err):
raise SdlMixerError(f"Error calling {func.__name__}: {err.decode('utf-8')}")
else:
return rv
__all__ = ('SoundController', 'Sound')


class Sound(assetlib.Asset):
Expand All @@ -57,7 +30,7 @@ class Sound(assetlib.Asset):
def background_parse(self, data):
file = rw_from_object(io.BytesIO(data))
# ^^^^ is a pure-python emulation, does not need cleanup.
return _call(
return mix_call(
Mix_LoadWAV_RW, file, False,
_check_error=lambda rv: not rv
)
Expand All @@ -78,11 +51,11 @@ def volume(self):
"""
The volume setting of this chunk, from 0.0 to 1.0
"""
return _call(Mix_VolumeChunk, self.load(), -1) / MIX_MAX_VOLUME
return mix_call(Mix_VolumeChunk, self.load(), -1) / MIX_MAX_VOLUME

@volume.setter
def volume(self, value):
_call(Mix_VolumeChunk, self.load(), int(value * MIX_MAX_VOLUME))
mix_call(Mix_VolumeChunk, self.load(), int(value * MIX_MAX_VOLUME))


@channel_finished
Expand All @@ -104,16 +77,16 @@ def allocated_channels(self):
Seems to default to 8.
"""
return _call(Mix_AllocateChannels, -1)
return mix_call(Mix_AllocateChannels, -1)

@allocated_channels.setter
def allocated_channels(self, value):
_call(Mix_AllocateChannels, value)
mix_call(Mix_AllocateChannels, value)

def __enter__(self):
super().__enter__()
_call(Mix_Init, MIX_INIT_FLAC | MIX_INIT_MOD | MIX_INIT_MP3 | MIX_INIT_OGG)
_call(
mix_call(Mix_Init, MIX_INIT_FLAC | MIX_INIT_MOD | MIX_INIT_MP3 | MIX_INIT_OGG)
mix_call(
Mix_OpenAudio,
44100, # Sample frequency, 44.1 kHz is CD quality
AUDIO_S16SYS, # Audio, 16-bit, system byte order. IDK is signed makes a difference
Expand All @@ -128,23 +101,23 @@ def __enter__(self):

# Register callback, keeping reference for later cleanup
self._finished_callback = channel_finished(self._on_channel_finished)
_call(Mix_ChannelFinished, self._finished_callback)
mix_call(Mix_ChannelFinished, self._finished_callback)

def __exit__(self, *exc):
# Unregister callback and release reference
_call(Mix_ChannelFinished, _filler_channel_finished)
mix_call(Mix_ChannelFinished, _filler_channel_finished)
self._finished_callback = None
# Cleanup SDL_mixer
_call(Mix_CloseAudio)
_call(Mix_Quit)
mix_call(Mix_CloseAudio)
mix_call(Mix_Quit)
super().__exit__(*exc)

def on_play_sound(self, event, signal):
sound = event.sound
chunk = event.sound.load()

try:
channel = _call(
channel = mix_call(
Mix_PlayChannel,
-1, # Auto-pick channel
chunk,
Expand Down
Loading

0 comments on commit 92a2a20

Please sign in to comment.