ya2 · news · projects · code · about

renamed modules
[pmachines.git] / pmachines / gui / menu.py
diff --git a/pmachines/gui/menu.py b/pmachines/gui/menu.py
new file mode 100644 (file)
index 0000000..ab2248f
--- /dev/null
@@ -0,0 +1,340 @@
+from logging import info, debug
+from sys import platform, exit
+from os import environ, system
+from webbrowser import open_new_tab
+from xmlrpc.client import ServerProxy
+from panda3d.core import Texture, TextNode, WindowProperties, LVector2i, \
+    TextProperties, TextPropertiesManager
+from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
+    DirectOptionMenu, DirectSlider
+from direct.gui.DirectGuiGlobals import FLAT
+from direct.gui.OnscreenText import OnscreenText
+from direct.showbase.DirectObject import DirectObject
+from ya2.utils.cursor import MouseCursor
+from ya2.p3d.p3d import LibP3d
+
+
+class Menu(DirectObject):
+
+    def __init__(self, fsm, lang_mgr, opt_file, music, pipeline, scenes,
+                 fun_test, pos_mgr):
+        super().__init__()
+        self._fsm = fsm
+        self._lang_mgr = lang_mgr
+        self._opt_file = opt_file
+        self._music = music
+        self._pipeline = pipeline
+        self._scenes = scenes
+        self._fun_test = fun_test
+        self._pos_mgr = pos_mgr
+        self._enforced_res = ''
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04),
+            (.5, .5, .5, 1), (.01, .01))
+        self._font = base.loader.load_font(
+            'assets/fonts/Hanken-Book.ttf')
+        self._font.clear()
+        self._font.set_pixels_per_unit(60)
+        self._font.set_minfilter(Texture.FTLinearMipmapLinear)
+        self._font.set_outline((0, 0, 0, 1), .8, .2)
+        self._widgets = []
+        self._common = {
+            'scale': .12,
+            'text_font': self._font,
+            'text_fg': (.9, .9, .9, 1),
+            'relief': FLAT,
+            'frameColor': (.4, .4, .4, .14),
+            'rolloverSound': loader.load_sfx(
+                'assets/audio/sfx/rollover.ogg'),
+            'clickSound': loader.load_sfx(
+                'assets/audio/sfx/click.ogg')}
+        self._common_btn = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self._common
+        hlc = self._common_btn['frameColor']
+        hlc = (hlc[0] + .2, hlc[1] + .2, hlc[2] + .2, hlc[3] + .2)
+        self._common_opt = {
+            'item_frameColor': self._common_btn['frameColor'],
+            'popupMarker_frameColor': self._common_btn['frameColor'],
+            'item_relief': self._common_btn['relief'],
+            'item_text_font': self._common_btn['text_font'],
+            'item_text_fg': self._common_btn['text_fg'],
+            'textMayChange': 1,
+            'highlightColor': hlc,
+            'text_align': TextNode.A_center,
+        } | self._common_btn
+        f_s = self._common_opt['frameSize']
+        self._common_opt['frameSize'] = f_s[0], f_s[1] - .56, f_s[2], f_s[3]
+        self._common_slider = self._common | {
+            'range': (0, 1),
+            'thumb_frameColor': (.4, .4, .4, .4),
+            'thumb_scale': 1.6,
+            'scale': .4}
+        del self._common_slider['rolloverSound']
+        del self._common_slider['clickSound']
+        self._set_main()
+
+    def enforce_res(self, val):
+        self._enforced_res = val
+        info('enforced resolution: ' + val)
+
+    def _set_main(self):
+        self._pos_mgr.reset()
+        self._widgets = []
+        self._widgets += [DirectButton(
+            text=_('Play'), pos=(0, 1, .6), command=self.on_play,
+            **self._common_btn)]
+        self._pos_mgr.register('play', LibP3d.wdg_pos(self._widgets[-1]))
+        self._widgets += [DirectButton(
+            text=_('Options'), pos=(0, 1, .2), command=self.on_options,
+            **self._common_btn)]
+        self._pos_mgr.register('options', LibP3d.wdg_pos(self._widgets[-1]))
+        self._widgets += [DirectButton(
+            text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
+            **self._common_btn)]
+        self._pos_mgr.register('credits', LibP3d.wdg_pos(self._widgets[-1]))
+
+        def btn_exit():
+            if self._fun_test:
+                ServerProxy('http://localhost:6000').destroy()
+            exit()
+        self._widgets += [DirectButton(
+            text=_('Exit'), pos=(0, 1, -.6), command=lambda: btn_exit(),
+            **self._common_btn)]
+        self._pos_mgr.register('exit', LibP3d.wdg_pos(self._widgets[-1]))
+        self._rearrange_width()
+        self.accept('enforce_resolution', self.enforce_res)
+
+    def _set_options(self):
+        self._pos_mgr.reset()
+        self._widgets = []
+        self._lang_funcs = [lambda: _('English'), lambda: _('Italian')]
+        items = [fnc() for fnc in self._lang_funcs]
+        inititem = {
+            'en': _('English'),
+            'it': _('Italian')
+        }[self._opt_file['settings']['language']]
+        btn = DirectOptionMenu(
+            text=_('Language'), items=items, initialitem=inititem,
+            pos=(0, 1, .8), command=self.on_language, **self._common_opt)
+        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
+        btn.popupMenu['relief'] = self._common_btn['relief']
+        self._widgets += [btn]
+        pos_lang = LibP3d.wdg_pos(self._widgets[-1])
+        self._pos_mgr.register('languages', pos_lang)
+        pos_eng = pos_lang[0] + 300, pos_lang[1] - 57
+        pos_it = pos_lang[0] + 300, pos_lang[1] + 33
+        self._pos_mgr.register('english', pos_eng)
+        self._pos_mgr.register('italian', pos_it)
+        self._widgets += [OnscreenText(
+            _('Volume'), pos=(-.1, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_right)]
+        self._widgets += [DirectSlider(
+            pos=(.5, 1, .57),
+            value=self._opt_file['settings']['volume'],
+            command=self.on_volume,
+            **self._common_slider)]
+        vol_pos = LibP3d.wdg_pos(self._widgets[-1])
+        self._pos_mgr.register('volume', vol_pos)
+        self._pos_mgr.register('volume_0', [vol_pos[0] - 153, vol_pos[1]])
+        self._slider = self._widgets[-1]
+        self._widgets += [DirectCheckButton(
+            text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['fullscreen'],
+            **self._common_btn)]
+        self._pos_mgr.register('fullscreen', LibP3d.wdg_pos(self._widgets[-1]))
+        res = self._opt_file['settings']['resolution']
+        d_i = base.pipe.get_display_information()
+        def _res(idx):
+            return d_i.get_display_mode_width(idx), \
+                d_i.get_display_mode_height(idx)
+        resolutions = [
+            _res(idx) for idx in range(d_i.get_total_display_modes())]
+        resolutions = list(set(resolutions))
+        resolutions = sorted(resolutions)
+        resolutions = [(str(_res[0]), str(_res[1])) for _res in resolutions]
+        resolutions = ['x'.join(_res) for _res in resolutions]
+        if not res:
+            res = resolutions[-1]
+        btn = DirectOptionMenu(
+            text=_('Resolution'), items=resolutions, initialitem=res,
+            pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
+        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
+        btn.popupMenu['relief'] = self._common_btn['relief']
+        self._widgets += [btn]
+        pos_res = LibP3d.wdg_pos(self._widgets[-1])
+        self._pos_mgr.register('resolutions', pos_res)  # 680 365
+        self._pos_mgr.register('res_1440x900', [pos_res[0] + 320, pos_res[1] + 75])
+        self._pos_mgr.register('res_1360x768', [pos_res[0] + 430, pos_res[1] -285])
+        self._widgets += [DirectCheckButton(
+            text=_('Antialiasing'), pos=(0, 1, -.2), command=self.on_aa,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['antialiasing'],
+            **self._common_btn)]
+        self._pos_mgr.register('aa', LibP3d.wdg_pos(self._widgets[-1]))
+        self._widgets += [DirectCheckButton(
+            text=_('Shadows'), pos=(0, 1, -.45), command=self.on_shadows,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['shadows'],
+            **self._common_btn)]
+        self._pos_mgr.register('shadows', LibP3d.wdg_pos(self._widgets[-1]))
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+        self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
+        self.accept('enforce_resolution', self.enforce_res)
+
+    def _set_credits(self):
+        self._pos_mgr.reset()
+        self._widgets = []
+        tp_scale = TextProperties()
+        tp_scale.set_text_scale(.64)
+        TextPropertiesManager.getGlobalPtr().setProperties('scale', tp_scale)
+        self._widgets += [OnscreenText(
+            _('Code and gfx\n  \1scale\1Flavio Calva\2\n\n\nMusic\n  \1scale\1Stefan Grossmann\2'),
+            pos=(-.9, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_left)]
+        self._widgets += [DirectButton(
+            text=_('Website'), pos=(-.6, 1, .29), command=self.on_website,
+            **self._common_btn | {'scale': .08})]
+        self._widgets += [OnscreenText(
+            _('Special thanks to:\n  \1scale\1rdb\2\n  \1scale\1Luisa Tenuta\2\n  \1scale\1Damiana Ercolani\2'),
+            pos=(.1, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_left)]
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+        self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
+        self.accept('enforce_resolution', self.enforce_res)
+
+    def on_play(self):
+        self._pos_mgr.reset()
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._widgets = []
+        cmn = self._common_btn.copy() | {
+            'frameSize': (-2.4, 2.4, -2.4, 2.4),
+            'frameColor': (1, 1, 1, .8),
+            'text_scale': .64}
+        left = - (dx := .8) * (min(4, len(self._scenes)) - 1) / 2
+        for i, cls in enumerate(self._scenes):
+            top = .1 if len(self._scenes) < 5 else .6
+            row = 0 if i < 4 else 1
+            self._widgets += [DirectButton(
+                text=cls.name(), pos=(left + dx * (i % 4), 1, top - dx * row),
+                command=self.start, extraArgs=[cls], text_wordwrap=6,
+                frameTexture='assets/images/scenes/%s.dds' % cls.__name__,
+                **cmn)]
+            name = cls.__name__[5:].lower()
+            self._pos_mgr.register(name, LibP3d.wdg_pos(self._widgets[-1]))
+            for j in range(4):
+                tnode = self._widgets[-1].component('text%s' % j).textNode
+                height = - tnode.getLineHeight() / 2
+                height += (tnode.get_height() - tnode.get_line_height()) / 2
+                self._widgets[-1].component('text%s' % j).set_pos(0, 0, height)
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+        self._pos_mgr.register('back', LibP3d.wdg_pos(self._widgets[-1]))
+
+    def start(self, cls):
+        self._fsm.demand('Scene', cls)
+
+    def on_options(self):
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_options()
+
+    def on_credits(self):
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_credits()
+
+    def _rearrange_width(self):
+        max_width = 0
+        for wdg in self._widgets:
+            t_n = wdg.component('text0')
+            u_l = t_n.textNode.get_upper_left_3d()
+            l_r = t_n.textNode.get_lower_right_3d()
+            max_width = max(l_r[0] - u_l[0], max_width)
+        for wdg in self._widgets:
+            m_w = max_width / 2 + .8
+            wdg['frameSize'] = -m_w, m_w, wdg['frameSize'][2], wdg['frameSize'][3]
+
+    def on_language(self, arg):
+        lang_code = {
+            _('English'): 'en_EN',
+            _('Italian'): 'it_IT'}[arg]
+        self._lang_mgr.set_lang(lang_code)
+        self._opt_file['settings']['language'] = lang_code[:2]
+        self._opt_file.store()
+        self.on_options()
+
+    def on_volume(self):
+        self._opt_file['settings']['volume'] = self._slider['value']
+        self._music.set_volume(self._slider['value'])
+
+    def on_fullscreen(self, arg):
+        props = WindowProperties()
+        props.set_fullscreen(arg)
+        if not self._fun_test:
+            base.win.request_properties(props)
+            # if we actually switch to fullscreen during the tests then
+            # exwm inside qemu can't restore the correct resolution
+            # i may re-enable this if/when i run the tests onto a
+            # physical machine
+        self._opt_file['settings']['fullscreen'] = int(arg)
+        self._opt_file.store()
+
+    def on_resolution(self, arg):
+        info('on resolution: %s (%s)' % (arg, self._enforced_res))
+        arg = self._enforced_res or arg
+        info('set resolution: %s' % arg)
+        props = WindowProperties()
+        props.set_size(LVector2i(*[int(_res) for _res in arg.split('x')]))
+        base.win.request_properties(props)
+        self._opt_file['settings']['resolution'] = arg
+        self._opt_file.store()
+
+    def on_aa(self, arg):
+        self._pipeline.msaa_samples = 4 if arg else 1
+        debug(f'msaa: {self._pipeline.msaa_samples}')
+        self._opt_file['settings']['antialiasing'] = int(arg)
+        self._opt_file.store()
+
+    def on_shadows(self, arg):
+        self._pipeline.enable_shadows = int(arg)
+        debug(f'shadows: {self._pipeline.enable_shadows}')
+        self._opt_file['settings']['shadows'] = int(arg)
+        self._opt_file.store()
+
+    def on_website(self):
+        if platform.startswith('linux'):
+            environ['LD_LIBRARY_PATH'] = ''
+            system('xdg-open https://www.ya2.it')
+        else:
+            open_new_tab('https://www.ya2.it')
+
+    def on_back(self):
+        self._opt_file.store()
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_main()
+
+    def destroy(self):
+        [wdg.destroy() for wdg in self._widgets]
+        self._cursor.destroy()
+        self.ignore('enforce_resolution')