1 from logging
import info
, debug
2 from sys
import platform
, exit
3 from os
import environ
, system
4 from webbrowser
import open_new_tab
5 from xmlrpc
.client
import ServerProxy
6 from panda3d
.core
import Texture
, TextNode
, WindowProperties
, LVector2i
, \
7 TextProperties
, TextPropertiesManager
8 from direct
.gui
.DirectGui
import DirectButton
, DirectCheckButton
, \
9 DirectOptionMenu
, DirectSlider
10 from direct
.gui
.DirectGuiGlobals
import FLAT
11 from direct
.gui
.OnscreenText
import OnscreenText
12 from direct
.showbase
.DirectObject
import DirectObject
13 from ya2
.utils
.cursor
import MouseCursor
14 from ya2
.p3d
.p3d
import LibP3d
17 class Menu(DirectObject
):
19 def __init__(self
, fsm
, lang_mgr
, opt_file
, music
, pipeline
, scenes
,
23 self
._lang
_mgr
= lang_mgr
24 self
._opt
_file
= opt_file
26 self
._pipeline
= pipeline
28 self
._fun
_test
= fun_test
29 self
._pos
_mgr
= pos_mgr
30 self
._enforced
_res
= ''
31 self
._cursor
= MouseCursor(
32 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04),
33 (.5, .5, .5, 1), (.01, .01))
34 self
._font
= base
.loader
.load_font(
35 'assets/fonts/Hanken-Book.ttf')
37 self
._font
.set_pixels_per_unit(60)
38 self
._font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
39 self
._font
.set_outline((0, 0, 0, 1), .8, .2)
43 'text_font': self
._font
,
44 'text_fg': (.9, .9, .9, 1),
46 'frameColor': (.4, .4, .4, .14),
47 'rolloverSound': loader
.load_sfx(
48 'assets/audio/sfx/rollover.ogg'),
49 'clickSound': loader
.load_sfx(
50 'assets/audio/sfx/click.ogg')}
51 self
._common
_btn
= {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self
._common
52 hlc
= self
._common
_btn
['frameColor']
53 hlc
= (hlc
[0] + .2, hlc
[1] + .2, hlc
[2] + .2, hlc
[3] + .2)
55 'item_frameColor': self
._common
_btn
['frameColor'],
56 'popupMarker_frameColor': self
._common
_btn
['frameColor'],
57 'item_relief': self
._common
_btn
['relief'],
58 'item_text_font': self
._common
_btn
['text_font'],
59 'item_text_fg': self
._common
_btn
['text_fg'],
61 'highlightColor': hlc
,
62 'text_align': TextNode
.A_center
,
64 f_s
= self
._common
_opt
['frameSize']
65 self
._common
_opt
['frameSize'] = f_s
[0], f_s
[1] - .56, f_s
[2], f_s
[3]
66 self
._common
_slider
= self
._common |
{
68 'thumb_frameColor': (.4, .4, .4, .4),
71 del self
._common
_slider
['rolloverSound']
72 del self
._common
_slider
['clickSound']
75 def enforce_res(self
, val
):
76 self
._enforced
_res
= val
77 info('enforced resolution: ' + val
)
82 self
._widgets
+= [DirectButton(
83 text
=_('Play'), pos
=(0, 1, .6), command
=self
.on_play
,
85 self
._pos
_mgr
.register('play', LibP3d
.wdg_pos(self
._widgets
[-1]))
86 self
._widgets
+= [DirectButton(
87 text
=_('Options'), pos
=(0, 1, .2), command
=self
.on_options
,
89 self
._pos
_mgr
.register('options', LibP3d
.wdg_pos(self
._widgets
[-1]))
90 self
._widgets
+= [DirectButton(
91 text
=_('Credits'), pos
=(0, 1, -.2), command
=self
.on_credits
,
93 self
._pos
_mgr
.register('credits', LibP3d
.wdg_pos(self
._widgets
[-1]))
97 ServerProxy('http://localhost:6000').destroy()
99 self
._widgets
+= [DirectButton(
100 text
=_('Exit'), pos
=(0, 1, -.6), command
=lambda: btn_exit(),
102 self
._pos
_mgr
.register('exit', LibP3d
.wdg_pos(self
._widgets
[-1]))
103 self
._rearrange
_width
()
104 self
.accept('enforce_resolution', self
.enforce_res
)
106 def _set_options(self
):
107 self
._pos
_mgr
.reset()
109 self
._lang
_funcs
= [lambda: _('English'), lambda: _('Italian')]
110 items
= [fnc() for fnc
in self
._lang
_funcs
]
114 }[self
._opt
_file
['settings']['language']]
115 btn
= DirectOptionMenu(
116 text
=_('Language'), items
=items
, initialitem
=inititem
,
117 pos
=(0, 1, .8), command
=self
.on_language
, **self
._common
_opt
)
118 btn
.popupMenu
['frameColor'] = self
._common
_btn
['frameColor']
119 btn
.popupMenu
['relief'] = self
._common
_btn
['relief']
120 self
._widgets
+= [btn
]
121 pos_lang
= LibP3d
.wdg_pos(self
._widgets
[-1])
122 self
._pos
_mgr
.register('languages', pos_lang
)
123 pos_eng
= pos_lang
[0] + 300, pos_lang
[1] - 57
124 pos_it
= pos_lang
[0] + 300, pos_lang
[1] + 33
125 self
._pos
_mgr
.register('english', pos_eng
)
126 self
._pos
_mgr
.register('italian', pos_it
)
127 self
._widgets
+= [OnscreenText(
128 _('Volume'), pos
=(-.1, .55), font
=self
._common
['text_font'],
129 scale
=self
._common
['scale'], fg
=self
._common
['text_fg'],
130 align
=TextNode
.A_right
)]
131 self
._widgets
+= [DirectSlider(
133 value
=self
._opt
_file
['settings']['volume'],
134 command
=self
.on_volume
,
135 **self
._common
_slider
)]
136 vol_pos
= LibP3d
.wdg_pos(self
._widgets
[-1])
137 self
._pos
_mgr
.register('volume', vol_pos
)
138 self
._pos
_mgr
.register('volume_0', [vol_pos
[0] - 153, vol_pos
[1]])
139 self
._slider
= self
._widgets
[-1]
140 self
._widgets
+= [DirectCheckButton(
141 text
=_('Fullscreen'), pos
=(0, 1, .3), command
=self
.on_fullscreen
,
142 indicator_frameColor
=self
._common
_opt
['highlightColor'],
143 indicator_relief
=self
._common
_btn
['relief'],
144 indicatorValue
=self
._opt
_file
['settings']['fullscreen'],
146 self
._pos
_mgr
.register('fullscreen', LibP3d
.wdg_pos(self
._widgets
[-1]))
147 res
= self
._opt
_file
['settings']['resolution']
148 d_i
= base
.pipe
.get_display_information()
150 return d_i
.get_display_mode_width(idx
), \
151 d_i
.get_display_mode_height(idx
)
153 _res(idx
) for idx
in range(d_i
.get_total_display_modes())]
154 resolutions
= list(set(resolutions
))
155 resolutions
= sorted(resolutions
)
156 resolutions
= [(str(_res
[0]), str(_res
[1])) for _res
in resolutions
]
157 resolutions
= ['x'.join(_res
) for _res
in resolutions
]
159 res
= resolutions
[-1]
160 btn
= DirectOptionMenu(
161 text
=_('Resolution'), items
=resolutions
, initialitem
=res
,
162 pos
=(0, 1, .05), command
=self
.on_resolution
, **self
._common
_opt
)
163 btn
.popupMenu
['frameColor'] = self
._common
_btn
['frameColor']
164 btn
.popupMenu
['relief'] = self
._common
_btn
['relief']
165 self
._widgets
+= [btn
]
166 pos_res
= LibP3d
.wdg_pos(self
._widgets
[-1])
167 self
._pos
_mgr
.register('resolutions', pos_res
) # 680 365
168 self
._pos
_mgr
.register('res_1440x900', [pos_res
[0] + 320, pos_res
[1] + 75])
169 self
._pos
_mgr
.register('res_1360x768', [pos_res
[0] + 430, pos_res
[1] -285])
170 self
._widgets
+= [DirectCheckButton(
171 text
=_('Antialiasing'), pos
=(0, 1, -.2), command
=self
.on_aa
,
172 indicator_frameColor
=self
._common
_opt
['highlightColor'],
173 indicator_relief
=self
._common
_btn
['relief'],
174 indicatorValue
=self
._opt
_file
['settings']['antialiasing'],
176 self
._pos
_mgr
.register('aa', LibP3d
.wdg_pos(self
._widgets
[-1]))
177 self
._widgets
+= [DirectCheckButton(
178 text
=_('Shadows'), pos
=(0, 1, -.45), command
=self
.on_shadows
,
179 indicator_frameColor
=self
._common
_opt
['highlightColor'],
180 indicator_relief
=self
._common
_btn
['relief'],
181 indicatorValue
=self
._opt
_file
['settings']['shadows'],
183 self
._pos
_mgr
.register('shadows', LibP3d
.wdg_pos(self
._widgets
[-1]))
184 self
._widgets
+= [DirectButton(
185 text
=_('Back'), pos
=(0, 1, -.8), command
=self
.on_back
,
187 self
._pos
_mgr
.register('back', LibP3d
.wdg_pos(self
._widgets
[-1]))
188 self
.accept('enforce_resolution', self
.enforce_res
)
190 def _set_credits(self
):
191 self
._pos
_mgr
.reset()
193 tp_scale
= TextProperties()
194 tp_scale
.set_text_scale(.64)
195 TextPropertiesManager
.getGlobalPtr().setProperties('scale', tp_scale
)
196 self
._widgets
+= [OnscreenText(
197 _('Code and gfx\n \1scale\1Flavio Calva\2\n\n\nMusic\n \1scale\1Stefan Grossmann\2'),
198 pos
=(-.9, .55), font
=self
._common
['text_font'],
199 scale
=self
._common
['scale'], fg
=self
._common
['text_fg'],
200 align
=TextNode
.A_left
)]
201 self
._widgets
+= [DirectButton(
202 text
=_('Website'), pos
=(-.6, 1, .29), command
=self
.on_website
,
203 **self
._common
_btn |
{'scale': .08})]
204 self
._widgets
+= [OnscreenText(
205 _('Special thanks to:\n \1scale\1rdb\2\n \1scale\1Luisa Tenuta\2\n \1scale\1Damiana Ercolani\2'),
206 pos
=(.1, .55), font
=self
._common
['text_font'],
207 scale
=self
._common
['scale'], fg
=self
._common
['text_fg'],
208 align
=TextNode
.A_left
)]
209 self
._widgets
+= [DirectButton(
210 text
=_('Back'), pos
=(0, 1, -.8), command
=self
.on_back
,
212 self
._pos
_mgr
.register('back', LibP3d
.wdg_pos(self
._widgets
[-1]))
213 self
.accept('enforce_resolution', self
.enforce_res
)
216 self
._pos
_mgr
.reset()
218 self
._cursor
= MouseCursor(
219 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
222 cmn
= self
._common
_btn
.copy() |
{
223 'frameSize': (-2.4, 2.4, -2.4, 2.4),
224 'frameColor': (1, 1, 1, .8),
226 left
= - (dx
:= .8) * (min(4, len(self
._scenes
)) - 1) / 2
227 for i
, cls
in enumerate(self
._scenes
):
228 top
= .1 if len(self
._scenes
) < 5 else .6
229 row
= 0 if i
< 4 else 1
230 self
._widgets
+= [DirectButton(
231 text
=cls
.name(), pos
=(left
+ dx
* (i
% 4), 1, top
- dx
* row
),
232 command
=self
.start
, extraArgs
=[cls
], text_wordwrap
=6,
233 frameTexture
='assets/images/scenes/%s.dds' % cls
.__name
__,
235 name
= cls
.__name
__[5:].lower()
236 self
._pos
_mgr
.register(name
, LibP3d
.wdg_pos(self
._widgets
[-1]))
238 tnode
= self
._widgets
[-1].component('text%s' % j
).textNode
239 height
= - tnode
.getLineHeight() / 2
240 height
+= (tnode
.get_height() - tnode
.get_line_height()) / 2
241 self
._widgets
[-1].component('text%s' % j
).set_pos(0, 0, height
)
242 self
._widgets
+= [DirectButton(
243 text
=_('Back'), pos
=(0, 1, -.8), command
=self
.on_back
,
245 self
._pos
_mgr
.register('back', LibP3d
.wdg_pos(self
._widgets
[-1]))
247 def start(self
, cls
):
248 self
._fsm
.demand('Scene', cls
)
250 def on_options(self
):
252 self
._cursor
= MouseCursor(
253 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
257 def on_credits(self
):
259 self
._cursor
= MouseCursor(
260 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
264 def _rearrange_width(self
):
266 for wdg
in self
._widgets
:
267 t_n
= wdg
.component('text0')
268 u_l
= t_n
.textNode
.get_upper_left_3d()
269 l_r
= t_n
.textNode
.get_lower_right_3d()
270 max_width
= max(l_r
[0] - u_l
[0], max_width
)
271 for wdg
in self
._widgets
:
272 m_w
= max_width
/ 2 + .8
273 wdg
['frameSize'] = -m_w
, m_w
, wdg
['frameSize'][2], wdg
['frameSize'][3]
275 def on_language(self
, arg
):
277 _('English'): 'en_EN',
278 _('Italian'): 'it_IT'}[arg
]
279 self
._lang
_mgr
.set_lang(lang_code
)
280 self
._opt
_file
['settings']['language'] = lang_code
[:2]
281 self
._opt
_file
.store()
285 self
._opt
_file
['settings']['volume'] = self
._slider
['value']
286 self
._music
.set_volume(self
._slider
['value'])
288 def on_fullscreen(self
, arg
):
289 props
= WindowProperties()
290 props
.set_fullscreen(arg
)
291 if not self
._fun
_test
:
292 base
.win
.request_properties(props
)
293 # if we actually switch to fullscreen during the tests then
294 # exwm inside qemu can't restore the correct resolution
295 # i may re-enable this if/when i run the tests onto a
297 self
._opt
_file
['settings']['fullscreen'] = int(arg
)
298 self
._opt
_file
.store()
300 def on_resolution(self
, arg
):
301 info('on resolution: %s (%s)' % (arg
, self
._enforced
_res
))
302 arg
= self
._enforced
_res
or arg
303 info('set resolution: %s' % arg
)
304 props
= WindowProperties()
305 props
.set_size(LVector2i(*[int(_res
) for _res
in arg
.split('x')]))
306 base
.win
.request_properties(props
)
307 self
._opt
_file
['settings']['resolution'] = arg
308 self
._opt
_file
.store()
310 def on_aa(self
, arg
):
311 self
._pipeline
.msaa_samples
= 4 if arg
else 1
312 debug(f
'msaa: {self._pipeline.msaa_samples}')
313 self
._opt
_file
['settings']['antialiasing'] = int(arg
)
314 self
._opt
_file
.store()
316 def on_shadows(self
, arg
):
317 self
._pipeline
.enable_shadows
= int(arg
)
318 debug(f
'shadows: {self._pipeline.enable_shadows}')
319 self
._opt
_file
['settings']['shadows'] = int(arg
)
320 self
._opt
_file
.store()
322 def on_website(self
):
323 if platform
.startswith('linux'):
324 environ
['LD_LIBRARY_PATH'] = ''
325 system('xdg-open https://www.ya2.it')
327 open_new_tab('https://www.ya2.it')
330 self
._opt
_file
.store()
332 self
._cursor
= MouseCursor(
333 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
338 [wdg
.destroy() for wdg
in self
._widgets
]
339 self
._cursor
.destroy()
340 self
.ignore('enforce_resolution')