ya2 · news · projects · code · about

dds and bam
[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 27 self._cursor = MouseCursor(
420ce99a 28 'assets/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
407412a5 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 181 self._cursor = MouseCursor(
420ce99a 182 'assets/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 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),
bb956c26
FC
187 'frameColor': (1, 1, 1, .8),
188 'text_scale': .64}
a5fddddc 189 left = - (dx := .8) * (min(4, len(scenes)) - 1) / 2
9914cfc9 190 for i, cls in enumerate(scenes):
a5fddddc
FC
191 top = .1 if len(scenes) < 5 else .6
192 row = 0 if i < 4 else 1
28acea3a 193 self._widgets += [DirectButton(
a5fddddc 194 text=cls.name(), pos=(left + dx * (i % 4), 1, top - dx * row),
bb956c26 195 command=self.start, extraArgs=[cls], text_wordwrap=6,
420ce99a 196 frameTexture='assets/images/scenes/%s.dds' % cls.__name__,
28acea3a 197 **cmn)]
bb956c26
FC
198 for j in range(4):
199 tnode = self._widgets[-1].component('text%s' % j).textNode
200 height = - tnode.getLineHeight() / 2
201 height += (tnode.get_height() - tnode.get_line_height()) / 2
202 self._widgets[-1].component('text%s' % j).set_pos(0, 0, height)
0d5a5427
FC
203 self._widgets += [DirectButton(
204 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
205 **self._common_btn)]
206
8c9bf90e
FC
207 def start(self, cls):
208 self._fsm.demand('Scene', cls)
4071c6d8
FC
209
210 def on_options(self):
211 self.destroy()
638deddf 212 self._cursor = MouseCursor(
420ce99a 213 'assets/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 214 (.01, .01))
4071c6d8
FC
215 self._set_options()
216
2612dfc8
FC
217 def on_credits(self):
218 self.destroy()
638deddf 219 self._cursor = MouseCursor(
420ce99a 220 'assets/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 221 (.01, .01))
2612dfc8
FC
222 self._set_credits()
223
75b89b83
FC
224 def _rearrange_width(self):
225 max_width = 0
226 for wdg in self._widgets:
227 t_n = wdg.component('text0')
228 u_l = t_n.textNode.get_upper_left_3d()
229 l_r = t_n.textNode.get_lower_right_3d()
230 max_width = max(l_r[0] - u_l[0], max_width)
231 for wdg in self._widgets:
232 m_w = max_width / 2 + .8
233 wdg['frameSize'] = -m_w, m_w, wdg['frameSize'][2], wdg['frameSize'][3]
234
4071c6d8
FC
235 def on_language(self, arg):
236 lang_code = {
237 _('English'): 'en_EN',
238 _('Italian'): 'it_IT'}[arg]
239 self._lang_mgr.set_lang(lang_code)
240 self._opt_file['settings']['language'] = lang_code[:2]
241 self._opt_file.store()
242 self.on_options()
243
e1e44d5c
FC
244 def on_volume(self):
245 self._opt_file['settings']['volume'] = self._slider['value']
246 self._music.set_volume(self._slider['value'])
247
6fff1464
FC
248 def on_fullscreen(self, arg):
249 props = WindowProperties()
250 props.set_fullscreen(arg)
251 base.win.request_properties(props)
252 self._opt_file['settings']['fullscreen'] = int(arg)
253 self._opt_file.store()
254
255 def on_resolution(self, arg):
256 props = WindowProperties()
257 props.set_size(LVector2i(*[int(_res) for _res in arg.split('x')]))
258 base.win.request_properties(props)
259 self._opt_file['settings']['resolution'] = arg
260 self._opt_file.store()
261
a9aba267
FC
262 def on_aa(self, arg):
263 self._pipeline.msaa_samples = 4 if arg else 1
94a18c21 264 debug(f'msaa: {self._pipeline.msaa_samples}')
a9aba267
FC
265 self._opt_file['settings']['antialiasing'] = int(arg)
266 self._opt_file.store()
267
5fdf77d0
FC
268 def on_shadows(self, arg):
269 self._pipeline.enable_shadows = int(arg)
94a18c21 270 debug(f'shadows: {self._pipeline.enable_shadows}')
5fdf77d0
FC
271 self._opt_file['settings']['shadows'] = int(arg)
272 self._opt_file.store()
273
2612dfc8
FC
274 def on_website(self):
275 if platform.startswith('linux'):
276 environ['LD_LIBRARY_PATH'] = ''
277 system('xdg-open https://www.ya2.it')
278 else:
279 open_new_tab('https://www.ya2.it')
280
4071c6d8 281 def on_back(self):
e1e44d5c 282 self._opt_file.store()
4071c6d8 283 self.destroy()
638deddf 284 self._cursor = MouseCursor(
420ce99a 285 'assets/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
638deddf 286 (.01, .01))
4071c6d8
FC
287 self._set_main()
288
289 def destroy(self):
e1e44d5c 290 [wdg.destroy() for wdg in self._widgets]
407412a5 291 self._cursor.destroy()