ya2 · news · projects · code · about

functional tests: editor
[pmachines.git] / pmachines / editor / start_items.py
CommitLineData
9981d472
FC
1from collections import namedtuple
2from glob import glob
3from importlib import import_module
4from os.path import basename
5from inspect import isclass
c48bfe58 6from panda3d.core import Texture, TextNode, LPoint3f
9981d472 7from direct.gui.OnscreenImage import OnscreenImage
3466af49 8from direct.gui.DirectGui import DirectButton, DirectEntry, DirectOptionMenu, OkDialog, DirectFrame
9981d472
FC
9from direct.gui.DirectGuiGlobals import FLAT, NORMAL
10from direct.gui.OnscreenText import OnscreenText
11from direct.showbase.DirectObject import DirectObject
a810a9ed 12from ya2.utils.gfx import DirectGuiMixin
bf77b5d5 13from pmachines.items.item import Item, ItemStrategy
c617fd93 14from pmachines.editor.augmented_frame import AugmentedDirectFrame
3466af49 15from pmachines.gui.options_page import DirectOptionMenuTestable
9981d472
FC
16
17
18class StartItems(DirectObject):
19
3466af49 20 def __init__(self, json_items, pos_mgr):
9981d472
FC
21 super().__init__()
22 self.__items = json_items
23 self.__json = self.__items[0]
3466af49 24 self.__pos_mgr = pos_mgr
9981d472
FC
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')}
a810a9ed 41 tooltip_args = self._common['text_font'], self._common['scale'], self._common['text_fg']
9981d472 42 w, h = .8, 1.04
c617fd93 43 self._frm = AugmentedDirectFrame(frameColor=(.4, .4, .4, .06),
c48bfe58 44 frameSize=(0, w, -h, 0),
c617fd93
FC
45 parent=base.a2dTopLeft,
46 pos=(0, 0, 0),
47 delta_drag=LPoint3f(w, 0, -h),
3466af49
FC
48 collapse_pos=(w - .06, 1, -h + .06),
49 pos_mgr=pos_mgr,
50 frame_name='start')
9981d472
FC
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__]))
a810a9ed 61 t, item_class_entry = self.__add_row_option(_('class'), '', new_items, self.on_edit_class, _('class of the item'))
3466af49
FC
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 = ['']
9981d472
FC
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:
3466af49
FC
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'))
9981d472
FC
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)
a810a9ed 122 b = DirectButton(
9981d472
FC
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'))
a810a9ed 129 b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
3466af49 130 pos_mgr['editor_start_close'] = b.pos_pixel()
a810a9ed
FC
131 b.set_tooltip(_('Close'), *tooltip_args)
132 b = DirectButton(
9981d472
FC
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'))
a810a9ed 139 b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
3466af49 140 pos_mgr['editor_start_save'] = b.pos_pixel()
a810a9ed
FC
141 b.set_tooltip(_('Save'), *tooltip_args)
142 b = DirectButton(
9981d472
FC
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'))
a810a9ed 149 b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
3466af49 150 pos_mgr['editor_start_delete'] = b.pos_pixel()
a810a9ed
FC
151 b.set_tooltip(_('Delete'), *tooltip_args)
152 b = DirectButton(
9981d472
FC
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'))
a810a9ed 159 b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
3466af49 160 pos_mgr['editor_start_new'] = b.pos_pixel()
a810a9ed
FC
161 b.set_tooltip(_('New'), *tooltip_args)
162 b = DirectButton(
9981d472
FC
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'))
a810a9ed 169 b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
3466af49 170 pos_mgr['editor_start_next'] = b.pos_pixel()
a810a9ed 171 b.set_tooltip(_('Next'), *tooltip_args)
9981d472 172
3466af49 173 def __add_row(self, id_, label, text, callback, tooltip):
9981d472 174 tw = 10
a810a9ed 175 tooltip_args = self._common['text_font'], self._common['scale'], self._common['text_fg']
9981d472
FC
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)
a810a9ed
FC
194 e.__class__ = type('DirectEntryMixed', (DirectEntry, DirectGuiMixin), {})
195 e.set_tooltip(tooltip, *tooltip_args)
3466af49 196 self.__pos_mgr[f'editor_start_{id_}'] = e.pos_pixel()
9981d472
FC
197 self.__z -= .1
198 return t, e
199
a810a9ed
FC
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']
9981d472
FC
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)
3466af49 209 e = DirectOptionMenuTestable(
9981d472
FC
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'))
a810a9ed
FC
230 e.__class__ = type('DirectOptionMenuMixed', (DirectOptionMenu, DirectGuiMixin), {})
231 e.set_tooltip(tooltip, *tooltip_args)
9981d472
FC
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):
3466af49
FC
243 if txt:
244 self.__json['model_scale'] = float(txt)
245 else:
246 del self.__json['model_scale']
9981d472
FC
247
248 def on_edit_mass(self, txt):
3466af49
FC
249 if txt:
250 self.__json['mass'] = float(txt)
251 else:
252 del self.__json['mass']
9981d472
FC
253
254 def on_edit_restitution(self, txt):
3466af49
FC
255 if txt:
256 self.__json['restitution'] = float(txt)
257 else:
258 del self.__json['restitution']
9981d472
FC
259
260 def on_edit_friction(self, txt):
3466af49
FC
261 if txt:
262 self.__json['friction'] = float(txt)
263 else:
264 del self.__json['friction']
9981d472
FC
265
266 def on_edit_id(self, txt):
3466af49
FC
267 if txt:
268 self.__json['id'] = txt
269 else:
270 del self.__json['id']
9981d472
FC
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):
3466af49
FC
278 if txt:
279 self.__json['strategy_args'] = txt
280 else:
281 del self.__json['strategy_args']
9981d472
FC
282
283 def on_edit_class(self, txt):
284 if txt:
285 self.__json['class'] = txt
286
287 def on_edit_count(self, txt):
3466af49
FC
288 if txt:
289 self.__json['count'] = int(txt)
290 else:
291 del self.__json['count']
9981d472
FC
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')