ya2 · news · projects · code · about

xmlrpc
[pmachines.git] / gui / menu.py
CommitLineData
94a18c21 1from logging import info, debug
2612dfc8
FC
2from sys import platform, exit
3from os import environ, system
8c9bf90e
FC
4from glob import glob
5from importlib import import_module
6from inspect import isclass
2612dfc8 7from webbrowser import open_new_tab
158b04a8 8from xmlrpc.client import ServerProxy
2612dfc8
FC
9from panda3d.core import Texture, TextNode, WindowProperties, LVector2i, \
10 TextProperties, TextPropertiesManager
4071c6d8 11from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
6fff1464 12 DirectOptionMenu, DirectSlider, DirectCheckButton
4071c6d8 13from direct.gui.DirectGuiGlobals import FLAT
e1e44d5c 14from direct.gui.OnscreenText import OnscreenText
74ed9732 15from direct.showbase.DirectObject import DirectObject
b35b1f62
FC
16from ya2.utils.cursor import MouseCursor
17from logics.scene import Scene
0d5a5427 18from panda3d.bullet import BulletWorld
4071c6d8
FC
19
20
74ed9732 21class Menu(DirectObject):
4071c6d8 22
e982cdde 23 def __init__(self, fsm, lang_mgr, opt_file, music, pipeline, scenes, fun_test):
74ed9732 24 super().__init__()
4071c6d8
FC
25 self._fsm = fsm
26 self._lang_mgr = lang_mgr
27 self._opt_file = opt_file
e1e44d5c 28 self._music = music
a9aba267 29 self._pipeline = pipeline
6168d0c2 30 self._scenes = scenes
e982cdde 31 self._fun_test = fun_test
74ed9732 32 self._enforced_res = ''
407412a5 33 self._cursor = MouseCursor(
7e487769 34 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
407412a5 35 (.01, .01))
4071c6d8
FC
36 self._font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
37 self._font.clear()
38 self._font.set_pixels_per_unit(60)
39 self._font.set_minfilter(Texture.FTLinearMipmapLinear)
40 self._font.set_outline((0, 0, 0, 1), .8, .2)
e1e44d5c 41 self._widgets = []
4071c6d8
FC
42 self._common = {
43 'scale': .12,
44 'text_font': self._font,
45 'text_fg': (.9, .9, .9, 1),
46 'relief': FLAT,
47 'frameColor': (.4, .4, .4, .14),
48 'rolloverSound': loader.load_sfx('assets/audio/sfx/rollover.ogg'),
49 'clickSound': loader.load_sfx('assets/audio/sfx/click.ogg')}
75b89b83 50 self._common_btn = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self._common
6fff1464
FC
51 hlc = self._common_btn['frameColor']
52 hlc = (hlc[0] + .2, hlc[1] + .2, hlc[2] + .2, hlc[3] + .2)
53 self._common_opt = {
54 'item_frameColor': self._common_btn['frameColor'],
55 'popupMarker_frameColor': self._common_btn['frameColor'],
56 'item_relief': self._common_btn['relief'],
57 'item_text_font': self._common_btn['text_font'],
58 'item_text_fg': self._common_btn['text_fg'],
59 'textMayChange': 1,
60 'highlightColor': hlc,
61 'text_align': TextNode.A_center,
62 } | self._common_btn
63 f_s = self._common_opt['frameSize']
64 self._common_opt['frameSize'] = f_s[0], f_s[1] - .56, f_s[2], f_s[3]
e1e44d5c
FC
65 self._common_slider = self._common | {
66 'range': (0, 1),
67 'thumb_frameColor': (.4, .4, .4, .4),
68 'thumb_scale': 1.6,
69 'scale': .4}
70 del self._common_slider['rolloverSound']
71 del self._common_slider['clickSound']
4071c6d8
FC
72 self._set_main()
73
74ed9732
FC
74 def enforce_res(self, val):
75 self._enforced_res = val
76 info('enforced resolution: ' + val)
77
4071c6d8 78 def _set_main(self):
e1e44d5c
FC
79 self._widgets = []
80 self._widgets += [DirectButton(
4071c6d8
FC
81 text=_('Play'), pos=(0, 1, .6), command=self.on_play,
82 **self._common_btn)]
e1e44d5c 83 self._widgets += [DirectButton(
2612dfc8 84 text=_('Options'), pos=(0, 1, .2), command=self.on_options,
4071c6d8 85 **self._common_btn)]
e1e44d5c 86 self._widgets += [DirectButton(
2612dfc8 87 text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
4071c6d8 88 **self._common_btn)]
158b04a8
FC
89 def btn_exit():
90 if self._fun_test:
91 ServerProxy('http://localhost:6000').destroy()
92 exit()
e1e44d5c 93 self._widgets += [DirectButton(
158b04a8 94 text=_('Exit'), pos=(0, 1, -.6), command=lambda: btn_exit(),
4071c6d8 95 **self._common_btn)]
75b89b83 96 self._rearrange_width()
74ed9732 97 self.accept('enforce_resolution', self.enforce_res)
4071c6d8
FC
98
99 def _set_options(self):
e1e44d5c 100 self._widgets = []
4071c6d8
FC
101 self._lang_funcs = [lambda: _('English'), lambda: _('Italian')]
102 items = [fnc() for fnc in self._lang_funcs]
103 inititem = {
104 'en': _('English'),
105 'it': _('Italian')
106 }[self._opt_file['settings']['language']]
4071c6d8
FC
107 btn = DirectOptionMenu(
108 text=_('Language'), items=items, initialitem=inititem,
a9aba267 109 pos=(0, 1, .8), command=self.on_language, **self._common_opt)
4071c6d8
FC
110 btn.popupMenu['frameColor'] = self._common_btn['frameColor']
111 btn.popupMenu['relief'] = self._common_btn['relief']
e1e44d5c
FC
112 self._widgets += [btn]
113 self._widgets += [OnscreenText(
a9aba267 114 _('Volume'), pos=(-.1, .55), font=self._common['text_font'],
e1e44d5c
FC
115 scale=self._common['scale'], fg=self._common['text_fg'],
116 align=TextNode.A_right)]
117 self._widgets += [DirectSlider(
a9aba267 118 pos=(.5, 1, .57),
e1e44d5c
FC
119 value=self._opt_file['settings']['volume'],
120 command=self.on_volume,
121 **self._common_slider)]
122 self._slider = self._widgets[-1]
6fff1464 123 self._widgets += [DirectCheckButton(
a9aba267 124 text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
6fff1464
FC
125 indicator_frameColor=self._common_opt['highlightColor'],
126 indicator_relief=self._common_btn['relief'],
127 indicatorValue=self._opt_file['settings']['fullscreen'],
128 **self._common_btn)]
129 res = self._opt_file['settings']['resolution']
130 d_i = base.pipe.get_display_information()
131 def _res(idx):
132 return d_i.get_display_mode_width(idx), \
133 d_i.get_display_mode_height(idx)
134 resolutions = [
135 _res(idx) for idx in range(d_i.get_total_display_modes())]
136 resolutions = list(set(resolutions))
137 resolutions = sorted(resolutions)
138 resolutions = [(str(_res[0]), str(_res[1])) for _res in resolutions]
139 resolutions = ['x'.join(_res) for _res in resolutions]
140 if not res:
141 res = resolutions[-1]
142 btn = DirectOptionMenu(
143 text=_('Resolution'), items=resolutions, initialitem=res,
a9aba267 144 pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
6fff1464
FC
145 btn.popupMenu['frameColor'] = self._common_btn['frameColor']
146 btn.popupMenu['relief'] = self._common_btn['relief']
147 self._widgets += [btn]
a9aba267
FC
148 self._widgets += [DirectCheckButton(
149 text=_('Antialiasing'), pos=(0, 1, -.2), command=self.on_aa,
150 indicator_frameColor=self._common_opt['highlightColor'],
151 indicator_relief=self._common_btn['relief'],
152 indicatorValue=self._opt_file['settings']['antialiasing'],
153 **self._common_btn)]
5fdf77d0
FC
154 self._widgets += [DirectCheckButton(
155 text=_('Shadows'), pos=(0, 1, -.45), command=self.on_shadows,
156 indicator_frameColor=self._common_opt['highlightColor'],
157 indicator_relief=self._common_btn['relief'],
158 indicatorValue=self._opt_file['settings']['shadows'],
159 **self._common_btn)]
e1e44d5c 160 self._widgets += [DirectButton(
a9aba267 161 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
4071c6d8 162 **self._common_btn)]
74ed9732 163 self.accept('enforce_resolution', self.enforce_res)
4071c6d8 164
2612dfc8
FC
165 def _set_credits(self):
166 self._widgets = []
167 tp_scale = TextProperties()
168 tp_scale.set_text_scale(.64)
169 TextPropertiesManager.getGlobalPtr().setProperties('scale', tp_scale)
170 self._widgets += [OnscreenText(
171 _('Code and gfx\n \1scale\1Flavio Calva\2\n\n\nMusic\n \1scale\1Stefan Grossmann\2'),
172 pos=(-.9, .55), font=self._common['text_font'],
173 scale=self._common['scale'], fg=self._common['text_fg'],
174 align=TextNode.A_left)]
175 self._widgets += [DirectButton(
176 text=_('Website'), pos=(-.6, 1, .29), command=self.on_website,
177 **self._common_btn | {'scale': .08})]
178 self._widgets += [OnscreenText(
e67dbe53 179 _('Special thanks to:\n \1scale\1rdb\2\n \1scale\1Luisa Tenuta\2\n \1scale\1Damiana Ercolani\2'),
2612dfc8
FC
180 pos=(.1, .55), font=self._common['text_font'],
181 scale=self._common['scale'], fg=self._common['text_fg'],
182 align=TextNode.A_left)]
183 self._widgets += [DirectButton(
184 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
185 **self._common_btn)]
74ed9732 186 self.accept('enforce_resolution', self.enforce_res)
2612dfc8 187
4071c6d8 188 def on_play(self):
0d5a5427 189 self.destroy()
638deddf 190 self._cursor = MouseCursor(
7e487769 191 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 192 (.01, .01))
0d5a5427 193 self._widgets = []
8c9bf90e
FC
194 cmn = self._common_btn.copy() | {
195 'frameSize': (-2.4, 2.4, -2.4, 2.4),
bb956c26
FC
196 'frameColor': (1, 1, 1, .8),
197 'text_scale': .64}
6168d0c2
FC
198 left = - (dx := .8) * (min(4, len(self._scenes)) - 1) / 2
199 for i, cls in enumerate(self._scenes):
200 top = .1 if len(self._scenes) < 5 else .6
a5fddddc 201 row = 0 if i < 4 else 1
28acea3a 202 self._widgets += [DirectButton(
a5fddddc 203 text=cls.name(), pos=(left + dx * (i % 4), 1, top - dx * row),
bb956c26 204 command=self.start, extraArgs=[cls], text_wordwrap=6,
420ce99a 205 frameTexture='assets/images/scenes/%s.dds' % cls.__name__,
28acea3a 206 **cmn)]
bb956c26
FC
207 for j in range(4):
208 tnode = self._widgets[-1].component('text%s' % j).textNode
209 height = - tnode.getLineHeight() / 2
210 height += (tnode.get_height() - tnode.get_line_height()) / 2
211 self._widgets[-1].component('text%s' % j).set_pos(0, 0, height)
0d5a5427
FC
212 self._widgets += [DirectButton(
213 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
214 **self._common_btn)]
215
8c9bf90e
FC
216 def start(self, cls):
217 self._fsm.demand('Scene', cls)
4071c6d8
FC
218
219 def on_options(self):
220 self.destroy()
638deddf 221 self._cursor = MouseCursor(
7e487769 222 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 223 (.01, .01))
4071c6d8
FC
224 self._set_options()
225
2612dfc8
FC
226 def on_credits(self):
227 self.destroy()
638deddf 228 self._cursor = MouseCursor(
7e487769 229 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 230 (.01, .01))
2612dfc8
FC
231 self._set_credits()
232
75b89b83
FC
233 def _rearrange_width(self):
234 max_width = 0
235 for wdg in self._widgets:
236 t_n = wdg.component('text0')
237 u_l = t_n.textNode.get_upper_left_3d()
238 l_r = t_n.textNode.get_lower_right_3d()
239 max_width = max(l_r[0] - u_l[0], max_width)
240 for wdg in self._widgets:
241 m_w = max_width / 2 + .8
242 wdg['frameSize'] = -m_w, m_w, wdg['frameSize'][2], wdg['frameSize'][3]
243
4071c6d8
FC
244 def on_language(self, arg):
245 lang_code = {
246 _('English'): 'en_EN',
247 _('Italian'): 'it_IT'}[arg]
248 self._lang_mgr.set_lang(lang_code)
249 self._opt_file['settings']['language'] = lang_code[:2]
250 self._opt_file.store()
251 self.on_options()
252
e1e44d5c
FC
253 def on_volume(self):
254 self._opt_file['settings']['volume'] = self._slider['value']
255 self._music.set_volume(self._slider['value'])
256
6fff1464
FC
257 def on_fullscreen(self, arg):
258 props = WindowProperties()
259 props.set_fullscreen(arg)
e982cdde
FC
260 if not self._fun_test:
261 base.win.request_properties(props)
262 # if we actually switch to fullscreen during the tests then
263 # exwm inside qemu can't restore the correct resolution
264 # i may re-enable this if/when i run the tests onto a
265 # physical machine
6fff1464
FC
266 self._opt_file['settings']['fullscreen'] = int(arg)
267 self._opt_file.store()
268
269 def on_resolution(self, arg):
74ed9732
FC
270 info('on resolution: %s (%s)' % (arg, self._enforced_res))
271 arg = self._enforced_res or arg
272 info('set resolution: %s' % arg)
6fff1464
FC
273 props = WindowProperties()
274 props.set_size(LVector2i(*[int(_res) for _res in arg.split('x')]))
275 base.win.request_properties(props)
276 self._opt_file['settings']['resolution'] = arg
277 self._opt_file.store()
278
a9aba267
FC
279 def on_aa(self, arg):
280 self._pipeline.msaa_samples = 4 if arg else 1
94a18c21 281 debug(f'msaa: {self._pipeline.msaa_samples}')
a9aba267
FC
282 self._opt_file['settings']['antialiasing'] = int(arg)
283 self._opt_file.store()
284
5fdf77d0
FC
285 def on_shadows(self, arg):
286 self._pipeline.enable_shadows = int(arg)
94a18c21 287 debug(f'shadows: {self._pipeline.enable_shadows}')
5fdf77d0
FC
288 self._opt_file['settings']['shadows'] = int(arg)
289 self._opt_file.store()
290
2612dfc8
FC
291 def on_website(self):
292 if platform.startswith('linux'):
293 environ['LD_LIBRARY_PATH'] = ''
294 system('xdg-open https://www.ya2.it')
295 else:
296 open_new_tab('https://www.ya2.it')
297
4071c6d8 298 def on_back(self):
e1e44d5c 299 self._opt_file.store()
4071c6d8 300 self.destroy()
638deddf 301 self._cursor = MouseCursor(
7e487769 302 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 303 (.01, .01))
4071c6d8
FC
304 self._set_main()
305
306 def destroy(self):
e1e44d5c 307 [wdg.destroy() for wdg in self._widgets]
407412a5 308 self._cursor.destroy()
74ed9732 309 self.ignore('enforce_resolution')