ya2 · news · projects · code · about

removed changelog
[pmachines.git] / gui / menu.py
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
15
16
17 class Menu(DirectObject):
18
19 def __init__(self, fsm, lang_mgr, opt_file, music, pipeline, scenes,
20 fun_test, pos_mgr):
21 super().__init__()
22 self._fsm = fsm
23 self._lang_mgr = lang_mgr
24 self._opt_file = opt_file
25 self._music = music
26 self._pipeline = pipeline
27 self._scenes = scenes
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')
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)
40 self._widgets = []
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),
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)
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]
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']
73 self._set_main()
74
75 def enforce_res(self, val):
76 self._enforced_res = val
77 info('enforced resolution: ' + val)
78
79 def _set_main(self):
80 self._pos_mgr.reset()
81 self._widgets = []
82 self._widgets += [DirectButton(
83 text=_('Play'), pos=(0, 1, .6), command=self.on_play,
84 **self._common_btn)]
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,
88 **self._common_btn)]
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,
92 **self._common_btn)]
93 self._pos_mgr.register('credits', LibP3d.wdg_pos(self._widgets[-1]))
94
95 def btn_exit():
96 if self._fun_test:
97 ServerProxy('http://localhost:6000').destroy()
98 exit()
99 self._widgets += [DirectButton(
100 text=_('Exit'), pos=(0, 1, -.6), command=lambda: btn_exit(),
101 **self._common_btn)]
102 self._pos_mgr.register('exit', LibP3d.wdg_pos(self._widgets[-1]))
103 self._rearrange_width()
104 self.accept('enforce_resolution', self.enforce_res)
105
106 def _set_options(self):
107 self._pos_mgr.reset()
108 self._widgets = []
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']]
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(
132 pos=(.5, 1, .57),
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'],
145 **self._common_btn)]
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()
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,
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'],
175 **self._common_btn)]
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'],
182 **self._common_btn)]
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,
186 **self._common_btn)]
187 self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
188 self.accept('enforce_resolution', self.enforce_res)
189
190 def _set_credits(self):
191 self._pos_mgr.reset()
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(
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,
211 **self._common_btn)]
212 self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
213 self.accept('enforce_resolution', self.enforce_res)
214
215 def on_play(self):
216 self._pos_mgr.reset()
217 self.destroy()
218 self._cursor = MouseCursor(
219 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
220 (.01, .01))
221 self._widgets = []
222 cmn = self._common_btn.copy() | {
223 'frameSize': (-2.4, 2.4, -2.4, 2.4),
224 'frameColor': (1, 1, 1, .8),
225 'text_scale': .64}
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__,
234 **cmn)]
235 name = cls.__name__[5:].lower()
236 self._pos_mgr.register(name, LibP3d.wdg_pos(self._widgets[-1]))
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)
242 self._widgets += [DirectButton(
243 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
244 **self._common_btn)]
245 self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
246
247 def start(self, cls):
248 self._fsm.demand('Scene', cls)
249
250 def on_options(self):
251 self.destroy()
252 self._cursor = MouseCursor(
253 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
254 (.01, .01))
255 self._set_options()
256
257 def on_credits(self):
258 self.destroy()
259 self._cursor = MouseCursor(
260 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
261 (.01, .01))
262 self._set_credits()
263
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
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
284 def on_volume(self):
285 self._opt_file['settings']['volume'] = self._slider['value']
286 self._music.set_volume(self._slider['value'])
287
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
296 # physical machine
297 self._opt_file['settings']['fullscreen'] = int(arg)
298 self._opt_file.store()
299
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()
309
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()
315
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()
321
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
329 def on_back(self):
330 self._opt_file.store()
331 self.destroy()
332 self._cursor = MouseCursor(
333 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
334 (.01, .01))
335 self._set_main()
336
337 def destroy(self):
338 [wdg.destroy() for wdg in self._widgets]
339 self._cursor.destroy()
340 self.ignore('enforce_resolution')