ya2 · news · projects · code · about

abstract test positions in world units
[pmachines.git] / pmachines / scene.py
1 from os.path import exists
2 from os import makedirs
3 from glob import glob
4 from logging import debug, info
5 from importlib import import_module
6 from inspect import isclass
7 from panda3d.core import AmbientLight, DirectionalLight, Point3, Texture, \
8 TextPropertiesManager, TextNode, Spotlight, PerspectiveLens, BitMask32, NodePath
9 from panda3d.bullet import BulletPlaneShape, BulletGhostNode
10 from direct.gui.OnscreenImage import OnscreenImage
11 from direct.gui.OnscreenText import OnscreenText
12 from direct.gui.DirectGui import DirectButton, DirectFrame
13 from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
14 from direct.showbase.DirectObject import DirectObject
15 from pmachines.items.background import Background
16 from pmachines.gui.sidepanel import SidePanel
17 from ya2.utils.cursor import MouseCursor
18 from ya2.p3d.gfx import P3dGfxMgr
19 from ya2.p3d.p3d import LibP3d
20
21
22 class Scene(DirectObject):
23
24 def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb, scenes, pos_mgr, testing, mouse_coords):
25 super().__init__()
26 self._world = world
27 self._exit_cb = exit_cb
28 self._testing = testing
29 self._mouse_coords = mouse_coords
30 self._dbg_items = dbg_items
31 self._reload_cb = reload_cb
32 self._pos_mgr = pos_mgr
33 self._pos_mgr.reset()
34 self._scenes = scenes
35 self._start_evt_time = None
36 self._enforce_res = ''
37 self.accept('enforce_res', self.enforce_res)
38 self._set_camera()
39 self._cursor = MouseCursor(
40 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
41 (.01, .01))
42 self._set_gui()
43 self._set_lights()
44 self._set_input()
45 self._set_mouse_plane()
46 self.items = []
47 self._test_items = []
48 self.reset()
49 self._state = 'init'
50 self._paused = False
51 self._item_active = None
52 if auto_close_instr:
53 self.__store_state()
54 self.__restore_state()
55 else:
56 self._set_instructions()
57 self._bg = Background()
58 self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.items)
59 self._scene_tsk = taskMgr.add(self.on_frame, 'on_frame')
60
61 @staticmethod
62 def name():
63 return ''
64
65 def _instr_txt(self):
66 return ''
67
68 def _set_items(self):
69 self.items = []
70 self._test_items = []
71
72 def screenshot(self, task=None):
73 tex = Texture('screenshot')
74 buffer = base.win.make_texture_buffer('screenshot', 512, 512, tex, True )
75 cam = base.make_camera(buffer)
76 cam.reparent_to(render)
77 cam.node().get_lens().set_fov(base.camLens.get_fov())
78 cam.set_pos(0, -20, 0)
79 cam.look_at(0, 0, 0)
80 import simplepbr
81 simplepbr.init(
82 window=buffer,
83 camera_node=cam,
84 use_normal_maps=True,
85 use_emission_maps=False,
86 use_occlusion_maps=True,
87 msaa_samples=4,
88 enable_shadows=True)
89 base.graphicsEngine.renderFrame()
90 base.graphicsEngine.renderFrame()
91 fname = self.__class__.__name__
92 if not exists('assets/images/scenes'):
93 makedirs('assets/images/scenes')
94 buffer.save_screenshot('assets/images/scenes/%s.png' % fname)
95 # img = DirectButton(
96 # frameTexture=buffer.get_texture(), relief=FLAT,
97 # frameSize=(-.2, .2, -.2, .2))
98 return buffer.get_texture()
99
100 def current_bottom(self):
101 curr_bottom = 1
102 for item in self.items:
103 if item.repos_done:
104 curr_bottom = min(curr_bottom, item.get_bottom())
105 return curr_bottom
106
107 def reset(self):
108 [itm.destroy() for itm in self.items]
109 [itm.remove_node() for itm in self._test_items]
110 self.items = []
111 self._test_items = []
112 self._set_items()
113 self._set_test_items()
114 self._state = 'init'
115 self._commands = []
116 self._command_idx = 0
117 self._start_evt_time = None
118 if hasattr(self, '_success_txt'):
119 self._success_txt.destroy()
120 del self._success_txt
121 self.__right_btn['state'] = NORMAL
122
123 def enforce_res(self, val):
124 self._enforce_res = val
125 info('enforce res: ' + val)
126
127 def destroy(self):
128 self.ignore('enforce_res')
129 self._unset_gui()
130 self._unset_lights()
131 self._unset_input()
132 self._unset_mouse_plane()
133 [itm.destroy() for itm in self.items]
134 [itm.remove_node() for itm in self._test_items]
135 self._bg.destroy()
136 self._side_panel.destroy()
137 self._cursor.destroy()
138 taskMgr.remove(self._scene_tsk)
139 if hasattr(self, '_success_txt'):
140 self._success_txt.destroy()
141
142 def _set_camera(self):
143 base.camera.set_pos(0, -20, 0)
144 base.camera.look_at(0, 0, 0)
145
146 def __load_img_btn(self, path, col):
147 img = OnscreenImage('assets/images/buttons/%s.dds' % path)
148 img.set_transparency(True)
149 img.set_color(col)
150 img.detach_node()
151 return img
152
153 def _set_gui(self):
154 def load_images_btn(path, col):
155 colors = {
156 'gray': [
157 (.6, .6, .6, 1), # ready
158 (1, 1, 1, 1), # press
159 (.8, .8, .8, 1), # rollover
160 (.4, .4, .4, .4)],
161 'green': [
162 (.1, .68, .1, 1),
163 (.1, 1, .1, 1),
164 (.1, .84, .1, 1),
165 (.4, .1, .1, .4)]}[col]
166 return [self.__load_img_btn(path, col) for col in colors]
167 abl, abr = base.a2dBottomLeft, base.a2dBottomRight
168 btn_info = [
169 ('home', self.on_home, NORMAL, abl, 'gray'),
170 ('information', self._set_instructions, NORMAL, abl, 'gray'),
171 ('right', self.on_play, NORMAL, abr, 'green'),
172 #('next', self.on_next, DISABLED, abr, 'gray'),
173 #('previous', self.on_prev, DISABLED, abr, 'gray'),
174 #('rewind', self.reset, NORMAL, abr, 'gray')
175 ]
176 num_l = num_r = 0
177 btns = []
178 for binfo in btn_info:
179 imgs = load_images_btn(binfo[0], binfo[4])
180 if binfo[3] == base.a2dBottomLeft:
181 sign, num = 1, num_l
182 num_l += 1
183 else:
184 sign, num = -1, num_r
185 num_r += 1
186 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
187 btn = DirectButton(
188 image=imgs, scale=.05, pos=(sign * (.06 + .11 * num), 1, .06),
189 parent=binfo[3], command=binfo[1], state=binfo[2], relief=FLAT,
190 frameColor=fcols[0] if binfo[2] == NORMAL else fcols[1],
191 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
192 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
193 btn.set_transparency(True)
194 self._pos_mgr.register(binfo[0], LibP3d.wdg_pos(btn))
195 btns += [btn]
196 self.__home_btn, self.__info_btn, self.__right_btn = btns
197 # , self.__next_btn, self.__prev_btn, self.__rewind_btn
198 if self._dbg_items:
199 self._info_txt = OnscreenText(
200 '', parent=base.a2dTopRight, scale=0.04,
201 pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
202 if self._mouse_coords:
203 self._coords_txt = OnscreenText(
204 '', parent=base.a2dTopRight, scale=0.04,
205 pos=(-.03, -.12), fg=(.9, .9, .9, 1), align=TextNode.A_right)
206 def update_coords(task):
207 pos = None
208 for hit in self._get_hits():
209 if hit.get_node() == self._mouse_plane_node:
210 pos = hit.get_hit_pos()
211 if pos:
212 txt = '%s %s' % (round(pos.x, 3),
213 round(pos.z, 3))
214 self._coords_txt['text'] = txt
215 return task.cont
216 self._coords_tsk = taskMgr.add(update_coords, 'update_coords')
217
218 def _unset_gui(self):
219 btns = [
220 self.__home_btn, self.__info_btn, self.__right_btn,
221 #self.__next_btn, self.__prev_btn, self.__rewind_btn
222 ]
223 [btn.destroy() for btn in btns]
224 if self._dbg_items:
225 self._info_txt.destroy()
226 if self._mouse_coords:
227 taskMgr.remove(self._coords_tsk)
228 self._coords_txt.destroy()
229
230 def _set_spotlight(self, name, pos, look_at, color, shadows=False):
231 light = Spotlight(name)
232 if shadows:
233 light.setLens(PerspectiveLens())
234 light_np = render.attach_new_node(light)
235 light_np.set_pos(pos)
236 light_np.look_at(look_at)
237 light.set_color(color)
238 render.set_light(light_np)
239 return light_np
240
241 def _set_lights(self):
242 alight = AmbientLight('alight') # for ao
243 alight.set_color((.15, .15, .15, 1))
244 self._alnp = render.attach_new_node(alight)
245 render.set_light(self._alnp)
246 self._key_light = self._set_spotlight(
247 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
248 self._shadow_light = self._set_spotlight(
249 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
250 self._shadow_light.node().set_shadow_caster(True, 2048, 2048)
251 self._shadow_light.node().get_lens().set_film_size(2048, 2048)
252 self._shadow_light.node().get_lens().set_near_far(1, 256)
253 self._shadow_light.node().set_camera_mask(BitMask32(0x01))
254
255 def _unset_lights(self):
256 for light in [self._alnp, self._key_light, self._shadow_light]:
257 render.clear_light(light)
258 light.remove_node()
259
260 def _set_input(self):
261 self.accept('mouse1', self.on_click_l)
262 self.accept('mouse1-up', self.on_release)
263 self.accept('mouse3', self.on_click_r)
264 self.accept('mouse3-up', self.on_release)
265
266 def _unset_input(self):
267 for evt in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
268 self.ignore(evt)
269
270 def _set_mouse_plane(self):
271 shape = BulletPlaneShape((0, -1, 0), 0)
272 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
273 self._mouse_plane_node = BulletGhostNode('mouse plane')
274 self._mouse_plane_node.addShape(shape)
275 #np = render.attachNewNode(self._mouse_plane_node)
276 #self._world.attachRigidBody(self._mouse_plane_node)
277 self._world.attach_ghost(self._mouse_plane_node)
278
279 def _unset_mouse_plane(self):
280 self._world.remove_ghost(self._mouse_plane_node)
281
282 def _get_hits(self):
283 if not base.mouseWatcherNode.has_mouse(): return []
284 p_from, p_to = P3dGfxMgr.world_from_to(base.mouseWatcherNode.get_mouse())
285 return self._world.ray_test_all(p_from, p_to).get_hits()
286
287 def _update_info(self, item):
288 txt = ''
289 if item:
290 txt = '%.3f %.3f\n%.3f°' % (
291 item._np.get_x(), item._np.get_z(), item._np.get_r())
292 self._info_txt['text'] = txt
293
294 def _on_click(self, method):
295 if self._paused:
296 return
297 for hit in self._get_hits():
298 if hit.get_node() == self._mouse_plane_node:
299 pos = hit.get_hit_pos()
300 for hit in self._get_hits():
301 for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
302 if not self._item_active:
303 self._item_active = item
304 getattr(item, method)(pos)
305 img = 'move' if method == 'on_click_l' else 'rotate'
306 if not (img == 'rotate' and not item._instantiated):
307 self._cursor.set_image('assets/images/buttons/%s.dds' % img)
308
309 def on_click_l(self):
310 self._on_click('on_click_l')
311
312 def on_click_r(self):
313 self._on_click('on_click_r')
314
315 def on_release(self):
316 if self._item_active and not self._item_active._first_command:
317 self._commands = self._commands[:self._command_idx]
318 self._commands += [self._item_active]
319 self._command_idx += 1
320 #self.__prev_btn['state'] = NORMAL
321 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
322 #self.__prev_btn['frameColor'] = fcols[0]
323 #if self._item_active._command_idx == len(self._item_active._commands) - 1:
324 # self.__next_btn['state'] = DISABLED
325 # self.__next_btn['frameColor'] = fcols[1]
326 self._item_active = None
327 [item.on_release() for item in self.items]
328 self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
329
330 def repos(self):
331 for item in self.items:
332 item.repos_done = False
333 self.items = sorted(self.items, key=lambda itm: itm.__class__.__name__)
334 [item.on_aspect_ratio_changed() for item in self.items]
335 self._side_panel.update(self.items)
336 max_x = -float('inf')
337 for item in self.items:
338 if not item._instantiated:
339 max_x = max(item._np.get_x(), max_x)
340 for item in self.items:
341 if not item._instantiated:
342 item.repos_x(max_x)
343
344 def on_aspect_ratio_changed(self):
345 self.repos()
346
347 def _win_condition(self):
348 pass
349
350 def _fail_condition(self):
351 return all(itm.fail_condition() for itm in self.items) and not self._paused and self._state == 'playing'
352
353 def on_frame(self, task):
354 hits = self._get_hits()
355 pos = None
356 for hit in self._get_hits():
357 if hit.get_node() == self._mouse_plane_node:
358 pos = hit.get_hit_pos()
359 hit_nodes = [hit.get_node() for hit in hits]
360 if self._item_active:
361 items_hit = [self._item_active]
362 else:
363 items_hit = [itm for itm in self.items if itm.node in hit_nodes]
364 items_no_hit = [itm for itm in self.items if itm not in items_hit]
365 [itm.on_mouse_on() for itm in items_hit]
366 [itm.on_mouse_off() for itm in items_no_hit]
367 if pos and self._item_active:
368 self._item_active.on_mouse_move(pos)
369 if self._dbg_items:
370 self._update_info(items_hit[0] if items_hit else None)
371 if self._win_condition():
372 self._start_evt_time = None
373 self._set_fail() if self._enforce_res == 'fail' else self._set_win()
374 elif self._state == 'playing' and self._fail_condition():
375 self._start_evt_time = None
376 self._set_win() if self._enforce_res == 'win' else self._set_fail()
377 elif self._testing and self._start_evt_time and globalClock.getFrameTime() - self._start_evt_time > 5.0:
378 self._start_evt_time = None
379 self._set_win() if self._enforce_res == 'win' else self._set_fail()
380 if any(itm._overlapping for itm in self.items):
381 self._cursor.cursor_img.img.set_color(.9, .1, .1, 1)
382 else:
383 self._cursor.cursor_img.img.set_color(.9, .9, .9, 1)
384 return task.cont
385
386 def cb_inst(self, item):
387 self.items += [item]
388
389 def on_play(self):
390 self._state = 'playing'
391 #self.__prev_btn['state'] = DISABLED
392 #self.__next_btn['state'] = DISABLED
393 self.__right_btn['state'] = DISABLED
394 [itm.play() for itm in self.items]
395 self._start_evt_time = globalClock.getFrameTime()
396
397 def on_next(self):
398 self._commands[self._command_idx].redo()
399 self._command_idx += 1
400 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
401 #self.__prev_btn['state'] = NORMAL
402 #self.__prev_btn['frameColor'] = fcols[0]
403 more_commands = self._command_idx < len(self._commands)
404 #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
405 #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
406
407 def on_prev(self):
408 self._command_idx -= 1
409 self._commands[self._command_idx].undo()
410 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
411 #self.__next_btn['state'] = NORMAL
412 #self.__next_btn['frameColor'] = fcols[0]
413 #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
414 #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
415
416 def on_home(self):
417 self._exit_cb()
418
419 def _set_instructions(self):
420 self._paused = True
421 self.__store_state()
422 mgr = TextPropertiesManager.get_global_ptr()
423 for name in ['mouse_l', 'mouse_r']:
424 graphic = OnscreenImage('assets/images/buttons/%s.dds' % name)
425 graphic.set_scale(.5)
426 graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
427 graphic.get_texture().set_anisotropic_degree(2)
428 mgr.set_graphic(name, graphic)
429 graphic.set_z(-.2)
430 graphic.set_transparency(True)
431 graphic.detach_node()
432 frm = DirectFrame(frameColor=(.4, .4, .4, .06),
433 frameSize=(-.6, .6, -.3, .3))
434 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
435 font.clear()
436 font.set_pixels_per_unit(60)
437 font.set_minfilter(Texture.FTLinearMipmapLinear)
438 font.set_outline((0, 0, 0, 1), .8, .2)
439 self._txt = OnscreenText(
440 self._instr_txt(), parent=frm, font=font, scale=0.06,
441 fg=(.9, .9, .9, 1), align=TextNode.A_left)
442 u_l = self._txt.textNode.get_upper_left_3d()
443 l_r = self._txt.textNode.get_lower_right_3d()
444 w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
445 btn_scale = .05
446 mar = .06 # margin
447 z = h / 2 - font.get_line_height() * self._txt['scale'][1]
448 z += (btn_scale + 2 * mar) / 2
449 self._txt['pos'] = -w / 2, z
450 u_l = self._txt.textNode.get_upper_left_3d()
451 l_r = self._txt.textNode.get_lower_right_3d()
452 c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
453 fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
454 frm['frameSize'] = fsz
455 colors = [
456 (.6, .6, .6, 1), # ready
457 (1, 1, 1, 1), # press
458 (.8, .8, .8, 1), # rollover
459 (.4, .4, .4, .4)]
460 imgs = [self.__load_img_btn('exitRight', col) for col in colors]
461 btn = DirectButton(
462 image=imgs, scale=btn_scale,
463 pos=(l_r[0] - btn_scale, 1, l_r[2] - mar - btn_scale),
464 parent=frm, command=self.__on_close_instructions, extraArgs=[frm],
465 relief=FLAT, frameColor=(.6, .6, .6, .08),
466 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
467 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
468 btn.set_transparency(True)
469 self._pos_mgr.register('close_instructions', LibP3d.wdg_pos(btn))
470
471 def _set_win(self):
472 loader.load_sfx('assets/audio/sfx/success.ogg').play()
473 self._paused = True
474 self.__store_state()
475 frm = DirectFrame(frameColor=(.4, .4, .4, .06),
476 frameSize=(-.6, .6, -.3, .3))
477 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
478 font.clear()
479 font.set_pixels_per_unit(60)
480 font.set_minfilter(Texture.FTLinearMipmapLinear)
481 font.set_outline((0, 0, 0, 1), .8, .2)
482 self._txt = OnscreenText(
483 _('You win!'),
484 parent=frm,
485 font=font, scale=0.2,
486 fg=(.9, .9, .9, 1))
487 u_l = self._txt.textNode.get_upper_left_3d()
488 l_r = self._txt.textNode.get_lower_right_3d()
489 w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
490 btn_scale = .05
491 mar = .06 # margin
492 z = h / 2 - font.get_line_height() * self._txt['scale'][1]
493 z += (btn_scale + 2 * mar) / 2
494 self._txt['pos'] = 0, z
495 u_l = self._txt.textNode.get_upper_left_3d()
496 l_r = self._txt.textNode.get_lower_right_3d()
497 c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
498 fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
499 frm['frameSize'] = fsz
500 colors = [
501 (.6, .6, .6, 1), # ready
502 (1, 1, 1, 1), # press
503 (.8, .8, .8, 1), # rollover
504 (.4, .4, .4, .4)]
505 imgs = [self.__load_img_btn('home', col) for col in colors]
506 btn = DirectButton(
507 image=imgs, scale=btn_scale,
508 pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
509 parent=frm, command=self._on_end_home, extraArgs=[frm],
510 relief=FLAT, frameColor=(.6, .6, .6, .08),
511 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
512 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
513 btn.set_transparency(True)
514 self._pos_mgr.register('home_win', LibP3d.wdg_pos(btn))
515 imgs = [self.__load_img_btn('rewind', col) for col in colors]
516 btn = DirectButton(
517 image=imgs, scale=btn_scale,
518 pos=(0, 1, l_r[2] - mar - btn_scale),
519 parent=frm, command=self._on_restart, extraArgs=[frm],
520 relief=FLAT, frameColor=(.6, .6, .6, .08),
521 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
522 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
523 self._pos_mgr.register('replay', LibP3d.wdg_pos(btn))
524 btn.set_transparency(True)
525 enabled = self._scenes.index(self.__class__) < len(self._scenes) - 1
526 if enabled:
527 next_scene = self._scenes[self._scenes.index(self.__class__) + 1]
528 else:
529 next_scene = None
530 imgs = [self.__load_img_btn('right', col) for col in colors]
531 btn = DirectButton(
532 image=imgs, scale=btn_scale,
533 pos=(2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
534 parent=frm, command=self._on_next_scene,
535 extraArgs=[frm, next_scene], relief=FLAT,
536 frameColor=(.6, .6, .6, .08),
537 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
538 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
539 btn['state'] = NORMAL if enabled else DISABLED
540 self._pos_mgr.register('next', LibP3d.wdg_pos(btn))
541 btn.set_transparency(True)
542
543 def _set_fail(self):
544 loader.load_sfx('assets/audio/sfx/success.ogg').play()
545 self._paused = True
546 self.__store_state()
547 frm = DirectFrame(frameColor=(.4, .4, .4, .06),
548 frameSize=(-.6, .6, -.3, .3))
549 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
550 font.clear()
551 font.set_pixels_per_unit(60)
552 font.set_minfilter(Texture.FTLinearMipmapLinear)
553 font.set_outline((0, 0, 0, 1), .8, .2)
554 self._txt = OnscreenText(
555 _('You have failed!'),
556 parent=frm,
557 font=font, scale=0.2,
558 fg=(.9, .9, .9, 1))
559 u_l = self._txt.textNode.get_upper_left_3d()
560 l_r = self._txt.textNode.get_lower_right_3d()
561 w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
562 btn_scale = .05
563 mar = .06 # margin
564 z = h / 2 - font.get_line_height() * self._txt['scale'][1]
565 z += (btn_scale + 2 * mar) / 2
566 self._txt['pos'] = 0, z
567 u_l = self._txt.textNode.get_upper_left_3d()
568 l_r = self._txt.textNode.get_lower_right_3d()
569 c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
570 fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
571 frm['frameSize'] = fsz
572 colors = [
573 (.6, .6, .6, 1), # ready
574 (1, 1, 1, 1), # press
575 (.8, .8, .8, 1), # rollover
576 (.4, .4, .4, .4)]
577 imgs = [self.__load_img_btn('home', col) for col in colors]
578 btn = DirectButton(
579 image=imgs, scale=btn_scale,
580 pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
581 parent=frm, command=self._on_end_home, extraArgs=[frm],
582 relief=FLAT, frameColor=(.6, .6, .6, .08),
583 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
584 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
585 self._pos_mgr.register('home_win', LibP3d.wdg_pos(btn))
586 btn.set_transparency(True)
587 imgs = [self.__load_img_btn('rewind', col) for col in colors]
588 btn = DirectButton(
589 image=imgs, scale=btn_scale,
590 pos=(0, 1, l_r[2] - mar - btn_scale),
591 parent=frm, command=self._on_restart, extraArgs=[frm],
592 relief=FLAT, frameColor=(.6, .6, .6, .08),
593 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
594 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
595 self._pos_mgr.register('replay', LibP3d.wdg_pos(btn))
596 btn.set_transparency(True)
597
598 def _on_restart(self, frm):
599 self.__on_close_instructions(frm)
600 self.reset()
601
602 def _on_end_home(self, frm):
603 self.__on_close_instructions(frm)
604 self.on_home()
605
606 def _on_next_scene(self, frm, scene):
607 self.__on_close_instructions(frm)
608 self._reload_cb(scene)
609
610 def __store_state(self):
611 btns = [
612 self.__home_btn, self.__info_btn, self.__right_btn,
613 #self.__next_btn, self.__prev_btn, self.__rewind_btn
614 ]
615 self.__btn_state = [btn['state'] for btn in btns]
616 for btn in btns:
617 btn['state'] = DISABLED
618 [itm.store_state() for itm in self.items]
619
620 def __restore_state(self):
621 btns = [
622 self.__home_btn, self.__info_btn, self.__right_btn,
623 #self.__next_btn, self.__prev_btn, self.__rewind_btn
624 ]
625 for btn, state in zip(btns, self.__btn_state):
626 btn['state'] = state
627 [itm.restore_state() for itm in self.items]
628 self._paused = False
629
630 def __on_close_instructions(self, frm):
631 frm.remove_node()
632 self.__restore_state()
633
634 def _set_test_items(self):
635 def frame_after(task):
636 self._define_test_items()
637 for itm in self._test_items:
638 self._pos_mgr.register(itm.name, P3dGfxMgr.pos2d_p2d(itm))
639 taskMgr.doMethodLater(.01, frame_after, 'frame after')
640
641 def _set_test_item(self, name, pos):
642 self._test_items += [NodePath(name)]
643 self._test_items[-1].set_pos(pos[0], 0, pos[1])