ya2 · news · projects · code · about

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