From 3466af49a058f388ae8621c37d646ca344ca517d Mon Sep 17 00:00:00 2001 From: Flavio Calva Date: Sat, 21 Jan 2023 09:12:40 +0200 Subject: [PATCH] functional tests: editor --- assets/scenes/domino.json | 25 ++-- pmachines/application/application.py | 2 +- pmachines/editor/augmented_frame.py | 5 + pmachines/editor/inspector.py | 96 ++++++++---- pmachines/editor/scene.py | 45 ++++-- pmachines/editor/scene_list.py | 9 +- pmachines/editor/start_items.py | 104 ++++++++++--- pmachines/items/basketball.py | 2 +- pmachines/items/box.py | 4 +- pmachines/items/domino.py | 4 +- pmachines/items/shelf.py | 4 +- pmachines/items/teetertooter.py | 4 +- pmachines/items/test_item.py | 6 +- pmachines/scene/scene.py | 25 +++- prj.org | 2 - tests/functional_test.py | 215 ++++++++++++++++++++++++++- tests/test_functional.py | 12 +- ya2/utils/functional.py | 1 - 18 files changed, 457 insertions(+), 108 deletions(-) diff --git a/assets/scenes/domino.json b/assets/scenes/domino.json index 8f02ef4..461ab21 100644 --- a/assets/scenes/domino.json +++ b/assets/scenes/domino.json @@ -1,31 +1,32 @@ { + "instructions": "Goal: every domino piece must fall\n\nkeep \\5mouse_l\\5 pressed to drag an item\n\nkeep \\5mouse_r\\5 pressed to rotate an item", "items": [ { "class": "Shelf", + "mass": 0, "position": [ -1.2, 0, -0.6 - ], - "mass": 0 + ] }, { "class": "Shelf", + "mass": 0, "position": [ 1.2, 0, -0.6 - ], - "mass": 0 + ] }, { "class": "Domino", + "mass": 1, "position": [ -1.14, 0, -0.04 ], - "mass": 1, "strategy": "DownStrategy", "strategy_args": [ 60 @@ -33,12 +34,13 @@ }, { "class": "Domino", + "id": "test_piece", + "mass": 1, "position": [ -0.49, 0, -0.04 ], - "mass": 1, "strategy": "DownStrategy", "strategy_args": [ 60 @@ -46,12 +48,12 @@ }, { "class": "Domino", + "mass": 1, "position": [ 0.94, 0, -0.04 ], - "mass": 1, "strategy": "DownStrategy", "strategy_args": [ 60 @@ -59,12 +61,12 @@ }, { "class": "Domino", + "mass": 1, "position": [ 1.55, 0, -0.04 ], - "mass": 1, "strategy": "DownStrategy", "strategy_args": [ 60 @@ -72,19 +74,18 @@ }, { "class": "Domino", + "mass": 1, "position": [ 2.09, 0, -0.04 ], - "mass": 1, "strategy": "DownStrategy", "strategy_args": [ 88 ] } ], - "instructions": "Goal: every domino piece must fall\n\nkeep \\5mouse_l\\5 pressed to drag an item\n\nkeep \\5mouse_r\\5 pressed to rotate an item", "name": "Domino", "start_items": [ { @@ -140,5 +141,5 @@ } ] }, - "version": "fabe277a7cbd" -} + "version": "51b44031bbce" +} \ No newline at end of file diff --git a/pmachines/application/application.py b/pmachines/application/application.py index b60e5f6..0eb9593 100755 --- a/pmachines/application/application.py +++ b/pmachines/application/application.py @@ -198,7 +198,7 @@ class Pmachines: info('creating dirs: %s' % data_path) makedirs(data_path, exist_ok=True) optfile = args.optfile if args.optfile else 'options.ini' - info('data path: %s' % data_path) + info(f'{data_path=}') info('option file: %s' % optfile) info('fixed path: %s' % LogicsTools.platform_specific_path(data_path + '/' + optfile)) default_opt = { diff --git a/pmachines/editor/augmented_frame.py b/pmachines/editor/augmented_frame.py index 0b5aef8..4480c01 100644 --- a/pmachines/editor/augmented_frame.py +++ b/pmachines/editor/augmented_frame.py @@ -12,6 +12,10 @@ class AugmentedDirectFrame(DirectFrame): del kwargs['delta_drag'] collapse_pos = kwargs['collapse_pos'] del kwargs['collapse_pos'] + pos_mgr = kwargs['pos_mgr'] + del kwargs['pos_mgr'] + frame_name = kwargs['frame_name'] + del kwargs['frame_name'] super().__init__(*args, **kwargs) self.initialiseoptions(AugmentedDirectFrame) self['state'] = NORMAL @@ -28,6 +32,7 @@ class AugmentedDirectFrame(DirectFrame): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) self.__collapse_btn.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr[f'collapse_button_{frame_name}'] = self.__collapse_btn.pos_pixel() _font = base.loader.load_font( 'assets/fonts/Hanken-Book.ttf') _font.clear() diff --git a/pmachines/editor/inspector.py b/pmachines/editor/inspector.py index 9397424..d08961a 100644 --- a/pmachines/editor/inspector.py +++ b/pmachines/editor/inspector.py @@ -1,5 +1,6 @@ from collections import namedtuple from glob import glob +from logging import info from importlib import import_module from os.path import basename from inspect import isclass @@ -13,15 +14,17 @@ from pmachines.items.item import ItemStrategy, FixedStrategy, StillStrategy 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.gfx import DirectGuiMixin class Inspector(DirectObject): - def __init__(self, item, all_items): + def __init__(self, item, all_items, pos_mgr): super().__init__() self.__item = item self.__all_items = all_items + self.__pos_mgr = pos_mgr self._font = base.loader.load_font( 'assets/fonts/Hanken-Book.ttf') self._font.clear() @@ -45,7 +48,9 @@ class Inspector(DirectObject): parent=base.a2dTopRight, pos=(-w, 0, 0), delta_drag=LPoint3f(-w, 0, -h), - collapse_pos=(w - .06, 1, -h + .06)) + collapse_pos=(w - .06, 1, -h + .06), + pos_mgr=pos_mgr, + frame_name='inspector') self.__z = -.08 p = self.__item._np.get_pos() r = self.__item._np.get_r() @@ -62,15 +67,38 @@ class Inspector(DirectObject): _strategy_args = '' if 'strategy_args' in self.__item.json: _strategy_args = ' '.join(map(str, self.__item.json['strategy_args'])) - t, pos_entry = self.__add_row(_('position'), f'{round(p.x, 3)}, {round(p.z, 3)}', self.on_edit_position, _('position (e.g. 0.1 2.3 4.5)')) - t, rot_entry = self.__add_row(_('roll'), f'{round(r, 3)}', self.on_edit_roll, _('roll (e.g. 90)')) - t, scale_entry = self.__add_row(_('scale'), f'{round(s, 3)}', self.on_edit_scale, _('scale (e.g. 1.2)')) - t, mass_entry = self.__add_row(_('mass'), f'{round(m, 3)}', self.on_edit_mass, _('mass (default 1; 0 if fixed)')) - t, restitution_entry = self.__add_row(_('restitution'), f'{round(restitution, 3)}', self.on_edit_restitution, _('restitution (default 0.5)')) - t, friction_entry = self.__add_row(_('friction'), f'{round(f, 3)}', self.on_edit_friction, _('friction (default 0.5)')) - t, id_entry = self.__add_row(_('id'), _id, self.on_edit_id, _('id of the item (for the strategies)')) - t, strategy_entry = self.__add_row_option(_('strategy'), _strategy, self.on_edit_strategy, _('the strategy of the item')) - t, strategy_args_entry = self.__add_row(_('strategy_args'), _strategy_args, self.on_edit_strategy_args, _('the arguments of the strategy')) + t, pos_entry = self.__add_row('position', _('position'), f'{round(p.x, 3)} {round(p.z, 3)}', self.on_edit_position, _('position (e.g. 0.1 2.3 4.5)')) + t, rot_entry = self.__add_row('roll', _('roll'), f'{round(r, 3)}', self.on_edit_roll, _('roll (e.g. 90)')) + t, scale_entry = self.__add_row('scale', _('scale'), f'{round(s, 3)}', self.on_edit_scale, _('scale (e.g. 1.2)')) + t, mass_entry = self.__add_row('mass', _('mass'), f'{round(m, 3)}', self.on_edit_mass, _('mass (default 1; 0 if fixed)')) + t, restitution_entry = self.__add_row('restitution', _('restitution'), f'{round(restitution, 3)}', self.on_edit_restitution, _('restitution (default 0.5)')) + t, friction_entry = self.__add_row('friction', _('friction'), f'{round(f, 3)}', self.on_edit_friction, _('friction (default 0.5)')) + t, id_entry = self.__add_row('id', _('id'), _id, self.on_edit_id, _('id of the item (for the strategies)')) + item_modules = glob('pmachines/items/*.py') + item_modules = [basename(i)[:-3] for i in item_modules] + strategy_items = [''] + for item_module in item_modules: + mod_name = 'pmachines.items.' + item_module + for member in import_module(mod_name).__dict__.values(): + if isclass(member) and issubclass(member, ItemStrategy) and \ + member != ItemStrategy: + strategy_items = list(set(strategy_items + [member.__name__])) + t, strategy_entry = self.__add_row_option(_('strategy'), _strategy, strategy_items, self.on_edit_strategy, _('the strategy of the item')) + + def strategy_set(comps): + strategy_labels = [f'inspector_strategy_{i.lower()}' for i in strategy_items] + for i in strategy_labels: + if i in self.__pos_mgr: + del self.__pos_mgr[i] + for l, b in zip(strategy_labels, comps): + b.__class__ = type('DirectFrameMixed', (DirectFrame, DirectGuiMixin), {}) + p = b.pos_pixel() + self.__pos_mgr[l] = (p[0] + 5, p[1]) + strategy_entry._show_cb = strategy_set + p = strategy_entry.pos_pixel() + self.__pos_mgr['editor_inspector_strategy'] = (p[0] + 5, p[1]) + + t, strategy_args_entry = self.__add_row('strategy_args', _('strategy_args'), _strategy_args, self.on_edit_strategy_args, _('the arguments of the strategy')) fields = ['position', 'roll', 'scale', 'mass', 'restitution', 'friction', 'id', 'strategy', 'strategy_args'] Entries = namedtuple('Entries', fields) self.__entries = Entries(pos_entry, rot_entry, scale_entry, mass_entry, restitution_entry, friction_entry, id_entry, strategy_entry, strategy_args_entry) @@ -96,6 +124,7 @@ class Inspector(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr['editor_inspector_close'] = b.pos_pixel() b.set_tooltip(_('Close'), *tooltip_args) self.accept('item-rototranslated', self.__on_item_rototranslated) b = DirectButton( @@ -106,9 +135,10 @@ class Inspector(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr['editor_inspector_delete'] = b.pos_pixel() b.set_tooltip(_('Delete the item'), *tooltip_args) - def __add_row(self, label, text, callback, tooltip): + def __add_row(self, id_, label, text, callback, tooltip): tw = 10 tooltip_args = self._common['text_font'], self._common['scale'], self._common['text_fg'] t = OnscreenText( @@ -131,20 +161,12 @@ class Inspector(DirectObject): command=callback) e.__class__ = type('DirectEntryMixed', (DirectEntry, DirectGuiMixin), {}) e.set_tooltip(tooltip, *tooltip_args) + self.__pos_mgr[f'editor_inspector_{id_}'] = e.pos_pixel() self.__z -= .1 return t, e - def __add_row_option(self, label, text, callback, tooltip): + def __add_row_option(self, label, text, items, callback, tooltip): tooltip_args = self._common['text_font'], self._common['scale'], self._common['text_fg'] - item_modules = glob('pmachines/items/*.py') - item_modules = [basename(i)[:-3] for i in item_modules] - new_items = [''] - for item_module in item_modules: - mod_name = 'pmachines.items.' + item_module - for member in import_module(mod_name).__dict__.values(): - if isclass(member) and issubclass(member, ItemStrategy) and \ - member != ItemStrategy: - new_items = list(set(new_items + [member.__name__])) t = OnscreenText( label, pos=(.03, self.__z), parent=self._frm, @@ -152,11 +174,11 @@ class Inspector(DirectObject): scale=self._common['scale'], fg=self._common['text_fg'], wordwrap=20, align=TextNode.ALeft) - e = DirectOptionMenu( + e = DirectOptionMenuTestable( scale=self._common['scale'], initialitem=text, pos=(.30, 1, self.__z), - items=new_items, + items=items, parent=self._frm, command=callback, state=NORMAL, @@ -290,6 +312,7 @@ class Inspector(DirectObject): class PixelSpaceInspector(DirectObject): def __init__(self, item, all_items): + info('PixelSpaceInspector') super().__init__() self.__item = item self.__all_items = all_items @@ -417,10 +440,12 @@ class PixelSpaceInspector(DirectObject): class WorldSpaceInspector(DirectObject): - def __init__(self, item, all_items): + def __init__(self, item, all_items, pos_mgr): + info('WorldSpaceInspector') super().__init__() self.__item = item self.__all_items = all_items + self.__pos_mgr = pos_mgr self._font = base.loader.load_font( 'assets/fonts/Hanken-Book.ttf') self._font.clear() @@ -437,6 +462,7 @@ class WorldSpaceInspector(DirectObject): 'assets/audio/sfx/rollover.ogg'), 'clickSound': loader.load_sfx( 'assets/audio/sfx/click.ogg')} + tooltip_args = self._common['text_font'], self._common['scale'], self._common['text_fg'] w, h = .8, .36 self._frm = DirectFrame(frameColor=(.4, .4, .4, .06), frameSize=(0, w, -h, 0), @@ -447,8 +473,8 @@ class WorldSpaceInspector(DirectObject): _id = '' if 'id' in self.__item.json: _id = self.__item.json['id'] - t, pos_entry = self.__add_row(_('position'), f'{round(p.x, 3)}, {round(p.z, 3)}', self.on_edit_position) - t, id_entry = self.__add_row(_('id'), _id, self.on_edit_id) + t, pos_entry = self.__add_row('position', _('position'), f'{round(p.x, 3)} {round(p.z, 3)}', self.on_edit_position, _('position (e.g. 0.1 2.3 4.5)')) + t, id_entry = self.__add_row('id', _('id'), _id, self.on_edit_id, _('id of the item (for the strategies)')) fields = ['position', 'id'] Entries = namedtuple('Entries', fields) self.__entries = Entries(pos_entry, id_entry) @@ -466,24 +492,31 @@ class WorldSpaceInspector(DirectObject): (.4, .1, .1, .4)]}[col] return [self.__load_img_btn(path, col) for col in colors] fcols = (.4, .4, .4, .14), (.3, .3, .3, .05) - DirectButton( + b = DirectButton( image=load_images_btn('exitRight', 'gray'), scale=.05, pos=(.06, 1, -h + .06), parent=self._frm, command=self.destroy, state=NORMAL, relief=FLAT, frameColor=fcols[0], rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) + b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr['editor_inspector_test_close'] = b.pos_pixel() + b.set_tooltip(_('Close'), *tooltip_args) self.accept('item-rototranslated', self.__on_item_rototranslated) - DirectButton( + b = DirectButton( image=load_images_btn('trashcan', 'gray'), scale=.05, pos=(.18, 1, -h + .06), parent=self._frm, command=self.__delete_item, state=NORMAL, relief=FLAT, frameColor=fcols[0], rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) + b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr['editor_inspector_test_delete'] = b.pos_pixel() + b.set_tooltip(_('Delete the item'), *tooltip_args) - def __add_row(self, label, text, callback): + def __add_row(self, id_, label, text, callback, tooltip): tw = 10 + tooltip_args = self._common['text_font'], self._common['scale'], self._common['text_fg'] t = OnscreenText( label, pos=(.03, self.__z), parent=self._frm, @@ -502,6 +535,9 @@ class WorldSpaceInspector(DirectObject): parent=self._frm, text_fg=self._common['text_fg'], command=callback) + e.__class__ = type('DirectEntryMixed', (DirectEntry, DirectGuiMixin), {}) + e.set_tooltip(tooltip, *tooltip_args) + self.__pos_mgr[f'editor_inspector_test_{id_}'] = e.pos_pixel() self.__z -= .1 return t, e diff --git a/pmachines/editor/scene.py b/pmachines/editor/scene.py index 2492365..942ab53 100644 --- a/pmachines/editor/scene.py +++ b/pmachines/editor/scene.py @@ -9,7 +9,7 @@ import hashlib from panda3d.core import Texture, TextNode, LPoint3f from direct.gui.OnscreenImage import OnscreenImage from direct.gui.DirectGui import DirectButton, DirectEntry, \ - YesNoDialog, DirectOptionMenu + YesNoDialog, DirectOptionMenu, DirectFrame from direct.gui.DirectGuiGlobals import FLAT, NORMAL from direct.gui.OnscreenText import OnscreenText from direct.showbase.DirectObject import DirectObject @@ -20,16 +20,19 @@ from pmachines.editor.inspector import Inspector, PixelSpaceInspector, WorldSpac 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 class SceneEditor(DirectObject): - def __init__(self, json, json_name, context, add_item, items, world, mouse_plane_node): + def __init__(self, json, json_name, context, add_item, items, world, mouse_plane_node, pos_mgr): super().__init__() self.__items = items self.__json = json self.__world = world self.__mouse_plane_node = mouse_plane_node + self.__pos_mgr = pos_mgr + self.__dialog = None if not json_name: self.__json = json = { 'name': '', @@ -66,7 +69,9 @@ class SceneEditor(DirectObject): parent=base.a2dBottomCenter, pos=(-w/2, 0, 0), delta_drag=LPoint3f(0, 0, h), - collapse_pos=(.06, 1, .94)) + collapse_pos=(.06, 1, .94), + pos_mgr=self.__pos_mgr, + frame_name='scene') OnscreenText( _('Filename'), pos=(l - .03, h - .1), parent=self._frm, font=self._common['text_font'], @@ -84,6 +89,7 @@ class SceneEditor(DirectObject): text_fg=self._common['text_fg']) 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() OnscreenText( _('Name'), pos=(l - .03, h - .2), parent=self._frm, font=self._common['text_font'], @@ -101,6 +107,7 @@ class SceneEditor(DirectObject): text_fg=self._common['text_fg']) self.__name_entry.__class__ = type('DirectEntryMixed', (DirectEntry, DirectGuiMixin), {}) self.__name_entry.set_tooltip(_('The title of the scene'), *tooltip_args) + self.__pos_mgr['editor_scene_name'] = self.__name_entry.pos_pixel() OnscreenText( _('Description'), pos=(l - .03, h - .3), parent=self._frm, font=self._common['text_font'], @@ -127,6 +134,7 @@ class SceneEditor(DirectObject): self.__instructions_entry['extraArgs'] = [self.__instructions_entry] self.__instructions_entry.__class__ = type('DirectEntryMixed', (DirectEntry, DirectGuiMixin), {}) self.__instructions_entry.set_tooltip(_('The description of the scene'), *tooltip_args) + self.__pos_mgr['editor_scene_instructions'] = self.__instructions_entry.pos_pixel() def load_images_btn(path, col): colors = { 'gray': [ @@ -149,6 +157,7 @@ class SceneEditor(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + self.__pos_mgr['editor_close'] = b.pos_pixel() b.set_tooltip(_('Close the scene editor'), *tooltip_args) b = DirectButton( image=load_images_btn('save', 'gray'), scale=.05, @@ -158,6 +167,7 @@ class SceneEditor(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + self.__pos_mgr['editor_save'] = b.pos_pixel() b.set_tooltip(_('Save the scene'), *tooltip_args) b = DirectButton( image=load_images_btn('menuList', 'gray'), scale=.05, @@ -167,6 +177,7 @@ class SceneEditor(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + self.__pos_mgr['editor_sorting'] = b.pos_pixel() b.set_tooltip(_('Set the sorting of the scenes'), *tooltip_args) item_modules = glob('pmachines/items/*.py') item_modules = [basename(i)[:-3] for i in item_modules] @@ -183,9 +194,19 @@ class SceneEditor(DirectObject): scale=self._common['scale'], fg=self._common['text_fg'], align=TextNode.A_left) - b = DirectOptionMenu( + items = list(self.__new_items.keys()) + def new_item_test_set(comps): + item_labels = [f'new_item_{i.lower()}' for i in items] + for i in item_labels: + if i in self.__pos_mgr: + del self.__pos_mgr[i] + for l, b in zip(item_labels, comps): + b.__class__ = type('DirectFrameMixed', (DirectFrame, DirectGuiMixin), {}) + p = b.pos_pixel() + self.__pos_mgr[l] = (p[0] + 5, p[1]) + b = DirectOptionMenuTestable( scale=.05, - text=_('new item'), pos=(.02, 1, .4), items=list(self.__new_items.keys()), + text=_('new item'), pos=(.02, 1, .4), items=items, parent=self._frm, command=self.__on_new_item, state=NORMAL, relief=FLAT, item_relief=FLAT, frameColor=fcols[0], item_frameColor=fcols[0], @@ -197,7 +218,9 @@ class SceneEditor(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectOptionMenuMixed', (DirectOptionMenu, DirectGuiMixin), {}) + b._show_cb = new_item_test_set b.set_tooltip(_('Create a new item'), *tooltip_args) + self.__pos_mgr['editor_new_item'] = b.pos_pixel() b = DirectButton( image=load_images_btn('start_items', 'gray'), scale=.05, pos=(.06, 1, .58), @@ -206,6 +229,7 @@ class SceneEditor(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + self.__pos_mgr['editor_start'] = b.pos_pixel() b.set_tooltip(_('Initial items'), *tooltip_args) b = DirectButton( image=load_images_btn('plus', 'gray'), scale=.05, @@ -215,6 +239,7 @@ class SceneEditor(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + self.__pos_mgr['editor_new'] = b.pos_pixel() b.set_tooltip(_('New scene'), *tooltip_args) self.__test_items = [] self.__set_test_items() @@ -307,6 +332,7 @@ class SceneEditor(DirectObject): self.destroy() messenger.send('editor-stop') self.__dialog.cleanup() + self.__dialog = None def __on_save(self): self.__json['name'] = self.__name_entry.get() @@ -317,7 +343,7 @@ class SceneEditor(DirectObject): f.write(dumps(self.__json, indent=2, sort_keys=True)) def __on_scene_list(self): - self.__scene_list = SceneList() + self.__scene_list = SceneList(self.__pos_mgr) def __load_img_btn(self, path, col): img = OnscreenImage('assets/images/buttons/%s.dds' % path) @@ -341,9 +367,9 @@ class SceneEditor(DirectObject): if item.__class__ == PixelSpaceTestItem: self.__inspector = PixelSpaceInspector(item, self.__items) elif item.__class__ == WorldSpaceTestItem: - self.__inspector = WorldSpaceInspector(item, self.__items) + self.__inspector = WorldSpaceInspector(item, self.__items, self.__pos_mgr) else: - self.__inspector = Inspector(item, self.__items) + self.__inspector = Inspector(item, self.__items, self.__pos_mgr) def __on_inspector_destroy(self): self.__inspector = None @@ -354,7 +380,7 @@ class SceneEditor(DirectObject): messenger.send('new_scene') def __on_start_items(self): - self.__start_items = StartItems(self.__json['start_items']) + self.__start_items = StartItems(self.__json['start_items'], self.__pos_mgr) def __on_start_items_save(self, start_items): self.__json['start_items'] = start_items @@ -375,6 +401,7 @@ class SceneEditor(DirectObject): self.ignore('editor-inspector-destroy') self.ignore('editor-start-items-save') self.ignore('editor-start-items-destroy') + if self.__dialog: self.__actually_close(False) for t in self.__test_items: t.destroy() self.__test_items = [] if self.__start_items: diff --git a/pmachines/editor/scene_list.py b/pmachines/editor/scene_list.py index b94aa21..26f95af 100644 --- a/pmachines/editor/scene_list.py +++ b/pmachines/editor/scene_list.py @@ -10,7 +10,7 @@ from ya2.utils.gfx import DirectGuiMixin class SceneList: - def __init__(self): + def __init__(self, pos_mgr): self._font = base.loader.load_font( 'assets/fonts/Hanken-Book.ttf') self._font.clear() @@ -34,7 +34,9 @@ class SceneList: parent=base.a2dTopCenter, pos=(-w/2, 0, 0), delta_drag=LPoint3f(0, 0, -h), - collapse_pos=(w - .06, 1, -h + .06)) + collapse_pos=(w - .06, 1, -h + .06), + pos_mgr=pos_mgr, + frame_name='sorting') OnscreenText( _('Write the file names (without the extension), one file for each line'), pos=(w/2, -.08), parent=self._frm, @@ -64,6 +66,7 @@ class SceneList: self.__list_entry['extraArgs'] = [self.__list_entry] self.__list_entry.__class__ = type('DirectEntryMixed', (DirectEntry, DirectGuiMixin), {}) self.__list_entry.set_tooltip(_('the list of the scenes in the proper order'), *tooltip_args) + pos_mgr['editor_sorting_text'] = self.__list_entry.pos_pixel() def load_images_btn(path, col): colors = { 'gray': [ @@ -86,6 +89,7 @@ class SceneList: rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr['editor_sorting_close'] = b.pos_pixel() b.set_tooltip(_('Close the scene editor'), *tooltip_args) b = DirectButton( image=load_images_btn('save', 'gray'), scale=.05, @@ -95,6 +99,7 @@ class SceneList: rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr['editor_sorting_save'] = b.pos_pixel() b.set_tooltip(_('Save the scene list'), *tooltip_args) def __on_close(self): diff --git a/pmachines/editor/start_items.py b/pmachines/editor/start_items.py index 9cc9801..a214410 100644 --- a/pmachines/editor/start_items.py +++ b/pmachines/editor/start_items.py @@ -5,21 +5,23 @@ from os.path import basename from inspect import isclass from panda3d.core import Texture, TextNode, LPoint3f from direct.gui.OnscreenImage import OnscreenImage -from direct.gui.DirectGui import DirectButton, DirectEntry, DirectOptionMenu, OkDialog +from direct.gui.DirectGui import DirectButton, DirectEntry, DirectOptionMenu, OkDialog, DirectFrame from direct.gui.DirectGuiGlobals import FLAT, NORMAL from direct.gui.OnscreenText import OnscreenText from direct.showbase.DirectObject import DirectObject 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 class StartItems(DirectObject): - def __init__(self, json_items): + def __init__(self, json_items, pos_mgr): super().__init__() self.__items = json_items self.__json = self.__items[0] + self.__pos_mgr = pos_mgr self._font = base.loader.load_font( 'assets/fonts/Hanken-Book.ttf') self._font.clear() @@ -43,7 +45,9 @@ class StartItems(DirectObject): parent=base.a2dTopLeft, pos=(0, 0, 0), delta_drag=LPoint3f(w, 0, -h), - collapse_pos=(w - .06, 1, -h + .06)) + collapse_pos=(w - .06, 1, -h + .06), + pos_mgr=pos_mgr, + frame_name='start') self.__z = -.08 item_modules = glob('pmachines/items/*.py') item_modules = [basename(i)[:-3] for i in item_modules] @@ -55,21 +59,48 @@ class StartItems(DirectObject): member != Item: new_items = list(set(new_items + [member.__name__])) t, item_class_entry = self.__add_row_option(_('class'), '', new_items, self.on_edit_class, _('class of the item')) - t, count_entry = self.__add_row(_('count'), '', self.on_edit_count, _('number of the items')) - t, scale_entry = self.__add_row(_('scale'), '', self.on_edit_scale, _('scale (e.g. 1.2)')) - t, mass_entry = self.__add_row(_('mass'), '', self.on_edit_mass, _('mass (default 1)')) - t, restitution_entry = self.__add_row(_('restitution'), '', self.on_edit_restitution, _('restitution (default 0.5)')) - t, friction_entry = self.__add_row(_('friction'), '', self.on_edit_friction, _('friction (default 0.5)')) - t, id_entry = self.__add_row(_('id'), '', self.on_edit_id, _('id')) - new_items = [''] + + def item_class_set(comps): + class_labels = [f'start_class_{i.lower()}' for i in new_items] + for i in class_labels: + if i in self.__pos_mgr: + del self.__pos_mgr[i] + for l, b in zip(class_labels, comps): + b.__class__ = type('DirectFrameMixed', (DirectFrame, DirectGuiMixin), {}) + p = b.pos_pixel() + self.__pos_mgr[l] = (p[0] + 5, p[1]) + item_class_entry._show_cb = item_class_set + self.__pos_mgr['editor_start_class'] = item_class_entry.pos_pixel() + + t, count_entry = self.__add_row('count', _('count'), '', self.on_edit_count, _('number of the items')) + t, scale_entry = self.__add_row('scale', _('scale'), '', self.on_edit_scale, _('scale (e.g. 1.2)')) + t, mass_entry = self.__add_row('mass', _('mass'), '', self.on_edit_mass, _('mass (default 1)')) + t, restitution_entry = self.__add_row('restitution', _('restitution'), '', self.on_edit_restitution, _('restitution (default 0.5)')) + t, friction_entry = self.__add_row('friction', _('friction'), '', self.on_edit_friction, _('friction (default 0.5)')) + t, id_entry = self.__add_row('id', _('id'), '', self.on_edit_id, _('id')) + strategy_items = [''] for item_module in item_modules: mod_name = 'pmachines.items.' + item_module for member in import_module(mod_name).__dict__.values(): if isclass(member) and issubclass(member, ItemStrategy) and \ member != ItemStrategy: - new_items = list(set(new_items + [member.__name__])) - t, strategy_entry = self.__add_row_option(_('strategy'), '', new_items, self.on_edit_strategy, _('the strategy of the item')) - t, strategy_args_entry = self.__add_row(_('strategy_args'), '', self.on_edit_strategy_args, _('the arguments of the strategy')) + strategy_items = list(set(strategy_items + [member.__name__])) + t, strategy_entry = self.__add_row_option(_('strategy'), '', strategy_items, self.on_edit_strategy, _('the strategy of the item')) + + def strategy_set(comps): + strategy_labels = [f'start_strategy_{i.lower()}' for i in strategy_items] + for i in strategy_labels: + if i in self.__pos_mgr: + del self.__pos_mgr[i] + for l, b in zip(strategy_labels, comps): + b.__class__ = type('DirectFrameMixed', (DirectFrame, DirectGuiMixin), {}) + p = b.pos_pixel() + self.__pos_mgr[l] = (p[0] + 5, p[1]) + strategy_entry._show_cb = strategy_set + p = strategy_entry.pos_pixel() + self.__pos_mgr['editor_start_strategy'] = (p[0] + 5, p[1]) + + t, strategy_args_entry = self.__add_row('strategy_args', _('strategy_args'), '', self.on_edit_strategy_args, _('the arguments of the strategy')) fields = ['scale', 'mass', 'restitution', 'friction', 'id', 'strategy', 'strategy_args', 'item_class', 'count'] Entries = namedtuple('Entries', fields) self.__entries = Entries(scale_entry, mass_entry, restitution_entry, friction_entry, id_entry, strategy_entry, strategy_args_entry, item_class_entry, count_entry) @@ -96,6 +127,7 @@ class StartItems(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr['editor_start_close'] = b.pos_pixel() b.set_tooltip(_('Close'), *tooltip_args) b = DirectButton( image=load_images_btn('save', 'gray'), scale=.05, @@ -105,6 +137,7 @@ class StartItems(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr['editor_start_save'] = b.pos_pixel() b.set_tooltip(_('Save'), *tooltip_args) b = DirectButton( image=load_images_btn('trashcan', 'gray'), scale=.05, @@ -114,6 +147,7 @@ class StartItems(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr['editor_start_delete'] = b.pos_pixel() b.set_tooltip(_('Delete'), *tooltip_args) b = DirectButton( image=load_images_btn('plus', 'gray'), scale=.05, @@ -123,6 +157,7 @@ class StartItems(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr['editor_start_new'] = b.pos_pixel() b.set_tooltip(_('New'), *tooltip_args) b = DirectButton( image=load_images_btn('right', 'gray'), scale=.05, @@ -132,9 +167,10 @@ class StartItems(DirectObject): rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'), clickSound=loader.load_sfx('assets/audio/sfx/click.ogg')) b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {}) + pos_mgr['editor_start_next'] = b.pos_pixel() b.set_tooltip(_('Next'), *tooltip_args) - def __add_row(self, label, text, callback, tooltip): + def __add_row(self, id_, label, text, callback, tooltip): tw = 10 tooltip_args = self._common['text_font'], self._common['scale'], self._common['text_fg'] t = OnscreenText( @@ -157,13 +193,12 @@ class StartItems(DirectObject): command=callback) e.__class__ = type('DirectEntryMixed', (DirectEntry, DirectGuiMixin), {}) e.set_tooltip(tooltip, *tooltip_args) + self.__pos_mgr[f'editor_start_{id_}'] = e.pos_pixel() self.__z -= .1 return t, e def __add_row_option(self, label, text, items, callback, tooltip): tooltip_args = self._common['text_font'], self._common['scale'], self._common['text_fg'] - item_modules = glob('pmachines/items/*.py') - item_modules = [basename(i)[:-3] for i in item_modules] t = OnscreenText( label, pos=(.03, self.__z), parent=self._frm, @@ -171,7 +206,7 @@ class StartItems(DirectObject): scale=self._common['scale'], fg=self._common['text_fg'], wordwrap=20, align=TextNode.ALeft) - e = DirectOptionMenu( + e = DirectOptionMenuTestable( scale=self._common['scale'], initialitem=text, pos=(.30, 1, self.__z), @@ -205,19 +240,34 @@ class StartItems(DirectObject): return img def on_edit_scale(self, txt): - self.__json['model_scale'] = float(txt) + if txt: + self.__json['model_scale'] = float(txt) + else: + del self.__json['model_scale'] def on_edit_mass(self, txt): - self.__json['mass'] = float(txt) + if txt: + self.__json['mass'] = float(txt) + else: + del self.__json['mass'] def on_edit_restitution(self, txt): - self.__json['restitution'] = float(txt) + if txt: + self.__json['restitution'] = float(txt) + else: + del self.__json['restitution'] def on_edit_friction(self, txt): - self.__json['friction'] = float(txt) + if txt: + self.__json['friction'] = float(txt) + else: + del self.__json['friction'] def on_edit_id(self, txt): - self.__json['id'] = txt + if txt: + self.__json['id'] = txt + else: + del self.__json['id'] def on_edit_strategy(self, txt): if txt: @@ -225,14 +275,20 @@ class StartItems(DirectObject): self.__entries.strategy_args.set('') def on_edit_strategy_args(self, txt): - self.__json['strategy_args'] = txt + if txt: + self.__json['strategy_args'] = txt + else: + del self.__json['strategy_args'] def on_edit_class(self, txt): if txt: self.__json['class'] = txt def on_edit_count(self, txt): - self.__json['count'] = int(txt) + if txt: + self.__json['count'] = int(txt) + else: + del self.__json['count'] def __new_item(self): curr_index = self.__items.index(self.__json) diff --git a/pmachines/items/basketball.py b/pmachines/items/basketball.py index 55e8235..adba49e 100644 --- a/pmachines/items/basketball.py +++ b/pmachines/items/basketball.py @@ -4,7 +4,7 @@ from pmachines.items.item import Item class Basketball(Item): - def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.92, friction=.6): + def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, model_scale=1, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.92, friction=.6): super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/basketball/basketball.bam', json, .4, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction) def _set_shape(self, apply_scale=True): diff --git a/pmachines/items/box.py b/pmachines/items/box.py index e33c2dc..28217f0 100644 --- a/pmachines/items/box.py +++ b/pmachines/items/box.py @@ -4,8 +4,8 @@ from pmachines.items.item import Item, ItemStrategy class Box(Item): - def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.8, model_scale=1): - super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/box/box.bam', json, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=model_scale) + def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, model_scale=1, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.8): + super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/box/box.bam', json, model_scale=model_scale, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction) def _set_shape(self, apply_scale=True): self.node.add_shape(BulletBoxShape((.5, .5, .5))) diff --git a/pmachines/items/domino.py b/pmachines/items/domino.py index fb12d5e..1f862d8 100644 --- a/pmachines/items/domino.py +++ b/pmachines/items/domino.py @@ -4,8 +4,8 @@ from pmachines.items.item import Item, StillStrategy class Domino(Item): - def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6): - super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/domino/domino.bam', json, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction) + def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, model_scale=1, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6): + super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/domino/domino.bam', json, model_scale=model_scale, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction) def _set_shape(self, apply_scale=True): self.node.add_shape(BulletBoxShape((.1, .25, .5))) diff --git a/pmachines/items/shelf.py b/pmachines/items/shelf.py index 855fd7b..905e127 100644 --- a/pmachines/items/shelf.py +++ b/pmachines/items/shelf.py @@ -4,8 +4,8 @@ from pmachines.items.item import Item class Shelf(Item): - def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6): - super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/shelf/shelf.bam', json, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction) + def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, model_scale=1, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6): + super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/shelf/shelf.bam', json, model_scale=model_scale, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction) def _set_shape(self, apply_scale=True): self.node.add_shape(BulletBoxShape((1, .5, .05))) diff --git a/pmachines/items/teetertooter.py b/pmachines/items/teetertooter.py index 968b1c9..a653c50 100644 --- a/pmachines/items/teetertooter.py +++ b/pmachines/items/teetertooter.py @@ -5,8 +5,8 @@ from pmachines.items.item import Item class TeeterTooter(Item): - def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5): - super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/teeter_tooter/teeter_tooter.bam', json, exp_num_contacts=2, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=.5) + def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, model_scale=1, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5): + super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/teeter_tooter/teeter_tooter.bam', json, model_scale=.5, exp_num_contacts=2, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction) def _set_shape(self, apply_scale=True): scale = self._model_scale if apply_scale else 1 diff --git a/pmachines/items/test_item.py b/pmachines/items/test_item.py index 760409c..4275ea6 100644 --- a/pmachines/items/test_item.py +++ b/pmachines/items/test_item.py @@ -4,8 +4,9 @@ from pmachines.items.item import Item class TestItem(Item): - def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.8, model_scale=1): - super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/test_handle/test_handle.bam', json, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=model_scale) + def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, json, model_scale=1, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.8): + super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/test_handle/test_handle.bam', json, model_scale=model_scale, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction) + self.id = json['id'] self._np.set_bin('fixed', 0) self._np.set_depth_test(False) self._np.set_depth_write(False) @@ -13,6 +14,7 @@ class TestItem(Item): def _set_shape(self, apply_scale=True): self.node.add_shape(BulletBoxShape((.5, .5, .5))) + class PixelSpaceTestItem(TestItem): pass diff --git a/pmachines/scene/scene.py b/pmachines/scene/scene.py index 0343d89..5745a14 100644 --- a/pmachines/scene/scene.py +++ b/pmachines/scene/scene.py @@ -20,6 +20,7 @@ from pmachines.items.basketball import Basketball from pmachines.items.domino import Domino, DownStrategy from pmachines.items.shelf import Shelf 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.gfx import GfxTools, DirectGuiMixin @@ -68,7 +69,7 @@ class Scene(DirectObject): if auto_close_instr: self.__store_state() self.__restore_state() - else: + elif self.__json_name: self._set_instructions() self._bg = Background() self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.__items) @@ -516,7 +517,7 @@ class Scene(DirectObject): self._item_active.on_mouse_move(pos) if self._dbg_items: self._update_info(items_hit[0] if items_hit else None) - if not self.__scene_editor and self._win_condition(): + if not self.__scene_editor and self.__json_name and self._win_condition(): self._start_evt_time = None self._set_fail() if self._enforce_result == 'fail' else self._set_win() elif self._state == 'playing' and self._fail_condition(): @@ -800,6 +801,9 @@ class Scene(DirectObject): def _set_test_items(self): def frame_after(task): self._define_test_items() + for itm in self.items: + if itm.id: + self._pos_mgr[itm.id] = itm._model.pos2d_pixel() for itm in self._test_items: self._pos_mgr[itm.name] = itm.pos2d_pixel() taskMgr.doMethodLater(1.4, frame_after, 'frame after') # after the intro sequence @@ -831,9 +835,20 @@ class Scene(DirectObject): self.current_bottom, self.repos, {}) - self.__scene_editor = SceneEditor(self.json, self.__json_name, context, self.add_item, self.__items, self._world, self._mouse_plane_node) + self.__scene_editor = SceneEditor(self.json, self.__json_name, context, self.add_item, self.__items, self._world, self._mouse_plane_node, self._pos_mgr) def __on_inspector_delete(self, item): - self.__items.remove(item) - self.json['items'].remove(item.json) + if item.__class__ in [WorldSpaceTestItem, PixelSpaceTestItem]: + for i in self._test_items: + if item.id == i.name: + r = i + self._test_items.remove(r) + else: + self.__items.remove(item) + j = self.json['items'] + if item.__class__ == PixelSpaceTestItem: + j = self.json['test_items']['pixel_space'] + if item.__class__ == WorldSpaceTestItem: + j = self.json['test_items']['world_space'] + j.remove(item.json) item.destroy() diff --git a/prj.org b/prj.org index cb431ae..06bfb4b 100644 --- a/prj.org +++ b/prj.org @@ -3,8 +3,6 @@ #+CATEGORY: pmachines #+TAGS: bug(b) calendar(c) waiting(w) -* BACKLOG build -* BACKLOG define functional tests for the editor * BACKLOG second background * BACKLOG teeter-tooter with constraints (real teeter tooter) * BACKLOG magnet, road cone, bucket diff --git a/tests/functional_test.py b/tests/functional_test.py index 88a6e74..5f293f6 100644 --- a/tests/functional_test.py +++ b/tests/functional_test.py @@ -1,9 +1,3 @@ -'''Create ref: -* M-x fla-set-fun-test -* rm options.ini -* python main.py --functional-test --functional-ref & python -m ya2.tools.functional_test.py 1 -* python main.py --functional-test --functional-ref & python -m ya2.tools.functional_test.py 2 -* M-x fla-unset-fun-test''' from panda3d.core import load_prc_file_data load_prc_file_data('', 'window-type none') import datetime @@ -109,6 +103,12 @@ class FunctionalTest: # taskMgr.do_method_later(.28, mousemove, 'mousemove') # taskMgr.do_method_later(.28, mousedown, 'mousedown') + async def __emulate_keys(self, keys): + await asyncio.sleep(.4) + for k in keys: + await asyncio.sleep(.3) + system(f'xdotool key {k}') + async def _event(self, time, evt, mouse_args=None): await asyncio.sleep(time) if evt == 'mouseclick': @@ -117,6 +117,9 @@ class FunctionalTest: elif evt == 'mousedrag': #cback = lambda: self.__mouse_drag(*mouse_args) await self.__mouse_drag(*mouse_args) + elif evt == 'keyboard': + #cback = lambda: self.__mouse_drag(*mouse_args) + await self.__emulate_keys(mouse_args) #self._tasks += [( # self._curr_time + time, # cback, @@ -520,9 +523,207 @@ class FunctionalTest: await self._do_screenshots_play() await self._do_screenshots_exit() + async def _do_screenshots_editor(self): + await self._event(FunctionalTest.evt_time, 'mouseclick', ['play', 'left']) + await self._event(FunctionalTest.evt_time, 'mouseclick', ['domino', 'left']) + await self._event(FunctionalTest.evt_time, 'mouseclick', ['close_instructions', 'left']) + await self._event(FunctionalTest.evt_time, 'mouseclick', ['wrench', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_scene') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['collapse_button_scene', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_scene_collapsed') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['collapse_button_scene', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_scene_expanded') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_save', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_scene_save') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_scene_filename', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['a', 'b', 'c', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_scene_filename_changed') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_scene_filename', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'BackSpace', 'BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_scene_filename_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_scene_name', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['a', 'b', 'c', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_scene_name_changed') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_scene_name', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'BackSpace', 'BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_scene_name_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_scene_instructions', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['a', 'b', 'c', 'Return', 'BackSpace', 'BackSpace', 'BackSpace', 'BackSpace']) + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_sorting', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_sorting') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_sorting_text', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['a', 'b', 'c', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_sorting_changed') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_sorting_text', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'BackSpace', 'BackSpace', 'BackSpace']) + await self._screenshot(FunctionalTest.start_time, 'editor_sorting_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['collapse_button_sorting', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_sorting_collapsed') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['collapse_button_sorting', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_sorting_expanded') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_sorting_save', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_sorting_save') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_sorting_close', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_sorting_closed') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_start') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_class', 'left']) + await self._screenshot(FunctionalTest.screenshot_time, 'editor_start_class') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['start_class_box', 'left']) + await self._screenshot(FunctionalTest.screenshot_time, 'editor_start_class_box') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_strategy', 'left']) + await self._screenshot(FunctionalTest.screenshot_time, 'editor_start_strategy') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['start_strategy_upstrategy', 'left']) + await self._screenshot(FunctionalTest.screenshot_time, 'editor_start_strategy_up') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_count', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['0', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_count_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_count', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_count_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_scale', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['1', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_scale_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_scale', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_scale_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_mass', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['1', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_mass_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_mass', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_mass_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_restitution', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['1', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_restitution_modifed') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_restitution', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_restitution_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_friction', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['1', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_friction_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_friction', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_friction_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_id', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['a', 'b', 'c', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_id_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_id', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'BackSpace', 'BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_id_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_strategy_args', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['a', 'b', 'c', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_strategy_args_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_strategy_args', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'BackSpace', 'BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_strategy_args_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_save', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_save') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_next', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_next') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_new', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_new') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_delete', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_delete') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_start_close', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_start_close') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_new', 'left']) + await self._event(FunctionalTest.evt_time, 'mouseclick', ['collapse_button_scene', 'left']) + await self._event(FunctionalTest.evt_time, 'mouseclick', ['test_piece', 'left']) + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_strategy', 'left']) + await self._screenshot(FunctionalTest.screenshot_time, 'editor_inspector_strategy') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['inspector_strategy_upstrategy', 'left']) + await self._screenshot(FunctionalTest.screenshot_time, 'editor_inspector_strategy_up') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_position', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['1', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_position_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_position', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_position_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_roll', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['1', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_roll_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_roll', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_roll_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_scale', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['1', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_scale_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_scale', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_scale_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_mass', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['1', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_mass_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_mass', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_mass_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_restitution', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['1', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_restitution_modifed') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_restitution', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_restitution_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_friction', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['1', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_friction_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_friction', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_friction_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_id', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['a', 'b', 'c', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_id_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_id', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'BackSpace', 'BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_id_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_strategy_args', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['a', 'b', 'c', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_strategy_args_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_strategy_args', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'BackSpace', 'BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_strategy_args_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_delete', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_delete') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_close', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_close') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['drag_stop_0', 'left']) + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_test_position', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['1', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_test_position_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_test_position', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_test_position_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_test_id', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['a', 'b', 'c', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_test_id_modified') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_test_id', 'left']) + await self._event(FunctionalTest.evt_time, 'keyboard', ['BackSpace', 'BackSpace', 'BackSpace', 'Return']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_test_id_restored') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_test_delete', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_test_delete') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_inspector_test_close', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_inspector_test_close') + await self._screenshot(FunctionalTest.start_time, 'editor_new') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['wrench', 'left']) + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_new_item', 'left']) + await self._screenshot(FunctionalTest.screenshot_time, 'editor_new_item') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['new_item_box', 'left']) + await self._screenshot(FunctionalTest.screenshot_time, 'editor_new_item') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['editor_close', 'left']) + await self._screenshot(FunctionalTest.start_time, 'editor_close') + await self._screenshot(FunctionalTest.start_time, 'editor_scene_new') + await self._event(FunctionalTest.evt_time, 'mouseclick', ['home', 'left']) + await self._screenshot(FunctionalTest.start_time, 'home_from_editor') + + async def _do_screenshots_3(self): + info('_do_screenshots_3') + await self._screenshot(FunctionalTest.start_time, 'main_menu_3') + await self._do_screenshots_editor() + await self._do_screenshots_exit() + def _do_screenshots(self, idx): asyncio.run( - [self._do_screenshots_1, self._do_screenshots_2][int(idx) - 1]() + [self._do_screenshots_1, self._do_screenshots_2, self._do_screenshots_3][int(idx) - 1]() ) diff --git a/tests/test_functional.py b/tests/test_functional.py index 468ee86..a3ccb5e 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -123,7 +123,8 @@ class FunctionalTests(TestCase): self.__test_template( '/home/flavio/venv/bin/python main.py --functional-test', ['/home/flavio/venv/bin/python -m tests.functional_test.py 1', - 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2'], + 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2', + 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 3'], str(Path.home()) + '/.local/share/pmachines/tests/functional/') def test_appimage(self): @@ -135,7 +136,8 @@ class FunctionalTests(TestCase): self.__test_template( 'timeout 7200s ./dist/Pmachines%s-x86_64.AppImage --functional-test' % bld_branch, ['timeout 7200s ~/venv/bin/python -m tests.functional_test.py 1', - 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2'], + 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2', + 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 3'], str(Path.home()) + '/.local/share/pmachines/tests/functional/') # def test_flatpak(self): @@ -180,7 +182,8 @@ class FunctionalTests(TestCase): self.__test_template( 'timeout 7200s /home/flavio/.config/itch/apps/pmachines/pmachines --functional-test', ['timeout 7200s ~/venv/bin/python -m tests.functional_test.py 1', - 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2'], + 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2', + 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 3'], str(Path.home()) + '/.local/share/pmachines/tests/functional/') def test_windows(self): @@ -192,7 +195,8 @@ class FunctionalTests(TestCase): self.__test_template( 'timeout 7200s wine %s --functional-test' % abspath, ['timeout 7200s ~/venv/bin/python -m tests.functional_test.py 1', - 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2'], + 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 2', + 'timeout 7200s ~/venv/bin/python -m tests.functional_test.py 3'], str(Path.home()) + '/.wine/drive_c/users/flavio/AppData/Local/pmachines/tests/functional/') def test_versions(self): diff --git a/ya2/utils/functional.py b/ya2/utils/functional.py index 17c21c7..721cee2 100644 --- a/ya2/utils/functional.py +++ b/ya2/utils/functional.py @@ -109,7 +109,6 @@ class FunctionalTest: return self.__position_manager[target] def __screenshot(self, name): - print('herescreenshot') self.__file_names += [self.__path + name] r = base.screenshot(self.__path + name, False) info(f'screenshot {self.__path + name} ({r}; {getcwd()})') -- 2.30.2