view pytouhou/lib/sdl.pyx @ 792:11bc22bad1bf default tip

python: Replace the image crate with png We weren’t using any of its features anyway, so the png crate is exactly what we need, without the many heavy dependencies of image. https://github.com/image-rs/image-png/pull/670 will eventually make it even faster to build.
author Link Mauve <linkmauve@linkmauve.fr>
date Sat, 17 Jan 2026 22:22:25 +0100
parents 7e940ebeb5fd
children
line wrap: on
line source

# -*- encoding: utf-8 -*-
##
## Copyright (C) 2013 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##

import pytouhou.lib.gui as gui

from pytouhou.utils.helpers import get_logger

logger = get_logger(__name__)


GL_CONTEXT_MAJOR_VERSION = SDL_GL_CONTEXT_MAJOR_VERSION
GL_CONTEXT_MINOR_VERSION = SDL_GL_CONTEXT_MINOR_VERSION
GL_CONTEXT_PROFILE_MASK = SDL_GL_CONTEXT_PROFILE_MASK
GL_DOUBLEBUFFER = SDL_GL_DOUBLEBUFFER
GL_RED_SIZE = SDL_GL_RED_SIZE
GL_GREEN_SIZE = SDL_GL_GREEN_SIZE
GL_BLUE_SIZE = SDL_GL_BLUE_SIZE
GL_DEPTH_SIZE = SDL_GL_DEPTH_SIZE

GL_CONTEXT_PROFILE_CORE = SDL_GL_CONTEXT_PROFILE_CORE
GL_CONTEXT_PROFILE_COMPATIBILITY = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY
GL_CONTEXT_PROFILE_ES = SDL_GL_CONTEXT_PROFILE_ES

WINDOWPOS_CENTERED = SDL_WINDOWPOS_CENTERED
WINDOW_OPENGL = SDL_WINDOW_OPENGL
WINDOW_RESIZABLE = SDL_WINDOW_RESIZABLE

SCANCODE_Z = SDL_SCANCODE_Z
SCANCODE_X = SDL_SCANCODE_X
SCANCODE_P = SDL_SCANCODE_P
SCANCODE_LSHIFT = SDL_SCANCODE_LSHIFT
SCANCODE_UP = SDL_SCANCODE_UP
SCANCODE_DOWN = SDL_SCANCODE_DOWN
SCANCODE_LEFT = SDL_SCANCODE_LEFT
SCANCODE_RIGHT = SDL_SCANCODE_RIGHT
SCANCODE_LCTRL = SDL_SCANCODE_LCTRL
SCANCODE_ESCAPE = SDL_SCANCODE_ESCAPE
SCANCODE_HOME = SDL_SCANCODE_HOME

WINDOWEVENT_RESIZED = SDL_WINDOWEVENT_RESIZED

KEYDOWN = SDL_KEYDOWN
QUIT = SDL_QUIT
WINDOWEVENT = SDL_WINDOWEVENT


class SDLError(gui.Error):
    def __init__(self):
        error = SDL_GetError()
        Exception.__init__(self, error.decode())


class SDL:
    def __init__(self, *, video=True):
        self.video = video

    def __enter__(self):
        global keyboard_state

        IF UNAME_SYSNAME == "Windows":
            SDL_SetMainReady()
        init(SDL_INIT_VIDEO if self.video else 0)
        ttf_init()

        keyboard_state = SDL_GetKeyboardState(NULL)

    def __exit__(self, *args):
        TTF_Quit()
        SDL_Quit()


cdef class Window(gui.Window):
    def __init__(self, str title, int x, int y, int w, int h, Uint32 flags):
        title_bytes = title.encode()
        self.window = SDL_CreateWindow(title_bytes, x, y, w, h, flags)
        if self.window == NULL:
            raise SDLError()

    def __dealloc__(self):
        if self.context != NULL:
            SDL_GL_DeleteContext(self.context)
        if self.window != NULL:
            SDL_DestroyWindow(self.window)

    cdef void create_gl_context(self) except *:
        self.context = SDL_GL_CreateContext(self.window)
        if self.context == NULL:
            raise SDLError()

    cdef void present(self) nogil:
        if self.renderer == NULL:
            SDL_GL_SwapWindow(self.window)
        else:
            SDL_RenderPresent(self.renderer)

    cdef void set_window_size(self, int width, int height) nogil:
        SDL_SetWindowSize(self.window, width, height)

    cdef void set_swap_interval(self, int interval) except *:
        if SDL_GL_SetSwapInterval(interval) < 0:
            raise SDLError()

    cdef list get_events(self):
        cdef SDL_Event event
        ret = []
        while SDL_PollEvent(&event):
            if event.type == SDL_KEYDOWN:
                scancode = event.key.keysym.scancode
                if scancode == SDL_SCANCODE_ESCAPE:
                    ret.append((gui.PAUSE, None))
                elif scancode in (SDL_SCANCODE_P, SDL_SCANCODE_HOME):
                    ret.append((gui.SCREENSHOT, None))
                elif scancode == SDL_SCANCODE_DOWN:
                    ret.append((gui.DOWN, None))
                elif scancode == SDL_SCANCODE_F11:
                    ret.append((gui.FULLSCREEN, None))
                elif scancode == SDL_SCANCODE_RETURN:
                    mod = event.key.keysym.mod
                    if mod & KMOD_ALT:
                        ret.append((gui.FULLSCREEN, None))
            elif event.type == SDL_QUIT:
                ret.append((gui.EXIT, None))
            elif event.type == SDL_WINDOWEVENT:
                if event.window.event == SDL_WINDOWEVENT_RESIZED:
                    ret.append((gui.RESIZE, (event.window.data1, event.window.data2)))
        return ret

    cdef int get_keystate(self) nogil:
        cdef int keystate = 0
        cdef const Uint8 *keys = keyboard_state
        if keys[SCANCODE_Z]:
            keystate |= 1
        if keys[SCANCODE_X]:
            keystate |= 2
        if keys[SCANCODE_LSHIFT]:
            keystate |= 4
        if keys[SCANCODE_UP]:
            keystate |= 16
        if keys[SCANCODE_DOWN]:
            keystate |= 32
        if keys[SCANCODE_LEFT]:
            keystate |= 64
        if keys[SCANCODE_RIGHT]:
            keystate |= 128
        if keys[SCANCODE_LCTRL]:
            keystate |= 256
        return keystate

    cdef void toggle_fullscreen(self) nogil:
        ret = SDL_SetWindowFullscreen(self.window, 0 if self.is_fullscreen else SDL_WINDOW_FULLSCREEN_DESKTOP)
        if ret == -1:
            with gil:
                raise SDLError()
        self.is_fullscreen = not self.is_fullscreen

    # The following functions are there for the pure SDL backend.
    cdef bint create_renderer(self, Uint32 flags) except True:
        self.renderer = SDL_CreateRenderer(self.window, -1, flags)
        if self.renderer == NULL:
            raise SDLError()

    cpdef bint render_clear(self) except True:
        ret = SDL_RenderClear(self.renderer)
        if ret == -1:
            raise SDLError()

    cpdef bint render_copy(self, Texture texture, Rect srcrect, Rect dstrect) except True:
        ret = SDL_RenderCopy(self.renderer, texture.texture, &srcrect.rect, &dstrect.rect)
        if ret == -1:
            raise SDLError()

    cpdef bint render_copy_ex(self, Texture texture, Rect srcrect, Rect dstrect, double angle, bint flip) except True:
        ret = SDL_RenderCopyEx(self.renderer, texture.texture, &srcrect.rect, &dstrect.rect, angle, NULL, flip)
        if ret == -1:
            raise SDLError()

    cpdef bint render_set_clip_rect(self, Rect rect) except True:
        ret = SDL_RenderSetClipRect(self.renderer, &rect.rect)
        if ret == -1:
            raise SDLError()

    cpdef bint render_set_viewport(self, Rect rect) except True:
        ret = SDL_RenderSetViewport(self.renderer, &rect.rect)
        if ret == -1:
            raise SDLError()

    cdef Texture create_texture_from_surface(self, Surface surface):
        texture = Texture()
        texture.texture = SDL_CreateTextureFromSurface(self.renderer, surface.surface)
        if texture.texture == NULL:
            raise SDLError()
        return texture


cdef class Texture:
    cpdef set_color_mod(self, Uint8 r, Uint8 g, Uint8 b):
        ret = SDL_SetTextureColorMod(self.texture, r, g, b)
        if ret == -1:
            raise SDLError()

    cpdef set_alpha_mod(self, Uint8 alpha):
        ret = SDL_SetTextureAlphaMod(self.texture, alpha)
        if ret == -1:
            raise SDLError()

    cpdef set_blend_mode(self, SDL_BlendMode blend_mode):
        ret = SDL_SetTextureBlendMode(self.texture, blend_mode)
        if ret == -1:
            raise SDLError()


cdef class Rect:
    def __init__(self, int x, int y, int w, int h):
        self.rect.x = x
        self.rect.y = y
        self.rect.w = w
        self.rect.h = h


cdef class Color:
    def __init__(self, Uint8 b, Uint8 g, Uint8 r, Uint8 a=255):
        self.color.r = r
        self.color.g = g
        self.color.b = b
        self.color.a = a


cdef class Surface:
    def __init__(self, bytes data=None):
        self.data = data

    def __dealloc__(self):
        if self.surface != NULL:
            SDL_FreeSurface(self.surface)

    property pixels:
        def __get__(self):
            return bytes(self.surface.pixels[:self.surface.w * self.surface.h * 4])

    cdef bint blit(self, Surface other) except True:
        if SDL_BlitSurface(other.surface, NULL, self.surface, NULL) < 0:
            raise SDLError()

    cdef void set_alpha(self, Surface alpha_surface) nogil:
        nb_pixels = self.surface.w * self.surface.h
        image = self.surface.pixels
        alpha = alpha_surface.surface.pixels

        for i in range(nb_pixels):
            # Only use the red value, assume the others are equal.
            image[3+4*i] = alpha[3*i]


cdef class Font:
    def __init__(self, str filename, int ptsize):
        path = filename.encode()
        self.font = TTF_OpenFont(path, ptsize)
        if self.font == NULL:
            raise SDLError()

    def __dealloc__(self):
        if self.font != NULL:
            TTF_CloseFont(self.font)

    cdef Surface render(self, unicode text):
        cdef SDL_Color white
        white = SDL_Color(255, 255, 255, 255)
        surface = Surface()
        string = text.encode('utf-8')
        surface.surface = TTF_RenderUTF8_Blended(self.font, string, white)
        if surface.surface == NULL:
            raise SDLError()
        return surface


cdef bint init(Uint32 flags) except True:
    if SDL_Init(flags) < 0:
        raise SDLError()


cdef bint ttf_init() except True:
    if TTF_Init() < 0:
        raise SDLError()


cdef bint gl_set_attribute(SDL_GLattr attr, int value) except True:
    if SDL_GL_SetAttribute(attr, value) < 0:
        raise SDLError()


cdef Surface create_rgba_surface(bytes data, int width, int height):
    surface = Surface(data)
    cdef char *pixels = <char*>data
    depth = 32
    pitch = width * 4
    fmt = SDL_PIXELFORMAT_ABGR8888
    surface.surface = SDL_CreateRGBSurfaceWithFormatFrom(pixels, width, height, depth, pitch, fmt)
    if surface.surface == NULL:
        raise SDLError()
    return surface


cdef Uint32 get_ticks() nogil:
    return SDL_GetTicks()


cdef void delay(Uint32 ms) nogil:
    SDL_Delay(ms)


cpdef bint show_simple_message_box(unicode message) except True:
    text = message.encode('UTF-8')
    ret = SDL_ShowSimpleMessageBox(1, 'PyTouhou', text, NULL)
    if ret == -1:
        raise SDLError()