1 from os
.path
import exists
3 from importlib
import import_module
4 from inspect
import isclass
5 from panda3d
.core
import AmbientLight
, DirectionalLight
, Point3
, Texture
, \
6 TextPropertiesManager
, TextNode
, Spotlight
, PerspectiveLens
, BitMask32
7 from panda3d
.bullet
import BulletPlaneShape
, BulletGhostNode
8 from direct
.gui
.OnscreenImage
import OnscreenImage
9 from direct
.gui
.OnscreenText
import OnscreenText
10 from direct
.gui
.DirectGui
import DirectButton
, DirectFrame
11 from direct
.gui
.DirectGuiGlobals
import FLAT
, DISABLED
, NORMAL
12 from direct
.showbase
.DirectObject
import DirectObject
13 from pmachines
.items
.background
import Background
14 from pmachines
.sidepanel
import SidePanel
15 from lib
.engine
.gui
.cursor
import MouseCursor
16 from lib
.lib
.p3d
.gfx
import P3dGfxMgr
19 class Scene(DirectObject
):
21 def __init__(self
, world
, exit_cb
, auto_close_instr
, dbg_items
, reload_cb
):
24 self
._exit
_cb
= exit_cb
25 self
._dbg
_items
= dbg_items
26 self
._reload
_cb
= reload_cb
28 self
._cursor
= MouseCursor(
29 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
34 self
._set
_mouse
_plane
()
39 self
._item
_active
= None
42 self
.__restore
_state
()
44 self
._set
_instructions
()
45 self
._bg
= Background()
46 self
._side
_panel
= SidePanel(world
, self
._mouse
_plane
_node
, (-5, 4), (-3, 1), 1, self
.items
)
47 self
._scene
_tsk
= taskMgr
.add(self
.on_frame
, 'on_frame')
59 def screenshot(self
, task
=None):
60 tex
= Texture('screenshot')
61 buffer = base
.win
.make_texture_buffer('screenshot', 512, 512, tex
, True )
62 cam
= base
.make_camera(buffer)
63 cam
.reparent_to(render
)
64 cam
.node().get_lens().set_fov(base
.camLens
.get_fov())
65 cam
.set_pos(0, -20, 0)
72 use_emission_maps
=False,
73 use_occlusion_maps
=True,
76 base
.graphicsEngine
.renderFrame()
77 base
.graphicsEngine
.renderFrame()
78 fname
= self
.__class
__.__name
__
79 if not exists('assets/images/scenes'):
80 makedirs('assets/images/scenes')
81 buffer.save_screenshot('assets/images/scenes/%s.png' % fname
)
83 # frameTexture=buffer.get_texture(), relief=FLAT,
84 # frameSize=(-.2, .2, -.2, .2))
85 return buffer.get_texture()
87 def current_bottom(self
):
89 for item
in self
.items
:
91 curr_bottom
= min(curr_bottom
, item
.get_bottom())
95 [itm
.destroy() for itm
in self
.items
]
100 if hasattr(self
, '_success_txt'):
101 self
._success
_txt
.destroy()
102 del self
._success
_txt
103 self
.__right
_btn
['state'] = NORMAL
109 self
._unset
_mouse
_plane
()
110 [itm
.destroy() for itm
in self
.items
]
112 self
._side
_panel
.destroy()
113 self
._cursor
.destroy()
114 taskMgr
.remove(self
._scene
_tsk
)
115 if hasattr(self
, '_success_txt'):
116 self
._success
_txt
.destroy()
118 def _set_camera(self
):
119 base
.camera
.set_pos(0, -20, 0)
120 base
.camera
.look_at(0, 0, 0)
122 def __load_img_btn(self
, path
, col
):
123 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
124 img
.set_transparency(True)
130 def load_images_btn(path
, col
):
133 (.6, .6, .6, 1), # ready
134 (1, 1, 1, 1), # press
135 (.8, .8, .8, 1), # rollover
141 (.4, .1, .1, .4)]}[col
]
142 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
143 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
145 ('home', self
.on_home
, NORMAL
, abl
, 'gray'),
146 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray'),
147 ('right', self
.on_play
, NORMAL
, abr
, 'green'),
148 ('next', self
.on_next
, DISABLED
, abr
, 'gray'),
149 ('previous', self
.on_prev
, DISABLED
, abr
, 'gray'),
150 ('rewind', self
.reset
, NORMAL
, abr
, 'gray')]
153 for binfo
in btn_info
:
154 imgs
= load_images_btn(binfo
[0], binfo
[4])
155 if binfo
[3] == base
.a2dBottomLeft
:
159 sign
, num
= -1, num_r
161 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
163 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
164 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
165 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
166 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
167 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
168 btn
.set_transparency(True)
170 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
, self
.__next
_btn
, \
171 self
.__prev
_btn
, self
.__rewind
_btn
= btns
173 self
._info
_txt
= OnscreenText(
174 '', parent
=base
.a2dTopRight
, scale
=0.04,
175 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
177 def _unset_gui(self
):
179 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
180 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
181 [btn
.destroy() for btn
in btns
]
183 self
._info
_txt
.destroy()
185 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
186 light
= Spotlight(name
)
188 light
.setLens(PerspectiveLens())
189 light_np
= render
.attach_new_node(light
)
190 light_np
.set_pos(pos
)
191 light_np
.look_at(look_at
)
192 light
.set_color(color
)
193 render
.set_light(light_np
)
196 def _set_lights(self
):
197 alight
= AmbientLight('alight') # for ao
198 alight
.set_color((.15, .15, .15, 1))
199 self
._alnp
= render
.attach_new_node(alight
)
200 render
.set_light(self
._alnp
)
201 self
._key
_light
= self
._set
_spotlight
(
202 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
203 self
._shadow
_light
= self
._set
_spotlight
(
204 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
205 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
206 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
207 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
208 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
210 def _unset_lights(self
):
211 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
212 render
.clear_light(light
)
215 def _set_input(self
):
216 self
.accept('mouse1', self
.on_click_l
)
217 self
.accept('mouse1-up', self
.on_release
)
218 self
.accept('mouse3', self
.on_click_r
)
219 self
.accept('mouse3-up', self
.on_release
)
221 def _unset_input(self
):
222 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
225 def _set_mouse_plane(self
):
226 shape
= BulletPlaneShape((0, -1, 0), 0)
227 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
228 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
229 self
._mouse
_plane
_node
.addShape(shape
)
230 #np = render.attachNewNode(self._mouse_plane_node)
231 #self._world.attachRigidBody(self._mouse_plane_node)
232 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
234 def _unset_mouse_plane(self
):
235 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
238 if not base
.mouseWatcherNode
.has_mouse(): return []
239 p_from
, p_to
= P3dGfxMgr
.world_from_to(base
.mouseWatcherNode
.get_mouse())
240 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
242 def _update_info(self
, item
):
245 txt
= '%.3f %.3f\n%.3f°' % (
246 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
247 self
._info
_txt
['text'] = txt
249 def _on_click(self
, method
):
252 for hit
in self
._get
_hits
():
253 if hit
.get_node() == self
._mouse
_plane
_node
:
254 pos
= hit
.get_hit_pos()
255 for hit
in self
._get
_hits
():
256 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
257 if not self
._item
_active
:
258 self
._item
_active
= item
259 getattr(item
, method
)(pos
)
260 img
= 'move' if method
== 'on_click_l' else 'rotate'
261 if not (img
== 'rotate' and not item
._instantiated
):
262 self
._cursor
.set_image('assets/images/buttons/%s.dds' % img
)
264 def on_click_l(self
):
265 self
._on
_click
('on_click_l')
267 def on_click_r(self
):
268 self
._on
_click
('on_click_r')
270 def on_release(self
):
271 if self
._item
_active
and not self
._item
_active
._first
_command
:
272 self
._commands
= self
._commands
[:self
._command
_idx
]
273 self
._commands
+= [self
._item
_active
]
274 self
._command
_idx
+= 1
275 self
.__prev
_btn
['state'] = NORMAL
276 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
277 self
.__prev
_btn
['frameColor'] = fcols
[0]
278 if self
._item
_active
._command
_idx
== len(self
._item
_active
._commands
) - 1:
279 self
.__next
_btn
['state'] = DISABLED
280 self
.__next
_btn
['frameColor'] = fcols
[1]
281 self
._item
_active
= None
282 [item
.on_release() for item
in self
.items
]
283 self
._cursor
.set_image('assets/images/buttons/arrowUpLeft.dds')
286 for item
in self
.items
:
287 item
.repos_done
= False
288 self
.items
= sorted(self
.items
, key
=lambda itm
: itm
.__class
__.__name
__)
289 [item
.on_aspect_ratio_changed() for item
in self
.items
]
290 self
._side
_panel
.update(self
.items
)
291 max_x
= -float('inf')
292 for item
in self
.items
:
293 if not item
._instantiated
:
294 max_x
= max(item
._np
.get_x(), max_x
)
295 for item
in self
.items
:
296 if not item
._instantiated
:
299 def on_aspect_ratio_changed(self
):
302 def _win_condition(self
):
305 def _fail_condition(self
):
306 return all(itm
.fail_condition() for itm
in self
.items
) and not self
._paused
and self
._state
== 'playing'
308 def on_frame(self
, task
):
309 hits
= self
._get
_hits
()
311 for hit
in self
._get
_hits
():
312 if hit
.get_node() == self
._mouse
_plane
_node
:
313 pos
= hit
.get_hit_pos()
314 hit_nodes
= [hit
.get_node() for hit
in hits
]
315 if self
._item
_active
:
316 items_hit
= [self
._item
_active
]
318 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
319 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
320 [itm
.on_mouse_on() for itm
in items_hit
]
321 [itm
.on_mouse_off() for itm
in items_no_hit
]
322 if pos
and self
._item
_active
:
323 self
._item
_active
.on_mouse_move(pos
)
325 self
._update
_info
(items_hit
[0] if items_hit
else None)
326 if self
._win
_condition
():
328 elif self
._state
== 'playing' and self
._fail
_condition
():
330 if any(itm
._overlapping
for itm
in self
.items
):
331 self
._cursor
.cursor_img
.img
.set_color(.9, .1, .1, 1)
333 self
._cursor
.cursor_img
.img
.set_color(.9, .9, .9, 1)
336 def cb_inst(self
, item
):
340 self
._state
= 'playing'
341 self
.__prev
_btn
['state'] = DISABLED
342 self
.__next
_btn
['state'] = DISABLED
343 self
.__right
_btn
['state'] = DISABLED
344 [itm
.play() for itm
in self
.items
]
347 self
._commands
[self
._command
_idx
].redo()
348 self
._command
_idx
+= 1
349 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
350 self
.__prev
_btn
['state'] = NORMAL
351 self
.__prev
_btn
['frameColor'] = fcols
[0]
352 more_commands
= self
._command
_idx
< len(self
._commands
)
353 self
.__next
_btn
['state'] = NORMAL
if more_commands
else DISABLED
354 self
.__next
_btn
['frameColor'] = fcols
[0] if more_commands
else fcols
[1]
357 self
._command
_idx
-= 1
358 self
._commands
[self
._command
_idx
].undo()
359 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
360 self
.__next
_btn
['state'] = NORMAL
361 self
.__next
_btn
['frameColor'] = fcols
[0]
362 self
.__prev
_btn
['state'] = NORMAL
if self
._command
_idx
else DISABLED
363 self
.__prev
_btn
['frameColor'] = fcols
[0] if self
._command
_idx
else fcols
[1]
368 def _set_instructions(self
):
371 mgr
= TextPropertiesManager
.get_global_ptr()
372 for name
in ['mouse_l', 'mouse_r']:
373 graphic
= OnscreenImage('assets/images/buttons/%s.dds' % name
)
374 graphic
.set_scale(.5)
375 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
376 graphic
.get_texture().set_anisotropic_degree(2)
377 mgr
.set_graphic(name
, graphic
)
379 graphic
.set_transparency(True)
380 graphic
.detach_node()
381 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
382 frameSize
=(-.6, .6, -.3, .3))
383 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
385 font
.set_pixels_per_unit(60)
386 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
387 font
.set_outline((0, 0, 0, 1), .8, .2)
388 self
._txt
= OnscreenText(
389 self
._instr
_txt
(), parent
=frm
, font
=font
, scale
=0.06,
390 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
391 u_l
= self
._txt
.textNode
.get_upper_left_3d()
392 l_r
= self
._txt
.textNode
.get_lower_right_3d()
393 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
396 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
397 z
+= (btn_scale
+ 2 * mar
) / 2
398 self
._txt
['pos'] = -w
/ 2, z
399 u_l
= self
._txt
.textNode
.get_upper_left_3d()
400 l_r
= self
._txt
.textNode
.get_lower_right_3d()
401 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
402 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
403 frm
['frameSize'] = fsz
405 (.6, .6, .6, 1), # ready
406 (1, 1, 1, 1), # press
407 (.8, .8, .8, 1), # rollover
409 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
411 image
=imgs
, scale
=btn_scale
,
412 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
413 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
414 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
415 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
416 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
417 btn
.set_transparency(True)
420 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
423 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
424 frameSize
=(-.6, .6, -.3, .3))
425 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
427 font
.set_pixels_per_unit(60)
428 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
429 font
.set_outline((0, 0, 0, 1), .8, .2)
430 self
._txt
= OnscreenText(
433 font
=font
, scale
=0.2,
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]
440 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
441 z
+= (btn_scale
+ 2 * mar
) / 2
442 self
._txt
['pos'] = 0, 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
449 (.6, .6, .6, 1), # ready
450 (1, 1, 1, 1), # press
451 (.8, .8, .8, 1), # rollover
453 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
455 image
=imgs
, scale
=btn_scale
,
456 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
457 parent
=frm
, command
=self
._on
_end
_home
, 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 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
464 image
=imgs
, scale
=btn_scale
,
465 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
466 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
467 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
468 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
469 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
470 btn
.set_transparency(True)
472 for _file
in glob('pmachines/scenes/*.py'):
473 _fn
= _file
.replace('.py', '').replace('/', '.')
474 for member
in import_module(_fn
).__dict
__.values():
475 if isclass(member
) and issubclass(member
, Scene
) and \
478 scenes
= sorted(scenes
, key
=lambda elm
: elm
.sorting
)
479 enabled
= scenes
.index(self
.__class
__) < len(scenes
) - 1
481 next_scene
= scenes
[scenes
.index(self
.__class
__) + 1]
484 imgs
= [self
.__load
_img
_btn
('right', col
) for col
in colors
]
486 image
=imgs
, scale
=btn_scale
,
487 pos
=(2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
488 parent
=frm
, command
=self
._on
_next
_scene
,
489 extraArgs
=[frm
, next_scene
], relief
=FLAT
,
490 frameColor
=(.6, .6, .6, .08),
491 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
492 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
493 btn
['state'] = NORMAL
if enabled
else DISABLED
494 btn
.set_transparency(True)
497 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
500 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
501 frameSize
=(-.6, .6, -.3, .3))
502 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
504 font
.set_pixels_per_unit(60)
505 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
506 font
.set_outline((0, 0, 0, 1), .8, .2)
507 self
._txt
= OnscreenText(
508 _('You have failed!'),
510 font
=font
, scale
=0.2,
512 u_l
= self
._txt
.textNode
.get_upper_left_3d()
513 l_r
= self
._txt
.textNode
.get_lower_right_3d()
514 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
517 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
518 z
+= (btn_scale
+ 2 * mar
) / 2
519 self
._txt
['pos'] = 0, z
520 u_l
= self
._txt
.textNode
.get_upper_left_3d()
521 l_r
= self
._txt
.textNode
.get_lower_right_3d()
522 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
523 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
524 frm
['frameSize'] = fsz
526 (.6, .6, .6, 1), # ready
527 (1, 1, 1, 1), # press
528 (.8, .8, .8, 1), # rollover
530 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
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
_end
_home
, extraArgs
=[frm
],
535 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
536 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
537 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
538 btn
.set_transparency(True)
539 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
541 image
=imgs
, scale
=btn_scale
,
542 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
543 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
544 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
545 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
546 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
547 btn
.set_transparency(True)
549 def _on_restart(self
, frm
):
550 self
.__on
_close
_instructions
(frm
)
553 def _on_end_home(self
, frm
):
554 self
.__on
_close
_instructions
(frm
)
557 def _on_next_scene(self
, frm
, scene
):
558 self
.__on
_close
_instructions
(frm
)
559 self
._reload
_cb
(scene
)
561 def __store_state(self
):
563 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
564 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
565 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
567 btn
['state'] = DISABLED
568 [itm
.store_state() for itm
in self
.items
]
570 def __restore_state(self
):
572 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
573 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
574 for btn
, state
in zip(btns
, self
.__btn
_state
):
576 [itm
.restore_state() for itm
in self
.items
]
579 def __on_close_instructions(self
, frm
):
581 self
.__restore
_state
()