NinePatch

9-slice sprite scaling via Draw2D.draw_texture_region().

▶ Run in browser

Tags: 2d

Generates a test panel texture with distinct corners, edges, and centre, then renders NinePatchRect nodes at various sizes to demonstrate that:

  • Corners maintain their original pixel size

  • Edges stretch in one direction only

  • Centre fills the remaining space

Shows the engine’s in-memory texture API: the texture property on NinePatchRect accepts an RGBA uint8 numpy.ndarray directly, no file I/O required.

Source

  1"""NinePatch: 9-slice sprite scaling via Draw2D.draw_texture_region().
  2
  3# /// simvx
  4# web = { width = 900, height = 600 }
  5# ///
  6
  7Generates a test panel texture with distinct corners, edges, and centre,
  8then renders NinePatchRect nodes at various sizes to demonstrate that:
  9  - Corners maintain their original pixel size
 10  - Edges stretch in one direction only
 11  - Centre fills the remaining space
 12
 13Shows the engine's in-memory texture API: the ``texture`` property on
 14NinePatchRect accepts an RGBA uint8 ``numpy.ndarray`` directly, no file
 15I/O required.
 16"""
 17
 18
 19import numpy as np
 20
 21from simvx.core import NinePatchRect, Node2D, Text2D
 22from simvx.core.math.types import Vec2
 23from simvx.graphics import App
 24
 25
 26def _make_ninepatch_panel(size: int = 64, margin: int = 16) -> np.ndarray:
 27    """Generate a panel texture with visually distinct 9-slice regions.
 28
 29    Corners are bright red, edges are green (horizontal) / blue (vertical),
 30    and the centre is a dark grey. A 1px border outlines the whole texture.
 31    """
 32    img = np.zeros((size, size, 4), dtype=np.uint8)
 33
 34    for y in range(size):
 35        for x in range(size):
 36            in_left = x < margin
 37            in_right = x >= size - margin
 38            in_top = y < margin
 39            in_bottom = y >= size - margin
 40
 41            if (in_top or in_bottom) and (in_left or in_right):
 42                # Corners -- bright red/orange
 43                img[y, x] = [220, 80, 60, 255]
 44            elif in_top or in_bottom:
 45                # Horizontal edges -- green
 46                img[y, x] = [60, 180, 80, 255]
 47            elif in_left or in_right:
 48                # Vertical edges -- blue
 49                img[y, x] = [60, 100, 220, 255]
 50            else:
 51                # Centre -- dark grey
 52                img[y, x] = [80, 80, 90, 255]
 53
 54    # 1px border
 55    img[0, :] = [255, 255, 255, 255]
 56    img[-1, :] = [255, 255, 255, 255]
 57    img[:, 0] = [255, 255, 255, 255]
 58    img[:, -1] = [255, 255, 255, 255]
 59    return img
 60
 61
 62# ---------------------------------------------------------------------------
 63# Scene
 64# ---------------------------------------------------------------------------
 65
 66class NinePatchScene(Node2D):
 67    """Root scene displaying NinePatchRect nodes at different sizes."""
 68
 69    def on_ready(self):
 70        margin = 16
 71
 72        # Generate the panel pixels in memory and hand the ndarray directly to
 73        # NinePatchRect.texture: the renderer uploads it via
 74        # TextureManager.resolve() / load_from_array().
 75        panel = _make_ninepatch_panel(64, margin)
 76
 77        # Small -- just larger than the margins
 78        self.add_child(NinePatchRect(
 79            texture=panel,
 80            size=(80, 60),
 81            patch_margin_left=margin, patch_margin_right=margin,
 82            patch_margin_top=margin, patch_margin_bottom=margin,
 83            position=Vec2(40, 60), name="Small",
 84        ))
 85
 86        # Medium -- typical button/panel size
 87        self.add_child(NinePatchRect(
 88            texture=panel,
 89            size=(250, 100),
 90            patch_margin_left=margin, patch_margin_right=margin,
 91            patch_margin_top=margin, patch_margin_bottom=margin,
 92            position=Vec2(40, 160), name="Medium",
 93        ))
 94
 95        # Large -- wide dialogue box
 96        self.add_child(NinePatchRect(
 97            texture=panel,
 98            size=(500, 200),
 99            patch_margin_left=margin, patch_margin_right=margin,
100            patch_margin_top=margin, patch_margin_bottom=margin,
101            position=Vec2(40, 300), name="Large",
102        ))
103
104        # Tall narrow panel
105        self.add_child(NinePatchRect(
106            texture=panel,
107            size=(80, 250),
108            patch_margin_left=margin, patch_margin_right=margin,
109            patch_margin_top=margin, patch_margin_bottom=margin,
110            position=Vec2(580, 60), name="Tall",
111        ))
112
113        # Labels
114        self.add_child(
115            Text2D(text="NinePatchRect Demo -- 9-Slice Scaling", position=(10, 10), font_scale=1.5, name="Title"))
116        self.add_child(Text2D(text="Small (80x60)", position=(140, 75), name="LabelSmall"))
117        self.add_child(Text2D(text="Medium (250x100)", position=(300, 195), name="LabelMed"))
118        self.add_child(Text2D(text="Large (500x200)", position=(300, 385), name="LabelLarge"))
119        self.add_child(Text2D(text="Tall (80x250)", position=(580, 330), name="LabelTall"))
120
121
122if __name__ == "__main__":
123    app = App(width=900, height=600, title="SimVX NinePatchRect Demo")
124    app.run(NinePatchScene())