ya2 · news · projects · code · about

offscreen screenshots
[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(
163 _('Supporters\n \1scale\1rdb\2\n \1scale\1Luisa Tenuta\2\n \1scale\1Damiana Ercolani\2'),
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):
8c9bf90e
FC
172 scene_classes = []
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:
178 scene_classes += [member]
0d5a5427
FC
179 self.destroy()
180 self._widgets = []
8c9bf90e
FC
181 cmn = self._common_btn.copy() | {
182 'frameSize': (-2.4, 2.4, -2.4, 2.4),
183 'frameColor': (1, 1, 1, .8)}
0d5a5427 184 self._widgets += [DirectButton(
8c9bf90e 185 text=scene_classes[0].name(), pos=(-.4, 1, .1), command=self.start,
63e7aeb2
FC
186 extraArgs=[scene_classes[0]],
187 frameTexture='assets/images/scenes/%s.png' % scene_classes[0].__name__,
188 **cmn)]
8c9bf90e
FC
189 self._widgets += [DirectButton(
190 text=scene_classes[1].name(), pos=(.4, 1, .1), command=self.start,
63e7aeb2
FC
191 extraArgs=[scene_classes[1]],
192 frameTexture='assets/images/scenes/%s.png' % scene_classes[1].__name__,
193 **cmn)]
0d5a5427
FC
194 self._widgets += [DirectButton(
195 text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
196 **self._common_btn)]
197
8c9bf90e
FC
198 def start(self, cls):
199 self._fsm.demand('Scene', cls)
4071c6d8
FC
200
201 def on_options(self):
202 self.destroy()
203 self._set_options()
204
2612dfc8
FC
205 def on_credits(self):
206 self.destroy()
207 self._set_credits()
208
75b89b83
FC
209 def _rearrange_width(self):
210 max_width = 0
211 for wdg in self._widgets:
212 t_n = wdg.component('text0')
213 u_l = t_n.textNode.get_upper_left_3d()
214 l_r = t_n.textNode.get_lower_right_3d()
215 max_width = max(l_r[0] - u_l[0], max_width)
216 for wdg in self._widgets:
217 m_w = max_width / 2 + .8
218 wdg['frameSize'] = -m_w, m_w, wdg['frameSize'][2], wdg['frameSize'][3]
219
4071c6d8
FC
220 def on_language(self, arg):
221 lang_code = {
222 _('English'): 'en_EN',
223 _('Italian'): 'it_IT'}[arg]
224 self._lang_mgr.set_lang(lang_code)
225 self._opt_file['settings']['language'] = lang_code[:2]
226 self._opt_file.store()
227 self.on_options()
228
e1e44d5c
FC
229 def on_volume(self):
230 self._opt_file['settings']['volume'] = self._slider['value']
231 self._music.set_volume(self._slider['value'])
232
6fff1464
FC
233 def on_fullscreen(self, arg):
234 props = WindowProperties()
235 props.set_fullscreen(arg)
236 base.win.request_properties(props)
237 self._opt_file['settings']['fullscreen'] = int(arg)
238 self._opt_file.store()
239
240 def on_resolution(self, arg):
241 props = WindowProperties()
242 props.set_size(LVector2i(*[int(_res) for _res in arg.split('x')]))
243 base.win.request_properties(props)
244 self._opt_file['settings']['resolution'] = arg
245 self._opt_file.store()
246
a9aba267
FC
247 def on_aa(self, arg):
248 self._pipeline.msaa_samples = 4 if arg else 1
94a18c21 249 debug(f'msaa: {self._pipeline.msaa_samples}')
a9aba267
FC
250 self._opt_file['settings']['antialiasing'] = int(arg)
251 self._opt_file.store()
252
5fdf77d0
FC
253 def on_shadows(self, arg):
254 self._pipeline.enable_shadows = int(arg)
94a18c21 255 debug(f'shadows: {self._pipeline.enable_shadows}')
5fdf77d0
FC
256 self._opt_file['settings']['shadows'] = int(arg)
257 self._opt_file.store()
258
2612dfc8
FC
259 def on_website(self):
260 if platform.startswith('linux'):
261 environ['LD_LIBRARY_PATH'] = ''
262 system('xdg-open https://www.ya2.it')
263 else:
264 open_new_tab('https://www.ya2.it')
265
4071c6d8 266 def on_back(self):
e1e44d5c 267 self._opt_file.store()
4071c6d8
FC
268 self.destroy()
269 self._set_main()
270
271 def destroy(self):
e1e44d5c 272 [wdg.destroy() for wdg in self._widgets]
407412a5 273 self._cursor.destroy()