1 from os
.path
import exists
2 from os
import makedirs
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 game
.items
.background
import Background
16 from game
.sidepanel
import SidePanel
17 from ya2
.engine
.gui
.cursor
import MouseCursor
18 from ya2
.lib
.p3d
.gfx
import P3dGfxMgr
21 class Scene(DirectObject
):
23 def __init__(self
, world
, exit_cb
, auto_close_instr
, dbg_items
, reload_cb
, scenes
):
26 self
._exit
_cb
= exit_cb
27 self
._dbg
_items
= dbg_items
28 self
._reload
_cb
= reload_cb
30 self
._enforce
_res
= ''
31 self
.accept('enforce_res', self
.enforce_res
)
33 self
._cursor
= MouseCursor(
34 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
39 self
._set
_mouse
_plane
()
44 self
._item
_active
= None
47 self
.__restore
_state
()
49 self
._set
_instructions
()
50 self
._bg
= Background()
51 self
._side
_panel
= SidePanel(world
, self
._mouse
_plane
_node
, (-5, 4), (-3, 1), 1, self
.items
)
52 self
._scene
_tsk
= taskMgr
.add(self
.on_frame
, 'on_frame')
64 def screenshot(self
, task
=None):
65 tex
= Texture('screenshot')
66 buffer = base
.win
.make_texture_buffer('screenshot', 512, 512, tex
, True )
67 cam
= base
.make_camera(buffer)
68 cam
.reparent_to(render
)
69 cam
.node().get_lens().set_fov(base
.camLens
.get_fov())
70 cam
.set_pos(0, -20, 0)
77 use_emission_maps
=False,
78 use_occlusion_maps
=True,
81 base
.graphicsEngine
.renderFrame()
82 base
.graphicsEngine
.renderFrame()
83 fname
= self
.__class
__.__name
__
84 if not exists('assets/images/scenes'):
85 makedirs('assets/images/scenes')
86 buffer.save_screenshot('assets/images/scenes/%s.png' % fname
)
88 # frameTexture=buffer.get_texture(), relief=FLAT,
89 # frameSize=(-.2, .2, -.2, .2))
90 return buffer.get_texture()
92 def current_bottom(self
):
94 for item
in self
.items
:
96 curr_bottom
= min(curr_bottom
, item
.get_bottom())
100 [itm
.destroy() for itm
in self
.items
]
104 self
._command
_idx
= 0
105 if hasattr(self
, '_success_txt'):
106 self
._success
_txt
.destroy()
107 del self
._success
_txt
108 self
.__right
_btn
['state'] = NORMAL
110 def enforce_res(self
, val
):
111 self
._enforce
_res
= val
112 info('enforce res: ' + val
)
115 self
.ignore('enforce_res')
119 self
._unset
_mouse
_plane
()
120 [itm
.destroy() for itm
in self
.items
]
122 self
._side
_panel
.destroy()
123 self
._cursor
.destroy()
124 taskMgr
.remove(self
._scene
_tsk
)
125 if hasattr(self
, '_success_txt'):
126 self
._success
_txt
.destroy()
128 def _set_camera(self
):
129 base
.camera
.set_pos(0, -20, 0)
130 base
.camera
.look_at(0, 0, 0)
132 def __load_img_btn(self
, path
, col
):
133 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
134 img
.set_transparency(True)
140 def load_images_btn(path
, col
):
143 (.6, .6, .6, 1), # ready
144 (1, 1, 1, 1), # press
145 (.8, .8, .8, 1), # rollover
151 (.4, .1, .1, .4)]}[col
]
152 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
153 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
155 ('home', self
.on_home
, NORMAL
, abl
, 'gray'),
156 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray'),
157 ('right', self
.on_play
, NORMAL
, abr
, 'green'),
158 #('next', self.on_next, DISABLED, abr, 'gray'),
159 #('previous', self.on_prev, DISABLED, abr, 'gray'),
160 #('rewind', self.reset, NORMAL, abr, 'gray')
164 for binfo
in btn_info
:
165 imgs
= load_images_btn(binfo
[0], binfo
[4])
166 if binfo
[3] == base
.a2dBottomLeft
:
170 sign
, num
= -1, num_r
172 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
174 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
175 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
176 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
177 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
178 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
179 btn
.set_transparency(True)
181 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
= btns
182 # , self.__next_btn, self.__prev_btn, self.__rewind_btn
184 self
._info
_txt
= OnscreenText(
185 '', parent
=base
.a2dTopRight
, scale
=0.04,
186 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
188 def _unset_gui(self
):
190 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
191 #self.__next_btn, self.__prev_btn, self.__rewind_btn
193 [btn
.destroy() for btn
in btns
]
195 self
._info
_txt
.destroy()
197 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
198 light
= Spotlight(name
)
200 light
.setLens(PerspectiveLens())
201 light_np
= render
.attach_new_node(light
)
202 light_np
.set_pos(pos
)
203 light_np
.look_at(look_at
)
204 light
.set_color(color
)
205 render
.set_light(light_np
)
208 def _set_lights(self
):
209 alight
= AmbientLight('alight') # for ao
210 alight
.set_color((.15, .15, .15, 1))
211 self
._alnp
= render
.attach_new_node(alight
)
212 render
.set_light(self
._alnp
)
213 self
._key
_light
= self
._set
_spotlight
(
214 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
215 self
._shadow
_light
= self
._set
_spotlight
(
216 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
217 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
218 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
219 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
220 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
222 def _unset_lights(self
):
223 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
224 render
.clear_light(light
)
227 def _set_input(self
):
228 self
.accept('mouse1', self
.on_click_l
)
229 self
.accept('mouse1-up', self
.on_release
)
230 self
.accept('mouse3', self
.on_click_r
)
231 self
.accept('mouse3-up', self
.on_release
)
233 def _unset_input(self
):
234 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
237 def _set_mouse_plane(self
):
238 shape
= BulletPlaneShape((0, -1, 0), 0)
239 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
240 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
241 self
._mouse
_plane
_node
.addShape(shape
)
242 #np = render.attachNewNode(self._mouse_plane_node)
243 #self._world.attachRigidBody(self._mouse_plane_node)
244 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
246 def _unset_mouse_plane(self
):
247 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
250 if not base
.mouseWatcherNode
.has_mouse(): return []
251 p_from
, p_to
= P3dGfxMgr
.world_from_to(base
.mouseWatcherNode
.get_mouse())
252 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
254 def _update_info(self
, item
):
257 txt
= '%.3f %.3f\n%.3f°' % (
258 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
259 self
._info
_txt
['text'] = txt
261 def _on_click(self
, method
):
264 for hit
in self
._get
_hits
():
265 if hit
.get_node() == self
._mouse
_plane
_node
:
266 pos
= hit
.get_hit_pos()
267 for hit
in self
._get
_hits
():
268 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
269 if not self
._item
_active
:
270 self
._item
_active
= item
271 getattr(item
, method
)(pos
)
272 img
= 'move' if method
== 'on_click_l' else 'rotate'
273 if not (img
== 'rotate' and not item
._instantiated
):
274 self
._cursor
.set_image('assets/images/buttons/%s.dds' % img
)
276 def on_click_l(self
):
277 self
._on
_click
('on_click_l')
279 def on_click_r(self
):
280 self
._on
_click
('on_click_r')
282 def on_release(self
):
283 if self
._item
_active
and not self
._item
_active
._first
_command
:
284 self
._commands
= self
._commands
[:self
._command
_idx
]
285 self
._commands
+= [self
._item
_active
]
286 self
._command
_idx
+= 1
287 #self.__prev_btn['state'] = NORMAL
288 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
289 #self.__prev_btn['frameColor'] = fcols[0]
290 #if self._item_active._command_idx == len(self._item_active._commands) - 1:
291 # self.__next_btn['state'] = DISABLED
292 # self.__next_btn['frameColor'] = fcols[1]
293 self
._item
_active
= None
294 [item
.on_release() for item
in self
.items
]
295 self
._cursor
.set_image('assets/images/buttons/arrowUpLeft.dds')
298 for item
in self
.items
:
299 item
.repos_done
= False
300 self
.items
= sorted(self
.items
, key
=lambda itm
: itm
.__class
__.__name
__)
301 [item
.on_aspect_ratio_changed() for item
in self
.items
]
302 self
._side
_panel
.update(self
.items
)
303 max_x
= -float('inf')
304 for item
in self
.items
:
305 if not item
._instantiated
:
306 max_x
= max(item
._np
.get_x(), max_x
)
307 for item
in self
.items
:
308 if not item
._instantiated
:
311 def on_aspect_ratio_changed(self
):
314 def _win_condition(self
):
317 def _fail_condition(self
):
318 return all(itm
.fail_condition() for itm
in self
.items
) and not self
._paused
and self
._state
== 'playing'
320 def on_frame(self
, task
):
321 hits
= self
._get
_hits
()
323 for hit
in self
._get
_hits
():
324 if hit
.get_node() == self
._mouse
_plane
_node
:
325 pos
= hit
.get_hit_pos()
326 hit_nodes
= [hit
.get_node() for hit
in hits
]
327 if self
._item
_active
:
328 items_hit
= [self
._item
_active
]
330 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
331 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
332 [itm
.on_mouse_on() for itm
in items_hit
]
333 [itm
.on_mouse_off() for itm
in items_no_hit
]
334 if pos
and self
._item
_active
:
335 self
._item
_active
.on_mouse_move(pos
)
337 self
._update
_info
(items_hit
[0] if items_hit
else None)
338 if self
._win
_condition
():
339 self
._set
_fail
() if self
._enforce
_res
== 'fail' else self
._set
_win
()
340 elif self
._state
== 'playing' and self
._fail
_condition
():
341 self
._set
_win
() if self
._enforce
_res
== 'win' else self
._set
_fail
()
342 if any(itm
._overlapping
for itm
in self
.items
):
343 self
._cursor
.cursor_img
.img
.set_color(.9, .1, .1, 1)
345 self
._cursor
.cursor_img
.img
.set_color(.9, .9, .9, 1)
348 def cb_inst(self
, item
):
352 self
._state
= 'playing'
353 #self.__prev_btn['state'] = DISABLED
354 #self.__next_btn['state'] = DISABLED
355 self
.__right
_btn
['state'] = DISABLED
356 [itm
.play() for itm
in self
.items
]
359 self
._commands
[self
._command
_idx
].redo()
360 self
._command
_idx
+= 1
361 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
362 #self.__prev_btn['state'] = NORMAL
363 #self.__prev_btn['frameColor'] = fcols[0]
364 more_commands
= self
._command
_idx
< len(self
._commands
)
365 #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
366 #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
369 self
._command
_idx
-= 1
370 self
._commands
[self
._command
_idx
].undo()
371 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
372 #self.__next_btn['state'] = NORMAL
373 #self.__next_btn['frameColor'] = fcols[0]
374 #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
375 #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
380 def _set_instructions(self
):
383 mgr
= TextPropertiesManager
.get_global_ptr()
384 for name
in ['mouse_l', 'mouse_r']:
385 graphic
= OnscreenImage('assets/images/buttons/%s.dds' % name
)
386 graphic
.set_scale(.5)
387 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
388 graphic
.get_texture().set_anisotropic_degree(2)
389 mgr
.set_graphic(name
, graphic
)
391 graphic
.set_transparency(True)
392 graphic
.detach_node()
393 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
394 frameSize
=(-.6, .6, -.3, .3))
395 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
397 font
.set_pixels_per_unit(60)
398 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
399 font
.set_outline((0, 0, 0, 1), .8, .2)
400 self
._txt
= OnscreenText(
401 self
._instr
_txt
(), parent
=frm
, font
=font
, scale
=0.06,
402 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
403 u_l
= self
._txt
.textNode
.get_upper_left_3d()
404 l_r
= self
._txt
.textNode
.get_lower_right_3d()
405 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
408 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
409 z
+= (btn_scale
+ 2 * mar
) / 2
410 self
._txt
['pos'] = -w
/ 2, z
411 u_l
= self
._txt
.textNode
.get_upper_left_3d()
412 l_r
= self
._txt
.textNode
.get_lower_right_3d()
413 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
414 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
415 frm
['frameSize'] = fsz
417 (.6, .6, .6, 1), # ready
418 (1, 1, 1, 1), # press
419 (.8, .8, .8, 1), # rollover
421 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
423 image
=imgs
, scale
=btn_scale
,
424 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
425 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
426 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
427 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
428 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
429 btn
.set_transparency(True)
432 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
435 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
436 frameSize
=(-.6, .6, -.3, .3))
437 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
439 font
.set_pixels_per_unit(60)
440 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
441 font
.set_outline((0, 0, 0, 1), .8, .2)
442 self
._txt
= OnscreenText(
445 font
=font
, scale
=0.2,
447 u_l
= self
._txt
.textNode
.get_upper_left_3d()
448 l_r
= self
._txt
.textNode
.get_lower_right_3d()
449 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
452 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
453 z
+= (btn_scale
+ 2 * mar
) / 2
454 self
._txt
['pos'] = 0, z
455 u_l
= self
._txt
.textNode
.get_upper_left_3d()
456 l_r
= self
._txt
.textNode
.get_lower_right_3d()
457 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
458 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
459 frm
['frameSize'] = fsz
461 (.6, .6, .6, 1), # ready
462 (1, 1, 1, 1), # press
463 (.8, .8, .8, 1), # rollover
465 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
467 image
=imgs
, scale
=btn_scale
,
468 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
469 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
470 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
471 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
472 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
473 btn
.set_transparency(True)
474 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
476 image
=imgs
, scale
=btn_scale
,
477 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
478 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
479 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
480 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
481 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
482 btn
.set_transparency(True)
483 enabled
= self
._scenes
.index(self
.__class
__) < len(self
._scenes
) - 1
485 next_scene
= self
._scenes
[self
._scenes
.index(self
.__class
__) + 1]
488 imgs
= [self
.__load
_img
_btn
('right', col
) for col
in colors
]
490 image
=imgs
, scale
=btn_scale
,
491 pos
=(2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
492 parent
=frm
, command
=self
._on
_next
_scene
,
493 extraArgs
=[frm
, next_scene
], relief
=FLAT
,
494 frameColor
=(.6, .6, .6, .08),
495 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
496 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
497 btn
['state'] = NORMAL
if enabled
else DISABLED
498 btn
.set_transparency(True)
501 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
504 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
505 frameSize
=(-.6, .6, -.3, .3))
506 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
508 font
.set_pixels_per_unit(60)
509 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
510 font
.set_outline((0, 0, 0, 1), .8, .2)
511 self
._txt
= OnscreenText(
512 _('You have failed!'),
514 font
=font
, scale
=0.2,
516 u_l
= self
._txt
.textNode
.get_upper_left_3d()
517 l_r
= self
._txt
.textNode
.get_lower_right_3d()
518 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
521 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
522 z
+= (btn_scale
+ 2 * mar
) / 2
523 self
._txt
['pos'] = 0, z
524 u_l
= self
._txt
.textNode
.get_upper_left_3d()
525 l_r
= self
._txt
.textNode
.get_lower_right_3d()
526 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
527 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
528 frm
['frameSize'] = fsz
530 (.6, .6, .6, 1), # ready
531 (1, 1, 1, 1), # press
532 (.8, .8, .8, 1), # rollover
534 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
536 image
=imgs
, scale
=btn_scale
,
537 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
538 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
539 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
540 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
541 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
542 btn
.set_transparency(True)
543 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
545 image
=imgs
, scale
=btn_scale
,
546 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
547 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
548 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
549 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
550 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
551 btn
.set_transparency(True)
553 def _on_restart(self
, frm
):
554 self
.__on
_close
_instructions
(frm
)
557 def _on_end_home(self
, frm
):
558 self
.__on
_close
_instructions
(frm
)
561 def _on_next_scene(self
, frm
, scene
):
562 self
.__on
_close
_instructions
(frm
)
563 self
._reload
_cb
(scene
)
565 def __store_state(self
):
567 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
568 #self.__next_btn, self.__prev_btn, self.__rewind_btn
570 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
572 btn
['state'] = DISABLED
573 [itm
.store_state() for itm
in self
.items
]
575 def __restore_state(self
):
577 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
578 #self.__next_btn, self.__prev_btn, self.__rewind_btn
580 for btn
, state
in zip(btns
, self
.__btn
_state
):
582 [itm
.restore_state() for itm
in self
.items
]
585 def __on_close_instructions(self
, frm
):
587 self
.__restore
_state
()