ya2 · news · projects · code · about

test handle
[pmachines.git] / pmachines / editor / inspector.py
CommitLineData
ee65fee0 1from collections import namedtuple
2aeb9f68
FC
2from glob import glob
3from importlib import import_module
4from os.path import basename
5from inspect import isclass
ee65fee0
FC
6from panda3d.core import Texture, TextNode
7from direct.gui.OnscreenImage import OnscreenImage
2aeb9f68 8from direct.gui.DirectGui import DirectButton, DirectFrame, DirectEntry, DirectOptionMenu, OkDialog
ee65fee0
FC
9from direct.gui.DirectGuiGlobals import FLAT, NORMAL
10from direct.gui.OnscreenText import OnscreenText
11from direct.showbase.DirectObject import DirectObject
3133e30f 12from ya2.p3d.gfx import P3dGfxMgr
2aeb9f68
FC
13from pmachines.items.item import ItemStrategy, FixedStrategy, StillStrategy
14from pmachines.items.box import HitStrategy
15from pmachines.items.domino import DownStrategy, UpStrategy
ee65fee0
FC
16
17
18class Inspector(DirectObject):
19
2aeb9f68 20 def __init__(self, item, all_items):
ee65fee0
FC
21 super().__init__()
22 self.__item = item
2aeb9f68 23 self.__all_items = all_items
ee65fee0
FC
24 self._font = base.loader.load_font(
25 'assets/fonts/Hanken-Book.ttf')
26 self._font.clear()
27 self._font.set_pixels_per_unit(60)
28 self._font.set_minfilter(Texture.FTLinearMipmapLinear)
29 self._font.set_outline((0, 0, 0, 1), .8, .2)
30 self._common = {
31 'scale': .046,
32 'text_font': self._font,
33 'text_fg': (.9, .9, .9, 1),
34 'relief': FLAT,
35 'frameColor': (.4, .4, .4, .14),
36 'rolloverSound': loader.load_sfx(
37 'assets/audio/sfx/rollover.ogg'),
38 'clickSound': loader.load_sfx(
39 'assets/audio/sfx/click.ogg')}
2aeb9f68 40 w, h = .8, 1.04
ee65fee0
FC
41 self._frm = DirectFrame(frameColor=(.4, .4, .4, .06),
42 frameSize=(0, w, -h, 0),
43 parent=base.a2dTopRight,
44 pos=(-w, 0, 0))
45 self.__z = -.08
46 p = self.__item._np.get_pos()
47 r = self.__item._np.get_r()
48 s = self.__item._np.get_scale()[0]
49 m = self.__item._mass
50 restitution = self.__item._restitution
51 f = self.__item._friction
2aeb9f68
FC
52 _id = ''
53 if 'id' in self.__item.json:
54 _id = self.__item.json['id']
55 _strategy = ''
56 if 'strategy' in self.__item.json:
57 _strategy = self.__item.json['strategy']
58 _strategy_args = ''
59 if 'strategy_args' in self.__item.json:
60 _strategy_args = ' '.join(self.__item.json['strategy_args'])
ee65fee0
FC
61 t, pos_entry = self.__add_row(_('position'), f'{round(p.x, 3)}, {round(p.z, 3)}', self.on_edit_position)
62 t, rot_entry = self.__add_row(_('roll'), f'{round(r, 3)}', self.on_edit_roll)
63 t, scale_entry = self.__add_row(_('scale'), f'{round(s, 3)}', self.on_edit_scale)
64 t, mass_entry = self.__add_row(_('mass'), f'{round(m, 3)}', self.on_edit_mass)
65 t, restitution_entry = self.__add_row(_('restitution'), f'{round(restitution, 3)}', self.on_edit_restitution)
66 t, friction_entry = self.__add_row(_('friction'), f'{round(f, 3)}', self.on_edit_friction)
2aeb9f68
FC
67 t, id_entry = self.__add_row(_('id'), _id, self.on_edit_id)
68 t, strategy_entry = self.__add_row_option(_('strategy'), _strategy, self.on_edit_strategy)
69 t, strategy_args_entry = self.__add_row(_('strategy_args'), _strategy_args, self.on_edit_strategy_args)
70 fields = ['position', 'roll', 'scale', 'mass', 'restitution', 'friction', 'id', 'strategy', 'strategy_args']
ee65fee0 71 Entries = namedtuple('Entries', fields)
2aeb9f68 72 self.__entries = Entries(pos_entry, rot_entry, scale_entry, mass_entry, restitution_entry, friction_entry, id_entry, strategy_entry, strategy_args_entry)
ee65fee0
FC
73 def load_images_btn(path, col):
74 colors = {
75 'gray': [
76 (.6, .6, .6, 1), # ready
77 (1, 1, 1, 1), # press
78 (.8, .8, .8, 1), # rollover
79 (.4, .4, .4, .4)],
80 'green': [
81 (.1, .68, .1, 1),
82 (.1, 1, .1, 1),
83 (.1, .84, .1, 1),
84 (.4, .1, .1, .4)]}[col]
85 return [self.__load_img_btn(path, col) for col in colors]
86 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
87 DirectButton(
88 image=load_images_btn('exitRight', 'gray'), scale=.05,
89 pos=(.06, 1, -h + .06),
90 parent=self._frm, command=self.destroy, state=NORMAL, relief=FLAT,
91 frameColor=fcols[0],
92 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
93 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
94 self.accept('item-rototranslated', self.__on_item_rototranslated)
2aeb9f68
FC
95 DirectButton(
96 image=load_images_btn('trashcan', 'gray'), scale=.05,
97 pos=(.18, 1, -h + .06),
98 parent=self._frm, command=self.__delete_item, state=NORMAL, relief=FLAT,
99 frameColor=fcols[0],
100 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
101 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
ee65fee0
FC
102
103 def __add_row(self, label, text, callback):
104 tw = 10
105 t = OnscreenText(
106 label,
107 pos=(.03, self.__z), parent=self._frm,
108 font=self._common['text_font'],
109 scale=self._common['scale'],
110 fg=self._common['text_fg'],
111 wordwrap=20, align=TextNode.ALeft)
112 e = DirectEntry(
113 scale=self._common['scale'],
114 pos=(.30, 1, self.__z),
115 entryFont=self._font,
116 width=tw,
117 cursorKeys=True,
118 frameColor=self._common['frameColor'],
119 initialText=text,
120 parent=self._frm,
121 text_fg=self._common['text_fg'],
122 command=callback)
123 self.__z -= .1
124 return t, e
125
2aeb9f68
FC
126 def __add_row_option(self, label, text, callback):
127 item_modules = glob('pmachines/items/*.py')
128 item_modules = [basename(i)[:-3] for i in item_modules]
129 new_items = ['']
130 for item_module in item_modules:
131 mod_name = 'pmachines.items.' + item_module
132 for member in import_module(mod_name).__dict__.values():
133 if isclass(member) and issubclass(member, ItemStrategy) and \
134 member != ItemStrategy:
135 new_items = list(set(new_items + [member.__name__]))
136 t = OnscreenText(
137 label,
138 pos=(.03, self.__z), parent=self._frm,
139 font=self._common['text_font'],
140 scale=self._common['scale'],
141 fg=self._common['text_fg'],
142 wordwrap=20, align=TextNode.ALeft)
143 e = DirectOptionMenu(
144 scale=self._common['scale'],
145 initialitem=text,
146 pos=(.30, 1, self.__z),
147 items=new_items,
148 parent=self._frm,
149 command=callback,
150 state=NORMAL,
151 relief=FLAT,
152 item_relief=FLAT,
153 frameColor=self._common['frameColor'],
154 item_frameColor=self._common['frameColor'],
155 popupMenu_frameColor=self._common['frameColor'],
156 popupMarker_frameColor=self._common['frameColor'],
157 text_font=self._font,
158 text_fg=self._common['text_fg'],
159 highlightColor=(.9, .9, .9, .9),
160 item_text_font=self._font,
161 item_text_fg=self._common['text_fg'],
162 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
163 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
164 self.__z -= .1
165 return t, e
166
ee65fee0
FC
167 def __load_img_btn(self, path, col):
168 img = OnscreenImage('assets/images/buttons/%s.dds' % path)
169 img.set_transparency(True)
170 img.set_color(col)
171 img.detach_node()
172 return img
173
174 def __on_item_rototranslated(self, np):
175 pos = np.get_pos()
176 r = np.get_r()
177 self.__entries.position.set('%s %s' % (str(round(pos.x, 3)), str(round(pos.z, 3))))
178 self.__entries.roll.set('%s' % str(round(r, 3)))
2aeb9f68
FC
179 self.__item.json['position'] = list(pos)
180 self.__item.json['roll'] = round(r, 3)
181
182 def __delete_item(self):
183 messenger.send('editor-inspector-delete', [self.__item])
184 self.destroy()
ee65fee0
FC
185
186 @property
187 def item(self):
188 return self.__item
189
190 def on_edit_position(self, txt):
191 x, z = map(float, txt.split())
192 self.__item.position = [x, 0, z]
193
194 def on_edit_roll(self, txt):
195 self.__item.roll = float(txt)
196
197 def on_edit_scale(self, txt):
198 self.__item.scale = float(txt)
199
200 def on_edit_mass(self, txt):
201 self.__item.mass = float(txt)
202
203 def on_edit_restitution(self, txt):
204 self.__item.restitution = float(txt)
205
206 def on_edit_friction(self, txt):
207 self.__item.friction = float(txt)
208
2aeb9f68
FC
209 def on_edit_id(self, txt):
210 self.__item.id = txt
211
212 def on_edit_strategy(self, txt):
213 if not txt:
214 self.__entries.strategy_args.set('')
215 return
216 name2class = {
217 'StillStrategy': StillStrategy,
218 'UpStrategy': UpStrategy,
219 'HitStrategy': HitStrategy,
220 'DownStrategy': DownStrategy,
221 'FixedStrategy': FixedStrategy}
222 class_ = name2class[txt]
223 args = []
224 error = False
225 if txt == 'StillStrategy':
226 args += [self.__item._np]
227 if txt in ['UpStrategy', 'DownStrategy']:
228 args += [self.__item._np]
229 try:
230 args += [float(self.__entries.strategy_args.get())]
231 except ValueError:
232 error = True
233 if txt == 'HitStrategy':
234 for item in self.__all_items:
235 if item.id == self.__entries.strategy_args.get():
236 args += [item]
237 args += [self.__item.node]
238 args += [self.__item._world]
239 if not error:
240 self.__item.strategy = class_(*args)
241 self.__item.strategy_json = txt
242 else:
243 self.__show_error_popup()
244
245 def on_edit_strategy_args(self, txt):
246 self.__item.strategy_args_json = txt
247
248 def __show_error_popup(self):
249 self.__dialog = OkDialog(dialogName='Strategy args errors',
250 text=_('There are errors in the strategy args.'),
251 command=self.__actually_close)
252 self.__dialog['frameColor'] = (.4, .4, .4, .14)
253 self.__dialog['relief'] = FLAT
254 self.__dialog.component('text0')['fg'] = (.9, .9, .9, 1)
255 self.__dialog.component('text0')['font'] = self._font
256 for b in self.__dialog.buttonList:
257 b['frameColor'] = (.4, .4, .4, .14)
258 b.component('text0')['fg'] = (.9, .9, .9, 1)
259 b.component('text0')['font'] = self._font
260 b.component('text1')['fg'] = (.9, .1, .1, 1)
261 b.component('text1')['font'] = self._font
262 b.component('text2')['fg'] = (.9, .9, .1, 1)
263 b.component('text2')['font'] = self._font
264
265 def __actually_close(self, arg):
266 self.__entries.strategy.set('')
267 self.__entries.strategy_args.set('')
268 self.__dialog.cleanup()
269
ee65fee0
FC
270 def destroy(self):
271 self._frm.destroy()
272 self.ignore('item-rototranslated')
273 messenger.send('editor-inspector-destroy')
3133e30f
FC
274
275
276class PixelSpaceInspector(DirectObject):
277
278 def __init__(self, item, all_items):
279 super().__init__()
280 self.__item = item
281 self.__all_items = all_items
282 self._font = base.loader.load_font(
283 'assets/fonts/Hanken-Book.ttf')
284 self._font.clear()
285 self._font.set_pixels_per_unit(60)
286 self._font.set_minfilter(Texture.FTLinearMipmapLinear)
287 self._font.set_outline((0, 0, 0, 1), .8, .2)
288 self._common = {
289 'scale': .046,
290 'text_font': self._font,
291 'text_fg': (.9, .9, .9, 1),
292 'relief': FLAT,
293 'frameColor': (.4, .4, .4, .14),
294 'rolloverSound': loader.load_sfx(
295 'assets/audio/sfx/rollover.ogg'),
296 'clickSound': loader.load_sfx(
297 'assets/audio/sfx/click.ogg')}
298 w, h = .8, .36
299 self._frm = DirectFrame(frameColor=(.4, .4, .4, .06),
300 frameSize=(0, w, -h, 0),
301 parent=base.a2dTopRight,
302 pos=(-w, 0, 0))
303 self.__z = -.08
304 p = self.__item._np.get_pos()
305 _id = ''
306 if 'id' in self.__item.json:
307 _id = self.__item.json['id']
308 t, pos_entry = self.__add_row(_('position'), f'{round(p.x, 3)}, {round(p.z, 3)}', self.on_edit_position)
309 t, id_entry = self.__add_row(_('id'), _id, self.on_edit_id)
310 fields = ['position', 'id']
311 Entries = namedtuple('Entries', fields)
312 self.__entries = Entries(pos_entry, id_entry)
313 def load_images_btn(path, col):
314 colors = {
315 'gray': [
316 (.6, .6, .6, 1), # ready
317 (1, 1, 1, 1), # press
318 (.8, .8, .8, 1), # rollover
319 (.4, .4, .4, .4)],
320 'green': [
321 (.1, .68, .1, 1),
322 (.1, 1, .1, 1),
323 (.1, .84, .1, 1),
324 (.4, .1, .1, .4)]}[col]
325 return [self.__load_img_btn(path, col) for col in colors]
326 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
327 DirectButton(
328 image=load_images_btn('exitRight', 'gray'), scale=.05,
329 pos=(.06, 1, -h + .06),
330 parent=self._frm, command=self.destroy, state=NORMAL, relief=FLAT,
331 frameColor=fcols[0],
332 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
333 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
334 self.accept('item-rototranslated', self.__on_item_rototranslated)
335 DirectButton(
336 image=load_images_btn('trashcan', 'gray'), scale=.05,
337 pos=(.18, 1, -h + .06),
338 parent=self._frm, command=self.__delete_item, state=NORMAL, relief=FLAT,
339 frameColor=fcols[0],
340 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
341 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
342
343 def __add_row(self, label, text, callback):
344 tw = 10
345 t = OnscreenText(
346 label,
347 pos=(.03, self.__z), parent=self._frm,
348 font=self._common['text_font'],
349 scale=self._common['scale'],
350 fg=self._common['text_fg'],
351 wordwrap=20, align=TextNode.ALeft)
352 e = DirectEntry(
353 scale=self._common['scale'],
354 pos=(.30, 1, self.__z),
355 entryFont=self._font,
356 width=tw,
357 cursorKeys=True,
358 frameColor=self._common['frameColor'],
359 initialText=text,
360 parent=self._frm,
361 text_fg=self._common['text_fg'],
362 command=callback)
363 self.__z -= .1
364 return t, e
365
366 def __load_img_btn(self, path, col):
367 img = OnscreenImage('assets/images/buttons/%s.dds' % path)
368 img.set_transparency(True)
369 img.set_color(col)
370 img.detach_node()
371 return img
372
373 def __on_item_rototranslated(self, np):
374 pos = P3dGfxMgr.pos2d_p2d(np)
375 self.__entries.position.set('%s %s' % (str(round(pos[0], 3)), str(round(pos[1], 3))))
376 self.__item.json['position'] = list(pos)
377
378 def __delete_item(self):
379 messenger.send('editor-inspector-delete', [self.__item])
380 self.destroy()
381
382 @property
383 def item(self):
384 return self.__item
385
386 def on_edit_position(self, txt):
387 x, z = map(float, txt.split())
388 self.__item.position = [x, 0, z]
389
390 def on_edit_id(self, txt):
391 self.__item.id = txt
392
393 def __actually_close(self, arg):
394 self.__entries.strategy.set('')
395 self.__entries.strategy_args.set('')
396 self.__dialog.cleanup()
397
398 def destroy(self):
399 self._frm.destroy()
400 self.ignore('item-rototranslated')
401 messenger.send('editor-inspector-destroy')
402
403
404class WorldSpaceInspector(DirectObject):
405
406 def __init__(self, item, all_items):
407 super().__init__()
408 self.__item = item
409 self.__all_items = all_items
410 self._font = base.loader.load_font(
411 'assets/fonts/Hanken-Book.ttf')
412 self._font.clear()
413 self._font.set_pixels_per_unit(60)
414 self._font.set_minfilter(Texture.FTLinearMipmapLinear)
415 self._font.set_outline((0, 0, 0, 1), .8, .2)
416 self._common = {
417 'scale': .046,
418 'text_font': self._font,
419 'text_fg': (.9, .9, .9, 1),
420 'relief': FLAT,
421 'frameColor': (.4, .4, .4, .14),
422 'rolloverSound': loader.load_sfx(
423 'assets/audio/sfx/rollover.ogg'),
424 'clickSound': loader.load_sfx(
425 'assets/audio/sfx/click.ogg')}
426 w, h = .8, .36
427 self._frm = DirectFrame(frameColor=(.4, .4, .4, .06),
428 frameSize=(0, w, -h, 0),
429 parent=base.a2dTopRight,
430 pos=(-w, 0, 0))
431 self.__z = -.08
432 p = self.__item._np.get_pos()
433 _id = ''
434 if 'id' in self.__item.json:
435 _id = self.__item.json['id']
436 t, pos_entry = self.__add_row(_('position'), f'{round(p.x, 3)}, {round(p.z, 3)}', self.on_edit_position)
437 t, id_entry = self.__add_row(_('id'), _id, self.on_edit_id)
438 fields = ['position', 'id']
439 Entries = namedtuple('Entries', fields)
440 self.__entries = Entries(pos_entry, id_entry)
441 def load_images_btn(path, col):
442 colors = {
443 'gray': [
444 (.6, .6, .6, 1), # ready
445 (1, 1, 1, 1), # press
446 (.8, .8, .8, 1), # rollover
447 (.4, .4, .4, .4)],
448 'green': [
449 (.1, .68, .1, 1),
450 (.1, 1, .1, 1),
451 (.1, .84, .1, 1),
452 (.4, .1, .1, .4)]}[col]
453 return [self.__load_img_btn(path, col) for col in colors]
454 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
455 DirectButton(
456 image=load_images_btn('exitRight', 'gray'), scale=.05,
457 pos=(.06, 1, -h + .06),
458 parent=self._frm, command=self.destroy, state=NORMAL, relief=FLAT,
459 frameColor=fcols[0],
460 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
461 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
462 self.accept('item-rototranslated', self.__on_item_rototranslated)
463 DirectButton(
464 image=load_images_btn('trashcan', 'gray'), scale=.05,
465 pos=(.18, 1, -h + .06),
466 parent=self._frm, command=self.__delete_item, state=NORMAL, relief=FLAT,
467 frameColor=fcols[0],
468 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
469 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
470
471 def __add_row(self, label, text, callback):
472 tw = 10
473 t = OnscreenText(
474 label,
475 pos=(.03, self.__z), parent=self._frm,
476 font=self._common['text_font'],
477 scale=self._common['scale'],
478 fg=self._common['text_fg'],
479 wordwrap=20, align=TextNode.ALeft)
480 e = DirectEntry(
481 scale=self._common['scale'],
482 pos=(.30, 1, self.__z),
483 entryFont=self._font,
484 width=tw,
485 cursorKeys=True,
486 frameColor=self._common['frameColor'],
487 initialText=text,
488 parent=self._frm,
489 text_fg=self._common['text_fg'],
490 command=callback)
491 self.__z -= .1
492 return t, e
493
494 def __load_img_btn(self, path, col):
495 img = OnscreenImage('assets/images/buttons/%s.dds' % path)
496 img.set_transparency(True)
497 img.set_color(col)
498 img.detach_node()
499 return img
500
501 def __on_item_rototranslated(self, np):
502 pos = np.get_pos()
503 self.__entries.position.set('%s %s' % (str(round(pos.x, 3)), str(round(pos.z, 3))))
504 self.__item.json['position'] = list(pos)
505
506 def __delete_item(self):
507 messenger.send('editor-inspector-delete', [self.__item])
508 self.destroy()
509
510 @property
511 def item(self):
512 return self.__item
513
514 def on_edit_position(self, txt):
515 x, z = map(float, txt.split())
516 self.__item.position = [x, 0, z]
517
518 def on_edit_id(self, txt):
519 self.__item.id = txt
520
521 def __actually_close(self, arg):
522 self.__entries.strategy.set('')
523 self.__entries.strategy_args.set('')
524 self.__dialog.cleanup()
525
526 def destroy(self):
527 self._frm.destroy()
528 self.ignore('item-rototranslated')
529 messenger.send('editor-inspector-destroy')