ya2 · news · projects · code · about

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