1 from collections
import namedtuple
2 from logging
import info
3 from panda3d
.core
import Texture
, TextNode
, LPoint3f
4 from direct
.gui
.OnscreenImage
import OnscreenImage
5 from direct
.gui
.DirectGui
import DirectButton
, DirectFrame
, DirectEntry
, DirectOptionMenu
, OkDialog
6 from direct
.gui
.DirectGuiGlobals
import FLAT
, NORMAL
7 from direct
.gui
.OnscreenText
import OnscreenText
8 from direct
.showbase
.DirectObject
import DirectObject
9 from pmachines
.items
.item
import FixedStrategy
, StillStrategy
10 from pmachines
.items
.box
import HitStrategy
11 from pmachines
.items
.domino
import DownStrategy
, UpStrategy
12 from pmachines
.editor
.augmented_frame
import AugmentedDirectFrame
13 from ya2
.utils
.gui
.base_page
import DirectOptionMenuTestable
14 from ya2
.utils
.gfx
import DirectGuiMixin
17 class Inspector(DirectObject
):
19 def __init__(self
, item
, all_items
, pos_mgr
, strategy_items
):
22 self
.__all
_items
= all_items
23 self
.__pos
_mgr
= pos_mgr
24 self
._font
= base
.loader
.load_font(
25 'assets/fonts/Hanken-Book.ttf')
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)
32 'text_font': self
._font
,
33 'text_fg': (.9, .9, .9, 1),
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')}
40 tooltip_args
= self
._common
['text_font'], self
._common
['scale'], self
._common
['text_fg']
42 self
._frm
= AugmentedDirectFrame(frameColor
=(.4, .4, .4, .06),
43 frameSize
=(0, w
, -h
, 0),
44 parent
=base
.a2dTopRight
,
46 delta_drag
=LPoint3f(-w
, 0, -h
),
47 collapse_pos
=(w
- .06, 1, -h
+ .06),
49 frame_name
='inspector')
51 p
= self
.__item
._np
.get_pos()
52 r
= self
.__item
._np
.get_r()
53 s
= self
.__item
._np
.get_scale()[0]
55 restitution
= self
.__item
._restitution
56 f
= self
.__item
._friction
58 if 'id' in self
.__item
.json
:
59 _id
= self
.__item
.json
['id']
61 if 'strategy' in self
.__item
.json
:
62 _strategy
= self
.__item
.json
['strategy']
64 if 'strategy_args' in self
.__item
.json
:
65 _strategy_args
= ' '.join(map(str, self
.__item
.json
['strategy_args']))
66 t
, pos_entry
= self
.__add
_row
('position', _('position'), f
'{round(p.x, 3)} {round(p.z, 3)}', self
.on_edit_position
, _('position (e.g. 0.1 2.3 4.5)'))
67 t
, rot_entry
= self
.__add
_row
('roll', _('roll'), f
'{round(r, 3)}', self
.on_edit_roll
, _('roll (e.g. 90)'))
68 t
, scale_entry
= self
.__add
_row
('scale', _('scale'), f
'{round(s, 3)}', self
.on_edit_scale
, _('scale (e.g. 1.2)'))
69 t
, mass_entry
= self
.__add
_row
('mass', _('mass'), f
'{round(m, 3)}', self
.on_edit_mass
, _('mass (default 1; 0 if fixed)'))
70 t
, restitution_entry
= self
.__add
_row
('restitution', _('restitution'), f
'{round(restitution, 3)}', self
.on_edit_restitution
, _('restitution (default 0.5)'))
71 t
, friction_entry
= self
.__add
_row
('friction', _('friction'), f
'{round(f, 3)}', self
.on_edit_friction
, _('friction (default 0.5)'))
72 t
, id_entry
= self
.__add
_row
('id', _('id'), _id
, self
.on_edit_id
, _('id of the item (for the strategies)'))
73 # item_modules = glob('pmachines/items/*.py')
74 # item_modules = [basename(i)[:-3] for i in item_modules]
75 # strategy_items = ['']
76 # for item_module in item_modules:
77 # mod_name = 'pmachines.items.' + item_module
78 # for member in import_module(mod_name).__dict__.values():
79 # if isclass(member) and issubclass(member, ItemStrategy) and \
80 # member != ItemStrategy:
81 # strategy_items = list(set(strategy_items + [member.__name__]))
82 strategy_names
= [s
.__name
__ for s
in strategy_items
]
83 t
, strategy_entry
= self
.__add
_row
_option
(_('strategy'), _strategy
, strategy_names
, self
.on_edit_strategy
, _('the strategy of the item'))
85 def strategy_set(comps
):
86 strategy_labels
= [f
'inspector_strategy_{i.lower()}' for i
in strategy_names
]
87 for i
in strategy_labels
:
88 if i
in self
.__pos
_mgr
:
90 for l
, b
in zip(strategy_labels
, comps
):
91 b
.__class
__ = type('DirectFrameMixed', (DirectFrame
, DirectGuiMixin
), {})
93 self
.__pos
_mgr
[l
] = (p
[0] + 5, p
[1])
94 strategy_entry
._show
_cb
= strategy_set
95 p
= strategy_entry
.pos_pixel()
96 self
.__pos
_mgr
['editor_inspector_strategy'] = (p
[0] + 5, p
[1])
98 t
, strategy_args_entry
= self
.__add
_row
('strategy_args', _('strategy_args'), _strategy_args
, self
.on_edit_strategy_args
, _('the arguments of the strategy'))
99 fields
= ['position', 'roll', 'scale', 'mass', 'restitution', 'friction', 'id', 'strategy', 'strategy_args']
100 Entries
= namedtuple('Entries', fields
)
101 self
.__entries
= Entries(pos_entry
, rot_entry
, scale_entry
, mass_entry
, restitution_entry
, friction_entry
, id_entry
, strategy_entry
, strategy_args_entry
)
102 def load_images_btn(path
, col
):
105 (.6, .6, .6, 1), # ready
106 (1, 1, 1, 1), # press
107 (.8, .8, .8, 1), # rollover
113 (.4, .1, .1, .4)]}[col
]
114 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
115 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
117 image
=load_images_btn('exitRight', 'gray'), scale
=.05,
118 pos
=(.06, 1, -h
+ .06),
119 parent
=self
._frm
, command
=self
.destroy
, state
=NORMAL
, relief
=FLAT
,
121 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
122 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
123 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
124 pos_mgr
['editor_inspector_close'] = b
.pos_pixel()
125 b
.set_tooltip(_('Close'), *tooltip_args
)
126 self
.accept('item-rototranslated', self
.__on
_item
_rototranslated
)
128 image
=load_images_btn('trashcan', 'gray'), scale
=.05,
129 pos
=(.18, 1, -h
+ .06),
130 parent
=self
._frm
, command
=self
.__delete
_item
, state
=NORMAL
, relief
=FLAT
,
132 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
133 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
134 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
135 pos_mgr
['editor_inspector_delete'] = b
.pos_pixel()
136 b
.set_tooltip(_('Delete the item'), *tooltip_args
)
138 def __add_row(self
, id_
, label
, text
, callback
, tooltip
):
140 tooltip_args
= self
._common
['text_font'], self
._common
['scale'], self
._common
['text_fg']
143 pos
=(.03, self
.__z
), parent
=self
._frm
,
144 font
=self
._common
['text_font'],
145 scale
=self
._common
['scale'],
146 fg
=self
._common
['text_fg'],
147 wordwrap
=20, align
=TextNode
.ALeft
)
149 scale
=self
._common
['scale'],
150 pos
=(.30, 1, self
.__z
),
151 entryFont
=self
._font
,
154 frameColor
=self
._common
['frameColor'],
157 text_fg
=self
._common
['text_fg'],
159 e
.__class
__ = type('DirectEntryMixed', (DirectEntry
, DirectGuiMixin
), {})
160 e
.set_tooltip(tooltip
, *tooltip_args
)
161 self
.__pos
_mgr
[f
'editor_inspector_{id_}'] = e
.pos_pixel()
165 def __add_row_option(self
, label
, text
, items
, callback
, tooltip
):
166 tooltip_args
= self
._common
['text_font'], self
._common
['scale'], self
._common
['text_fg']
169 pos
=(.03, self
.__z
), parent
=self
._frm
,
170 font
=self
._common
['text_font'],
171 scale
=self
._common
['scale'],
172 fg
=self
._common
['text_fg'],
173 wordwrap
=20, align
=TextNode
.ALeft
)
174 e
= DirectOptionMenuTestable(
175 scale
=self
._common
['scale'],
177 pos
=(.30, 1, self
.__z
),
184 frameColor
=self
._common
['frameColor'],
185 item_frameColor
=self
._common
['frameColor'],
186 popupMenu_frameColor
=self
._common
['frameColor'],
187 popupMarker_frameColor
=self
._common
['frameColor'],
188 text_font
=self
._font
,
189 text_fg
=self
._common
['text_fg'],
190 highlightColor
=(.9, .9, .9, .9),
191 item_text_font
=self
._font
,
192 item_text_fg
=self
._common
['text_fg'],
193 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
194 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
195 e
.__class
__ = type('DirectOptionMenuMixed', (DirectOptionMenu
, DirectGuiMixin
), {})
196 e
.set_tooltip(tooltip
, *tooltip_args
)
200 def __load_img_btn(self
, path
, col
):
201 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
202 img
.set_transparency(True)
207 def __on_item_rototranslated(self
, np
):
210 self
.__entries
.position
.set('%s %s' % (str(round(pos
.x
, 3)), str(round(pos
.z
, 3))))
211 self
.__entries
.roll
.set('%s' % str(round(r
, 3)))
212 self
.__item
.json
['position'] = list(pos
)
213 self
.__item
.json
['roll'] = round(r
, 3)
215 def __delete_item(self
):
216 messenger
.send('editor-inspector-delete', [self
.__item
])
223 def on_edit_position(self
, txt
):
224 x
, z
= map(float, txt
.split())
225 self
.__item
.position
= [x
, 0, z
]
227 def on_edit_roll(self
, txt
):
228 self
.__item
.roll
= float(txt
)
230 def on_edit_scale(self
, txt
):
231 self
.__item
.scale
= float(txt
)
233 def on_edit_mass(self
, txt
):
234 self
.__item
.mass
= float(txt
)
236 def on_edit_restitution(self
, txt
):
237 self
.__item
.restitution
= float(txt
)
239 def on_edit_friction(self
, txt
):
240 self
.__item
.friction
= float(txt
)
242 def on_edit_id(self
, txt
):
245 def on_edit_strategy(self
, txt
):
247 self
.__entries
.strategy_args
.set('')
250 'StillStrategy': StillStrategy
,
251 'UpStrategy': UpStrategy
,
252 'HitStrategy': HitStrategy
,
253 'DownStrategy': DownStrategy
,
254 'FixedStrategy': FixedStrategy
}
255 class_
= name2class
[txt
]
258 if txt
== 'StillStrategy':
259 args
+= [self
.__item
._np
]
260 if txt
in ['UpStrategy', 'DownStrategy']:
261 args
+= [self
.__item
._np
]
263 args
+= [float(self
.__entries
.strategy_args
.get())]
266 if txt
== 'HitStrategy':
267 for item
in self
.__all
_items
:
268 if item
.id == self
.__entries
.strategy_args
.get():
270 args
+= [self
.__item
.node
]
271 args
+= [self
.__item
._world
]
273 self
.__item
.strategy
= class_(*args
)
274 self
.__item
.strategy_json
= txt
276 self
.__show
_error
_popup
()
278 def on_edit_strategy_args(self
, txt
):
279 self
.__item
.strategy_args_json
= txt
281 def __show_error_popup(self
):
282 self
.__dialog
= OkDialog(dialogName
='Strategy args errors',
283 text
=_('There are errors in the strategy args.'),
284 command
=self
.__actually
_close
)
285 self
.__dialog
['frameColor'] = (.4, .4, .4, .14)
286 self
.__dialog
['relief'] = FLAT
287 self
.__dialog
.component('text0')['fg'] = (.9, .9, .9, 1)
288 self
.__dialog
.component('text0')['font'] = self
._font
289 for b
in self
.__dialog
.buttonList
:
290 b
['frameColor'] = (.4, .4, .4, .14)
291 b
.component('text0')['fg'] = (.9, .9, .9, 1)
292 b
.component('text0')['font'] = self
._font
293 b
.component('text1')['fg'] = (.9, .1, .1, 1)
294 b
.component('text1')['font'] = self
._font
295 b
.component('text2')['fg'] = (.9, .9, .1, 1)
296 b
.component('text2')['font'] = self
._font
298 def __actually_close(self
, arg
):
299 self
.__entries
.strategy
.set('')
300 self
.__entries
.strategy_args
.set('')
301 self
.__dialog
.cleanup()
305 self
.ignore('item-rototranslated')
306 messenger
.send('editor-inspector-destroy')
309 class PixelSpaceInspector(DirectObject
):
311 def __init__(self
, item
, all_items
):
312 info('PixelSpaceInspector')
315 self
.__all
_items
= all_items
316 self
._font
= base
.loader
.load_font(
317 'assets/fonts/Hanken-Book.ttf')
319 self
._font
.set_pixels_per_unit(60)
320 self
._font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
321 self
._font
.set_outline((0, 0, 0, 1), .8, .2)
324 'text_font': self
._font
,
325 'text_fg': (.9, .9, .9, 1),
327 'frameColor': (.4, .4, .4, .14),
328 'rolloverSound': loader
.load_sfx(
329 'assets/audio/sfx/rollover.ogg'),
330 'clickSound': loader
.load_sfx(
331 'assets/audio/sfx/click.ogg')}
333 self
._frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
334 frameSize
=(0, w
, -h
, 0),
335 parent
=base
.a2dTopRight
,
338 p
= self
.__item
._np
.get_pos()
340 if 'id' in self
.__item
.json
:
341 _id
= self
.__item
.json
['id']
342 t
, pos_entry
= self
.__add
_row
(_('position'), f
'{round(p.x, 3)}, {round(p.z, 3)}', self
.on_edit_position
)
343 t
, id_entry
= self
.__add
_row
(_('id'), _id
, self
.on_edit_id
)
344 fields
= ['position', 'id']
345 Entries
= namedtuple('Entries', fields
)
346 self
.__entries
= Entries(pos_entry
, id_entry
)
347 def load_images_btn(path
, col
):
350 (.6, .6, .6, 1), # ready
351 (1, 1, 1, 1), # press
352 (.8, .8, .8, 1), # rollover
358 (.4, .1, .1, .4)]}[col
]
359 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
360 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
362 image
=load_images_btn('exitRight', 'gray'), scale
=.05,
363 pos
=(.06, 1, -h
+ .06),
364 parent
=self
._frm
, command
=self
.destroy
, state
=NORMAL
, relief
=FLAT
,
366 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
367 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
368 self
.accept('item-rototranslated', self
.__on
_item
_rototranslated
)
370 image
=load_images_btn('trashcan', 'gray'), scale
=.05,
371 pos
=(.18, 1, -h
+ .06),
372 parent
=self
._frm
, command
=self
.__delete
_item
, state
=NORMAL
, relief
=FLAT
,
374 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
375 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
377 def __add_row(self
, label
, text
, callback
):
381 pos
=(.03, self
.__z
), parent
=self
._frm
,
382 font
=self
._common
['text_font'],
383 scale
=self
._common
['scale'],
384 fg
=self
._common
['text_fg'],
385 wordwrap
=20, align
=TextNode
.ALeft
)
387 scale
=self
._common
['scale'],
388 pos
=(.30, 1, self
.__z
),
389 entryFont
=self
._font
,
392 frameColor
=self
._common
['frameColor'],
395 text_fg
=self
._common
['text_fg'],
400 def __load_img_btn(self
, path
, col
):
401 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
402 img
.set_transparency(True)
407 def __on_item_rototranslated(self
, np
):
408 pos
= np
.pos2d_pixel()
409 self
.__entries
.position
.set('%s %s' % (str(round(pos
[0], 3)), str(round(pos
[1], 3))))
410 self
.__item
.json
['position'] = list(pos
)
412 def __delete_item(self
):
413 messenger
.send('editor-inspector-delete', [self
.__item
])
420 def on_edit_position(self
, txt
):
421 x
, z
= map(float, txt
.split())
422 self
.__item
.position
= [x
, 0, z
]
424 def on_edit_id(self
, txt
):
427 def __actually_close(self
, arg
):
428 self
.__entries
.strategy
.set('')
429 self
.__entries
.strategy_args
.set('')
430 self
.__dialog
.cleanup()
434 self
.ignore('item-rototranslated')
435 messenger
.send('editor-inspector-destroy')
438 class WorldSpaceInspector(DirectObject
):
440 def __init__(self
, item
, all_items
, pos_mgr
):
441 info('WorldSpaceInspector')
444 self
.__all
_items
= all_items
445 self
.__pos
_mgr
= pos_mgr
446 self
._font
= base
.loader
.load_font(
447 'assets/fonts/Hanken-Book.ttf')
449 self
._font
.set_pixels_per_unit(60)
450 self
._font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
451 self
._font
.set_outline((0, 0, 0, 1), .8, .2)
454 'text_font': self
._font
,
455 'text_fg': (.9, .9, .9, 1),
457 'frameColor': (.4, .4, .4, .14),
458 'rolloverSound': loader
.load_sfx(
459 'assets/audio/sfx/rollover.ogg'),
460 'clickSound': loader
.load_sfx(
461 'assets/audio/sfx/click.ogg')}
462 tooltip_args
= self
._common
['text_font'], self
._common
['scale'], self
._common
['text_fg']
464 self
._frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
465 frameSize
=(0, w
, -h
, 0),
466 parent
=base
.a2dTopRight
,
469 p
= self
.__item
._np
.get_pos()
471 if 'id' in self
.__item
.json
:
472 _id
= self
.__item
.json
['id']
473 t
, pos_entry
= self
.__add
_row
('position', _('position'), f
'{round(p.x, 3)} {round(p.z, 3)}', self
.on_edit_position
, _('position (e.g. 0.1 2.3 4.5)'))
474 t
, id_entry
= self
.__add
_row
('id', _('id'), _id
, self
.on_edit_id
, _('id of the item (for the strategies)'))
475 fields
= ['position', 'id']
476 Entries
= namedtuple('Entries', fields
)
477 self
.__entries
= Entries(pos_entry
, id_entry
)
478 def load_images_btn(path
, col
):
481 (.6, .6, .6, 1), # ready
482 (1, 1, 1, 1), # press
483 (.8, .8, .8, 1), # rollover
489 (.4, .1, .1, .4)]}[col
]
490 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
491 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
493 image
=load_images_btn('exitRight', 'gray'), scale
=.05,
494 pos
=(.06, 1, -h
+ .06),
495 parent
=self
._frm
, command
=self
.destroy
, state
=NORMAL
, relief
=FLAT
,
497 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
498 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
499 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
500 pos_mgr
['editor_inspector_test_close'] = b
.pos_pixel()
501 b
.set_tooltip(_('Close'), *tooltip_args
)
502 self
.accept('item-rototranslated', self
.__on
_item
_rototranslated
)
504 image
=load_images_btn('trashcan', 'gray'), scale
=.05,
505 pos
=(.18, 1, -h
+ .06),
506 parent
=self
._frm
, command
=self
.__delete
_item
, state
=NORMAL
, relief
=FLAT
,
508 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
509 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
510 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
511 pos_mgr
['editor_inspector_test_delete'] = b
.pos_pixel()
512 b
.set_tooltip(_('Delete the item'), *tooltip_args
)
514 def __add_row(self
, id_
, label
, text
, callback
, tooltip
):
516 tooltip_args
= self
._common
['text_font'], self
._common
['scale'], self
._common
['text_fg']
519 pos
=(.03, self
.__z
), parent
=self
._frm
,
520 font
=self
._common
['text_font'],
521 scale
=self
._common
['scale'],
522 fg
=self
._common
['text_fg'],
523 wordwrap
=20, align
=TextNode
.ALeft
)
525 scale
=self
._common
['scale'],
526 pos
=(.30, 1, self
.__z
),
527 entryFont
=self
._font
,
530 frameColor
=self
._common
['frameColor'],
533 text_fg
=self
._common
['text_fg'],
535 e
.__class
__ = type('DirectEntryMixed', (DirectEntry
, DirectGuiMixin
), {})
536 e
.set_tooltip(tooltip
, *tooltip_args
)
537 self
.__pos
_mgr
[f
'editor_inspector_test_{id_}'] = e
.pos_pixel()
541 def __load_img_btn(self
, path
, col
):
542 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
543 img
.set_transparency(True)
548 def __on_item_rototranslated(self
, np
):
550 self
.__entries
.position
.set('%s %s' % (str(round(pos
.x
, 3)), str(round(pos
.z
, 3))))
551 self
.__item
.json
['position'] = list(pos
)
553 def __delete_item(self
):
554 messenger
.send('editor-inspector-delete', [self
.__item
])
561 def on_edit_position(self
, txt
):
562 x
, z
= map(float, txt
.split())
563 self
.__item
.position
= [x
, 0, z
]
565 def on_edit_id(self
, txt
):
568 def __actually_close(self
, arg
):
569 self
.__entries
.strategy
.set('')
570 self
.__entries
.strategy_args
.set('')
571 self
.__dialog
.cleanup()
575 self
.ignore('item-rototranslated')
576 messenger
.send('editor-inspector-destroy')