ya2 · news · projects · code · about

enforced resolution
[pmachines.git] / game / 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
FC
7from webbrowser import open_new_tab
8from panda3d.core import Texture, TextNode, WindowProperties, LVector2i, \
9 TextProperties, TextPropertiesManager
4071c6d8 10from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
6fff1464 11 DirectOptionMenu, DirectSlider, DirectCheckButton
4071c6d8 12from direct.gui.DirectGuiGlobals import FLAT
e1e44d5c 13from direct.gui.OnscreenText import OnscreenText
74ed9732 14from direct.showbase.DirectObject import DirectObject
407412a5 15from lib.engine.gui.cursor import MouseCursor
a0462193 16from game.scene import Scene
0d5a5427 17from panda3d.bullet import BulletWorld
4071c6d8
FC
18
19
74ed9732 20class Menu(DirectObject):
4071c6d8 21
e982cdde 22 def __init__(self, fsm, lang_mgr, opt_file, music, pipeline, scenes, fun_test):
74ed9732 23 super().__init__()
4071c6d8
FC
24 self._fsm = fsm
25 self._lang_mgr = lang_mgr
26 self._opt_file = opt_file
e1e44d5c 27 self._music = music
a9aba267 28 self._pipeline = pipeline
6168d0c2 29 self._scenes = scenes
e982cdde 30 self._fun_test = fun_test
74ed9732 31 self._enforced_res = ''
407412a5 32 self._cursor = MouseCursor(
7e487769 33 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
407412a5 34 (.01, .01))
4071c6d8
FC
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)
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),
47 'rolloverSound': loader.load_sfx('assets/audio/sfx/rollover.ogg'),
48 'clickSound': loader.load_sfx('assets/audio/sfx/click.ogg')}
75b89b83 49 self._common_btn = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self._common
6fff1464
FC
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]
e1e44d5c
FC
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']
4071c6d8
FC
71 self._set_main()
72
74ed9732
FC
73 def enforce_res(self, val):
74 self._enforced_res = val
75 info('enforced resolution: ' + val)
76
4071c6d8 77 def _set_main(self):
e1e44d5c
FC
78 self._widgets = []
79 self._widgets += [DirectButton(
4071c6d8
FC
80 text=_('Play'), pos=(0, 1, .6), command=self.on_play,
81 **self._common_btn)]
e1e44d5c 82 self._widgets += [DirectButton(
2612dfc8 83 text=_('Options'), pos=(0, 1, .2), command=self.on_options,
4071c6d8 84 **self._common_btn)]
e1e44d5c 85 self._widgets += [DirectButton(
2612dfc8 86 text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
4071c6d8 87 **self._common_btn)]
e1e44d5c 88 self._widgets += [DirectButton(
2612dfc8 89 text=_('Exit'), pos=(0, 1, -.6), command=lambda: exit(),
4071c6d8 90 **self._common_btn)]
75b89b83 91 self._rearrange_width()
74ed9732 92 self.accept('enforce_resolution', self.enforce_res)
4071c6d8
FC
93
94 def _set_options(self):
e1e44d5c 95 self._widgets = []
4071c6d8
FC
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']]
4071c6d8
FC
102 btn = DirectOptionMenu(
103 text=_('Language'), items=items, initialitem=inititem,
a9aba267 104 pos=(0, 1, .8), command=self.on_language, **self._common_opt)
4071c6d8
FC
105 btn.popupMenu['frameColor'] = self._common_btn['frameColor']
106 btn.popupMenu['relief'] = self._common_btn['relief']
e1e44d5c
FC
107 self._widgets += [btn]
108 self._widgets += [OnscreenText(
a9aba267 109 _('Volume'), pos=(-.1, .55), font=self._common['text_font'],
e1e44d5c
FC
110 scale=self._common['scale'], fg=self._common['text_fg'],
111 align=TextNode.A_right)]
112 self._widgets += [DirectSlider(
a9aba267 113 pos=(.5, 1, .57),
e1e44d5c
FC
114 value=self._opt_file['settings']['volume'],
115 command=self.on_volume,
116 **self._common_slider)]
117 self._slider = self._widgets[-1]
6fff1464 118 self._widgets += [DirectCheckButton(
a9aba267 119 text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
6fff1464
FC
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,
a9aba267 139 pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
6fff1464
FC
140 btn.popupMenu['frameColor'] = self._common_btn['frameColor']
141 btn.popupMenu['relief'] = self._common_btn['relief']
142 self._widgets += [btn]
a9aba267
FC
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)]
5fdf77d0
FC
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)]
e1e44d5c 155 self._widgets += [DirectButton(
a9aba267 156 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
4071c6d8 157 **self._common_btn)]
74ed9732 158 self.accept('enforce_resolution', self.enforce_res)
4071c6d8 159
2612dfc8
FC
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(
e67dbe53 174 _('Special thanks to:\n \1scale\1rdb\2\n \1scale\1Luisa Tenuta\2\n \1scale\1Damiana Ercolani\2'),
2612dfc8
FC
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)]
74ed9732 181 self.accept('enforce_resolution', self.enforce_res)
2612dfc8 182
4071c6d8 183 def on_play(self):
0d5a5427 184 self.destroy()
638deddf 185 self._cursor = MouseCursor(
7e487769 186 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 187 (.01, .01))
0d5a5427 188 self._widgets = []
8c9bf90e
FC
189 cmn = self._common_btn.copy() | {
190 'frameSize': (-2.4, 2.4, -2.4, 2.4),
bb956c26
FC
191 'frameColor': (1, 1, 1, .8),
192 'text_scale': .64}
6168d0c2
FC
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
a5fddddc 196 row = 0 if i < 4 else 1
28acea3a 197 self._widgets += [DirectButton(
a5fddddc 198 text=cls.name(), pos=(left + dx * (i % 4), 1, top - dx * row),
bb956c26 199 command=self.start, extraArgs=[cls], text_wordwrap=6,
420ce99a 200 frameTexture='assets/images/scenes/%s.dds' % cls.__name__,
28acea3a 201 **cmn)]
bb956c26
FC
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)
0d5a5427
FC
207 self._widgets += [DirectButton(
208 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
209 **self._common_btn)]
210
8c9bf90e
FC
211 def start(self, cls):
212 self._fsm.demand('Scene', cls)
4071c6d8
FC
213
214 def on_options(self):
215 self.destroy()
638deddf 216 self._cursor = MouseCursor(
7e487769 217 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 218 (.01, .01))
4071c6d8
FC
219 self._set_options()
220
2612dfc8
FC
221 def on_credits(self):
222 self.destroy()
638deddf 223 self._cursor = MouseCursor(
7e487769 224 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 225 (.01, .01))
2612dfc8
FC
226 self._set_credits()
227
75b89b83
FC
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
4071c6d8
FC
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
e1e44d5c
FC
248 def on_volume(self):
249 self._opt_file['settings']['volume'] = self._slider['value']
250 self._music.set_volume(self._slider['value'])
251
6fff1464
FC
252 def on_fullscreen(self, arg):
253 props = WindowProperties()
254 props.set_fullscreen(arg)
e982cdde
FC
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
6fff1464
FC
261 self._opt_file['settings']['fullscreen'] = int(arg)
262 self._opt_file.store()
263
264 def on_resolution(self, arg):
74ed9732
FC
265 info('on resolution: %s (%s)' % (arg, self._enforced_res))
266 arg = self._enforced_res or arg
267 info('set resolution: %s' % arg)
6fff1464
FC
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
a9aba267
FC
274 def on_aa(self, arg):
275 self._pipeline.msaa_samples = 4 if arg else 1
94a18c21 276 debug(f'msaa: {self._pipeline.msaa_samples}')
a9aba267
FC
277 self._opt_file['settings']['antialiasing'] = int(arg)
278 self._opt_file.store()
279
5fdf77d0
FC
280 def on_shadows(self, arg):
281 self._pipeline.enable_shadows = int(arg)
94a18c21 282 debug(f'shadows: {self._pipeline.enable_shadows}')
5fdf77d0
FC
283 self._opt_file['settings']['shadows'] = int(arg)
284 self._opt_file.store()
285
2612dfc8
FC
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
4071c6d8 293 def on_back(self):
e1e44d5c 294 self._opt_file.store()
4071c6d8 295 self.destroy()
638deddf 296 self._cursor = MouseCursor(
7e487769 297 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 298 (.01, .01))
4071c6d8
FC
299 self._set_main()
300
301 def destroy(self):
e1e44d5c 302 [wdg.destroy() for wdg in self._widgets]
407412a5 303 self._cursor.destroy()
74ed9732 304 self.ignore('enforce_resolution')