ya2 · news · projects · code · about

b5440a0cb9be91fab04d8ba23b13af88ec10c67a
[pmachines.git] / pmachines / editor / inspector.py
1 from collections import namedtuple
2 from glob import glob
3 from importlib import import_module
4 from os.path import basename
5 from inspect import isclass
6 from panda3d.core import Texture, TextNode
7 from direct.gui.OnscreenImage import OnscreenImage
8 from direct.gui.DirectGui import DirectButton, DirectFrame, DirectEntry, DirectOptionMenu, OkDialog
9 from direct.gui.DirectGuiGlobals import FLAT, NORMAL
10 from direct.gui.OnscreenText import OnscreenText
11 from direct.showbase.DirectObject import DirectObject
12 from pmachines.items.item import ItemStrategy, FixedStrategy, StillStrategy
13 from pmachines.items.box import HitStrategy
14 from pmachines.items.domino import DownStrategy, UpStrategy
15
16
17 class Inspector(DirectObject):
18
19 def __init__(self, item, all_items):
20 super().__init__()
21 self.__item = item
22 self.__all_items = all_items
23 self._font = base.loader.load_font(
24 'assets/fonts/Hanken-Book.ttf')
25 self._font.clear()
26 self._font.set_pixels_per_unit(60)
27 self._font.set_minfilter(Texture.FTLinearMipmapLinear)
28 self._font.set_outline((0, 0, 0, 1), .8, .2)
29 self._common = {
30 'scale': .046,
31 'text_font': self._font,
32 'text_fg': (.9, .9, .9, 1),
33 'relief': FLAT,
34 'frameColor': (.4, .4, .4, .14),
35 'rolloverSound': loader.load_sfx(
36 'assets/audio/sfx/rollover.ogg'),
37 'clickSound': loader.load_sfx(
38 'assets/audio/sfx/click.ogg')}
39 w, h = .8, 1.04
40 self._frm = DirectFrame(frameColor=(.4, .4, .4, .06),
41 frameSize=(0, w, -h, 0),
42 parent=base.a2dTopRight,
43 pos=(-w, 0, 0))
44 self.__z = -.08
45 p = self.__item._np.get_pos()
46 r = self.__item._np.get_r()
47 s = self.__item._np.get_scale()[0]
48 m = self.__item._mass
49 restitution = self.__item._restitution
50 f = self.__item._friction
51 _id = ''
52 if 'id' in self.__item.json:
53 _id = self.__item.json['id']
54 _strategy = ''
55 if 'strategy' in self.__item.json:
56 _strategy = self.__item.json['strategy']
57 _strategy_args = ''
58 if 'strategy_args' in self.__item.json:
59 _strategy_args = ' '.join(self.__item.json['strategy_args'])
60 t, pos_entry = self.__add_row(_('position'), f'{round(p.x, 3)}, {round(p.z, 3)}', self.on_edit_position)
61 t, rot_entry = self.__add_row(_('roll'), f'{round(r, 3)}', self.on_edit_roll)
62 t, scale_entry = self.__add_row(_('scale'), f'{round(s, 3)}', self.on_edit_scale)
63 t, mass_entry = self.__add_row(_('mass'), f'{round(m, 3)}', self.on_edit_mass)
64 t, restitution_entry = self.__add_row(_('restitution'), f'{round(restitution, 3)}', self.on_edit_restitution)
65 t, friction_entry = self.__add_row(_('friction'), f'{round(f, 3)}', self.on_edit_friction)
66 t, id_entry = self.__add_row(_('id'), _id, self.on_edit_id)
67 t, strategy_entry = self.__add_row_option(_('strategy'), _strategy, self.on_edit_strategy)
68 t, strategy_args_entry = self.__add_row(_('strategy_args'), _strategy_args, self.on_edit_strategy_args)
69 fields = ['position', 'roll', 'scale', 'mass', 'restitution', 'friction', 'id', 'strategy', 'strategy_args']
70 Entries = namedtuple('Entries', fields)
71 self.__entries = Entries(pos_entry, rot_entry, scale_entry, mass_entry, restitution_entry, friction_entry, id_entry, strategy_entry, strategy_args_entry)
72 def load_images_btn(path, col):
73 colors = {
74 'gray': [
75 (.6, .6, .6, 1), # ready
76 (1, 1, 1, 1), # press
77 (.8, .8, .8, 1), # rollover
78 (.4, .4, .4, .4)],
79 'green': [
80 (.1, .68, .1, 1),
81 (.1, 1, .1, 1),
82 (.1, .84, .1, 1),
83 (.4, .1, .1, .4)]}[col]
84 return [self.__load_img_btn(path, col) for col in colors]
85 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
86 DirectButton(
87 image=load_images_btn('exitRight', 'gray'), scale=.05,
88 pos=(.06, 1, -h + .06),
89 parent=self._frm, command=self.destroy, state=NORMAL, relief=FLAT,
90 frameColor=fcols[0],
91 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
92 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
93 self.accept('item-rototranslated', self.__on_item_rototranslated)
94 DirectButton(
95 image=load_images_btn('trashcan', 'gray'), scale=.05,
96 pos=(.18, 1, -h + .06),
97 parent=self._frm, command=self.__delete_item, state=NORMAL, relief=FLAT,
98 frameColor=fcols[0],
99 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
100 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
101
102 def __add_row(self, label, text, callback):
103 tw = 10
104 t = OnscreenText(
105 label,
106 pos=(.03, self.__z), parent=self._frm,
107 font=self._common['text_font'],
108 scale=self._common['scale'],
109 fg=self._common['text_fg'],
110 wordwrap=20, align=TextNode.ALeft)
111 e = DirectEntry(
112 scale=self._common['scale'],
113 pos=(.30, 1, self.__z),
114 entryFont=self._font,
115 width=tw,
116 cursorKeys=True,
117 frameColor=self._common['frameColor'],
118 initialText=text,
119 parent=self._frm,
120 text_fg=self._common['text_fg'],
121 command=callback)
122 self.__z -= .1
123 return t, e
124
125 def __add_row_option(self, label, text, callback):
126 item_modules = glob('pmachines/items/*.py')
127 item_modules = [basename(i)[:-3] for i in item_modules]
128 new_items = ['']
129 for item_module in item_modules:
130 mod_name = 'pmachines.items.' + item_module
131 for member in import_module(mod_name).__dict__.values():
132 if isclass(member) and issubclass(member, ItemStrategy) and \
133 member != ItemStrategy:
134 new_items = list(set(new_items + [member.__name__]))
135 t = OnscreenText(
136 label,
137 pos=(.03, self.__z), parent=self._frm,
138 font=self._common['text_font'],
139 scale=self._common['scale'],
140 fg=self._common['text_fg'],
141 wordwrap=20, align=TextNode.ALeft)
142 e = DirectOptionMenu(
143 scale=self._common['scale'],
144 initialitem=text,
145 pos=(.30, 1, self.__z),
146 items=new_items,
147 parent=self._frm,
148 command=callback,
149 state=NORMAL,
150 relief=FLAT,
151 item_relief=FLAT,
152 frameColor=self._common['frameColor'],
153 item_frameColor=self._common['frameColor'],
154 popupMenu_frameColor=self._common['frameColor'],
155 popupMarker_frameColor=self._common['frameColor'],
156 text_font=self._font,
157 text_fg=self._common['text_fg'],
158 highlightColor=(.9, .9, .9, .9),
159 item_text_font=self._font,
160 item_text_fg=self._common['text_fg'],
161 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
162 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
163 self.__z -= .1
164 return t, e
165
166 def __load_img_btn(self, path, col):
167 img = OnscreenImage('assets/images/buttons/%s.dds' % path)
168 img.set_transparency(True)
169 img.set_color(col)
170 img.detach_node()
171 return img
172
173 def __on_item_rototranslated(self, np):
174 pos = np.get_pos()
175 r = np.get_r()
176 self.__entries.position.set('%s %s' % (str(round(pos.x, 3)), str(round(pos.z, 3))))
177 self.__entries.roll.set('%s' % str(round(r, 3)))
178 self.__item.json['position'] = list(pos)
179 self.__item.json['roll'] = round(r, 3)
180
181 def __delete_item(self):
182 messenger.send('editor-inspector-delete', [self.__item])
183 self.destroy()
184
185 @property
186 def item(self):
187 return self.__item
188
189 def on_edit_position(self, txt):
190 x, z = map(float, txt.split())
191 self.__item.position = [x, 0, z]
192
193 def on_edit_roll(self, txt):
194 self.__item.roll = float(txt)
195
196 def on_edit_scale(self, txt):
197 self.__item.scale = float(txt)
198
199 def on_edit_mass(self, txt):
200 self.__item.mass = float(txt)
201
202 def on_edit_restitution(self, txt):
203 self.__item.restitution = float(txt)
204
205 def on_edit_friction(self, txt):
206 self.__item.friction = float(txt)
207
208 def on_edit_id(self, txt):
209 self.__item.id = txt
210
211 def on_edit_strategy(self, txt):
212 if not txt:
213 self.__entries.strategy_args.set('')
214 return
215 name2class = {
216 'StillStrategy': StillStrategy,
217 'UpStrategy': UpStrategy,
218 'HitStrategy': HitStrategy,
219 'DownStrategy': DownStrategy,
220 'FixedStrategy': FixedStrategy}
221 class_ = name2class[txt]
222 args = []
223 error = False
224 if txt == 'StillStrategy':
225 args += [self.__item._np]
226 if txt in ['UpStrategy', 'DownStrategy']:
227 args += [self.__item._np]
228 try:
229 args += [float(self.__entries.strategy_args.get())]
230 except ValueError:
231 error = True
232 if txt == 'HitStrategy':
233 for item in self.__all_items:
234 if item.id == self.__entries.strategy_args.get():
235 args += [item]
236 args += [self.__item.node]
237 args += [self.__item._world]
238 if not error:
239 self.__item.strategy = class_(*args)
240 self.__item.strategy_json = txt
241 else:
242 self.__show_error_popup()
243
244 def on_edit_strategy_args(self, txt):
245 self.__item.strategy_args_json = txt
246
247 def __show_error_popup(self):
248 self.__dialog = OkDialog(dialogName='Strategy args errors',
249 text=_('There are errors in the strategy args.'),
250 command=self.__actually_close)
251 self.__dialog['frameColor'] = (.4, .4, .4, .14)
252 self.__dialog['relief'] = FLAT
253 self.__dialog.component('text0')['fg'] = (.9, .9, .9, 1)
254 self.__dialog.component('text0')['font'] = self._font
255 for b in self.__dialog.buttonList:
256 b['frameColor'] = (.4, .4, .4, .14)
257 b.component('text0')['fg'] = (.9, .9, .9, 1)
258 b.component('text0')['font'] = self._font
259 b.component('text1')['fg'] = (.9, .1, .1, 1)
260 b.component('text1')['font'] = self._font
261 b.component('text2')['fg'] = (.9, .9, .1, 1)
262 b.component('text2')['font'] = self._font
263
264 def __actually_close(self, arg):
265 self.__entries.strategy.set('')
266 self.__entries.strategy_args.set('')
267 self.__dialog.cleanup()
268
269 def destroy(self):
270 self._frm.destroy()
271 self.ignore('item-rototranslated')
272 messenger.send('editor-inspector-destroy')