ya2 · news · projects · code · about

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