"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: pmachines/editor/augmented_frame.py:48
+#: pmachines/editor/augmented_frame.py:53
msgid "Collapse/Expand"
msgstr "Nascondi/Mostra"
-#: pmachines/editor/inspector.py:65 pmachines/editor/inspector.py:322
-#: pmachines/editor/inspector.py:450
+#: pmachines/editor/inspector.py:70 pmachines/editor/inspector.py:345
+#: pmachines/editor/inspector.py:476
msgid "position"
msgstr "posizione"
-#: pmachines/editor/inspector.py:65
+#: pmachines/editor/inspector.py:70 pmachines/editor/inspector.py:476
msgid "position (e.g. 0.1 2.3 4.5)"
msgstr "posizione (e.g. 0.1 2.3 4.5)"
-#: pmachines/editor/inspector.py:66
+#: pmachines/editor/inspector.py:71
msgid "roll"
msgstr "roll"
-#: pmachines/editor/inspector.py:66
+#: pmachines/editor/inspector.py:71
msgid "roll (e.g. 90)"
msgstr "roll (e.g. 90)"
-#: pmachines/editor/inspector.py:67 pmachines/editor/start_items.py:59
+#: pmachines/editor/inspector.py:72 pmachines/editor/start_items.py:76
msgid "scale"
msgstr "scala"
-#: pmachines/editor/inspector.py:67 pmachines/editor/start_items.py:59
+#: pmachines/editor/inspector.py:72 pmachines/editor/start_items.py:76
msgid "scale (e.g. 1.2)"
msgstr "scala (e.g. 1.2)"
-#: pmachines/editor/inspector.py:68 pmachines/editor/start_items.py:60
+#: pmachines/editor/inspector.py:73 pmachines/editor/start_items.py:77
msgid "mass"
msgstr "massa"
-#: pmachines/editor/inspector.py:68
+#: pmachines/editor/inspector.py:73
msgid "mass (default 1; 0 if fixed)"
msgstr "massa (default 1; 0 se fisso)"
-#: pmachines/editor/inspector.py:69 pmachines/editor/start_items.py:61
+#: pmachines/editor/inspector.py:74 pmachines/editor/start_items.py:78
msgid "restitution"
msgstr "rimbalzo"
-#: pmachines/editor/inspector.py:69 pmachines/editor/start_items.py:61
+#: pmachines/editor/inspector.py:74 pmachines/editor/start_items.py:78
msgid "restitution (default 0.5)"
msgstr "rimbalzo (default 0.5)"
-#: pmachines/editor/inspector.py:70 pmachines/editor/start_items.py:62
+#: pmachines/editor/inspector.py:75 pmachines/editor/start_items.py:79
msgid "friction"
msgstr "frizione"
-#: pmachines/editor/inspector.py:70 pmachines/editor/start_items.py:62
+#: pmachines/editor/inspector.py:75 pmachines/editor/start_items.py:79
msgid "friction (default 0.5)"
msgstr "frizione (default 0.5)"
-#: pmachines/editor/inspector.py:71 pmachines/editor/inspector.py:323
-#: pmachines/editor/inspector.py:451 pmachines/editor/start_items.py:63
+#: pmachines/editor/inspector.py:76 pmachines/editor/inspector.py:346
+#: pmachines/editor/inspector.py:477 pmachines/editor/start_items.py:80
msgid "id"
msgstr "id"
-#: pmachines/editor/inspector.py:71
+#: pmachines/editor/inspector.py:76 pmachines/editor/inspector.py:477
msgid "id of the item (for the strategies)"
msgstr "id dell'oggetto (per le strategie)"
-#: pmachines/editor/inspector.py:72 pmachines/editor/start_items.py:71
+#: pmachines/editor/inspector.py:86 pmachines/editor/start_items.py:88
msgid "strategy"
msgstr "strategia"
-#: pmachines/editor/inspector.py:72 pmachines/editor/start_items.py:71
+#: pmachines/editor/inspector.py:86 pmachines/editor/start_items.py:88
msgid "the strategy of the item"
msgstr "la strategia dell'oggetto"
-#: pmachines/editor/inspector.py:73 pmachines/editor/start_items.py:72
+#: pmachines/editor/inspector.py:101 pmachines/editor/start_items.py:103
msgid "strategy_args"
msgstr "argomenti strategia"
-#: pmachines/editor/inspector.py:73 pmachines/editor/start_items.py:72
+#: pmachines/editor/inspector.py:101 pmachines/editor/start_items.py:103
msgid "the arguments of the strategy"
msgstr "gli argomenti della strategia"
-#: pmachines/editor/inspector.py:99 pmachines/editor/start_items.py:99
+#: pmachines/editor/inspector.py:128 pmachines/editor/inspector.py:504
+#: pmachines/editor/start_items.py:131
msgid "Close"
msgstr "Chiudi"
-#: pmachines/editor/inspector.py:109
+#: pmachines/editor/inspector.py:139 pmachines/editor/inspector.py:515
msgid "Delete the item"
msgstr "Cancella l'oggetto"
-#: pmachines/editor/inspector.py:264 pmachines/editor/start_items.py:309
+#: pmachines/editor/inspector.py:286 pmachines/editor/start_items.py:365
msgid "There are errors in the strategy args."
msgstr "Ci sono errori negli argomenti della strategia."
-#: pmachines/editor/scene.py:71
+#: pmachines/editor/scene.py:76
msgid "Filename"
msgstr "Filename"
-#: pmachines/editor/scene.py:86
+#: pmachines/editor/scene.py:92
msgid "The name of the file without the extension"
msgstr "Il nome del file senza estensione"
-#: pmachines/editor/scene.py:88
+#: pmachines/editor/scene.py:95
msgid "Name"
msgstr "Nome"
-#: pmachines/editor/scene.py:103
+#: pmachines/editor/scene.py:110
msgid "The title of the scene"
msgstr "Il titolo della scena"
-#: pmachines/editor/scene.py:105
+#: pmachines/editor/scene.py:113
msgid "Description"
msgstr "Descrizione"
-#: pmachines/editor/scene.py:129
+#: pmachines/editor/scene.py:137
msgid "The description of the scene"
msgstr "La descrizione della scena"
-#: pmachines/editor/scene.py:152 pmachines/editor/scene_list.py:89
+#: pmachines/editor/scene.py:162 pmachines/editor/scene_list.py:93
msgid "Close the scene editor"
msgstr "Chiudi l'editor della scena"
-#: pmachines/editor/scene.py:161
+#: pmachines/editor/scene.py:172
msgid "Save the scene"
msgstr "Salva la scena"
-#: pmachines/editor/scene.py:170
+#: pmachines/editor/scene.py:182
msgid "Set the sorting of the scenes"
msgstr "Imposta l'ordinamento delle scene"
-#: pmachines/editor/scene.py:181 pmachines/editor/scene.py:188
+#: pmachines/editor/scene.py:193 pmachines/editor/scene.py:210
msgid "new item"
msgstr "nuovo oggetto"
-#: pmachines/editor/scene.py:200
+#: pmachines/editor/scene.py:223
msgid "Create a new item"
msgstr "Crea un nuovo oggetto"
-#: pmachines/editor/scene.py:209
+#: pmachines/editor/scene.py:234
msgid "Initial items"
msgstr "Oggetti iniziali"
-#: pmachines/editor/scene.py:218
+#: pmachines/editor/scene.py:244
msgid "New scene"
msgstr "Nuova scena"
-#: pmachines/editor/scene.py:263 pmachines/editor/scene_list.py:107
+#: pmachines/editor/scene.py:289 pmachines/editor/scene_list.py:112
msgid "You have unsaved changes. Really quit?"
msgstr "Hai modifiche non salvate, vuoi veramente uscire?"
-#: pmachines/editor/scene_list.py:39
+#: pmachines/editor/scene_list.py:41
msgid "Write the file names (without the extension), one file for each line"
msgstr "Scrivi i nomi dei file (senza estensione), uno per riga."
-#: pmachines/editor/scene_list.py:66
+#: pmachines/editor/scene_list.py:68
msgid "the list of the scenes in the proper order"
msgstr "la lista della scene nell'ordine"
-#: pmachines/editor/scene_list.py:98
+#: pmachines/editor/scene_list.py:103
msgid "Save the scene list"
msgstr "Salva la lista delle scene"
-#: pmachines/editor/start_items.py:57
+#: pmachines/editor/start_items.py:61
msgid "class"
msgstr "classe"
-#: pmachines/editor/start_items.py:57
+#: pmachines/editor/start_items.py:61
msgid "class of the item"
msgstr "classe dell'oggetto"
-#: pmachines/editor/start_items.py:58
+#: pmachines/editor/start_items.py:75
msgid "count"
msgstr "quantità"
-#: pmachines/editor/start_items.py:58
+#: pmachines/editor/start_items.py:75
msgid "number of the items"
msgstr "numero degli oggetti"
-#: pmachines/editor/start_items.py:60
+#: pmachines/editor/start_items.py:77
msgid "mass (default 1)"
msgstr "massa (default 1)"
-#: pmachines/editor/start_items.py:108
+#: pmachines/editor/start_items.py:141
msgid "Save"
msgstr "Salva"
-#: pmachines/editor/start_items.py:117
+#: pmachines/editor/start_items.py:151
msgid "Delete"
msgstr "Cancella"
-#: pmachines/editor/start_items.py:126
+#: pmachines/editor/start_items.py:161
msgid "New"
msgstr "Nuovo"
-#: pmachines/editor/start_items.py:135
+#: pmachines/editor/start_items.py:171
msgid "Next"
msgstr "Prossimo"
-#: pmachines/gui/main_page.py:19
-msgid "Play"
-msgstr "Gioca"
-
-#: pmachines/gui/main_page.py:24
-msgid "Options"
-msgstr "Opzioni"
-
-#: pmachines/gui/main_page.py:29
-msgid "Credits"
-msgstr "Riconoscimenti"
-
-#: pmachines/gui/main_page.py:39 pmachines/scene/scene.py:305
-msgid "Exit"
-msgstr "Esci"
-
-#: pmachines/gui/credits_page.py:25
+#: pmachines/gui/credits_page.py:11
msgid ""
"Code and gfx\n"
" \ 1scale\ 1Flavio Calva\ 2\n"
"Music\n"
" \ 1scale\ 1Stefan Grossmann\ 2"
-#: pmachines/gui/credits_page.py:30
+#: pmachines/gui/credits_page.py:17
msgid "Website"
msgstr "Sito web"
-#: pmachines/gui/credits_page.py:33
+#: pmachines/gui/credits_page.py:23
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
+#: pmachines/gui/credits_page.py:29 pmachines/gui/options_page.py:132
+#: pmachines/gui/play_page.py:32
msgid "Back"
msgstr "Indietro"
-#: pmachines/gui/options_page.py:34 pmachines/gui/options_page.py:37
+#: pmachines/gui/main_page.py:11
+msgid "Play"
+msgstr "Gioca"
+
+#: pmachines/gui/main_page.py:18
+msgid "Options"
+msgstr "Opzioni"
+
+#: pmachines/gui/main_page.py:25
+msgid "Credits"
+msgstr "Riconoscimenti"
+
+#: pmachines/gui/main_page.py:32 pmachines/scene/scene.py:307
+msgid "Exit"
+msgstr "Esci"
+
+#: pmachines/gui/options_page.py:30 pmachines/gui/options_page.py:33
#: 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:30 pmachines/gui/options_page.py:34
#: pmachines/gui/options_page.py:146
msgid "Italian"
msgstr "Italiano"
-#: pmachines/gui/options_page.py:50
+#: pmachines/gui/options_page.py:47
msgid "Language"
msgstr "Linguaggio"
-#: pmachines/gui/options_page.py:60
+#: pmachines/gui/options_page.py:56
msgid "Volume"
msgstr "Volume"
-#: pmachines/gui/options_page.py:78
+#: pmachines/gui/options_page.py:70
msgid "Fullscreen"
msgstr "Schermo intero"
-#: pmachines/gui/options_page.py:109
+#: pmachines/gui/options_page.py:101
msgid "Resolution"
msgstr "Risoluzione"
-#: pmachines/gui/options_page.py:121
+#: pmachines/gui/options_page.py:114
msgid "Antialiasing"
msgstr "Antialiasing"
-#: pmachines/gui/options_page.py:129
+#: pmachines/gui/options_page.py:123
msgid "Shadows"
msgstr "Ombre"
-#: pmachines/scene/scene.py:106
+#: pmachines/scene/scene.py:108
msgid "Scene: "
msgstr "Scena: "
-#: pmachines/scene/scene.py:306
+#: pmachines/scene/scene.py:308
msgid "Instructions"
msgstr "Istruzioni"
-#: pmachines/scene/scene.py:307
+#: pmachines/scene/scene.py:309
msgid "Run"
msgstr "Vai"
-#: pmachines/scene/scene.py:313
+#: pmachines/scene/scene.py:315
msgid "Editor"
msgstr "Editor"
-#: pmachines/scene/scene.py:636
+#: pmachines/scene/scene.py:638
msgid "You win!"
msgstr "Hai vinto!"
-#: pmachines/scene/scene.py:716
+#: pmachines/scene/scene.py:718
msgid "You have failed!"
msgstr "Hai perso!"
from ya2.utils.log import LogManager
LogManager.before_init_setup('pmachines')
from sys import argv
-from ya2.utils.gui import GuiTools
+from ya2.utils.gui.gui import GuiTools
if '--version' in argv: GuiTools.no_window()
from os.path import exists
from p3d_appimage import AppImageBuilder
from ya2.utils.functional import FunctionalTest
from ya2.utils.asserts import Assert
from ya2.utils.gfx import DirectGuiMixin
+from ya2.utils.gui.gui import GuiTools
class MainFsm(FSM):
self.__do_asserts()
DirectGuiMixin.clear_tooltips()
- def enterScene(self, cls):
- self._pmachines.on_scene_enter(cls)
+ def enterScene(self, scene_name):
+ self._pmachines.on_scene_enter(scene_name)
def exitScene(self):
self._pmachines.on_scene_exit()
self.log_mgr = WindowedLogManager.init_cls()
self._pos_mgr = {}
self._prepare_window(args)
+ GuiTools.init()
self._fsm = MainFsm(self)
self._fsm.demand('Start') # otherwise it is Off and cleanup in tests won't work
if args.update:
def on_menu_enter(self):
self._menu_bg = Background()
self._menu = Menu(
- self._fsm, self.lang_mgr, self._options,
+ lambda scene_name: self._fsm.demand('Scene', scene_name),
+ self.lang_mgr.set_language, self._options,
self._pipeline, self.scenes(), self._args.functional_test or self._args.functional_ref,
self._pos_mgr)
from pmachines.items.box import HitStrategy
from pmachines.items.domino import DownStrategy, UpStrategy
from pmachines.editor.augmented_frame import AugmentedDirectFrame
-from pmachines.gui.options_page import DirectOptionMenuTestable
+from ya2.utils.gui.base_page import DirectOptionMenuTestable
from ya2.utils.gfx import DirectGuiMixin
from pmachines.editor.start_items import StartItems
from ya2.utils.gfx import Point, DirectGuiMixin
from pmachines.editor.augmented_frame import AugmentedDirectFrame
-from pmachines.gui.options_page import DirectOptionMenuTestable
+from ya2.utils.gui.base_page import DirectOptionMenuTestable
class SceneEditor(DirectObject):
initialText=json_name,
parent=self._frm,
text_fg=self._common['text_fg'])
+ self.__filenamename_entry['text_fg'] = (1, 0, 0, 1)
self.__filenamename_entry.__class__ = type('DirectEntryMixed', (DirectEntry, DirectGuiMixin), {})
self.__filenamename_entry.set_tooltip(_('The name of the file without the extension'), *tooltip_args)
self.__pos_mgr['editor_scene_filename'] = self.__filenamename_entry.pos_pixel()
from ya2.utils.gfx import DirectGuiMixin
from pmachines.items.item import Item, ItemStrategy
from pmachines.editor.augmented_frame import AugmentedDirectFrame
-from pmachines.gui.options_page import DirectOptionMenuTestable
+from ya2.utils.gui.base_page import DirectOptionMenuTestable
class StartItems(DirectObject):
+++ /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
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
+from ya2.utils.gui.base_page import BasePage, TextInfo, ButtonInfo
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 _build_widgets(self):
+ text_dev = TextInfo(
+ text=_('Code and gfx\n \1scale\1Flavio Calva\2\n\n\nMusic\n \1scale\1Stefan Grossmann\2'),
+ pos=(-.9, .55),
+ align='left')
+ self._add_text(text_dev)
+ website_button = ButtonInfo(
+ id='website',
+ text=_('Website'),
+ pos=(-.6, .29),
+ command=self.__on_website,
+ scale=.08)
+ self._add_button(website_button)
+ thanks_text = TextInfo(
+ text=_('Special thanks to:\n \1scale\1rdb\2\n \1scale\1Luisa Tenuta\2\n \1scale\1Damiana Ercolani\2'),
+ pos=(.1, .55),
+ align='left')
+ self._add_text(thanks_text)
+ back_button = ButtonInfo(
+ id='back',
+ text=_('Back'),
+ pos=(0, -.8),
+ command=self._set_page,
+ command_args=['main'])
+ self._add_button(back_button)
- def on_website(self):
+ def __on_website(self):
if platform.startswith('linux'):
environ['LD_LIBRARY_PATH'] = ''
system('xdg-open https://www.ya2.it')
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
+from ya2.utils.gui.base_page import BasePage, ButtonInfo
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]
+ def _build_widgets(self):
+ play_args = ButtonInfo(
+ id='play',
+ text=_('Play'),
+ pos=(0, .6),
+ command=self._set_page,
+ command_args=['play'])
+ self._add_button(play_args)
+ option_args = ButtonInfo(
+ id='options',
+ text=_('Options'),
+ pos=(0, .2),
+ command=self._set_page,
+ command_args=['options'])
+ self._add_button(option_args)
+ credits_args = ButtonInfo(
+ id='credits',
+ text=_('Credits'),
+ pos=(0, -.2),
+ command=self._set_page,
+ command_args=['credits'])
+ self._add_button(credits_args)
+ exit_args = ButtonInfo(
+ id='exit',
+ text=_('Exit'),
+ pos=(0, -.6),
+ command=self.__on_exit)
+ self._add_button(exit_args)
+ self._rearrange_buttons_width()
+
+ def __on_exit(self):
+ if self._page_info.running_functional_tests:
+ ServerProxy('http://localhost:7000').destroy()
+ exit()
-from collections import namedtuple
-from panda3d.core import TextNode
-from direct.gui.DirectGuiGlobals import FLAT
-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
+from ya2.utils.gui.menu import BaseMenu
+from ya2.utils.gui.cursor import MouseCursorInfo
-class Menu:
+class Menu(BaseMenu):
- def __init__(self, application_fsm, language_manager, option_file,
+ def __init__(self, start_scene, set_language, option_file,
gfx_pipeline, scenes,
running_functional_tests, test_positions):
- super().__init__()
- self.__application_fsm = application_fsm
- self.__language_manager = language_manager
+ c = MouseCursorInfo(
+ 'assets/images/buttons/arrowUpLeft.dds',
+ running_functional_tests,
+ (.04, 1, .04),
+ (.5, .5, .5, 1),
+ (.01, .01))
+ super().__init__(c, running_functional_tests, test_positions)
+ self.__start_scene = start_scene
+ self.__set_language = set_language
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': GuiTools.load_font('assets/fonts/Hanken-Book.ttf'),
- 'text_fg': (.9, .9, .9, 1),
- 'relief': FLAT,
- 'frameColor': (.4, .4, .4, .14),
- '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': h,
- 'text_align': TextNode.A_center,
- } | 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 slider['rolloverSound']
- del slider['clickSound']
- GuiArgs = namedtuple('GuiArgs', 'button option slider')
- gui_args = GuiArgs(button, option, slider)
- return gui_args
-
- def set_page(self, page_name):
- self.destroy()
- if self.__page: self.__page.destroy()
+ def _set_page(self, page_name):
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):
- if self.__page:
- self.__page.destroy()
- self.__page = None
+ case 'main': p = MainPage(self._page_info)
+ case 'credits': p = CreditsPage(self._page_info)
+ case 'options': p = OptionsPage(self._page_info, self.__option_file, self.__set_language, self.__gfx_pipeline)
+ case 'play': p = PlayPage(self._page_info, self.__scenes, self.__start_scene)
+ return p
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 panda3d.core import WindowProperties, LVector2i
+from direct.showbase.DirectObject import DirectObject
+from direct.gui.DirectGui import DirectFrame
+from ya2.utils.gfx import DirectGuiMixin, pos_pixel
from ya2.utils.audio import AudioTools
-from pmachines.gui.base_page import BasePage
+from ya2.utils.gui.base_page import BasePage, OptionMenuInfo, TextInfo, SliderInfo, CheckButtonInfo, ButtonInfo
-class DirectOptionMenuTestable(DirectOptionMenu):
+class OptionsPage(BasePage, DirectObject):
- def __init__(self, parent=None, **kw):
- DirectOptionMenu.__init__(self, parent, **kw)
- self.initialiseoptions(DirectOptionMenuTestable)
+ def __init__(self, page_info, option_file, set_language, gfx_pipeline):
+ DirectObject.__init__(self)
+ self.__option_file = option_file
+ self.__set_language = set_language
+ self.__gfx_pipeline = gfx_pipeline
+ self.__enforced_resolution = ''
+ super().__init__(page_info)
- 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()])
+ def _build_widgets(self):
+ self.__build_language()
+ self.__build_volume()
+ self.__build_fullscreen()
+ self.__build_resolution()
+ self.__build_aa()
+ self.__build_shadows()
+ self.__build_back()
-
-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 = []
+ def __build_language(self):
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']]
+ }[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]
+ if element in self._page_info.test_positions:
+ del self._page_info.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']
+ self._page_info.test_positions[lng] = (pos[0] + 5, pos[1])
+ language_menu = OptionMenuInfo(
+ id='languages',
+ text=_('Language'),
+ items=items,
+ initial_item=inititem,
+ pos=(0, .8),
+ command=self.on_language)
+ self._add_option_menu(language_menu, lang_cb)
+
+ def __build_volume(self):
+ t = TextInfo(
+ text=_('Volume'),
+ pos=(-.56, .55),
+ align='left')
+ self._add_text(t)
+ s = SliderInfo(
+ pos=(.3, 1, .57),
+ value=self.__option_file['settings']['volume'],
+ scale=(.28, 1, .5),
+ command=self.on_volume)
+ self._slider = self._add_slider(s)
+
+ def __build_fullscreen(self):
+ c = CheckButtonInfo(
+ id='fullscreen',
+ text=_('Fullscreen'),
+ pos=(0, .3),
+ command=self.on_fullscreen,
+ value=self.__option_file['settings']['fullscreen'])
+ self._add_check_button(c)
+
+ def __build_resolution(self):
+ res = self.__option_file['settings']['resolution']
d_i = base.pipe.get_display_information()
def _res(idx):
return d_i.get_display_mode_width(idx), \
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]
+ if element in self._page_info.test_positions:
+ del self._page_info.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._page_info.test_positions['res_' + tgt_res] = (pos[0] + 5, pos[1])
+ r = OptionMenuInfo(
+ id='resolutions',
+ text=_('Resolution'),
+ items=resolutions,
+ initial_item=res,
+ pos=(0, .05),
+ command=self.on_resolution)
+ o = self._add_option_menu(r, res_cb)
+ pos_res = o.pos_pixel()
+ self._page_info.test_positions['res_1440x900'] = [pos_res[0] + 320, pos_res[1] + 75]
+ self._page_info.test_positions['res_1360x768'] = [pos_res[0] + 430, pos_res[1] -285]
+
+ def __build_aa(self):
+ c = CheckButtonInfo(
+ id='aa',
+ text=_('Antialiasing'),
+ pos=(0, -.2),
+ command=self.on_aa,
+ value=self.__option_file['settings']['antialiasing'])
+ self._add_check_button(c)
+
+ def __build_shadows(self):
+ c = CheckButtonInfo(
+ id='shadows',
+ text=_('Shadows'),
+ pos=(0, -.45),
+ command=self.on_shadows,
+ value=self.__option_file['settings']['shadows'])
+ self._add_check_button(c)
+
+ def __build_back(self):
+ back_button = ButtonInfo(
+ id='back',
+ text=_('Back'),
+ pos=(0, -.8),
+ command=self._set_page,
+ command_args=['main'])
+ self._add_button(back_button)
self.accept('enforce_resolution', self.enforce_resolution)
+ def enforce_resolution(self, resolution):
+ self.__enforced_resolution = resolution
+ info('enforced resolution: ' + 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')
+ self.__set_language(lang_code)
+ self.__option_file['settings']['language'] = lang_code[:2]
+ self.__option_file.store()
+ self._set_page('options')
def on_volume(self):
- self._option_file['settings']['volume'] = self._slider['value']
+ 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:
+ if not self._page_info.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()
+ 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('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()
+ 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()
+ 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()
+ 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()
+
+ def on_back(self):
+ self.__option_file.store()
+ super().on_back()
+
+ def destroy(self):
+ super().destroy()
+ self.ignore('enforce_resolution')
-from direct.gui.DirectGui import DirectButton
-from ya2.utils.gfx import DirectGuiMixin
-from pmachines.gui.base_page import BasePage
+from ya2.utils.gui.base_page import BasePage, ButtonInfo
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
+ def __init__(self, page_info, scenes, start_scene):
+ self.__scenes = scenes
+ self.__start_scene = start_scene
+ super().__init__(page_info)
+
+ def _build_widgets(self):
+ 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
+ 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)
+ i = ButtonInfo(
+ id=scene_name.lower(),
+ text=Scene.name(scene_name),
+ pos=(left + dx * (i % 4), top - dx * row),
+ command=self.__start_scene,
+ command_args=[scene_name],
+ wordwrap=6,
+ texture='assets/images/scenes/%s.dds' % scene_name,
+ size=(-2.4, 2.4, -2.4, 2.4),
+ text_scale=.64,
+ color=(1, 1, 1, .4) if Scene.is_done(scene_name) else (1, 1, 1, .8),
+ text_color=(.9, .9, .9, .4) if Scene.is_done(scene_name) else (.9, .9, .9, 1))
+ self._add_button(i, True)
+ back_button = ButtonInfo(
+ id='back',
+ text=_('Back'),
+ pos=(0, -.8),
+ command=self._set_page,
+ command_args=['main'])
+ self._add_button(back_button)
from pmachines.items.teetertooter import TeeterTooter
from pmachines.items.test_item import PixelSpaceTestItem, WorldSpaceTestItem
from pmachines.editor.scene import SceneEditor
-from ya2.utils.cursor import MouseCursor
+from ya2.utils.gui.cursor import MouseCursor, MouseCursorInfo
from ya2.utils.gfx import GfxTools, DirectGuiMixin
-from ya2.utils.gui import GuiTools
+from ya2.utils.gui.gui import GuiTools
class Scene(DirectObject):
self.json = {}
self.accept('enforce_result', self.enforce_result)
self._set_camera()
- self._cursor = MouseCursor(
- 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
- (.01, .01), testing)
+ c = MouseCursorInfo(
+ 'assets/images/buttons/arrowUpLeft.dds', testing, (.04, 1, .04), (.5, .5, .5, 1),
+ (.01, .01))
+ self._cursor = MouseCursor(c)
self.__set_font()
self._set_gui()
self._set_lights()
* BACKLOG actions: rewind, prev, next
* BACKLOG (python 3.11) manylinux2014_x86_64
* BACKLOG (when panda3d provides it) android build (test with the emulator of android studio)
-* BACKLOG (when itch.io's client works with wine) functional tests for windows-itch.io
+* BACKLOG (test with wine 8.0 | when itch.io's client works with wine) functional tests for windows-itch.io
* calendar :calendar:
** publish post q1; reschedule
SCHEDULED: <2023-03-20 Mon +1y>
--- /dev/null
+from panda3d.core import load_prc_file_data
+load_prc_file_data('', 'window-type none')
+from pathlib import Path
+import sys
+if '' in sys.path: sys.path.remove('')
+sys.path.append(str(Path(__file__).parent.parent.parent))
+from unittest import TestCase
+from direct.showbase.ShowBase import ShowBase
+from ya2.utils.gui.cursor import MouseCursor, MouseCursorInfo
+
+
+class CursorTests(TestCase):
+
+ def setUp(self):
+ self.__app = ShowBase()
+
+ def tearDown(self):
+ self.__app.destroy()
+
+ def test_cursor(self):
+ i = MouseCursorInfo('tests/assets/images/icon16.png', False, (1, 1, 1), (1, 1, 1, 1), (0, 0))
+ c = MouseCursor(i)
+ c.set_image('tests/assets/images/icon32.png')
+ self.assertEqual('tests/assets/images/icon32.png', c._MouseCursor__image.get_texture().get_filename())
+ c.set_color((0, 1, 0, 1))
+ self.assertEqual((0, 1, 0, 1), c._MouseCursor__image.get_color())
+ c.show()
+ self.assertFalse(c._MouseCursor__image.is_hidden())
+ c.hide()
+ self.assertTrue(c._MouseCursor__image.is_hidden())
+ c.destroy()
+ self.assertNotIn('on_frame_cursor', [t.name for t in taskMgr.getTasks() + taskMgr.getDoLaters()])
+ self.assertTrue(c._MouseCursor__image.is_empty())
--- /dev/null
+from panda3d.core import load_prc_file_data
+load_prc_file_data('', 'window-type offscreen')
+from pathlib import Path
+import sys
+if '' in sys.path: sys.path.remove('')
+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, Texture
+from direct.showbase.ShowBase import ShowBase
+from ya2.utils.gui.gui import GuiTools
+import ya2
+
+
+class TestApp(ShowBase): pass
+
+
+class GraphicsToolsTests(TestCase):
+
+ def setUp(self):
+ self.__app = TestApp()
+ get_model_path().append_directory(str(Path(__file__).parent.parent.parent))
+
+ def tearDown(self):
+ self.__app.destroy()
+
+ @patch.object(ya2.utils.gui.gui, 'load_prc_file_data')
+ def test_no_window(self, l_mock):
+ GuiTools.no_window()
+ l_mock.assert_called_once()
+ l_args = l_mock.call_args_list[0].args
+ 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)
+++ /dev/null
-from panda3d.core import load_prc_file_data
-load_prc_file_data('', 'window-type none')
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-sys.path.append(str(Path(__file__).parent.parent.parent))
-from unittest import TestCase
-from direct.showbase.ShowBase import ShowBase
-from ya2.utils.cursor import MouseCursor
-
-
-class CursorTests(TestCase):
-
- def setUp(self):
- self.__app = ShowBase()
-
- def tearDown(self):
- self.__app.destroy()
-
- def test_cursor(self):
- c = MouseCursor('tests/assets/images/icon16.png', (1, 1, 1), (1, 1, 1, 1), (0, 0), False)
- c.set_image('tests/assets/images/icon32.png')
- self.assertEqual('tests/assets/images/icon32.png', c._MouseCursor__image.get_texture().get_filename())
- c.set_color((0, 1, 0, 1))
- self.assertEqual((0, 1, 0, 1), c._MouseCursor__image.get_color())
- c.show()
- self.assertFalse(c._MouseCursor__image.is_hidden())
- c.hide()
- self.assertTrue(c._MouseCursor__image.is_hidden())
- c.destroy()
- self.assertNotIn('on_frame_cursor', [t.name for t in taskMgr.getTasks() + taskMgr.getDoLaters()])
- self.assertTrue(c._MouseCursor__image.is_empty())
+++ /dev/null
-from panda3d.core import load_prc_file_data
-load_prc_file_data('', 'window-type offscreen')
-from pathlib import Path
-import sys
-if '' in sys.path: sys.path.remove('')
-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, Texture
-from direct.showbase.ShowBase import ShowBase
-from ya2.utils.gui import GuiTools
-import ya2
-
-
-class TestApp(ShowBase): pass
-
-
-class GraphicsToolsTests(TestCase):
-
- def setUp(self):
- self.__app = TestApp()
- get_model_path().append_directory(str(Path(__file__).parent.parent.parent))
-
- def tearDown(self):
- self.__app.destroy()
-
- @patch.object(ya2.utils.gui, 'load_prc_file_data')
- def test_no_window(self, l_mock):
- GuiTools.no_window()
- l_mock.assert_called_once()
- l_args = l_mock.call_args_list[0].args
- 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)
+++ /dev/null
-from panda3d.core import WindowProperties, Texture
-from direct.gui.OnscreenImage import OnscreenImage
-from ya2.utils.logics import LogicsTools
-
-
-class MouseCursor:
-
- def __init__(self, image_path, scale, color, hotspot_position, functional_test):
- self.__functional_test = functional_test
- if not image_path: return
- self.__hide_system_cursor()
- self.__set_image(image_path, scale, color)
- self.__set_hotspot(hotspot_position, scale)
- self._tsk = taskMgr.add(self.__on_frame, 'on_frame_cursor')
-
- def __hide_system_cursor(self):
- p = WindowProperties()
- p.set_cursor_hidden(True)
- if LogicsTools.windowed: base.win.request_properties(p)
-
- def __set_image(self, image_path, scale, color):
- self.__image = OnscreenImage(image_path, scale=scale)
- self.__image.set_color(color)
- self.__image.set_bin('gui-popup', 50)
- alpha_formats = [Texture.FRgba]
- if self.__image.get_texture().get_format() in alpha_formats:
- self.__image.set_transparency(True)
-
- def __set_hotspot(self, hotspot_position, scale):
- self.__hotspot_dx = scale[0] * (1 - 2 * hotspot_position[0])
- self.__hotspot_dy = scale[2] * (1 - 2 * hotspot_position[1])
-
- def __on_frame(self, task):
- m = base.mouseWatcherNode
- if not m or not m.hasMouse(): return task.again
- mouse = m.get_mouse_x(), m.get_mouse_y()
- h_x = mouse[0] * base.get_aspect_ratio() + self.__hotspot_dx
- self.__image.set_pos((h_x, 1, mouse[1] - self.__hotspot_dy))
- return task.again
-
- def set_image(self, image):
- self.__image.set_texture(loader.load_texture(image), 1)
-
- def set_color(self, color):
- self.__image.set_color(color)
-
- def show(self):
- if not self.__functional_test: return self.__image.show()
-
- def hide(self):
- return self.__image.hide()
-
- def destroy(self):
- taskMgr.remove(self._tsk)
- self.__image.destroy()
+++ /dev/null
-from panda3d.core import load_prc_file_data, Texture
-from ya2.utils.gfx import Point
-
-class GuiTools:
-
- @staticmethod
- def no_window():
- load_prc_file_data('', 'window-type none')
-
- @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
--- /dev/null
+from dataclasses import dataclass
+from panda3d.core import TextNode
+from direct.gui.OnscreenText import OnscreenText
+from direct.gui.DirectGui import DirectButton, DirectOptionMenu, DirectSlider, DirectCheckButton
+from direct.gui.DirectGuiGlobals import FLAT
+from ya2.utils.audio import AudioTools
+from ya2.utils.gfx import DirectGuiMixin, GfxTools
+from ya2.utils.gui.gui import GuiTools
+
+
+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()])
+
+
+@dataclass
+class BaseInfo:
+ id: ... = ''
+ text: ... = None
+ pos: ... = None
+ scale: ... = None
+ wordwrap: ... = None
+ texture: ... = None
+ text_scale: ... = None
+ font: ... = None
+ color: ... = None
+
+ def to_p3d_args(self):
+ d = {}
+ d['text'] = self.text
+ d['pos'] = self.pos or (0, 1, 0)
+ d['scale'] = self.scale or .12
+ d['font'] = self.font or GuiTools.load_font('assets/fonts/Hanken-Book.ttf')
+ d['fg'] = self.color or (.9, .9, .9, 1)
+ return d
+
+
+@dataclass
+class ButtonInfo(BaseInfo):
+ command: ... = None
+ command_args: ... = None
+ size: ... = None
+ text_color: ... = None
+ relief: ... = None
+ rollover_sound: ... = None
+ click_sound: ... = None
+
+ def to_p3d_args(self):
+ d = super().to_p3d_args()
+ d['frameSize'] = self.size or (-4.8, 4.8, -.6, 1.2)
+ d['command'] = self.command
+ d['extraArgs'] = self.command_args or []
+ d['text_fg'] = self.text_color or d.pop('fg')
+ if 'fg' in d: del d['fg']
+ d['text_font'] = self.font or d.pop('font')
+ d['frameColor'] = self.color or (.4, .4, .4, .14)
+ d['relief'] = self.relief or FLAT
+ d['rolloverSound'] = self.rollover_sound or AudioTools.load_sfx('assets/audio/sfx/rollover.ogg')
+ d['clickSound'] = self.click_sound or AudioTools.load_sfx('assets/audio/sfx/click.ogg')
+ d['pos'] = (self.pos[0], 1, self.pos[1]) if len(self.pos) == 2 else self.pos
+ d['text_wordwrap'] = self.wordwrap
+ d['frameTexture'] = self.texture
+ d['text_scale'] = self.text_scale or 1
+ return d
+
+ def __build_gui_args(self):
+ base = {
+ 'scale': .12,
+ 'text_font': GuiTools.load_font('assets/fonts/Hanken-Book.ttf'),
+ 'text_fg': (.9, .9, .9, 1),
+ 'relief': FLAT,
+ 'frameColor': (.4, .4, .4, .14),
+ 'rolloverSound': AudioTools.load_sfx('assets/audio/sfx/rollover.ogg'),
+ 'clickSound': AudioTools.load_sfx('assets/audio/sfx/click.ogg')}
+ button = {'size': (-4.8, 4.8, -.6, 1.2)} | base
+ return button
+
+
+@dataclass
+class SliderInfo(ButtonInfo):
+ range: ... = None
+ scale: ... = None
+ value: ... = None
+
+ def to_p3d_args(self):
+ d = super().to_p3d_args()
+ d['range'] = self.range or (0, 1)
+ d['thumb_frameColor'] = (.4, .4, .4, .4)
+ d['thumb_scale'] = 1.6
+ d['thumb_relief'] = d['relief']
+ d['scale'] = self.scale or .4
+ d['value'] = self.value or 0
+ del d['rolloverSound']
+ del d['clickSound']
+ del d['frameSize']
+ return d
+
+
+@dataclass
+class OptionMenuInfo(ButtonInfo):
+ items: ... = None
+ initial_item: ... = None
+ item_color: ... = None
+ popup_color: ... = None
+ item_relief: ... = None
+ item_text_font: ... = None
+ item_text_fg: ... = None
+ highlight_color: ... = None
+ text_align: ... = None
+
+ def to_p3d_args(self):
+ d = super().to_p3d_args()
+ d['items'] = self.items
+ d['initialitem'] = self.initial_item
+ d['item_frameColor'] = d['frameColor']
+ d['popupMarker_frameColor'] = d['frameColor']
+ d['popupMarker_relief'] = d['relief']
+ d['item_relief'] = d['relief']
+ d['item_text_font'] = d['text_font']
+ d['item_text_fg'] = d['text_fg']
+ d['textMayChange'] = 1
+ h = d['frameColor']
+ h = (h[0] + .2, h[1] + .2, h[2] + .2, h[3] + .2)
+ d['highlightColor'] = self.highlight_color or h
+ d['text_align'] = TextNode.A_center
+ d['frameSize'] = (f:=d['frameSize'])[0], f[1] - .56, f[2], f[3]
+ return d
+
+
+@dataclass
+class CheckButtonInfo(ButtonInfo):
+ value: ... = None
+
+ def to_p3d_args(self):
+ d = super().to_p3d_args()
+ h = d['frameColor']
+ h = (h[0] + .2, h[1] + .2, h[2] + .2, h[3] + .2)
+ d['indicator_frameColor'] = h
+ d['indicator_relief'] = d['relief']
+ d['indicatorValue'] = self.value or 0
+ return d
+
+
+@dataclass
+class TextInfo(BaseInfo):
+ align: ... = None
+
+ def to_p3d_args(self):
+ d = super().to_p3d_args()
+ if self.align:
+ a = {'left': TextNode.A_left,
+ 'right': TextNode.A_right,
+ 'center': TextNode.A_center}[self.align]
+ d['align'] = a
+ return d
+
+
+class BasePage:
+
+ def __init__(self, page_info):
+ self.__widgets = []
+ self._page_info = page_info
+ for k in list(self._page_info.test_positions.keys()):
+ del self._page_info.test_positions[k]
+ self._build_widgets()
+
+ def _add_text(self, text_info):
+ t = OnscreenText(
+ **text_info.to_p3d_args())
+ self.__widgets += [t]
+ return t
+
+ def _add_button(self, button_args, vertically_centered=False):
+ b = DirectButton(**button_args.to_p3d_args())
+ b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
+ self._page_info.test_positions[button_args.id] = b.pos_pixel()
+ self.__widgets += [b]
+ if vertically_centered:
+ for j in range(4):
+ tnode = b.component('text%s' % j).textNode
+ height = - tnode.getLineHeight() / 2
+ height += (tnode.get_height() - tnode.get_line_height()) / 2
+ b.component('text%s' % j).set_pos(0, 0, height)
+ return b
+
+ def _add_check_button(self, button_args):
+ b = DirectCheckButton(**button_args.to_p3d_args())
+ b.__class__ = type('DirectCheckButtonMixed', (DirectCheckButton, DirectGuiMixin), {})
+ self.__widgets += [b]
+ self._page_info.test_positions[button_args.id] = b.pos_pixel()
+ return b
+
+ def _add_option_menu(self, option_args, cb):
+ o = DirectOptionMenuTestable(**option_args.to_p3d_args())
+ o.__class__ = type('DirectOptionMenuMixed', (DirectOptionMenu, DirectGuiMixin), {})
+ o._show_cb = cb
+ o.popupMenu['frameColor'] = option_args.to_p3d_args()['frameColor']
+ o.popupMenu['relief'] = option_args.to_p3d_args()['relief']
+ self.__widgets += [o]
+ self._page_info.test_positions[option_args.id] = o.pos_pixel()
+ return o
+
+ def _add_slider(self, slider_args):
+ s = DirectSlider(**slider_args.to_p3d_args())
+ s.__class__ = type('DirectSliderMixed', (DirectSlider, DirectGuiMixin), {})
+ self._page_info.test_positions['volume'] = s.pos_pixel()
+ np_left = GfxTools.build_empty_node('left_slider')
+ np_left.set_pos(s.get_net_transform().get_pos())
+ np_left.set_x(np_left.get_x() - s.get_scale()[0])
+ lpos = np_left.pos_as_widget() # try with pos2d_pixel and remove pos_as_widget if ok
+ self.__widgets += [s]
+ self._page_info.test_positions['volume_0'] = lpos
+ return s
+
+ def _set_page(self, page_name):
+ self._page_info.set_page(page_name)
+ self.destroy()
+
+ def on_back(self):
+ self._set_page('main')
+
+ def _rearrange_buttons_width(self):
+ max_width = 0
+ for w in self.__widgets:
+ t = w.component('text0')
+ ul = t.textNode.get_upper_left_3d()
+ lr = t.textNode.get_lower_right_3d()
+ max_width = max(lr[0] - ul[0], max_width)
+ for w in self.__widgets:
+ m_w = max_width / 2 + .8
+ w['frameSize'] = -m_w, m_w, w['frameSize'][2], w['frameSize'][3]
+
+ def destroy(self):
+ for w in self.__widgets: w.destroy()
+ self._page_info = None
+ self.__widgets = []
--- /dev/null
+from dataclasses import dataclass
+from panda3d.core import WindowProperties, Texture
+from direct.gui.OnscreenImage import OnscreenImage
+from ya2.utils.logics import LogicsTools
+
+
+@dataclass
+class MouseCursorInfo:
+ image_path: ...
+ functional_test: ...
+ scale: ... = 1
+ color: ... = (1, 1, 1, 1)
+ hotspot_position: ... = (0, 0)
+
+
+class MouseCursor:
+
+ def __init__(self, info):
+ self.__functional_test = info.functional_test
+ if not info.image_path: return
+ self.__hide_system_cursor()
+ self.__set_image(info.image_path, info.scale, info.color)
+ self.__set_hotspot(info.hotspot_position, info.scale)
+ self._tsk = taskMgr.add(self.__on_frame, 'on_frame_cursor')
+
+ def __hide_system_cursor(self):
+ p = WindowProperties()
+ p.set_cursor_hidden(True)
+ if LogicsTools.windowed: base.win.request_properties(p)
+
+ def __set_image(self, image_path, scale, color):
+ self.__image = OnscreenImage(image_path, scale=scale)
+ self.__image.set_color(color)
+ self.__image.set_bin('gui-popup', 50)
+ alpha_formats = [Texture.FRgba]
+ if self.__image.get_texture().get_format() in alpha_formats:
+ self.__image.set_transparency(True)
+
+ def __set_hotspot(self, hotspot_position, scale):
+ self.__hotspot_dx = scale[0] * (1 - 2 * hotspot_position[0])
+ self.__hotspot_dy = scale[2] * (1 - 2 * hotspot_position[1])
+
+ def __on_frame(self, task):
+ m = base.mouseWatcherNode
+ if not m or not m.hasMouse(): return task.again
+ mouse = m.get_mouse_x(), m.get_mouse_y()
+ h_x = mouse[0] * base.get_aspect_ratio() + self.__hotspot_dx
+ self.__image.set_pos((h_x, 1, mouse[1] - self.__hotspot_dy))
+ return task.again
+
+ def set_image(self, image):
+ self.__image.set_texture(loader.load_texture(image), 1)
+
+ def set_color(self, color):
+ self.__image.set_color(color)
+
+ def show(self):
+ if not self.__functional_test: return self.__image.show()
+
+ def hide(self):
+ return self.__image.hide()
+
+ def destroy(self):
+ taskMgr.remove(self._tsk)
+ self.__image.destroy()
--- /dev/null
+from panda3d.core import load_prc_file_data, Texture, TextProperties, TextPropertiesManager
+from ya2.utils.gfx import Point
+
+class GuiTools:
+
+ @staticmethod
+ def init():
+ GuiTools.__create_text_properties_scale()
+
+ def __create_text_properties_scale():
+ (t := TextProperties()).set_text_scale(.64)
+ TextPropertiesManager.get_global_ptr().set_properties('scale', t)
+
+ @staticmethod
+ def no_window():
+ load_prc_file_data('', 'window-type none')
+
+ @staticmethod
+ def get_mouse():
+ return Point(base.mouseWatcherNode.get_mouse())
+
+ @staticmethod
+ 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
--- /dev/null
+from dataclasses import dataclass
+from ya2.utils.gui.cursor import MouseCursor
+
+
+@dataclass
+class PageInfo:
+ set_page: ...
+ test_positions: ...
+ running_functional_tests: ...
+
+
+class BaseMenu:
+
+ def __init__(self, cursor_info, running_functional_tests, test_positions):
+ self._page_info = PageInfo(self.set_page, test_positions, running_functional_tests)
+ self._page = None
+ self._cursor = MouseCursor(cursor_info)
+ self.set_page('main')
+
+ def set_page(self, page_name):
+ if self._page: self._page.destroy()
+ p = self._set_page(page_name)
+ self._page = p
+
+ def destroy(self):
+ if self._page: self._page = self._page.destroy()
+ self._cursor.destroy()