ya2 · news · projects · code · about

renamed modules
[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, \
e65a09cf 7 TextProperties, TextPropertiesManager
4071c6d8 8from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
e65a09cf 9 DirectOptionMenu, DirectSlider
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
2d1773b1 14from ya2.p3d.p3d import LibP3d
4071c6d8
FC
15
16
74ed9732 17class Menu(DirectObject):
4071c6d8 18
e65a09cf
FC
19 def __init__(self, fsm, lang_mgr, opt_file, music, pipeline, scenes,
20 fun_test, pos_mgr):
74ed9732 21 super().__init__()
4071c6d8
FC
22 self._fsm = fsm
23 self._lang_mgr = lang_mgr
24 self._opt_file = opt_file
e1e44d5c 25 self._music = music
a9aba267 26 self._pipeline = pipeline
6168d0c2 27 self._scenes = scenes
e982cdde 28 self._fun_test = fun_test
2d1773b1 29 self._pos_mgr = pos_mgr
74ed9732 30 self._enforced_res = ''
407412a5 31 self._cursor = MouseCursor(
e65a09cf
FC
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')
4071c6d8
FC
36 self._font.clear()
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)
e1e44d5c 40 self._widgets = []
4071c6d8
FC
41 self._common = {
42 'scale': .12,
43 'text_font': self._font,
44 'text_fg': (.9, .9, .9, 1),
45 'relief': FLAT,
46 'frameColor': (.4, .4, .4, .14),
e65a09cf
FC
47 'rolloverSound': loader.load_sfx(
48 'assets/audio/sfx/rollover.ogg'),
49 'clickSound': loader.load_sfx(
50 'assets/audio/sfx/click.ogg')}
75b89b83 51 self._common_btn = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self._common
6fff1464
FC
52 hlc = self._common_btn['frameColor']
53 hlc = (hlc[0] + .2, hlc[1] + .2, hlc[2] + .2, hlc[3] + .2)
54 self._common_opt = {
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'],
60 'textMayChange': 1,
61 'highlightColor': hlc,
62 'text_align': TextNode.A_center,
63 } | self._common_btn
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]
e1e44d5c
FC
66 self._common_slider = self._common | {
67 'range': (0, 1),
68 'thumb_frameColor': (.4, .4, .4, .4),
69 'thumb_scale': 1.6,
70 'scale': .4}
71 del self._common_slider['rolloverSound']
72 del self._common_slider['clickSound']
4071c6d8
FC
73 self._set_main()
74
74ed9732
FC
75 def enforce_res(self, val):
76 self._enforced_res = val
77 info('enforced resolution: ' + val)
78
4071c6d8 79 def _set_main(self):
2d1773b1 80 self._pos_mgr.reset()
e1e44d5c
FC
81 self._widgets = []
82 self._widgets += [DirectButton(
4071c6d8
FC
83 text=_('Play'), pos=(0, 1, .6), command=self.on_play,
84 **self._common_btn)]
2d1773b1 85 self._pos_mgr.register('play', LibP3d.wdg_pos(self._widgets[-1]))
e1e44d5c 86 self._widgets += [DirectButton(
2612dfc8 87 text=_('Options'), pos=(0, 1, .2), command=self.on_options,
4071c6d8 88 **self._common_btn)]
2d1773b1 89 self._pos_mgr.register('options', LibP3d.wdg_pos(self._widgets[-1]))
e1e44d5c 90 self._widgets += [DirectButton(
2612dfc8 91 text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
4071c6d8 92 **self._common_btn)]
2d1773b1 93 self._pos_mgr.register('credits', LibP3d.wdg_pos(self._widgets[-1]))
e65a09cf 94
158b04a8
FC
95 def btn_exit():
96 if self._fun_test:
97 ServerProxy('http://localhost:6000').destroy()
98 exit()
e1e44d5c 99 self._widgets += [DirectButton(
158b04a8 100 text=_('Exit'), pos=(0, 1, -.6), command=lambda: btn_exit(),
4071c6d8 101 **self._common_btn)]
2d1773b1 102 self._pos_mgr.register('exit', LibP3d.wdg_pos(self._widgets[-1]))
75b89b83 103 self._rearrange_width()
74ed9732 104 self.accept('enforce_resolution', self.enforce_res)
4071c6d8
FC
105
106 def _set_options(self):
2d1773b1 107 self._pos_mgr.reset()
e1e44d5c 108 self._widgets = []
4071c6d8
FC
109 self._lang_funcs = [lambda: _('English'), lambda: _('Italian')]
110 items = [fnc() for fnc in self._lang_funcs]
111 inititem = {
112 'en': _('English'),
113 'it': _('Italian')
114 }[self._opt_file['settings']['language']]
4071c6d8
FC
115 btn = DirectOptionMenu(
116 text=_('Language'), items=items, initialitem=inititem,
a9aba267 117 pos=(0, 1, .8), command=self.on_language, **self._common_opt)
4071c6d8
FC
118 btn.popupMenu['frameColor'] = self._common_btn['frameColor']
119 btn.popupMenu['relief'] = self._common_btn['relief']
e1e44d5c 120 self._widgets += [btn]
2d1773b1
FC
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)
e1e44d5c 127 self._widgets += [OnscreenText(
a9aba267 128 _('Volume'), pos=(-.1, .55), font=self._common['text_font'],
e1e44d5c
FC
129 scale=self._common['scale'], fg=self._common['text_fg'],
130 align=TextNode.A_right)]
131 self._widgets += [DirectSlider(
a9aba267 132 pos=(.5, 1, .57),
e1e44d5c
FC
133 value=self._opt_file['settings']['volume'],
134 command=self.on_volume,
135 **self._common_slider)]
2d1773b1
FC
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]])
e1e44d5c 139 self._slider = self._widgets[-1]
6fff1464 140 self._widgets += [DirectCheckButton(
a9aba267 141 text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
6fff1464
FC
142 indicator_frameColor=self._common_opt['highlightColor'],
143 indicator_relief=self._common_btn['relief'],
144 indicatorValue=self._opt_file['settings']['fullscreen'],
145 **self._common_btn)]
2d1773b1 146 self._pos_mgr.register('fullscreen', LibP3d.wdg_pos(self._widgets[-1]))
6fff1464
FC
147 res = self._opt_file['settings']['resolution']
148 d_i = base.pipe.get_display_information()
149 def _res(idx):
150 return d_i.get_display_mode_width(idx), \
151 d_i.get_display_mode_height(idx)
152 resolutions = [
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]
158 if not res:
159 res = resolutions[-1]
160 btn = DirectOptionMenu(
161 text=_('Resolution'), items=resolutions, initialitem=res,
a9aba267 162 pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
6fff1464
FC
163 btn.popupMenu['frameColor'] = self._common_btn['frameColor']
164 btn.popupMenu['relief'] = self._common_btn['relief']
165 self._widgets += [btn]
2d1773b1
FC
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])
a9aba267
FC
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'],
175 **self._common_btn)]
2d1773b1 176 self._pos_mgr.register('aa', LibP3d.wdg_pos(self._widgets[-1]))
5fdf77d0
FC
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'],
182 **self._common_btn)]
2d1773b1 183 self._pos_mgr.register('shadows', LibP3d.wdg_pos(self._widgets[-1]))
e1e44d5c 184 self._widgets += [DirectButton(
a9aba267 185 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
4071c6d8 186 **self._common_btn)]
2d1773b1 187 self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
74ed9732 188 self.accept('enforce_resolution', self.enforce_res)
4071c6d8 189
2612dfc8 190 def _set_credits(self):
2d1773b1 191 self._pos_mgr.reset()
2612dfc8
FC
192 self._widgets = []
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(
e67dbe53 205 _('Special thanks to:\n \1scale\1rdb\2\n \1scale\1Luisa Tenuta\2\n \1scale\1Damiana Ercolani\2'),
2612dfc8
FC
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,
211 **self._common_btn)]
2d1773b1 212 self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
74ed9732 213 self.accept('enforce_resolution', self.enforce_res)
2612dfc8 214
4071c6d8 215 def on_play(self):
2d1773b1 216 self._pos_mgr.reset()
0d5a5427 217 self.destroy()
638deddf 218 self._cursor = MouseCursor(
7e487769 219 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 220 (.01, .01))
0d5a5427 221 self._widgets = []
8c9bf90e
FC
222 cmn = self._common_btn.copy() | {
223 'frameSize': (-2.4, 2.4, -2.4, 2.4),
bb956c26
FC
224 'frameColor': (1, 1, 1, .8),
225 'text_scale': .64}
6168d0c2
FC
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
a5fddddc 229 row = 0 if i < 4 else 1
28acea3a 230 self._widgets += [DirectButton(
a5fddddc 231 text=cls.name(), pos=(left + dx * (i % 4), 1, top - dx * row),
bb956c26 232 command=self.start, extraArgs=[cls], text_wordwrap=6,
420ce99a 233 frameTexture='assets/images/scenes/%s.dds' % cls.__name__,
28acea3a 234 **cmn)]
2d1773b1
FC
235 name = cls.__name__[5:].lower()
236 self._pos_mgr.register(name, LibP3d.wdg_pos(self._widgets[-1]))
bb956c26
FC
237 for j in range(4):
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)
0d5a5427
FC
242 self._widgets += [DirectButton(
243 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
244 **self._common_btn)]
2d1773b1 245 self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
0d5a5427 246
8c9bf90e
FC
247 def start(self, cls):
248 self._fsm.demand('Scene', cls)
4071c6d8
FC
249
250 def on_options(self):
251 self.destroy()
638deddf 252 self._cursor = MouseCursor(
7e487769 253 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 254 (.01, .01))
4071c6d8
FC
255 self._set_options()
256
2612dfc8
FC
257 def on_credits(self):
258 self.destroy()
638deddf 259 self._cursor = MouseCursor(
7e487769 260 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 261 (.01, .01))
2612dfc8
FC
262 self._set_credits()
263
75b89b83
FC
264 def _rearrange_width(self):
265 max_width = 0
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]
274
4071c6d8
FC
275 def on_language(self, arg):
276 lang_code = {
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()
282 self.on_options()
283
e1e44d5c
FC
284 def on_volume(self):
285 self._opt_file['settings']['volume'] = self._slider['value']
286 self._music.set_volume(self._slider['value'])
287
6fff1464
FC
288 def on_fullscreen(self, arg):
289 props = WindowProperties()
290 props.set_fullscreen(arg)
e982cdde
FC
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
296 # physical machine
6fff1464
FC
297 self._opt_file['settings']['fullscreen'] = int(arg)
298 self._opt_file.store()
299
300 def on_resolution(self, arg):
74ed9732
FC
301 info('on resolution: %s (%s)' % (arg, self._enforced_res))
302 arg = self._enforced_res or arg
303 info('set resolution: %s' % arg)
6fff1464
FC
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()
309
a9aba267
FC
310 def on_aa(self, arg):
311 self._pipeline.msaa_samples = 4 if arg else 1
94a18c21 312 debug(f'msaa: {self._pipeline.msaa_samples}')
a9aba267
FC
313 self._opt_file['settings']['antialiasing'] = int(arg)
314 self._opt_file.store()
315
5fdf77d0
FC
316 def on_shadows(self, arg):
317 self._pipeline.enable_shadows = int(arg)
94a18c21 318 debug(f'shadows: {self._pipeline.enable_shadows}')
5fdf77d0
FC
319 self._opt_file['settings']['shadows'] = int(arg)
320 self._opt_file.store()
321
2612dfc8
FC
322 def on_website(self):
323 if platform.startswith('linux'):
324 environ['LD_LIBRARY_PATH'] = ''
325 system('xdg-open https://www.ya2.it')
326 else:
327 open_new_tab('https://www.ya2.it')
328
4071c6d8 329 def on_back(self):
e1e44d5c 330 self._opt_file.store()
4071c6d8 331 self.destroy()
638deddf 332 self._cursor = MouseCursor(
7e487769 333 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 334 (.01, .01))
4071c6d8
FC
335 self._set_main()
336
337 def destroy(self):
e1e44d5c 338 [wdg.destroy() for wdg in self._widgets]
407412a5 339 self._cursor.destroy()
74ed9732 340 self.ignore('enforce_resolution')