ya2 · news · projects · code · about

fixes for building
[pmachines.git] / pmachines / gui / menu.py
CommitLineData
94a18c21 1from logging import info, debug
2612dfc8
FC
2from sys import platform, exit
3from os import environ, system
4from webbrowser import open_new_tab
158b04a8 5from xmlrpc.client import ServerProxy
2612dfc8 6from panda3d.core import Texture, TextNode, WindowProperties, LVector2i, \
bf77b5d5 7 TextProperties, TextPropertiesManager
4071c6d8 8from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
d68aeda7 9 DirectOptionMenu, DirectSlider, DirectFrame
4071c6d8 10from direct.gui.DirectGuiGlobals import FLAT
e1e44d5c 11from direct.gui.OnscreenText import OnscreenText
74ed9732 12from direct.showbase.DirectObject import DirectObject
b35b1f62 13from ya2.utils.cursor import MouseCursor
88dde925 14from ya2.utils.gfx import GfxTools, DirectGuiMixin, pos_pixel
4071c6d8
FC
15
16
ea71df6e
FC
17class DirectOptionMenuTest(DirectOptionMenu):
18
19 def __init__(self, parent=None, **kw):
20 DirectOptionMenu.__init__(self, parent, **kw)
21 self.initialiseoptions(DirectOptionMenuTest)
22
23 def showPopupMenu(self, event=None):
d68aeda7
FC
24 #super().showPopupMenu(event) # it does not work with mixins
25 DirectOptionMenu.showPopupMenu(self, event)
ea71df6e
FC
26 self._show_cb([self.component(cmp) for cmp in self.components()])
27
28
29
74ed9732 30class Menu(DirectObject):
4071c6d8 31
e65a09cf
FC
32 def __init__(self, fsm, lang_mgr, opt_file, music, pipeline, scenes,
33 fun_test, pos_mgr):
74ed9732 34 super().__init__()
4071c6d8
FC
35 self._fsm = fsm
36 self._lang_mgr = lang_mgr
37 self._opt_file = opt_file
e1e44d5c 38 self._music = music
a9aba267 39 self._pipeline = pipeline
6168d0c2 40 self._scenes = scenes
e982cdde 41 self._fun_test = fun_test
2d1773b1 42 self._pos_mgr = pos_mgr
74ed9732 43 self._enforced_res = ''
407412a5 44 self._cursor = MouseCursor(
e65a09cf 45 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04),
d68aeda7 46 (.5, .5, .5, 1), (.01, .01), fun_test)
e65a09cf
FC
47 self._font = base.loader.load_font(
48 'assets/fonts/Hanken-Book.ttf')
4071c6d8
FC
49 self._font.clear()
50 self._font.set_pixels_per_unit(60)
51 self._font.set_minfilter(Texture.FTLinearMipmapLinear)
52 self._font.set_outline((0, 0, 0, 1), .8, .2)
e1e44d5c 53 self._widgets = []
4071c6d8
FC
54 self._common = {
55 'scale': .12,
56 'text_font': self._font,
57 'text_fg': (.9, .9, .9, 1),
58 'relief': FLAT,
59 'frameColor': (.4, .4, .4, .14),
e65a09cf
FC
60 'rolloverSound': loader.load_sfx(
61 'assets/audio/sfx/rollover.ogg'),
62 'clickSound': loader.load_sfx(
63 'assets/audio/sfx/click.ogg')}
75b89b83 64 self._common_btn = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self._common
6fff1464
FC
65 hlc = self._common_btn['frameColor']
66 hlc = (hlc[0] + .2, hlc[1] + .2, hlc[2] + .2, hlc[3] + .2)
67 self._common_opt = {
68 'item_frameColor': self._common_btn['frameColor'],
69 'popupMarker_frameColor': self._common_btn['frameColor'],
70 'item_relief': self._common_btn['relief'],
71 'item_text_font': self._common_btn['text_font'],
72 'item_text_fg': self._common_btn['text_fg'],
73 'textMayChange': 1,
74 'highlightColor': hlc,
75 'text_align': TextNode.A_center,
76 } | self._common_btn
77 f_s = self._common_opt['frameSize']
78 self._common_opt['frameSize'] = f_s[0], f_s[1] - .56, f_s[2], f_s[3]
e1e44d5c
FC
79 self._common_slider = self._common | {
80 'range': (0, 1),
81 'thumb_frameColor': (.4, .4, .4, .4),
82 'thumb_scale': 1.6,
83 'scale': .4}
84 del self._common_slider['rolloverSound']
85 del self._common_slider['clickSound']
4071c6d8
FC
86 self._set_main()
87
74ed9732
FC
88 def enforce_res(self, val):
89 self._enforced_res = val
90 info('enforced resolution: ' + val)
91
4071c6d8 92 def _set_main(self):
88dde925 93 for k in list(self._pos_mgr.keys()): del self._pos_mgr[k]
e1e44d5c
FC
94 self._widgets = []
95 self._widgets += [DirectButton(
4071c6d8
FC
96 text=_('Play'), pos=(0, 1, .6), command=self.on_play,
97 **self._common_btn)]
bf77b5d5 98 self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
63d69e94 99 self._pos_mgr['play'] = self._widgets[-1].pos_pixel()
e1e44d5c 100 self._widgets += [DirectButton(
2612dfc8 101 text=_('Options'), pos=(0, 1, .2), command=self.on_options,
4071c6d8 102 **self._common_btn)]
bf77b5d5 103 self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
63d69e94 104 self._pos_mgr['options'] = self._widgets[-1].pos_pixel()
e1e44d5c 105 self._widgets += [DirectButton(
2612dfc8 106 text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
4071c6d8 107 **self._common_btn)]
bf77b5d5 108 self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
63d69e94 109 self._pos_mgr['credits'] = self._widgets[-1].pos_pixel()
e65a09cf 110
158b04a8
FC
111 def btn_exit():
112 if self._fun_test:
a33967c7 113 ServerProxy('http://localhost:7000').destroy()
158b04a8 114 exit()
e1e44d5c 115 self._widgets += [DirectButton(
158b04a8 116 text=_('Exit'), pos=(0, 1, -.6), command=lambda: btn_exit(),
4071c6d8 117 **self._common_btn)]
bf77b5d5 118 self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
63d69e94 119 self._pos_mgr['exit'] = self._widgets[-1].pos_pixel()
75b89b83 120 self._rearrange_width()
74ed9732 121 self.accept('enforce_resolution', self.enforce_res)
4071c6d8
FC
122
123 def _set_options(self):
88dde925 124 for k in list(self._pos_mgr.keys()): del self._pos_mgr[k]
e1e44d5c 125 self._widgets = []
4071c6d8
FC
126 self._lang_funcs = [lambda: _('English'), lambda: _('Italian')]
127 items = [fnc() for fnc in self._lang_funcs]
128 inititem = {
129 'en': _('English'),
130 'it': _('Italian')
131 }[self._opt_file['settings']['language']]
ea71df6e
FC
132
133 def lang_cb(comps):
63d69e94
FC
134 for element in ['english', 'italian']:
135 if element in self._pos_mgr:
136 del self._pos_mgr[element]
ea71df6e 137 for lng, btn in zip(['english', 'italian'], comps):
d68aeda7 138 btn.__class__ = type('DirectFrameMixed', (DirectFrame, DirectGuiMixin), {})
bf77b5d5 139 pos = btn.pos_pixel()
63d69e94 140 self._pos_mgr[lng] = (pos[0] + 5, pos[1])
ea71df6e 141 btn = DirectOptionMenuTest(
4071c6d8 142 text=_('Language'), items=items, initialitem=inititem,
a9aba267 143 pos=(0, 1, .8), command=self.on_language, **self._common_opt)
bf77b5d5 144 btn.__class__ = type('DirectOptionMenuMixed', (DirectOptionMenu, DirectGuiMixin), {})
ea71df6e 145 btn._show_cb = lang_cb
4071c6d8
FC
146 btn.popupMenu['frameColor'] = self._common_btn['frameColor']
147 btn.popupMenu['relief'] = self._common_btn['relief']
e1e44d5c 148 self._widgets += [btn]
bf77b5d5 149 pos_lang = self._widgets[-1].pos_pixel()
63d69e94 150 self._pos_mgr['languages'] = pos_lang
e1e44d5c 151 self._widgets += [OnscreenText(
a9aba267 152 _('Volume'), pos=(-.1, .55), font=self._common['text_font'],
e1e44d5c
FC
153 scale=self._common['scale'], fg=self._common['text_fg'],
154 align=TextNode.A_right)]
155 self._widgets += [DirectSlider(
a9aba267 156 pos=(.5, 1, .57),
e1e44d5c
FC
157 value=self._opt_file['settings']['volume'],
158 command=self.on_volume,
159 **self._common_slider)]
bf77b5d5
FC
160 self._widgets[-1].__class__ = type('DirectSliderMixed', (DirectSlider, DirectGuiMixin), {})
161 vol_pos = self._widgets[-1].pos_pixel()
63d69e94 162 self._pos_mgr['volume'] = vol_pos
bf77b5d5 163 np_left = GfxTools.build_empty_node('left_slider')
f2ac3712
FC
164 np_left.set_pos(self._widgets[-1].get_net_transform().get_pos())
165 np_left.set_x(np_left.get_x() - self._widgets[-1].get_scale()[0])
bf77b5d5 166 lpos = np_left.pos_as_widget() # try with pos2d_pixel and remove pos_as_widget if ok
63d69e94 167 self._pos_mgr['volume_0'] = lpos
e1e44d5c 168 self._slider = self._widgets[-1]
6fff1464 169 self._widgets += [DirectCheckButton(
a9aba267 170 text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
6fff1464
FC
171 indicator_frameColor=self._common_opt['highlightColor'],
172 indicator_relief=self._common_btn['relief'],
173 indicatorValue=self._opt_file['settings']['fullscreen'],
174 **self._common_btn)]
bf77b5d5 175 self._widgets[-1].__class__ = type('DirectCheckButtonMixed', (DirectCheckButton, DirectGuiMixin), {})
63d69e94 176 self._pos_mgr['fullscreen'] = self._widgets[-1].pos_pixel()
6fff1464
FC
177 res = self._opt_file['settings']['resolution']
178 d_i = base.pipe.get_display_information()
179 def _res(idx):
180 return d_i.get_display_mode_width(idx), \
181 d_i.get_display_mode_height(idx)
182 resolutions = [
183 _res(idx) for idx in range(d_i.get_total_display_modes())]
184 resolutions = list(set(resolutions))
185 resolutions = sorted(resolutions)
186 resolutions = [(str(_res[0]), str(_res[1])) for _res in resolutions]
187 resolutions = ['x'.join(_res) for _res in resolutions]
188 if not res:
189 res = resolutions[-1]
ea71df6e
FC
190
191 def res_cb(comps):
63d69e94
FC
192 for element in ['res_1440x900', 'res_1360x768']:
193 if element in self._pos_mgr:
194 del self._pos_mgr[element]
ea71df6e
FC
195 for tgt_res in ['1440x900', '1360x768']:
196 for btn in comps:
197 if btn['text'] == tgt_res:
88dde925 198 pos = pos_pixel(btn)
63d69e94 199 self._pos_mgr['res_' + tgt_res] = (pos[0] + 5, pos[1])
ea71df6e 200 btn = DirectOptionMenuTest(
6fff1464 201 text=_('Resolution'), items=resolutions, initialitem=res,
a9aba267 202 pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
bf77b5d5 203 btn.__class__ = type('DirectOptionMenuMixed', (DirectOptionMenu, DirectGuiMixin), {})
ea71df6e 204 btn._show_cb = res_cb
6fff1464
FC
205 btn.popupMenu['frameColor'] = self._common_btn['frameColor']
206 btn.popupMenu['relief'] = self._common_btn['relief']
207 self._widgets += [btn]
bf77b5d5 208 pos_res = self._widgets[-1].pos_pixel()
63d69e94
FC
209 self._pos_mgr['resolutions'] = pos_res # 680 365
210 self._pos_mgr['res_1440x900'] = [pos_res[0] + 320, pos_res[1] + 75]
211 self._pos_mgr['res_1360x768'] = [pos_res[0] + 430, pos_res[1] -285]
a9aba267
FC
212 self._widgets += [DirectCheckButton(
213 text=_('Antialiasing'), pos=(0, 1, -.2), command=self.on_aa,
214 indicator_frameColor=self._common_opt['highlightColor'],
215 indicator_relief=self._common_btn['relief'],
216 indicatorValue=self._opt_file['settings']['antialiasing'],
217 **self._common_btn)]
bf77b5d5 218 self._widgets[-1].__class__ = type('DirectCheckButtonMixed', (DirectCheckButton, DirectGuiMixin), {})
63d69e94 219 self._pos_mgr['aa'] = self._widgets[-1].pos_pixel()
5fdf77d0
FC
220 self._widgets += [DirectCheckButton(
221 text=_('Shadows'), pos=(0, 1, -.45), command=self.on_shadows,
222 indicator_frameColor=self._common_opt['highlightColor'],
223 indicator_relief=self._common_btn['relief'],
224 indicatorValue=self._opt_file['settings']['shadows'],
225 **self._common_btn)]
bf77b5d5 226 self._widgets[-1].__class__ = type('DirectCheckButtonMixed', (DirectCheckButton, DirectGuiMixin), {})
63d69e94 227 self._pos_mgr['shadows'] = self._widgets[-1].pos_pixel()
e1e44d5c 228 self._widgets += [DirectButton(
a9aba267 229 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
4071c6d8 230 **self._common_btn)]
bf77b5d5 231 self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
63d69e94 232 self._pos_mgr['back'] = self._widgets[-1].pos_pixel()
74ed9732 233 self.accept('enforce_resolution', self.enforce_res)
4071c6d8 234
2612dfc8 235 def _set_credits(self):
88dde925 236 for k in list(self._pos_mgr.keys()): del self._pos_mgr[k]
2612dfc8
FC
237 self._widgets = []
238 tp_scale = TextProperties()
239 tp_scale.set_text_scale(.64)
240 TextPropertiesManager.getGlobalPtr().setProperties('scale', tp_scale)
241 self._widgets += [OnscreenText(
242 _('Code and gfx\n \1scale\1Flavio Calva\2\n\n\nMusic\n \1scale\1Stefan Grossmann\2'),
243 pos=(-.9, .55), font=self._common['text_font'],
244 scale=self._common['scale'], fg=self._common['text_fg'],
245 align=TextNode.A_left)]
246 self._widgets += [DirectButton(
247 text=_('Website'), pos=(-.6, 1, .29), command=self.on_website,
248 **self._common_btn | {'scale': .08})]
249 self._widgets += [OnscreenText(
e67dbe53 250 _('Special thanks to:\n \1scale\1rdb\2\n \1scale\1Luisa Tenuta\2\n \1scale\1Damiana Ercolani\2'),
2612dfc8
FC
251 pos=(.1, .55), font=self._common['text_font'],
252 scale=self._common['scale'], fg=self._common['text_fg'],
253 align=TextNode.A_left)]
254 self._widgets += [DirectButton(
255 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
256 **self._common_btn)]
bf77b5d5 257 self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
63d69e94 258 self._pos_mgr['back'] = self._widgets[-1].pos_pixel()
74ed9732 259 self.accept('enforce_resolution', self.enforce_res)
2612dfc8 260
4071c6d8 261 def on_play(self):
88dde925 262 for k in list(self._pos_mgr.keys()): del self._pos_mgr[k]
0d5a5427 263 self.destroy()
638deddf 264 self._cursor = MouseCursor(
7e487769 265 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
d68aeda7 266 (.01, .01), self._fun_test)
0d5a5427 267 self._widgets = []
8c9bf90e
FC
268 cmn = self._common_btn.copy() | {
269 'frameSize': (-2.4, 2.4, -2.4, 2.4),
bb956c26
FC
270 'frameColor': (1, 1, 1, .8),
271 'text_scale': .64}
6168d0c2 272 left = - (dx := .8) * (min(4, len(self._scenes)) - 1) / 2
07a1eaf3 273 from pmachines.scene.scene import Scene
f7eade0f 274 for i, scene_name in enumerate(self._scenes):
6168d0c2 275 top = .1 if len(self._scenes) < 5 else .6
a5fddddc 276 row = 0 if i < 4 else 1
92c29685 277 new_cmn = cmn.copy()
f7eade0f 278 if Scene.is_done(scene_name):
92c29685
FC
279 new_cmn['frameColor'] = (1, 1, 1, .4)
280 new_cmn['text_fg'] = (.9, .9, .9, .4)
28acea3a 281 self._widgets += [DirectButton(
f7eade0f
FC
282 text=Scene.name(scene_name), pos=(left + dx * (i % 4), 1, top - dx * row),
283 command=self.start, extraArgs=[scene_name], text_wordwrap=6,
284 frameTexture='assets/images/scenes/%s.dds' % scene_name,
92c29685 285 **new_cmn)]
bf77b5d5 286 self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
f7eade0f 287 name = scene_name.lower()
63d69e94 288 self._pos_mgr[name] = self._widgets[-1].pos_pixel()
bb956c26
FC
289 for j in range(4):
290 tnode = self._widgets[-1].component('text%s' % j).textNode
291 height = - tnode.getLineHeight() / 2
292 height += (tnode.get_height() - tnode.get_line_height()) / 2
293 self._widgets[-1].component('text%s' % j).set_pos(0, 0, height)
0d5a5427
FC
294 self._widgets += [DirectButton(
295 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
296 **self._common_btn)]
bf77b5d5 297 self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
63d69e94 298 self._pos_mgr['back'] = self._widgets[-1].pos_pixel()
0d5a5427 299
8c9bf90e
FC
300 def start(self, cls):
301 self._fsm.demand('Scene', cls)
4071c6d8
FC
302
303 def on_options(self):
304 self.destroy()
638deddf 305 self._cursor = MouseCursor(
7e487769 306 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
d68aeda7 307 (.01, .01), self._fun_test)
4071c6d8
FC
308 self._set_options()
309
2612dfc8
FC
310 def on_credits(self):
311 self.destroy()
638deddf 312 self._cursor = MouseCursor(
7e487769 313 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
d68aeda7 314 (.01, .01), self._fun_test)
2612dfc8
FC
315 self._set_credits()
316
75b89b83
FC
317 def _rearrange_width(self):
318 max_width = 0
319 for wdg in self._widgets:
320 t_n = wdg.component('text0')
321 u_l = t_n.textNode.get_upper_left_3d()
322 l_r = t_n.textNode.get_lower_right_3d()
323 max_width = max(l_r[0] - u_l[0], max_width)
324 for wdg in self._widgets:
325 m_w = max_width / 2 + .8
326 wdg['frameSize'] = -m_w, m_w, wdg['frameSize'][2], wdg['frameSize'][3]
327
4071c6d8
FC
328 def on_language(self, arg):
329 lang_code = {
330 _('English'): 'en_EN',
331 _('Italian'): 'it_IT'}[arg]
d68aeda7 332 self._lang_mgr.set_language(lang_code)
4071c6d8
FC
333 self._opt_file['settings']['language'] = lang_code[:2]
334 self._opt_file.store()
335 self.on_options()
336
e1e44d5c
FC
337 def on_volume(self):
338 self._opt_file['settings']['volume'] = self._slider['value']
339 self._music.set_volume(self._slider['value'])
340
6fff1464
FC
341 def on_fullscreen(self, arg):
342 props = WindowProperties()
343 props.set_fullscreen(arg)
e982cdde
FC
344 if not self._fun_test:
345 base.win.request_properties(props)
346 # if we actually switch to fullscreen during the tests then
347 # exwm inside qemu can't restore the correct resolution
348 # i may re-enable this if/when i run the tests onto a
349 # physical machine
6fff1464
FC
350 self._opt_file['settings']['fullscreen'] = int(arg)
351 self._opt_file.store()
352
353 def on_resolution(self, arg):
74ed9732
FC
354 info('on resolution: %s (%s)' % (arg, self._enforced_res))
355 arg = self._enforced_res or arg
356 info('set resolution: %s' % arg)
6fff1464
FC
357 props = WindowProperties()
358 props.set_size(LVector2i(*[int(_res) for _res in arg.split('x')]))
359 base.win.request_properties(props)
360 self._opt_file['settings']['resolution'] = arg
361 self._opt_file.store()
362
a9aba267
FC
363 def on_aa(self, arg):
364 self._pipeline.msaa_samples = 4 if arg else 1
94a18c21 365 debug(f'msaa: {self._pipeline.msaa_samples}')
a9aba267
FC
366 self._opt_file['settings']['antialiasing'] = int(arg)
367 self._opt_file.store()
368
5fdf77d0
FC
369 def on_shadows(self, arg):
370 self._pipeline.enable_shadows = int(arg)
94a18c21 371 debug(f'shadows: {self._pipeline.enable_shadows}')
5fdf77d0
FC
372 self._opt_file['settings']['shadows'] = int(arg)
373 self._opt_file.store()
374
2612dfc8
FC
375 def on_website(self):
376 if platform.startswith('linux'):
377 environ['LD_LIBRARY_PATH'] = ''
378 system('xdg-open https://www.ya2.it')
379 else:
380 open_new_tab('https://www.ya2.it')
381
4071c6d8 382 def on_back(self):
e1e44d5c 383 self._opt_file.store()
4071c6d8 384 self.destroy()
638deddf 385 self._cursor = MouseCursor(
7e487769 386 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
d68aeda7 387 (.01, .01), self._fun_test)
4071c6d8
FC
388 self._set_main()
389
390 def destroy(self):
e1e44d5c 391 [wdg.destroy() for wdg in self._widgets]
407412a5 392 self._cursor.destroy()
74ed9732 393 self.ignore('enforce_resolution')