1
+ from __future__ import annotations
2
+
3
+ import builtins
4
+ from typing import TypeVar , Type , Generic , Any , Callable
5
+
6
+ from pyglet .math import Vec2
7
+
8
+ import arcade
9
+ from arcade import SpriteList , Sprite , SpriteSolidColor , load_texture
10
+ from arcade .gui import UIManager , NinePatchTexture , UIInputText , UIWidget , UIBoxLayout
11
+ from arcade .types import RGBOrA255 , Color
12
+
13
+ GRID_REGULAR = arcade .color .GREEN .replace (a = 128 )
14
+ GRID_HIGHLIGHT = arcade .color .GREEN
15
+
16
+
17
+ TEX_GREY_PANEL_RAW = load_texture (":resources:gui_basic_assets/window/grey_panel.png" )
18
+
19
+ T = TypeVar ('T' )
20
+
21
+ def _tname (t : Any ) -> str :
22
+ if not isinstance (t , builtins .type ):
23
+ return t .__class__ .__name__
24
+ else :
25
+ return t .__name__
26
+
27
+
28
+ class TypedTextInput (UIInputText , Generic [T ]):
29
+ def __init__ (
30
+ self ,
31
+ parsed_type : Type [T ],
32
+ * ,
33
+ to_str : Callable [[T ], str ] = repr ,
34
+ from_str : Callable [[str ], T ] | None = None ,
35
+ x : float = 0 ,
36
+ y : float = 0 ,
37
+ width : float = 100 ,
38
+ height : float = 24 ,
39
+ text : str = "" ,
40
+ font_name = ("Arial" ,),
41
+ font_size : float = 12 ,
42
+ text_color : RGBOrA255 = (0 , 0 , 0 , 255 ),
43
+ error_color : RGBOrA255 = arcade .color .RED ,
44
+ multiline = False ,
45
+ size_hint = None ,
46
+ size_hint_min = None ,
47
+ size_hint_max = None ,
48
+ ** kwargs ,
49
+ ):
50
+ if not isinstance (type , builtins .type ):
51
+ raise TypeError (f"Expected a type, but got { type } " )
52
+ super ().__init__ (
53
+ x = x ,
54
+ y = y ,
55
+ width = width ,
56
+ height = height ,
57
+ text = text ,
58
+ font_name = font_name ,
59
+ font_size = font_size ,
60
+ text_color = text_color ,
61
+ multiline = multiline ,
62
+ caret_color = text_color ,
63
+ size_hint = size_hint ,
64
+ size_hint_min = size_hint_min ,
65
+ size_hint_max = size_hint_max ,
66
+ ** kwargs
67
+ )
68
+ self ._error_color = error_color
69
+ self ._parsed_type : Type [T ] = parsed_type
70
+ self ._to_str = to_str
71
+ self ._from_str = from_str or parsed_type
72
+ self ._parsed_value : T = self ._from_str (self .text )
73
+
74
+ @property
75
+ def value (self ) -> T :
76
+ return self ._parsed_value
77
+
78
+ @value .setter
79
+ def value (self , new_value : T ) -> None :
80
+ if not isinstance (new_value , self ._parsed_type ):
81
+ raise TypeError (
82
+ f"This { _tname (self )} is of inner type { _tname (self ._parsed_type )} "
83
+ f", but got { new_value !r} , a { _tname (new_value )} "
84
+ )
85
+ try :
86
+ self ._parsed_value = self ._from_str (new_value )
87
+ self .doc .text = self ._to_str (new_value )
88
+ self .color = self ._text_color
89
+ except Exception as e :
90
+ self .color = self ._error_color
91
+ raise e
92
+
93
+ self .trigger_full_render ()
94
+
95
+ @property
96
+ def color (self ) -> Color :
97
+ return self ._color
98
+
99
+ @color .setter
100
+ def color (self , new_color : RGBOrA255 ) -> None :
101
+ # lol, efficiency
102
+ validated = Color .from_iterable (new_color )
103
+ if self ._color == validated :
104
+ return
105
+
106
+ self .caret .color = validated
107
+ self .doc .set_style (
108
+ 0 , len (self .text ), dict (color = validated )
109
+ )
110
+ self .trigger_full_render ()
111
+
112
+ @property
113
+ def text (self ) -> str :
114
+ return self .doc .text
115
+
116
+ @text .setter
117
+ def text (self , new_text : str ) -> None :
118
+ try :
119
+ self .doc .text = new_text
120
+ validated : T = self ._from_str (new_text )
121
+ self ._parsed_value = validated
122
+ self .color = self ._text_color
123
+ except Exception as e :
124
+ self .color = self ._error_color
125
+ raise e
126
+
127
+
128
+
129
+ def draw_crosshair (
130
+ where : tuple [float , float ],
131
+ color = arcade .color .BLACK ,
132
+ radius : float = 20.0 ,
133
+ border_width : float = 1.0 ,
134
+ ) -> None :
135
+ x , y = where
136
+ arcade .draw .circle .draw_circle_outline (
137
+ x , y ,
138
+ radius ,
139
+ color = color ,
140
+ border_width = border_width
141
+ )
142
+ arcade .draw .draw_line (
143
+ x , y - radius , x , y + radius ,
144
+ color = color , line_width = border_width )
145
+
146
+ arcade .draw .draw_line (
147
+ x - radius , y , x + radius , y ,
148
+ color = color , line_width = border_width )
149
+
150
+
151
+ class MyGame (arcade .Window ):
152
+
153
+ def add_field_row (self , label_text : str , widget : UIWidget ) -> None :
154
+ children = (
155
+ arcade .gui .widgets .text .UITextArea (
156
+ text = label_text ,
157
+ width = 100 ,
158
+ height = 20 ,
159
+ color = arcade .color .BLACK ,
160
+ font_size = 12
161
+ ),
162
+ widget
163
+ )
164
+ row = UIBoxLayout (vertical = False , space_between = 10 , children = children )
165
+ self .rows .add (row )
166
+
167
+ def __init__ (
168
+ self ,
169
+ width : int = 1280 ,
170
+ height : int = 720 ,
171
+ grid_tile_px : int = 100
172
+ ):
173
+
174
+ super ().__init__ (width , height , "Collision Inspector" )
175
+ # why does this need a context again?
176
+ self .nine_patch = NinePatchTexture (
177
+ left = 5 , right = 5 , top = 5 , bottom = 5 , texture = TEX_GREY_PANEL_RAW )
178
+ self .ui = UIManager ()
179
+ self .spritelist : SpriteList [Sprite ] = arcade .SpriteList ()
180
+
181
+
182
+ textbox_template = dict (width = 40 , height = 20 , text_color = arcade .color .BLACK )
183
+ self .cursor_x_field = UIInputText (
184
+ text = "1.0" , ** textbox_template ).with_background (texture = self .nine_patch )
185
+
186
+ self .cursor_y_field = UIInputText (
187
+ text = "1.0" , ** textbox_template ).with_background (texture = self .nine_patch )
188
+
189
+ self .rows = UIBoxLayout (space_between = 20 ).with_background (color = arcade .color .GRAY )
190
+
191
+ self .grid_tile_px = grid_tile_px
192
+ self .ui .add (self .rows )
193
+
194
+ self .add_field_row ("Cursor Y" , self .cursor_y_field )
195
+ self .add_field_row ("Cursor X" , self .cursor_x_field )
196
+ self .ui .enable ()
197
+
198
+ # for y in range(8):
199
+ # for x in range(12):
200
+ # sprite = SpriteSolidColor(grid_tile_px, grid_tile_px, color=arcade.color.WHITE)
201
+ # sprite.position = x * 101 + 50, y * 101 + 50
202
+ # self.spritelist.append(sprite)
203
+ self .build_sprite_grid (8 , 12 , self .grid_tile_px , Vec2 (50 , 50 ))
204
+ self .background_color = arcade .color .DARK_GRAY
205
+ self .set_mouse_visible (False )
206
+ self .cursor = 0 , 0
207
+ self .from_mouse = True
208
+ self .on_widget = False
209
+
210
+ def build_sprite_grid (
211
+ self ,
212
+ columns : int ,
213
+ rows : int ,
214
+ grid_tile_px : int ,
215
+ offset : tuple [float , float ] = (0 , 0 )
216
+ ):
217
+ offset_x , offset_y = offset
218
+ self .spritelist .clear ()
219
+
220
+ for row in range (rows ):
221
+ x = offset_x + grid_tile_px * row
222
+ for column in range (columns ):
223
+ y = offset_y + grid_tile_px * column
224
+ sprite = SpriteSolidColor (grid_tile_px , grid_tile_px , color = arcade .color .WHITE )
225
+ sprite .position = x , y
226
+ self .spritelist .append (sprite )
227
+
228
+ def on_update (self , dt : float = 1 / 60 ):
229
+ self .cursor = Vec2 (self .mouse ["x" ], self .mouse ["y" ])
230
+
231
+ widgets = list (self .ui .get_widgets_at (self .cursor ))
232
+ on_widget = bool (len (widgets ))
233
+
234
+ if self .on_widget != on_widget :
235
+ self .set_mouse_visible (on_widget )
236
+ self .on_widget = on_widget
237
+
238
+ def on_draw (self ):
239
+ self .clear ()
240
+ # Reset color
241
+ for sprite in self .spritelist :
242
+ sprite .color = arcade .color .WHITE
243
+ # sprite.angle += 0.2
244
+
245
+ # Mark hits
246
+ hits = arcade .get_sprites_at_point (self .cursor , self .spritelist )
247
+ for hit in hits :
248
+ hit .color = arcade .color .BLUE
249
+
250
+ self .spritelist .draw ()
251
+ self .spritelist .draw_hit_boxes (color = arcade .color .GREEN )
252
+ if hits :
253
+ arcade .draw .rect .draw_rect_outline (rect = hits [0 ].rect , color = arcade .color .RED )
254
+ if not self .on_widget :
255
+ draw_crosshair (self .cursor )
256
+
257
+ self .ui .draw ()
258
+
259
+ MyGame ().run ()
0 commit comments