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/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 buffer.save_screenshot('assets/images/scenes/%s.png' % fname
)
81 # frameTexture=buffer.get_texture(), relief=FLAT,
82 # frameSize=(-.2, .2, -.2, .2))
83 return buffer.get_texture()
85 def current_bottom(self
):
87 for item
in self
.items
:
89 curr_bottom
= min(curr_bottom
, item
.get_bottom())
93 [itm
.destroy() for itm
in self
.items
]
98 if hasattr(self
, '_success_txt'):
99 self
._success
_txt
.destroy()
100 del self
._success
_txt
101 self
.__right
_btn
['state'] = NORMAL
107 self
._unset
_mouse
_plane
()
108 [itm
.destroy() for itm
in self
.items
]
110 self
._side
_panel
.destroy()
111 self
._cursor
.destroy()
112 taskMgr
.remove(self
._scene
_tsk
)
113 if hasattr(self
, '_success_txt'):
114 self
._success
_txt
.destroy()
116 def _set_camera(self
):
117 base
.camera
.set_pos(0, -20, 0)
118 base
.camera
.look_at(0, 0, 0)
120 def __load_img_btn(self
, path
, col
):
121 img
= OnscreenImage('assets/buttons/%s.dds' % path
)
122 img
.set_transparency(True)
128 def load_images_btn(path
, col
):
131 (.6, .6, .6, 1), # ready
132 (1, 1, 1, 1), # press
133 (.8, .8, .8, 1), # rollover
139 (.4, .1, .1, .4)]}[col
]
140 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
141 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
143 ('home', self
.on_home
, NORMAL
, abl
, 'gray'),
144 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray'),
145 ('right', self
.on_play
, NORMAL
, abr
, 'green'),
146 ('next', self
.on_next
, DISABLED
, abr
, 'gray'),
147 ('previous', self
.on_prev
, DISABLED
, abr
, 'gray'),
148 ('rewind', self
.reset
, NORMAL
, abr
, 'gray')]
151 for binfo
in btn_info
:
152 imgs
= load_images_btn(binfo
[0], binfo
[4])
153 if binfo
[3] == base
.a2dBottomLeft
:
157 sign
, num
= -1, num_r
159 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
161 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
162 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
163 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
164 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
165 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
166 btn
.set_transparency(True)
168 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
, self
.__next
_btn
, \
169 self
.__prev
_btn
, self
.__rewind
_btn
= btns
171 self
._info
_txt
= OnscreenText(
172 '', parent
=base
.a2dTopRight
, scale
=0.04,
173 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
175 def _unset_gui(self
):
177 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
178 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
179 [btn
.destroy() for btn
in btns
]
181 self
._info
_txt
.destroy()
183 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
184 light
= Spotlight(name
)
186 light
.setLens(PerspectiveLens())
187 light_np
= render
.attach_new_node(light
)
188 light_np
.set_pos(pos
)
189 light_np
.look_at(look_at
)
190 light
.set_color(color
)
191 render
.set_light(light_np
)
194 def _set_lights(self
):
195 alight
= AmbientLight('alight') # for ao
196 alight
.set_color((.15, .15, .15, 1))
197 self
._alnp
= render
.attach_new_node(alight
)
198 render
.set_light(self
._alnp
)
199 self
._key
_light
= self
._set
_spotlight
(
200 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
201 self
._shadow
_light
= self
._set
_spotlight
(
202 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
203 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
204 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
205 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
206 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
208 def _unset_lights(self
):
209 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
210 render
.clear_light(light
)
213 def _set_input(self
):
214 self
.accept('mouse1', self
.on_click_l
)
215 self
.accept('mouse1-up', self
.on_release
)
216 self
.accept('mouse3', self
.on_click_r
)
217 self
.accept('mouse3-up', self
.on_release
)
219 def _unset_input(self
):
220 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
223 def _set_mouse_plane(self
):
224 shape
= BulletPlaneShape((0, -1, 0), 0)
225 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
226 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
227 self
._mouse
_plane
_node
.addShape(shape
)
228 #np = render.attachNewNode(self._mouse_plane_node)
229 #self._world.attachRigidBody(self._mouse_plane_node)
230 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
232 def _unset_mouse_plane(self
):
233 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
236 if not base
.mouseWatcherNode
.has_mouse(): return []
237 p_from
, p_to
= P3dGfxMgr
.world_from_to(base
.mouseWatcherNode
.get_mouse())
238 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
240 def _update_info(self
, item
):
243 txt
= '%.3f %.3f\n%.3f°' % (
244 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
245 self
._info
_txt
['text'] = txt
247 def _on_click(self
, method
):
250 for hit
in self
._get
_hits
():
251 if hit
.get_node() == self
._mouse
_plane
_node
:
252 pos
= hit
.get_hit_pos()
253 for hit
in self
._get
_hits
():
254 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
255 if not self
._item
_active
:
256 self
._item
_active
= item
257 getattr(item
, method
)(pos
)
258 img
= 'move' if method
== 'on_click_l' else 'rotate'
259 if not (img
== 'rotate' and not item
._instantiated
):
260 self
._cursor
.set_image('assets/buttons/%s.dds' % img
)
262 def on_click_l(self
):
263 self
._on
_click
('on_click_l')
265 def on_click_r(self
):
266 self
._on
_click
('on_click_r')
268 def on_release(self
):
269 if self
._item
_active
and not self
._item
_active
._first
_command
:
270 self
._commands
= self
._commands
[:self
._command
_idx
]
271 self
._commands
+= [self
._item
_active
]
272 self
._command
_idx
+= 1
273 self
.__prev
_btn
['state'] = NORMAL
274 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
275 self
.__prev
_btn
['frameColor'] = fcols
[0]
276 if self
._item
_active
._command
_idx
== len(self
._item
_active
._commands
) - 1:
277 self
.__next
_btn
['state'] = DISABLED
278 self
.__next
_btn
['frameColor'] = fcols
[1]
279 self
._item
_active
= None
280 [item
.on_release() for item
in self
.items
]
281 self
._cursor
.set_image('assets/buttons/arrowUpLeft.dds')
284 for item
in self
.items
:
285 item
.repos_done
= False
286 self
.items
= sorted(self
.items
, key
=lambda itm
: itm
.__class
__.__name
__)
287 [item
.on_aspect_ratio_changed() for item
in self
.items
]
288 self
._side
_panel
.update(self
.items
)
289 max_x
= -float('inf')
290 for item
in self
.items
:
291 if not item
._instantiated
:
292 max_x
= max(item
._np
.get_x(), max_x
)
293 for item
in self
.items
:
294 if not item
._instantiated
:
297 def on_aspect_ratio_changed(self
):
300 def _win_condition(self
):
303 def _fail_condition(self
):
304 return all(itm
.fail_condition() for itm
in self
.items
) and not self
._paused
and self
._state
== 'playing'
306 def on_frame(self
, task
):
307 hits
= self
._get
_hits
()
309 for hit
in self
._get
_hits
():
310 if hit
.get_node() == self
._mouse
_plane
_node
:
311 pos
= hit
.get_hit_pos()
312 hit_nodes
= [hit
.get_node() for hit
in hits
]
313 if self
._item
_active
:
314 items_hit
= [self
._item
_active
]
316 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
317 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
318 [itm
.on_mouse_on() for itm
in items_hit
]
319 [itm
.on_mouse_off() for itm
in items_no_hit
]
320 if pos
and self
._item
_active
:
321 self
._item
_active
.on_mouse_move(pos
)
323 self
._update
_info
(items_hit
[0] if items_hit
else None)
324 if self
._win
_condition
():
326 elif self
._state
== 'playing' and self
._fail
_condition
():
328 if any(itm
._overlapping
for itm
in self
.items
):
329 self
._cursor
.cursor_img
.img
.set_color(.9, .1, .1, 1)
331 self
._cursor
.cursor_img
.img
.set_color(.9, .9, .9, 1)
334 def cb_inst(self
, item
):
338 self
._state
= 'playing'
339 self
.__prev
_btn
['state'] = DISABLED
340 self
.__next
_btn
['state'] = DISABLED
341 self
.__right
_btn
['state'] = DISABLED
342 [itm
.play() for itm
in self
.items
]
345 self
._commands
[self
._command
_idx
].redo()
346 self
._command
_idx
+= 1
347 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
348 self
.__prev
_btn
['state'] = NORMAL
349 self
.__prev
_btn
['frameColor'] = fcols
[0]
350 more_commands
= self
._command
_idx
< len(self
._commands
)
351 self
.__next
_btn
['state'] = NORMAL
if more_commands
else DISABLED
352 self
.__next
_btn
['frameColor'] = fcols
[0] if more_commands
else fcols
[1]
355 self
._command
_idx
-= 1
356 self
._commands
[self
._command
_idx
].undo()
357 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
358 self
.__next
_btn
['state'] = NORMAL
359 self
.__next
_btn
['frameColor'] = fcols
[0]
360 self
.__prev
_btn
['state'] = NORMAL
if self
._command
_idx
else DISABLED
361 self
.__prev
_btn
['frameColor'] = fcols
[0] if self
._command
_idx
else fcols
[1]
366 def _set_instructions(self
):
369 mgr
= TextPropertiesManager
.get_global_ptr()
370 for name
in ['mouse_l', 'mouse_r']:
371 graphic
= OnscreenImage('assets/buttons/%s.dds' % name
)
372 graphic
.set_scale(.5)
373 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
374 graphic
.get_texture().set_anisotropic_degree(2)
375 mgr
.set_graphic(name
, graphic
)
377 graphic
.set_transparency(True)
378 graphic
.detach_node()
379 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
380 frameSize
=(-.6, .6, -.3, .3))
381 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
383 font
.set_pixels_per_unit(60)
384 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
385 font
.set_outline((0, 0, 0, 1), .8, .2)
386 self
._txt
= OnscreenText(
387 self
._instr
_txt
(), parent
=frm
, font
=font
, scale
=0.06,
388 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
389 u_l
= self
._txt
.textNode
.get_upper_left_3d()
390 l_r
= self
._txt
.textNode
.get_lower_right_3d()
391 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
394 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
395 z
+= (btn_scale
+ 2 * mar
) / 2
396 self
._txt
['pos'] = -w
/ 2, z
397 u_l
= self
._txt
.textNode
.get_upper_left_3d()
398 l_r
= self
._txt
.textNode
.get_lower_right_3d()
399 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
400 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
401 frm
['frameSize'] = fsz
403 (.6, .6, .6, 1), # ready
404 (1, 1, 1, 1), # press
405 (.8, .8, .8, 1), # rollover
407 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
409 image
=imgs
, scale
=btn_scale
,
410 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
411 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
412 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
413 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
414 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
415 btn
.set_transparency(True)
418 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
421 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
422 frameSize
=(-.6, .6, -.3, .3))
423 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
425 font
.set_pixels_per_unit(60)
426 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
427 font
.set_outline((0, 0, 0, 1), .8, .2)
428 self
._txt
= OnscreenText(
431 font
=font
, scale
=0.2,
433 u_l
= self
._txt
.textNode
.get_upper_left_3d()
434 l_r
= self
._txt
.textNode
.get_lower_right_3d()
435 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
438 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
439 z
+= (btn_scale
+ 2 * mar
) / 2
440 self
._txt
['pos'] = 0, z
441 u_l
= self
._txt
.textNode
.get_upper_left_3d()
442 l_r
= self
._txt
.textNode
.get_lower_right_3d()
443 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
444 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
445 frm
['frameSize'] = fsz
447 (.6, .6, .6, 1), # ready
448 (1, 1, 1, 1), # press
449 (.8, .8, .8, 1), # rollover
451 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
453 image
=imgs
, scale
=btn_scale
,
454 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
455 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
456 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
457 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
458 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
459 btn
.set_transparency(True)
460 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
462 image
=imgs
, scale
=btn_scale
,
463 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
464 parent
=frm
, command
=self
._on
_restart
, 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)
470 for _file
in glob('pmachines/scenes/*.py'):
471 _fn
= _file
.replace('.py', '').replace('/', '.')
472 for member
in import_module(_fn
).__dict
__.values():
473 if isclass(member
) and issubclass(member
, Scene
) and \
476 scenes
= sorted(scenes
, key
=lambda elm
: elm
.sorting
)
477 enabled
= scenes
.index(self
.__class
__) < len(scenes
) - 1
479 next_scene
= scenes
[scenes
.index(self
.__class
__) + 1]
482 imgs
= [self
.__load
_img
_btn
('right', col
) for col
in colors
]
484 image
=imgs
, scale
=btn_scale
,
485 pos
=(2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
486 parent
=frm
, command
=self
._on
_next
_scene
,
487 extraArgs
=[frm
, next_scene
], relief
=FLAT
,
488 frameColor
=(.6, .6, .6, .08),
489 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
490 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
491 btn
['state'] = NORMAL
if enabled
else DISABLED
492 btn
.set_transparency(True)
495 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
498 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
499 frameSize
=(-.6, .6, -.3, .3))
500 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
502 font
.set_pixels_per_unit(60)
503 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
504 font
.set_outline((0, 0, 0, 1), .8, .2)
505 self
._txt
= OnscreenText(
506 _('You have failed!'),
508 font
=font
, scale
=0.2,
510 u_l
= self
._txt
.textNode
.get_upper_left_3d()
511 l_r
= self
._txt
.textNode
.get_lower_right_3d()
512 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
515 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
516 z
+= (btn_scale
+ 2 * mar
) / 2
517 self
._txt
['pos'] = 0, z
518 u_l
= self
._txt
.textNode
.get_upper_left_3d()
519 l_r
= self
._txt
.textNode
.get_lower_right_3d()
520 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
521 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
522 frm
['frameSize'] = fsz
524 (.6, .6, .6, 1), # ready
525 (1, 1, 1, 1), # press
526 (.8, .8, .8, 1), # rollover
528 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
530 image
=imgs
, scale
=btn_scale
,
531 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
532 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
533 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
534 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
535 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
536 btn
.set_transparency(True)
537 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
539 image
=imgs
, scale
=btn_scale
,
540 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
541 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
542 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
543 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
544 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
545 btn
.set_transparency(True)
547 def _on_restart(self
, frm
):
548 self
.__on
_close
_instructions
(frm
)
551 def _on_end_home(self
, frm
):
552 self
.__on
_close
_instructions
(frm
)
555 def _on_next_scene(self
, frm
, scene
):
556 self
.__on
_close
_instructions
(frm
)
557 self
._reload
_cb
(scene
)
559 def __store_state(self
):
561 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
562 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
563 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
565 btn
['state'] = DISABLED
566 [itm
.store_state() for itm
in self
.items
]
568 def __restore_state(self
):
570 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
571 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
572 for btn
, state
in zip(btns
, self
.__btn
_state
):
574 [itm
.restore_state() for itm
in self
.items
]
577 def __on_close_instructions(self
, frm
):
579 self
.__restore
_state
()