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