msgid "Next"
msgstr "Prossimo"
-#: pmachines/gui/menu.py:96
+#: pmachines/gui/main_page.py:19
msgid "Play"
msgstr "Gioca"
-#: pmachines/gui/menu.py:101
+#: pmachines/gui/main_page.py:24
msgid "Options"
msgstr "Opzioni"
-#: pmachines/gui/menu.py:106
+#: pmachines/gui/main_page.py:29
msgid "Credits"
msgstr "Riconoscimenti"
-#: pmachines/gui/menu.py:116 pmachines/scene/scene.py:305
+#: pmachines/gui/main_page.py:39 pmachines/scene/scene.py:305
msgid "Exit"
msgstr "Esci"
-#: pmachines/gui/menu.py:126 pmachines/gui/menu.py:129
-#: pmachines/gui/menu.py:330
-msgid "English"
-msgstr "Inglese"
-
-#: pmachines/gui/menu.py:126 pmachines/gui/menu.py:130
-#: pmachines/gui/menu.py:331
-msgid "Italian"
-msgstr "Italiano"
-
-#: pmachines/gui/menu.py:142
-msgid "Language"
-msgstr "Linguaggio"
-
-#: pmachines/gui/menu.py:152
-msgid "Volume"
-msgstr "Volume"
-
-#: pmachines/gui/menu.py:170
-msgid "Fullscreen"
-msgstr "Schermo intero"
-
-#: pmachines/gui/menu.py:201
-msgid "Resolution"
-msgstr "Risoluzione"
-
-#: pmachines/gui/menu.py:213
-msgid "Antialiasing"
-msgstr "Antialiasing"
-
-#: pmachines/gui/menu.py:221
-msgid "Shadows"
-msgstr "Ombre"
-
-#: pmachines/gui/menu.py:229 pmachines/gui/menu.py:255
-#: pmachines/gui/menu.py:295
-msgid "Back"
-msgstr "Indietro"
-
-#: pmachines/gui/menu.py:242
+#: pmachines/gui/credits_page.py:25
msgid ""
"Code and gfx\n"
" \ 1scale\ 1Flavio Calva\ 2\n"
"Music\n"
" \ 1scale\ 1Stefan Grossmann\ 2"
-#: pmachines/gui/menu.py:247
+#: pmachines/gui/credits_page.py:30
msgid "Website"
msgstr "Sito web"
-#: pmachines/gui/menu.py:250
+#: pmachines/gui/credits_page.py:33
msgid ""
"Special thanks to:\n"
" \ 1scale\ 1rdb\ 2\n"
" \ 1scale\ 1Luisa Tenuta\ 2\n"
" \ 1scale\ 1Damiana Ercolani\ 2"
+#: pmachines/gui/credits_page.py:38 pmachines/gui/play_page.py:43
+#: pmachines/gui/options_page.py:137
+msgid "Back"
+msgstr "Indietro"
+
+#: pmachines/gui/options_page.py:34 pmachines/gui/options_page.py:37
+#: pmachines/gui/options_page.py:145
+msgid "English"
+msgstr "Inglese"
+
+#: pmachines/gui/options_page.py:34 pmachines/gui/options_page.py:38
+#: pmachines/gui/options_page.py:146
+msgid "Italian"
+msgstr "Italiano"
+
+#: pmachines/gui/options_page.py:50
+msgid "Language"
+msgstr "Linguaggio"
+
+#: pmachines/gui/options_page.py:60
+msgid "Volume"
+msgstr "Volume"
+
+#: pmachines/gui/options_page.py:78
+msgid "Fullscreen"
+msgstr "Schermo intero"
+
+#: pmachines/gui/options_page.py:109
+msgid "Resolution"
+msgstr "Risoluzione"
+
+#: pmachines/gui/options_page.py:121
+msgid "Antialiasing"
+msgstr "Antialiasing"
+
+#: pmachines/gui/options_page.py:129
+msgid "Shadows"
+msgstr "Ombre"
+
#: pmachines/scene/scene.py:106
msgid "Scene: "
msgstr "Scena: "
from pmachines.items.background import Background
from pmachines.gui.menu import Menu
from pmachines.scene.scene import Scene
-from pmachines.application.persistent import Persistent
+from pmachines.application.persistency import Persistency
from ya2.utils.dictfile import DctFile
from ya2.utils.logics import LogicsTools
from ya2.utils.language import LanguageManager
def on_menu_enter(self):
self._menu_bg = Background()
self._menu = Menu(
- self._fsm, self.lang_mgr, self._options, self._music,
+ self._fsm, self.lang_mgr, self._options,
self._pipeline, self.scenes(), self._args.functional_test or self._args.functional_ref,
self._pos_mgr)
default_opt)
if not opt_exists:
self._options.store()
- self.__persistent = Persistent(self._options['save']['scenes_done'], self._options)
+ self.__persistent = Persistency(self._options['save']['scenes_done'], self._options)
Scene.scenes_done = self.__persistent.scenes_done
res = self._options['settings']['resolution']
if res:
--- /dev/null
+from json import loads, dumps
+
+
+class Persistency:
+
+ def __init__(self, scenes_done, option_file):
+ self.__scenes_done = scenes_done
+ self.__option_file = option_file
+ self.__fix_ini_parsing()
+
+ def __fix_ini_parsing(self):
+ if len(self.__scenes_done) == 1 and not self.__scenes_done[0]:
+ self.__scenes_done = []
+ if self.__scenes_done:
+ if not isinstance(self.__scenes_done, list): # empty list: []
+ self.__scenes_done = self.__scenes_done.strip("'")
+ if self.__scenes_done:
+ if not isinstance(self.__scenes_done, list):
+ self.__scenes_done = loads(self.__scenes_done)
+
+ def save_scene(self, name, version):
+ self.__compute_scenes_done(name, version)
+ self.__store_scenes()
+
+ def __compute_scenes_done(self, name, version):
+ other_scenes = [s for s in self.__scenes_done if s[0] != name]
+ self.__scenes_done = other_scenes + [(name, version)]
+
+ def __store_scenes(self):
+ self.__option_file['save']['scenes_done'] = f"'{dumps(self.__scenes_done)}'"
+ self.__option_file.store()
+
+ @property
+ def scenes_done(self): return self.__scenes_done
+++ /dev/null
-import json
-
-
-class Persistent:
-
- def __init__(self, scenes_done, opt_file):
- self.__scenes_done = scenes_done
- self.__fix_ini_parsing()
- self.__opt_file = opt_file
-
- def __fix_ini_parsing(self):
- if len(self.__scenes_done) == 1 and not self.__scenes_done[0]:
- self.__scenes_done = []
- #print(self.__scenes_done)
- #self.__scenes_done = self.__scenes_done[0]
- if self.__scenes_done:
- if not isinstance(self.__scenes_done, list): # empty list: []
- self.__scenes_done = self.__scenes_done.strip("'")
- if self.__scenes_done:
- if not isinstance(self.__scenes_done, list):
- self.__scenes_done = json.loads(self.__scenes_done)
-
- def save_scene(self, name, version):
- scenes = []
- scenes = [scene for scene in self.__scenes_done if scene[0] != name]
- self.__scenes_done = scenes + [(name, version)]
- self.__opt_file['save']['scenes_done'] = "'%s'" % json.dumps(self.__scenes_done)
- self.__opt_file.store()
-
- @property
- def scenes_done(self):
- return self.__scenes_done
--- /dev/null
+from logging import info
+from direct.showbase.DirectObject import DirectObject
+from ya2.utils.cursor import MouseCursor
+
+
+class BasePage(DirectObject):
+
+ def __init__(self, menu, running_functional_tests):
+ super().__init__()
+ self._menu = menu
+ self._enforced_resolution = ''
+ self._cursor = MouseCursor(
+ 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+ (.01, .01), running_functional_tests)
+
+ def enforce_resolution(self, resolution):
+ self._enforced_resolution = resolution
+ info('enforced resolution: ' + resolution)
+
+ def on_back(self):
+ self._option_file.store()
+ self._menu.set_page('main')
+ self.destroy()
+
+ def destroy(self):
+ [wdg.destroy() for wdg in self._widgets]
+ self._cursor.destroy()
+ self.ignore('enforce_resolution')
+ self._menu = None
--- /dev/null
+from sys import platform
+from os import environ, system
+from webbrowser import open_new_tab
+from panda3d.core import TextNode, \
+ TextProperties, TextPropertiesManager
+from direct.gui.DirectGui import DirectButton
+from direct.gui.OnscreenText import OnscreenText
+from ya2.utils.gfx import DirectGuiMixin
+from pmachines.gui.base_page import BasePage
+
+
+class CreditsPage(BasePage):
+
+ def __init__(self, menu, test_positions, gui_args, option_file, running_functional_tests):
+ super().__init__(menu, running_functional_tests)
+ self._test_positions = test_positions
+ self._option_file = option_file
+ self._running_functional_tests = running_functional_tests
+ for k in list(self._test_positions.keys()): del self._test_positions[k]
+ 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=gui_args.button['text_font'],
+ scale=gui_args.button['scale'], fg=gui_args.button['text_fg'],
+ align=TextNode.A_left)]
+ self._widgets += [DirectButton(
+ text=_('Website'), pos=(-.6, 1, .29), command=self.on_website,
+ **gui_args.button | {'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=gui_args.button['text_font'],
+ scale=gui_args.button['scale'], fg=gui_args.button['text_fg'],
+ align=TextNode.A_left)]
+ self._widgets += [DirectButton(
+ text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+ **gui_args.button)]
+ self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ self._test_positions['back'] = self._widgets[-1].pos_pixel()
+ self.accept('enforce_resolution', self.enforce_resolution)
+
+ 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')
--- /dev/null
+from sys import exit
+from xmlrpc.client import ServerProxy
+from direct.gui.DirectGui import DirectButton
+from ya2.utils.gfx import DirectGuiMixin
+from pmachines.gui.base_page import BasePage
+
+
+class MainPage(BasePage):
+
+ def __init__(self, menu, test_positions, gui_args, running_functional_tests, scenes, application_fsm):
+ super().__init__(menu, running_functional_tests)
+ self._running_functional_tests = running_functional_tests
+ self._test_positions = test_positions
+ self._scenes = scenes
+ self._application_fsm = application_fsm
+ for k in list(self._test_positions.keys()): del self._test_positions[k]
+ self._widgets = []
+ self._widgets += [DirectButton(
+ text=_('Play'), pos=(0, 1, .6), command=self.on_play,
+ **gui_args.button)]
+ self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ self._test_positions['play'] = self._widgets[-1].pos_pixel()
+ self._widgets += [DirectButton(
+ text=_('Options'), pos=(0, 1, .2), command=self.on_options,
+ **gui_args.button)]
+ self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ self._test_positions['options'] = self._widgets[-1].pos_pixel()
+ self._widgets += [DirectButton(
+ text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
+ **gui_args.button)]
+ self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ self._test_positions['credits'] = self._widgets[-1].pos_pixel()
+
+ def btn_exit():
+ if self._running_functional_tests:
+ ServerProxy('http://localhost:7000').destroy()
+ exit()
+ self._widgets += [DirectButton(
+ text=_('Exit'), pos=(0, 1, -.6), command=lambda: btn_exit(),
+ **gui_args.button)]
+ self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ self._test_positions['exit'] = self._widgets[-1].pos_pixel()
+ self._rearrange_width()
+ self.accept('enforce_resolution', self.enforce_resolution)
+
+ def on_options(self):
+ self._menu.set_page('options')
+ self.destroy()
+
+ def on_credits(self):
+ self._menu.set_page('credits')
+ self.destroy()
+
+ def on_play(self):
+ self._menu.set_page('play')
+ self.destroy()
+
+ 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]
-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, DirectFrame
+from collections import namedtuple
+from panda3d.core import TextNode
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.utils.gfx import GfxTools, DirectGuiMixin, pos_pixel
+from ya2.utils.gui import GuiTools
+from ya2.utils.audio import AudioTools
+from pmachines.gui.main_page import MainPage
+from pmachines.gui.play_page import PlayPage
+from pmachines.gui.options_page import OptionsPage
+from pmachines.gui.credits_page import CreditsPage
-class DirectOptionMenuTest(DirectOptionMenu):
+class Menu:
- def __init__(self, parent=None, **kw):
- DirectOptionMenu.__init__(self, parent, **kw)
- self.initialiseoptions(DirectOptionMenuTest)
-
- def showPopupMenu(self, event=None):
- #super().showPopupMenu(event) # it does not work with mixins
- DirectOptionMenu.showPopupMenu(self, event)
- self._show_cb([self.component(cmp) for cmp in self.components()])
-
-
-
-class Menu(DirectObject):
-
- def __init__(self, fsm, lang_mgr, opt_file, music, pipeline, scenes,
- fun_test, pos_mgr):
+ def __init__(self, application_fsm, language_manager, option_file,
+ gfx_pipeline, scenes,
+ running_functional_tests, test_positions):
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), fun_test)
- 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 = {
+ self.__application_fsm = application_fsm
+ self.__language_manager = language_manager
+ self.__option_file = option_file
+ self.__gfx_pipeline = gfx_pipeline
+ self.__scenes = scenes
+ self.__running_functional_tests = running_functional_tests
+ self.__test_positions = test_positions
+ self.__gui_args = self.__build_gui_args()
+ self.__page = None
+ self.set_page('main')
+
+ def __build_gui_args(self):
+ base = {
'scale': .12,
- 'text_font': self._font,
+ 'text_font': GuiTools.load_font('assets/fonts/Hanken-Book.ttf'),
'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'],
+ 'rolloverSound': AudioTools.load_sfx('assets/audio/sfx/rollover.ogg'),
+ 'clickSound': AudioTools.load_sfx('assets/audio/sfx/click.ogg')}
+ button = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | base
+ h = button['frameColor']
+ h = (h[0] + .2, h[1] + .2, h[2] + .2, h[3] + .2)
+ option = {
+ 'item_frameColor': button['frameColor'],
+ 'popupMarker_frameColor': button['frameColor'],
+ 'item_relief': button['relief'],
+ 'item_text_font': button['text_font'],
+ 'item_text_fg': button['text_fg'],
'textMayChange': 1,
- 'highlightColor': hlc,
+ 'highlightColor': h,
'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 | {
+ } | button
+ f = option['frameSize']
+ option['frameSize'] = f[0], f[1] - .56, f[2], f[3]
+ slider = base | {
'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):
- for k in list(self._pos_mgr.keys()): del self._pos_mgr[k]
- self._widgets = []
- self._widgets += [DirectButton(
- text=_('Play'), pos=(0, 1, .6), command=self.on_play,
- **self._common_btn)]
- self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['play'] = self._widgets[-1].pos_pixel()
- self._widgets += [DirectButton(
- text=_('Options'), pos=(0, 1, .2), command=self.on_options,
- **self._common_btn)]
- self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['options'] = self._widgets[-1].pos_pixel()
- self._widgets += [DirectButton(
- text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
- **self._common_btn)]
- self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['credits'] = self._widgets[-1].pos_pixel()
-
- def btn_exit():
- if self._fun_test:
- ServerProxy('http://localhost:7000').destroy()
- exit()
- self._widgets += [DirectButton(
- text=_('Exit'), pos=(0, 1, -.6), command=lambda: btn_exit(),
- **self._common_btn)]
- self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['exit'] = self._widgets[-1].pos_pixel()
- self._rearrange_width()
- self.accept('enforce_resolution', self.enforce_res)
-
- def _set_options(self):
- for k in list(self._pos_mgr.keys()): del self._pos_mgr[k]
- 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']]
-
- def lang_cb(comps):
- for element in ['english', 'italian']:
- if element in self._pos_mgr:
- del self._pos_mgr[element]
- for lng, btn in zip(['english', 'italian'], comps):
- btn.__class__ = type('DirectFrameMixed', (DirectFrame, DirectGuiMixin), {})
- pos = btn.pos_pixel()
- self._pos_mgr[lng] = (pos[0] + 5, pos[1])
- btn = DirectOptionMenuTest(
- text=_('Language'), items=items, initialitem=inititem,
- pos=(0, 1, .8), command=self.on_language, **self._common_opt)
- btn.__class__ = type('DirectOptionMenuMixed', (DirectOptionMenu, DirectGuiMixin), {})
- btn._show_cb = lang_cb
- btn.popupMenu['frameColor'] = self._common_btn['frameColor']
- btn.popupMenu['relief'] = self._common_btn['relief']
- self._widgets += [btn]
- pos_lang = self._widgets[-1].pos_pixel()
- self._pos_mgr['languages'] = pos_lang
- 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)]
- self._widgets[-1].__class__ = type('DirectSliderMixed', (DirectSlider, DirectGuiMixin), {})
- vol_pos = self._widgets[-1].pos_pixel()
- self._pos_mgr['volume'] = vol_pos
- np_left = GfxTools.build_empty_node('left_slider')
- np_left.set_pos(self._widgets[-1].get_net_transform().get_pos())
- np_left.set_x(np_left.get_x() - self._widgets[-1].get_scale()[0])
- lpos = np_left.pos_as_widget() # try with pos2d_pixel and remove pos_as_widget if ok
- self._pos_mgr['volume_0'] = lpos
- 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._widgets[-1].__class__ = type('DirectCheckButtonMixed', (DirectCheckButton, DirectGuiMixin), {})
- self._pos_mgr['fullscreen'] = self._widgets[-1].pos_pixel()
- 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]
-
- def res_cb(comps):
- for element in ['res_1440x900', 'res_1360x768']:
- if element in self._pos_mgr:
- del self._pos_mgr[element]
- for tgt_res in ['1440x900', '1360x768']:
- for btn in comps:
- if btn['text'] == tgt_res:
- pos = pos_pixel(btn)
- self._pos_mgr['res_' + tgt_res] = (pos[0] + 5, pos[1])
- btn = DirectOptionMenuTest(
- text=_('Resolution'), items=resolutions, initialitem=res,
- pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
- btn.__class__ = type('DirectOptionMenuMixed', (DirectOptionMenu, DirectGuiMixin), {})
- btn._show_cb = res_cb
- btn.popupMenu['frameColor'] = self._common_btn['frameColor']
- btn.popupMenu['relief'] = self._common_btn['relief']
- self._widgets += [btn]
- pos_res = self._widgets[-1].pos_pixel()
- self._pos_mgr['resolutions'] = pos_res # 680 365
- self._pos_mgr['res_1440x900'] = [pos_res[0] + 320, pos_res[1] + 75]
- self._pos_mgr['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._widgets[-1].__class__ = type('DirectCheckButtonMixed', (DirectCheckButton, DirectGuiMixin), {})
- self._pos_mgr['aa'] = self._widgets[-1].pos_pixel()
- 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._widgets[-1].__class__ = type('DirectCheckButtonMixed', (DirectCheckButton, DirectGuiMixin), {})
- self._pos_mgr['shadows'] = self._widgets[-1].pos_pixel()
- self._widgets += [DirectButton(
- text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
- **self._common_btn)]
- self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['back'] = self._widgets[-1].pos_pixel()
- self.accept('enforce_resolution', self.enforce_res)
-
- def _set_credits(self):
- for k in list(self._pos_mgr.keys()): del self._pos_mgr[k]
- 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._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['back'] = self._widgets[-1].pos_pixel()
- self.accept('enforce_resolution', self.enforce_res)
-
- def on_play(self):
- for k in list(self._pos_mgr.keys()): del self._pos_mgr[k]
- self.destroy()
- self._cursor = MouseCursor(
- 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
- (.01, .01), self._fun_test)
- 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
- from pmachines.scene.scene import Scene
- for i, scene_name in enumerate(self._scenes):
- top = .1 if len(self._scenes) < 5 else .6
- row = 0 if i < 4 else 1
- new_cmn = cmn.copy()
- if Scene.is_done(scene_name):
- new_cmn['frameColor'] = (1, 1, 1, .4)
- new_cmn['text_fg'] = (.9, .9, .9, .4)
- self._widgets += [DirectButton(
- text=Scene.name(scene_name), pos=(left + dx * (i % 4), 1, top - dx * row),
- command=self.start, extraArgs=[scene_name], text_wordwrap=6,
- frameTexture='assets/images/scenes/%s.dds' % scene_name,
- **new_cmn)]
- self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- name = scene_name.lower()
- self._pos_mgr[name] = self._widgets[-1].pos_pixel()
- 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._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
- self._pos_mgr['back'] = self._widgets[-1].pos_pixel()
-
- 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._fun_test)
- 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._fun_test)
- 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_language(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')
+ del slider['rolloverSound']
+ del slider['clickSound']
+ GuiArgs = namedtuple('GuiArgs', 'button option slider')
+ gui_args = GuiArgs(button, option, slider)
+ return gui_args
- def on_back(self):
- self._opt_file.store()
+ def set_page(self, page_name):
self.destroy()
- self._cursor = MouseCursor(
- 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
- (.01, .01), self._fun_test)
- self._set_main()
+ if self.__page: self.__page.destroy()
+ match page_name:
+ case 'main': p = MainPage(self, self.__test_positions, self.__gui_args, self.__running_functional_tests, self.__scenes, self.__application_fsm)
+ case 'credits': p = CreditsPage(self, self.__test_positions, self.__gui_args, self.__option_file, self.__running_functional_tests)
+ case 'options': p = OptionsPage(self, self.__test_positions, self.__option_file, self.__gui_args, self.__language_manager, self.__running_functional_tests, self.__gfx_pipeline)
+ case 'play': p = PlayPage(self, self.__test_positions, self.__gui_args, self.__scenes, self.__option_file, self.__running_functional_tests, self.__application_fsm)
+ self.__page = p
def destroy(self):
- [wdg.destroy() for wdg in self._widgets]
- self._cursor.destroy()
- self.ignore('enforce_resolution')
+ if self.__page:
+ self.__page.destroy()
+ self.__page = None
--- /dev/null
+from logging import info, debug
+from panda3d.core import TextNode, WindowProperties, LVector2i
+from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
+ DirectOptionMenu, DirectSlider, DirectFrame
+from direct.gui.OnscreenText import OnscreenText
+from ya2.utils.gfx import GfxTools, DirectGuiMixin, pos_pixel
+from ya2.utils.audio import AudioTools
+from pmachines.gui.base_page import BasePage
+
+
+class DirectOptionMenuTestable(DirectOptionMenu):
+
+ def __init__(self, parent=None, **kw):
+ DirectOptionMenu.__init__(self, parent, **kw)
+ self.initialiseoptions(DirectOptionMenuTestable)
+
+ def showPopupMenu(self, event=None):
+ #super().showPopupMenu(event) # it does not work with mixins
+ DirectOptionMenu.showPopupMenu(self, event)
+ self._show_cb([self.component(c) for c in self.components()])
+
+
+class OptionsPage(BasePage):
+
+ def __init__(self, menu, test_positions, option_file, gui_args, language_manager, running_functional_tests, gfx_pipeline):
+ super().__init__(menu, running_functional_tests)
+ self._test_positions = test_positions
+ self._option_file = option_file
+ self._language_manager = language_manager
+ self._running_functional_tests = running_functional_tests
+ self._gfx_pipeline = gfx_pipeline
+ for k in list(self._test_positions.keys()): del self._test_positions[k]
+ self._widgets = []
+ self._lang_funcs = [lambda: _('English'), lambda: _('Italian')]
+ items = [fnc() for fnc in self._lang_funcs]
+ inititem = {
+ 'en': _('English'),
+ 'it': _('Italian')
+ }[self._option_file['settings']['language']]
+
+ def lang_cb(comps):
+ for element in ['english', 'italian']:
+ if element in self._test_positions:
+ del self._test_positions[element]
+ for lng, btn in zip(['english', 'italian'], comps):
+ btn.__class__ = type('DirectFrameMixed', (DirectFrame, DirectGuiMixin), {})
+ pos = btn.pos_pixel()
+ self._test_positions[lng] = (pos[0] + 5, pos[1])
+ btn = DirectOptionMenuTestable(
+ text=_('Language'), items=items, initialitem=inititem,
+ pos=(0, 1, .8), command=self.on_language, **gui_args.option)
+ btn.__class__ = type('DirectOptionMenuMixed', (DirectOptionMenu, DirectGuiMixin), {})
+ btn._show_cb = lang_cb
+ btn.popupMenu['frameColor'] = gui_args.button['frameColor']
+ btn.popupMenu['relief'] = gui_args.button['relief']
+ self._widgets += [btn]
+ pos_lang = self._widgets[-1].pos_pixel()
+ self._test_positions['languages'] = pos_lang
+ self._widgets += [OnscreenText(
+ _('Volume'), pos=(-.1, .55), font=gui_args.button['text_font'],
+ scale=gui_args.button['scale'], fg=gui_args.button['text_fg'],
+ align=TextNode.A_right)]
+ self._widgets += [DirectSlider(
+ pos=(.5, 1, .57),
+ value=self._option_file['settings']['volume'],
+ command=self.on_volume,
+ **gui_args.slider)]
+ self._widgets[-1].__class__ = type('DirectSliderMixed', (DirectSlider, DirectGuiMixin), {})
+ vol_pos = self._widgets[-1].pos_pixel()
+ self._test_positions['volume'] = vol_pos
+ np_left = GfxTools.build_empty_node('left_slider')
+ np_left.set_pos(self._widgets[-1].get_net_transform().get_pos())
+ np_left.set_x(np_left.get_x() - self._widgets[-1].get_scale()[0])
+ lpos = np_left.pos_as_widget() # try with pos2d_pixel and remove pos_as_widget if ok
+ self._test_positions['volume_0'] = lpos
+ self._slider = self._widgets[-1]
+ self._widgets += [DirectCheckButton(
+ text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
+ indicator_frameColor=gui_args.option['highlightColor'],
+ indicator_relief=gui_args.button['relief'],
+ indicatorValue=self._option_file['settings']['fullscreen'],
+ **gui_args.button)]
+ self._widgets[-1].__class__ = type('DirectCheckButtonMixed', (DirectCheckButton, DirectGuiMixin), {})
+ self._test_positions['fullscreen'] = self._widgets[-1].pos_pixel()
+ res = self._option_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]
+
+ def res_cb(comps):
+ for element in ['res_1440x900', 'res_1360x768']:
+ if element in self._test_positions:
+ del self._test_positions[element]
+ for tgt_res in ['1440x900', '1360x768']:
+ for btn in comps:
+ if btn['text'] == tgt_res:
+ pos = pos_pixel(btn)
+ self._test_positions['res_' + tgt_res] = (pos[0] + 5, pos[1])
+ btn = DirectOptionMenuTestable(
+ text=_('Resolution'), items=resolutions, initialitem=res,
+ pos=(0, 1, .05), command=self.on_resolution, **gui_args.option)
+ btn.__class__ = type('DirectOptionMenuMixed', (DirectOptionMenu, DirectGuiMixin), {})
+ btn._show_cb = res_cb
+ btn.popupMenu['frameColor'] = gui_args.button['frameColor']
+ btn.popupMenu['relief'] = gui_args.button['relief']
+ self._widgets += [btn]
+ pos_res = self._widgets[-1].pos_pixel()
+ self._test_positions['resolutions'] = pos_res # 680 365
+ self._test_positions['res_1440x900'] = [pos_res[0] + 320, pos_res[1] + 75]
+ self._test_positions['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=gui_args.option['highlightColor'],
+ indicator_relief=gui_args.button['relief'],
+ indicatorValue=self._option_file['settings']['antialiasing'],
+ **gui_args.button)]
+ self._widgets[-1].__class__ = type('DirectCheckButtonMixed', (DirectCheckButton, DirectGuiMixin), {})
+ self._test_positions['aa'] = self._widgets[-1].pos_pixel()
+ self._widgets += [DirectCheckButton(
+ text=_('Shadows'), pos=(0, 1, -.45), command=self.on_shadows,
+ indicator_frameColor=gui_args.option['highlightColor'],
+ indicator_relief=gui_args.button['relief'],
+ indicatorValue=self._option_file['settings']['shadows'],
+ **gui_args.button)]
+ self._widgets[-1].__class__ = type('DirectCheckButtonMixed', (DirectCheckButton, DirectGuiMixin), {})
+ self._test_positions['shadows'] = self._widgets[-1].pos_pixel()
+ self._widgets += [DirectButton(
+ text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+ **gui_args.button)]
+ self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ self._test_positions['back'] = self._widgets[-1].pos_pixel()
+ self.accept('enforce_resolution', self.enforce_resolution)
+
+ def on_language(self, arg):
+ lang_code = {
+ _('English'): 'en_EN',
+ _('Italian'): 'it_IT'}[arg]
+ self._language_manager.set_language(lang_code)
+ self._option_file['settings']['language'] = lang_code[:2]
+ self._option_file.store()
+ self._menu.set_page('options')
+
+ def on_volume(self):
+ self._option_file['settings']['volume'] = self._slider['value']
+ AudioTools.set_volume(self._slider['value'])
+
+ def on_fullscreen(self, arg):
+ props = WindowProperties()
+ props.set_fullscreen(arg)
+ if not self._running_functional_tests:
+ 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._option_file['settings']['fullscreen'] = int(arg)
+ self._option_file.store()
+
+ def on_resolution(self, arg):
+ info('on resolution: %s (%s)' % (arg, self._enforced_resolution))
+ arg = self._enforced_resolution 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._option_file['settings']['resolution'] = arg
+ self._option_file.store()
+
+ def on_aa(self, arg):
+ self._gfx_pipeline.msaa_samples = 4 if arg else 1
+ debug(f'msaa: {self._gfx_pipeline.msaa_samples}')
+ self._option_file['settings']['antialiasing'] = int(arg)
+ self._option_file.store()
+
+ def on_shadows(self, arg):
+ self._gfx_pipeline.enable_shadows = int(arg)
+ debug(f'shadows: {self._gfx_pipeline.enable_shadows}')
+ self._option_file['settings']['shadows'] = int(arg)
+ self._option_file.store()
--- /dev/null
+from direct.gui.DirectGui import DirectButton
+from ya2.utils.gfx import DirectGuiMixin
+from pmachines.gui.base_page import BasePage
+
+
+class PlayPage(BasePage):
+
+ def __init__(self, menu, test_positions, gui_args, scenes, option_file, running_functional_tests, application_fsm):
+ super().__init__(menu, running_functional_tests)
+ self._test_positions = test_positions
+ self._scenes = scenes
+ self._option_file = option_file
+ self._application_fsm = application_fsm
+ for k in list(self._test_positions.keys()): del self._test_positions[k]
+ self._widgets = []
+ cmn = gui_args.button.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
+ from pmachines.scene.scene import Scene
+ for i, scene_name in enumerate(self._scenes):
+ top = .1 if len(self._scenes) < 5 else .6
+ row = 0 if i < 4 else 1
+ new_cmn = cmn.copy()
+ if Scene.is_done(scene_name):
+ new_cmn['frameColor'] = (1, 1, 1, .4)
+ new_cmn['text_fg'] = (.9, .9, .9, .4)
+ self._widgets += [DirectButton(
+ text=Scene.name(scene_name), pos=(left + dx * (i % 4), 1, top - dx * row),
+ command=self.start, extraArgs=[scene_name], text_wordwrap=6,
+ frameTexture='assets/images/scenes/%s.dds' % scene_name,
+ **new_cmn)]
+ self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ name = scene_name.lower()
+ self._test_positions[name] = self._widgets[-1].pos_pixel()
+ 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,
+ **gui_args.button)]
+ self._widgets[-1].__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ self._test_positions['back'] = self._widgets[-1].pos_pixel()
+
+ def start(self, cls):
+ self._application_fsm.demand('Scene', cls)
-from panda3d.core import load_prc_file_data
+from panda3d.core import load_prc_file_data, AudioSound
load_prc_file_data('', 'window-type none')
from pathlib import Path
import sys
def tearDown(self):
self.__app.destroy()
- def test_audio(self):
+ def test_volume(self):
with (patch.object(base, 'musicManager', autospec=True) as m,
patch.object(base, 'sfxManagerList', autospec=True) as s):
AudioTools.set_volume(.8)
s_args = s[0].set_volume.call_args_list[0].args
self.assertAlmostEqual(s_args[0], .8, delta=.01)
self.assertEqual(len(s_args), 1)
+
+ def test_loading(self):
+ s = AudioTools.load_sfx('assets/audio/sfx/rollover.ogg')
+ self.assertIsInstance(s, AudioSound)
sys.path.append(str(Path(__file__).parent.parent.parent))
from unittest import TestCase
from unittest.mock import patch
-from panda3d.core import get_model_path
+from panda3d.core import get_model_path, Texture
from direct.showbase.ShowBase import ShowBase
from ya2.utils.gui import GuiTools
import ya2
self.assertEqual(l_args[0], '')
self.assertEqual(l_args[1], 'window-type none')
self.assertEqual(len(l_args), 2)
+
+ def test_font(self):
+ f = GuiTools.load_font('assets/fonts/Hanken-Book.ttf')
+ self.assertEqual(f.get_pixels_per_unit(), 60)
+ self.assertEqual(f.get_minfilter(), Texture.FTLinearMipmapLinear)
+ self.assertEqual(f.get_outline_color(), (0, 0, 0, 1))
+ self.assertAlmostEqual(f.get_outline_feather(), .2, delta=.01)
+ self.assertAlmostEqual(f.get_outline_width(), .8, delta=.01)
def set_volume(volume):
base.musicManager.set_volume(.8 * volume)
base.sfxManagerList[0].set_volume(volume)
+
+ @staticmethod
+ def load_sfx(path): return loader.load_sfx(path)
-from panda3d.core import load_prc_file_data
+from panda3d.core import load_prc_file_data, Texture
from ya2.utils.gfx import Point
class GuiTools:
@staticmethod
def get_mouse():
return Point(base.mouseWatcherNode.get_mouse())
+
+ def load_font(path):
+ font = base.loader.load_font(path)
+ font.clear()
+ font.set_pixels_per_unit(60)
+ font.set_minfilter(Texture.FTLinearMipmapLinear)
+ font.set_outline((0, 0, 0, 1), .8, .2)
+ return font