ya2 · news · projects · code · about

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