Source code for simvx.graphics.draw2d_texture

"""Textured quad drawing for Draw2D.

Handles texture registration, textured quad emission, and 9-patch rendering.
"""

from __future__ import annotations

import math


[docs] class Draw2DTextureMixin: """Mixin providing textured quad drawing for Draw2D.""" # Textured quads: list of (texture_id, verts, indices) _textured_quads: list[tuple[int, list[tuple], list[int]]] = [] # CPU-side texture data for streaming: texture_id -> PNG bytes _texture_data: dict[int, bytes] = {} _next_cpu_texture_id: int = 1
[docs] @classmethod def register_texture(cls, png_data: bytes) -> int: """Register a CPU-side texture for streaming. Returns a texture ID. Use this in CPU-only mode (web editor) where there's no GPU TextureManager. The returned ID can be passed to ``draw_texture()`` / ``draw_texture_region()``. """ tex_id = cls._next_cpu_texture_id cls._next_cpu_texture_id += 1 cls._texture_data[tex_id] = png_data return tex_id
[docs] @classmethod def register_texture_with_id(cls, texture_id: int, png_data: bytes) -> None: """Register PNG data for a known texture ID (for streaming sync).""" cls._texture_data[texture_id] = png_data
[docs] @classmethod def draw_texture( cls, texture_id: int, x: float, y: float, w: float, h: float, colour: tuple[float, ...] | None = None, rotation: float = 0.0, ): """Draw a textured quad at (x, y) with size (w, h). Args: texture_id: Bindless texture index from TextureManager. x, y: Top-left position in screen pixels. w, h: Width and height in screen pixels. colour: Optional RGBA tint (0.0-1.0 floats). Default white. rotation: Rotation in radians around the quad center. """ cls.draw_texture_region(texture_id, x, y, w, h, 0.0, 0.0, 1.0, 1.0, colour, rotation)
[docs] @classmethod def draw_texture_region( cls, texture_id: int, x: float, y: float, w: float, h: float, u0: float = 0.0, v0: float = 0.0, u1: float = 1.0, v1: float = 1.0, colour: tuple[float, ...] | None = None, rotation: float = 0.0, ): """Draw a sub-rectangle of a texture as a quad. Args: texture_id: Bindless texture index. x, y: Top-left position in screen pixels. w, h: Width and height in screen pixels. u0, v0, u1, v1: UV coordinates for the source region (0-1). colour: Optional RGBA tint (0.0-1.0 floats). Default white. rotation: Rotation in radians around the quad center. """ c = colour if colour else (1.0, 1.0, 1.0, 1.0) if len(c) == 3: c = (*c, 1.0) # Corner positions if abs(rotation) < 1e-6: x0, y0 = x, y x1, y1 = x + w, y x2, y2 = x + w, y + h x3, y3 = x, y + h else: cx, cy = x + w * 0.5, y + h * 0.5 cos_r, sin_r = math.cos(rotation), math.sin(rotation) hw, hh = w * 0.5, h * 0.5 corners = [(-hw, -hh), (hw, -hh), (hw, hh), (-hw, hh)] rotated = [(cx + px * cos_r - py * sin_r, cy + px * sin_r + py * cos_r) for px, py in corners] x0, y0 = rotated[0] x1, y1 = rotated[1] x2, y2 = rotated[2] x3, y3 = rotated[3] if cls._has_xf: x0, y0 = cls._xf_pt(x0, y0) x1, y1 = cls._xf_pt(x1, y1) x2, y2 = cls._xf_pt(x2, y2) x3, y3 = cls._xf_pt(x3, y3) verts = [ (x0, y0, u0, v0, *c), (x1, y1, u1, v0, *c), (x2, y2, u1, v1, *c), (x3, y3, u0, v1, *c), ] indices = [0, 1, 2, 0, 2, 3] cls._textured_quads.append((texture_id, verts, indices))
[docs] @classmethod def draw_nine_patch( cls, texture_id: int, x: float, y: float, w: float, h: float, tex_w: float, tex_h: float, margin_left: float = 0.0, margin_right: float = 0.0, margin_top: float = 0.0, margin_bottom: float = 0.0, draw_centre: bool = True, colour: tuple[float, ...] | None = None, ): """Draw a 9-slice textured rectangle. Divides the source texture into 9 regions using the margin values. Corners remain fixed-size, edges stretch in one direction, and the centre fills the remaining space. This is the standard 9-patch / 9-slice technique for scalable UI panels, buttons, and speech bubbles. Args: texture_id: Bindless texture index from TextureManager. x, y: Top-left position in screen pixels. w, h: Display width and height in screen pixels. tex_w, tex_h: Source texture dimensions in pixels. margin_left, margin_right: Left/right border widths (pixels in source texture). margin_top, margin_bottom: Top/bottom border heights (pixels in source texture). draw_centre: Whether to draw the centre region (default True). colour: Optional RGBA tint (0.0-1.0 floats). Default white. """ if tex_w <= 0 or tex_h <= 0: return # Clamp margins to texture and display bounds ml = min(margin_left, tex_w, w) mr = min(margin_right, tex_w - ml, max(0, w - ml)) mt = min(margin_top, tex_h, h) mb = min(margin_bottom, tex_h - mt, max(0, h - mt)) # Source (texture-space) and destination (screen-space) column/row boundaries sx = [0.0, ml, tex_w - mr, tex_w] sy = [0.0, mt, tex_h - mb, tex_h] dx = [x, x + ml, x + w - mr, x + w] dy = [y, y + mt, y + h - mb, y + h] for row in range(3): for col in range(3): if row == 1 and col == 1 and not draw_centre: continue sw = sx[col + 1] - sx[col] sh = sy[row + 1] - sy[row] dw = dx[col + 1] - dx[col] dh = dy[row + 1] - dy[row] if sw <= 0 or sh <= 0 or dw <= 0 or dh <= 0: continue cls.draw_texture_region( texture_id, dx[col], dy[row], dw, dh, sx[col] / tex_w, sy[row] / tex_h, sx[col + 1] / tex_w, sy[row + 1] / tex_h, colour=colour, )