ya2 · news · projects · code · about

functional tests: editor
[pmachines.git] / pmachines / editor / start_items.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, LPoint3f
7 from direct.gui.OnscreenImage import OnscreenImage
8 from direct.gui.DirectGui import DirectButton, DirectEntry, DirectOptionMenu, OkDialog, DirectFrame
9 from direct.gui.DirectGuiGlobals import FLAT, NORMAL
10 from direct.gui.OnscreenText import OnscreenText
11 from direct.showbase.DirectObject import DirectObject
12 from ya2.utils.gfx import DirectGuiMixin
13 from pmachines.items.item import Item, ItemStrategy
14 from pmachines.editor.augmented_frame import AugmentedDirectFrame
15 from pmachines.gui.options_page import DirectOptionMenuTestable
16
17
18 class StartItems(DirectObject):
19
20 def __init__(self, json_items, pos_mgr):
21 super().__init__()
22 self.__items = json_items
23 self.__json = self.__items[0]
24 self.__pos_mgr = pos_mgr
25 self._font = base.loader.load_font(
26 'assets/fonts/Hanken-Book.ttf')
27 self._font.clear()
28 self._font.set_pixels_per_unit(60)
29 self._font.set_minfilter(Texture.FTLinearMipmapLinear)
30 self._font.set_outline((0, 0, 0, 1), .8, .2)
31 self._common = {
32 'scale': .046,
33 'text_font': self._font,
34 'text_fg': (.9, .9, .9, 1),
35 'relief': FLAT,
36 'frameColor': (.4, .4, .4, .14),
37 'rolloverSound': loader.load_sfx(
38 'assets/audio/sfx/rollover.ogg'),
39 'clickSound': loader.load_sfx(
40 'assets/audio/sfx/click.ogg')}
41 tooltip_args = self._common['text_font'], self._common['scale'], self._common['text_fg']
42 w, h = .8, 1.04
43 self._frm = AugmentedDirectFrame(frameColor=(.4, .4, .4, .06),
44 frameSize=(0, w, -h, 0),
45 parent=base.a2dTopLeft,
46 pos=(0, 0, 0),
47 delta_drag=LPoint3f(w, 0, -h),
48 collapse_pos=(w - .06, 1, -h + .06),
49 pos_mgr=pos_mgr,
50 frame_name='start')
51 self.__z = -.08
52 item_modules = glob('pmachines/items/*.py')
53 item_modules = [basename(i)[:-3] for i in item_modules]
54 new_items = ['']
55 for item_module in item_modules:
56 mod_name = 'pmachines.items.' + item_module
57 for member in import_module(mod_name).__dict__.values():
58 if isclass(member) and issubclass(member, Item) and \
59 member != Item:
60 new_items = list(set(new_items + [member.__name__]))
61 t, item_class_entry = self.__add_row_option(_('class'), '', new_items, self.on_edit_class, _('class of the item'))
62
63 def item_class_set(comps):
64 class_labels = [f'start_class_{i.lower()}' for i in new_items]
65 for i in class_labels:
66 if i in self.__pos_mgr:
67 del self.__pos_mgr[i]
68 for l, b in zip(class_labels, comps):
69 b.__class__ = type('DirectFrameMixed', (DirectFrame, DirectGuiMixin), {})
70 p = b.pos_pixel()
71 self.__pos_mgr[l] = (p[0] + 5, p[1])
72 item_class_entry._show_cb = item_class_set
73 self.__pos_mgr['editor_start_class'] = item_class_entry.pos_pixel()
74
75 t, count_entry = self.__add_row('count', _('count'), '', self.on_edit_count, _('number of the items'))
76 t, scale_entry = self.__add_row('scale', _('scale'), '', self.on_edit_scale, _('scale (e.g. 1.2)'))
77 t, mass_entry = self.__add_row('mass', _('mass'), '', self.on_edit_mass, _('mass (default 1)'))
78 t, restitution_entry = self.__add_row('restitution', _('restitution'), '', self.on_edit_restitution, _('restitution (default 0.5)'))
79 t, friction_entry = self.__add_row('friction', _('friction'), '', self.on_edit_friction, _('friction (default 0.5)'))
80 t, id_entry = self.__add_row('id', _('id'), '', self.on_edit_id, _('id'))
81 strategy_items = ['']
82 for item_module in item_modules:
83 mod_name = 'pmachines.items.' + item_module
84 for member in import_module(mod_name).__dict__.values():
85 if isclass(member) and issubclass(member, ItemStrategy) and \
86 member != ItemStrategy:
87 strategy_items = list(set(strategy_items + [member.__name__]))
88 t, strategy_entry = self.__add_row_option(_('strategy'), '', strategy_items, self.on_edit_strategy, _('the strategy of the item'))
89
90 def strategy_set(comps):
91 strategy_labels = [f'start_strategy_{i.lower()}' for i in strategy_items]
92 for i in strategy_labels:
93 if i in self.__pos_mgr:
94 del self.__pos_mgr[i]
95 for l, b in zip(strategy_labels, comps):
96 b.__class__ = type('DirectFrameMixed', (DirectFrame, DirectGuiMixin), {})
97 p = b.pos_pixel()
98 self.__pos_mgr[l] = (p[0] + 5, p[1])
99 strategy_entry._show_cb = strategy_set
100 p = strategy_entry.pos_pixel()
101 self.__pos_mgr['editor_start_strategy'] = (p[0] + 5, p[1])
102
103 t, strategy_args_entry = self.__add_row('strategy_args', _('strategy_args'), '', self.on_edit_strategy_args, _('the arguments of the strategy'))
104 fields = ['scale', 'mass', 'restitution', 'friction', 'id', 'strategy', 'strategy_args', 'item_class', 'count']
105 Entries = namedtuple('Entries', fields)
106 self.__entries = Entries(scale_entry, mass_entry, restitution_entry, friction_entry, id_entry, strategy_entry, strategy_args_entry, item_class_entry, count_entry)
107 self.__set(self.__json)
108 def load_images_btn(path, col):
109 colors = {
110 'gray': [
111 (.6, .6, .6, 1), # ready
112 (1, 1, 1, 1), # press
113 (.8, .8, .8, 1), # rollover
114 (.4, .4, .4, .4)],
115 'green': [
116 (.1, .68, .1, 1),
117 (.1, 1, .1, 1),
118 (.1, .84, .1, 1),
119 (.4, .1, .1, .4)]}[col]
120 return [self.__load_img_btn(path, col) for col in colors]
121 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
122 b = DirectButton(
123 image=load_images_btn('exitRight', 'gray'), scale=.05,
124 pos=(.54, 1, -h + .06),
125 parent=self._frm, command=self.destroy, state=NORMAL, relief=FLAT,
126 frameColor=fcols[0],
127 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
128 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
129 b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
130 pos_mgr['editor_start_close'] = b.pos_pixel()
131 b.set_tooltip(_('Close'), *tooltip_args)
132 b = DirectButton(
133 image=load_images_btn('save', 'gray'), scale=.05,
134 pos=(.42, 1, -h + .06),
135 parent=self._frm, command=self.__save, state=NORMAL, relief=FLAT,
136 frameColor=fcols[0],
137 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
138 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
139 b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
140 pos_mgr['editor_start_save'] = b.pos_pixel()
141 b.set_tooltip(_('Save'), *tooltip_args)
142 b = DirectButton(
143 image=load_images_btn('trashcan', 'gray'), scale=.05,
144 pos=(.3, 1, -h + .06),
145 parent=self._frm, command=self.__delete_item, state=NORMAL, relief=FLAT,
146 frameColor=fcols[0],
147 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
148 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
149 b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
150 pos_mgr['editor_start_delete'] = b.pos_pixel()
151 b.set_tooltip(_('Delete'), *tooltip_args)
152 b = DirectButton(
153 image=load_images_btn('plus', 'gray'), scale=.05,
154 pos=(.06, 1, -h + .06),
155 parent=self._frm, command=self.__new_item, state=NORMAL, relief=FLAT,
156 frameColor=fcols[0],
157 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
158 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
159 b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
160 pos_mgr['editor_start_new'] = b.pos_pixel()
161 b.set_tooltip(_('New'), *tooltip_args)
162 b = DirectButton(
163 image=load_images_btn('right', 'gray'), scale=.05,
164 pos=(.18, 1, -h + .06),
165 parent=self._frm, command=self.__next_item, state=NORMAL, relief=FLAT,
166 frameColor=fcols[0],
167 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
168 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
169 b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
170 pos_mgr['editor_start_next'] = b.pos_pixel()
171 b.set_tooltip(_('Next'), *tooltip_args)
172
173 def __add_row(self, id_, label, text, callback, tooltip):
174 tw = 10
175 tooltip_args = self._common['text_font'], self._common['scale'], self._common['text_fg']
176 t = OnscreenText(
177 label,
178 pos=(.03, self.__z), parent=self._frm,
179 font=self._common['text_font'],
180 scale=self._common['scale'],
181 fg=self._common['text_fg'],
182 wordwrap=20, align=TextNode.ALeft)
183 e = DirectEntry(
184 scale=self._common['scale'],
185 pos=(.30, 1, self.__z),
186 entryFont=self._font,
187 width=tw,
188 cursorKeys=True,
189 frameColor=self._common['frameColor'],
190 initialText=text,
191 parent=self._frm,
192 text_fg=self._common['text_fg'],
193 command=callback)
194 e.__class__ = type('DirectEntryMixed', (DirectEntry, DirectGuiMixin), {})
195 e.set_tooltip(tooltip, *tooltip_args)
196 self.__pos_mgr[f'editor_start_{id_}'] = e.pos_pixel()
197 self.__z -= .1
198 return t, e
199
200 def __add_row_option(self, label, text, items, callback, tooltip):
201 tooltip_args = self._common['text_font'], self._common['scale'], self._common['text_fg']
202 t = OnscreenText(
203 label,
204 pos=(.03, self.__z), parent=self._frm,
205 font=self._common['text_font'],
206 scale=self._common['scale'],
207 fg=self._common['text_fg'],
208 wordwrap=20, align=TextNode.ALeft)
209 e = DirectOptionMenuTestable(
210 scale=self._common['scale'],
211 initialitem=text,
212 pos=(.30, 1, self.__z),
213 items=items,
214 parent=self._frm,
215 command=callback,
216 state=NORMAL,
217 relief=FLAT,
218 item_relief=FLAT,
219 frameColor=self._common['frameColor'],
220 item_frameColor=self._common['frameColor'],
221 popupMenu_frameColor=self._common['frameColor'],
222 popupMarker_frameColor=self._common['frameColor'],
223 text_font=self._font,
224 text_fg=self._common['text_fg'],
225 highlightColor=(.9, .9, .9, .9),
226 item_text_font=self._font,
227 item_text_fg=self._common['text_fg'],
228 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
229 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
230 e.__class__ = type('DirectOptionMenuMixed', (DirectOptionMenu, DirectGuiMixin), {})
231 e.set_tooltip(tooltip, *tooltip_args)
232 self.__z -= .1
233 return t, e
234
235 def __load_img_btn(self, path, col):
236 img = OnscreenImage('assets/images/buttons/%s.dds' % path)
237 img.set_transparency(True)
238 img.set_color(col)
239 img.detach_node()
240 return img
241
242 def on_edit_scale(self, txt):
243 if txt:
244 self.__json['model_scale'] = float(txt)
245 else:
246 del self.__json['model_scale']
247
248 def on_edit_mass(self, txt):
249 if txt:
250 self.__json['mass'] = float(txt)
251 else:
252 del self.__json['mass']
253
254 def on_edit_restitution(self, txt):
255 if txt:
256 self.__json['restitution'] = float(txt)
257 else:
258 del self.__json['restitution']
259
260 def on_edit_friction(self, txt):
261 if txt:
262 self.__json['friction'] = float(txt)
263 else:
264 del self.__json['friction']
265
266 def on_edit_id(self, txt):
267 if txt:
268 self.__json['id'] = txt
269 else:
270 del self.__json['id']
271
272 def on_edit_strategy(self, txt):
273 if txt:
274 self.__json['strategy'] = txt
275 self.__entries.strategy_args.set('')
276
277 def on_edit_strategy_args(self, txt):
278 if txt:
279 self.__json['strategy_args'] = txt
280 else:
281 del self.__json['strategy_args']
282
283 def on_edit_class(self, txt):
284 if txt:
285 self.__json['class'] = txt
286
287 def on_edit_count(self, txt):
288 if txt:
289 self.__json['count'] = int(txt)
290 else:
291 del self.__json['count']
292
293 def __new_item(self):
294 curr_index = self.__items.index(self.__json)
295 self.__json = {}
296 curr_index = (curr_index + 1) % len(self.__items)
297 self.__items.insert(curr_index, self.__json)
298 self.__set(self.__json)
299 import pprint
300 pprint.pprint(self.__items)
301
302 def __next_item(self):
303 curr_index = self.__items.index(self.__json)
304 self.__json = self.__items[(curr_index + 1) % len(self.__items)]
305 self.__set(self.__json)
306 import pprint
307 pprint.pprint(self.__items)
308
309 def __delete_item(self):
310 curr_index = self.__items.index(self.__json)
311 if curr_index == len(self.__items): curr_index = 0
312 self.__items.remove(self.__json)
313 if not self.__items:
314 self.__items = [{}]
315 self.__json = self.__items[curr_index]
316 self.__set(self.__json)
317 import pprint
318 pprint.pprint(self.__items)
319
320 def __set(self, json):
321 if 'model_scale' in json:
322 self.__entries.scale.set(str(json['model_scale']))
323 else:
324 self.__entries.scale.set('')
325 if 'mass' in json:
326 self.__entries.mass.set(str(json['mass']))
327 else:
328 self.__entries.mass.set('')
329 if 'restitution' in json:
330 self.__entries.restitution.set(str(json['restitution']))
331 else:
332 self.__entries.restitution.set('')
333 if 'friction' in json:
334 self.__entries.friction.set(str(json['friction']))
335 else:
336 self.__entries.friction.set('')
337 if 'id' in json:
338 self.__entries.id.set(str(json['id']))
339 else:
340 self.__entries.id.set('')
341 if 'strategy' in json:
342 self.__entries.strategy.set(str(json['strategy']))
343 else:
344 self.__entries.strategy.set('')
345 if 'strategy_args' in json:
346 self.__entries.strategy_args.set(str(json['strategy_args']))
347 else:
348 self.__entries.strategy_args.set('')
349 if 'class' in json:
350 self.__entries.item_class.set(str(json['class']))
351 else:
352 self.__entries.item_class.set('')
353 if 'count' in json:
354 self.__entries.count.set(str(json['count']))
355 else:
356 self.__entries.count.set('')
357 import pprint
358 pprint.pprint(self.__items)
359
360 def __save(self):
361 messenger.send('editor-start-items-save', [self.__items])
362
363 def __show_error_popup(self):
364 self.__dialog = OkDialog(dialogName='Strategy args errors',
365 text=_('There are errors in the strategy args.'),
366 command=self.__actually_close)
367 self.__dialog['frameColor'] = (.4, .4, .4, .14)
368 self.__dialog['relief'] = FLAT
369 self.__dialog.component('text0')['fg'] = (.9, .9, .9, 1)
370 self.__dialog.component('text0')['font'] = self._font
371 for b in self.__dialog.buttonList:
372 b['frameColor'] = (.4, .4, .4, .14)
373 b.component('text0')['fg'] = (.9, .9, .9, 1)
374 b.component('text0')['font'] = self._font
375 b.component('text1')['fg'] = (.9, .1, .1, 1)
376 b.component('text1')['font'] = self._font
377 b.component('text2')['fg'] = (.9, .9, .1, 1)
378 b.component('text2')['font'] = self._font
379
380 def __actually_close(self, arg):
381 self.__entries.strategy.set('')
382 self.__entries.strategy_args.set('')
383 self.__dialog.cleanup()
384
385 def destroy(self):
386 self._frm.destroy()
387 messenger.send('editor-start-items-destroy')