diff --git a/.efrocachemap b/.efrocachemap index 972ee88e..a68440a1 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4072,26 +4072,26 @@ "build/assets/workspace/ninjafightplug.py": "https://files.ballistica.net/cache/ba1/c5/09/4f10b8a21ba87aa5509cff7a164b", "build/assets/workspace/onslaughtplug.py": "https://files.ballistica.net/cache/ba1/ff/0a/a354984f9c074dab0676ac7e4877", "build/assets/workspace/runaroundplug.py": "https://files.ballistica.net/cache/ba1/2a/1c/9ee5db6d1bceca7fa6638fb8abde", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/4f/46/80f41faa8a51075c2472d7429b77", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/30/cb/93ac10daaf9475d01f5d1064d358", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/34/4e/e2bf216edcd7d6a9a1bee3b55b69", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/5e/2b/c22b17ffa90f65bc906ae9e477d6", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/cf/ac/1485911ed48dac0c430b18687917", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/0d/99/2b9a15fa962b5ea354f8de27fe6c", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/03/e1/e795c72de227173711da1a162632", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/80/26/b82964ce5a5bb8509ca6d7516b71", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/2e/c8/67482dedb186c9dbca2dc66f7699", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/18/fa/c42a603705d7453279689706d2ee", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/bc/d2/89526b59dee86888c628028a406c", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/1d/e5/7425978a2c0112a46192a76563ce", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/ff/cd/21d36a06bbc2322d8e944f392ef0", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/97/ee/e2c3b4a6ee9abb170bd98f38c7de", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/be/13/229e4c027d0ca4b037dbf67134bd", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/53/bb/acf5f4b58a156c53e6ff8f0b58ad", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/f3/d7/426c5cd3ec71438a98474f39ba2c", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/90/79/74d6111711d655963a1e9a2728c5", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/c1/f7/95ff199637a86fe138309e3a5275", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/61/f0/9447fe7637ed96ce1840aa277271", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/b0/77/0fb608a5a11844433c94237b80fd", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/6b/37/9122b03565ee2d08d9fc6d30a221", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/a4/11/0f7c0fe6af5d5e46795f33b53f8e", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/59/f4/7e5d155abb264a76b633d6f4225f", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/dc/0a/131caf082f3877e5878ae349746d", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/fb/46/34eada1e570cce2107cb55a033e8", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/1d/3e/be779b3740cda2a7d98418d4007a", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/18/b7/4389f6000decbcde1044180b134a", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/4a/91/2c3bf6cca0baecc16138db7fe7ff", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/01/b4/31635bc46cbda94cc73fc019d28c", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/13/3d/50286e4d2fe1fc7507ba20b45ca2", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/d7/17/7f186bd856ea7bd18b2fc8d64639", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/b9/63/640aa2e767450800ff481c51b665", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/19/ca/ae8153fb5fd0ea1045d386c31134", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/70/e8/796d18c51690f54efcb3df22c048", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/e2/8a/0ae529b92bcf4afe5d1c9c797e96", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/a3/d7/f415b115cd0e348be8aa75e28101", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/58/77/7244f67e35fcb0ab4047d5cc6f5e", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/91/de/bef5f0a5b9a0c6d50f4e2922a959", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/75/a5/42ad0f7c2944b1033e5b12395743", "build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/d4/6a/dd303a200b98a56ba3b100277057", "build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/fc/2c/2996c558fb408a548fdd37398c9a", "build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/ed/28/b7a72be7ae1bd2b58dda4b6902a0", @@ -4108,14 +4108,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/71/f6/691482915ad58ea1e953cc23d74c", "build/prefab/lib/mac_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/b8/2b/6ec8c78980a62e3e0ee4b36ece04", "build/prefab/lib/mac_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/4e/56/a95c987b2a371759896b037fea86", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/2d/55/0b7f8677e535082c9224259aa9d7", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/63/2f/b4b402d2f3a47dad74a077db036c", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/b6/e4/fffa1b46696ecc804b5a66896359", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/ca/70/e5eec4f437e387d61ceba7f9fffe", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/47/8c/0142dfbee4871619a8d404898eef", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/f8/d9/9facb83ac8c0ef39c97c3211ebd2", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/4d/90/79a005d72aa7a4277d06571dcdb5", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/ff/3a/e319ce586b9cb93ac29282569184", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/60/31/7c82d61f768515a6f5f8f20a967b", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/ba/38/d98a316972f47a5509be6ab28a96", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/a5/8d/8120340f79d9f9244013ef24c9da", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/84/47/90ccac6eb182c16a973855a160ee", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/5f/7b/ea3d74c27c2272084b459814986f", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/f6/a9/a780cd9b23e680a9f5cc87e5ac21", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/8c/9e/3902170e5e8662ec1fccc720ed48", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/c9/d4/5fa5dc68d04b906ef22a10eae5ae", "src/assets/ba_data/python/babase/_mgen/__init__.py": "https://files.ballistica.net/cache/ba1/52/c6/c11130af7b10d6c0321add5518fa", "src/assets/ba_data/python/babase/_mgen/enums.py": "https://files.ballistica.net/cache/ba1/38/c3/1dedd5e74f2508efc5974c8815a1", "src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/d5/4a/0e480a855ce83709bd7f6761107d", diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d93a0e3..fbd05d95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.20 (build 21041, api 8, 2023-06-02) +### 1.7.20 (build 21042, api 8, 2023-06-02) - This seems like a good time for a `refactoring` release in anticipation of changes coming in 1.8. Basically this means that a lot of things will be diff --git a/config/featuresets/featureset_base.py b/config/featuresets/featureset_base.py index ed40bf24..97f52ecf 100644 --- a/config/featuresets/featureset_base.py +++ b/config/featuresets/featureset_base.py @@ -13,3 +13,5 @@ from batools.featureset import FeatureSet fset = FeatureSet.get_active() fset.requirements = {'core'} + +fset.soft_requirements = {'classic', 'plus'} diff --git a/config/featuresets/featureset_classic.py b/config/featuresets/featureset_classic.py index 26e0472d..1d8b5729 100644 --- a/config/featuresets/featureset_classic.py +++ b/config/featuresets/featureset_classic.py @@ -13,3 +13,9 @@ from batools.featureset import FeatureSet fset = FeatureSet.get_active() fset.requirements = {'base', 'scene_v1', 'ui_v1'} + +# We provide 'babase.app.classic'. +fset.has_python_app_subsystem = True + +# We want things to work without us. +fset.allow_as_soft_requirement = True diff --git a/config/featuresets/featureset_plus.py b/config/featuresets/featureset_plus.py index 45d78cef..33d894d6 100644 --- a/config/featuresets/featureset_plus.py +++ b/config/featuresets/featureset_plus.py @@ -14,3 +14,9 @@ fset = FeatureSet.get_active() fset.requirements = {'base'} fset.internal = True + +# We provide 'babase.app.plus'. +fset.has_python_app_subsystem = True + +# We want things to work without us. +fset.allow_as_soft_requirement = True diff --git a/config/spinoffconfig.py b/config/spinoffconfig.py index 6a4b757e..758bdd52 100644 --- a/config/spinoffconfig.py +++ b/config/spinoffconfig.py @@ -98,13 +98,27 @@ ctx.src_unchecked_paths = { 'ballisticakit-android/BallisticaKit/src/*/assets', } -# Files at or under these paths are considered 'project' files. +# Paths/names/suffixes we consider 'project' files. # These files are synced after all other files and go through # batools.project.Updater class as part of their filtering. # This allows them to update themselves in the same way as they # do when running 'make update' for the project; adding the final # filtered set of project source files to themself, etc. -ctx.project_file_paths = set() +ctx.project_file_paths = {'src/assets/ba_data/python/babase/_app.py'} +ctx.project_file_names = { + 'Makefile', + 'CMakeLists.txt', + '.meta_manifest_public.json', + '.meta_manifest_private.json', + '.asset_manifest_public.json', + '.asset_manifest_private.json', +} + +ctx.project_file_suffixes = { + '.vcxproj', + '.vcxproj.filters', + '.pbxproj', +} # Everything actually synced into dst will use the following filter rules: diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py index 80ee5077..0d6a4871 100644 --- a/src/assets/ba_data/python/babase/_app.py +++ b/src/assets/ba_data/python/babase/_app.py @@ -33,12 +33,13 @@ if TYPE_CHECKING: from babase._appintent import AppIntent from babase._appmode import AppMode - # WOULD-AUTOGEN-BEGIN + # __FEATURESET_APP_SUBSYSTEM_IMPORTS_BEGIN__ + # This section autogenerated by project-update. from baclassic import ClassicSubsystem from baplus import PlusSubsystem - # WOULD-AUTOGEN-END + # __FEATURESET_APP_SUBSYSTEM_IMPORTS_END__ class App: @@ -301,18 +302,6 @@ class App: # some of this stuff might try importing babase.app and that doesn't # exist yet as of our __init__() call. - # Init classic if present. - # classic_subsystem_type: type[ClassicSubsystem] | None - # try: - # from baclassic import ClassicSubsystem - - # classic_subsystem_type = ClassicSubsystem - # except ImportError: - # classic_subsystem_type = None - - # if classic_subsystem_type is not None: - # self._classic = classic_subsystem_type() - def _threadpool_no_wait_done(self, fut: Future) -> None: try: fut.result() @@ -330,7 +319,8 @@ class App: fut = self.threadpool.submit(call) fut.add_done_callback(self._threadpool_no_wait_done) - # WOULD-AUTOGEN-BEGIN + # __FEATURESET_APP_SUBSYSTEM_PROPERTIES_BEGIN__ + # This section autogenerated by project-update. @cached_property def classic(self) -> ClassicSubsystem | None: @@ -343,7 +333,7 @@ class App: except ImportError: return None except Exception: - logging.exception('Error importing baclassic') + logging.exception('Error importing baclassic.') return None @cached_property @@ -357,10 +347,10 @@ class App: except ImportError: return None except Exception: - logging.exception('Error importing baplus') + logging.exception('Error importing baplus.') return None - # WOULD-AUTOGEN-END + # __FEATURESET_APP_SUBSYSTEM_PROPERTIES_END__ def set_intent(self, intent: AppIntent) -> None: """Set the intent for the app. @@ -514,13 +504,14 @@ class App: """Decides which app modes to use to handle intents.""" def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode]: - # WOULD-AUTOGEN-BEGIN + # __DEFAULT_APP_MODE_SELECTION_BEGIN__ + # This section autogenerated by project-update. import bascenev1 return bascenev1.SceneV1AppMode - # WOULD-AUTOGEN-END + # __DEFAULT_APP_MODE_SELECTION_END__ def on_app_running(self) -> None: """Called when initially entering the running state.""" diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index dcee55c0..6c2ee7fd 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -30,7 +30,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21041 +TARGET_BALLISTICA_BUILD = 21042 TARGET_BALLISTICA_VERSION = '1.7.20' _g_env_config: EnvConfig | None = None diff --git a/src/ballistica/base/support/ui_v1_soft.h b/src/ballistica/base/support/ui_v1_soft.h index 0a0d8a53..8c2eaab9 100644 --- a/src/ballistica/base/support/ui_v1_soft.h +++ b/src/ballistica/base/support/ui_v1_soft.h @@ -37,6 +37,7 @@ class UIV1SoftInterface { virtual void OnLanguageChange() = 0; virtual auto GetRootWidget() -> ui_v1::Widget* = 0; virtual auto SendWidgetMessage(const WidgetMessage& m) -> int = 0; + virtual void ApplyAppConfig() = 0; }; } // namespace ballistica::base diff --git a/src/ballistica/base/ui/ui.cc b/src/ballistica/base/ui/ui.cc index 6604d553..4ed51333 100644 --- a/src/ballistica/base/ui/ui.cc +++ b/src/ballistica/base/ui/ui.cc @@ -8,12 +8,10 @@ #include "ballistica/base/input/input.h" #include "ballistica/base/logic/logic.h" #include "ballistica/base/python/base_python.h" +#include "ballistica/base/support/ui_v1_soft.h" #include "ballistica/base/ui/console.h" #include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/generic/utils.h" -#include "ballistica/ui_v1/widget/root_widget.h" -#include "ballistica/ui_v1/widget/stack_widget.h" -#include "ballistica/ui_v1/widget/text_widget.h" namespace ballistica::base { @@ -87,9 +85,9 @@ void UI::OnAppShutdown() { assert(g_base->InLogicThread()); } void UI::ApplyAppConfig() { assert(g_base->InLogicThread()); - ui_v1::TextWidget::set_always_use_internal_keyboard( - g_base->app_config->Resolve( - AppConfig::BoolID::kAlwaysUseInternalKeyboard)); + if (g_base->HaveUIV1()) { + g_base->ui_v1()->ApplyAppConfig(); + } } auto UI::MainMenuVisible() const -> bool { @@ -227,16 +225,6 @@ auto UI::SendWidgetMessage(const WidgetMessage& m) -> int { return false; } -void UI::DeleteWidget(ui_v1::Widget* widget) { - assert(widget); - if (widget) { - ui_v1::ContainerWidget* parent = widget->parent_widget(); - if (parent) { - parent->DeleteWidget(widget); - } - } -} - void UI::OnScreenSizeChange() { if (g_base->HaveUIV1()) { g_base->ui_v1()->OnScreenSizeChange(); diff --git a/src/ballistica/base/ui/ui.h b/src/ballistica/base/ui/ui.h index d7c9b109..f3b4a1a3 100644 --- a/src/ballistica/base/ui/ui.h +++ b/src/ballistica/base/ui/ui.h @@ -84,9 +84,6 @@ class UI { // Send message to the active widget. auto SendWidgetMessage(const WidgetMessage& msg) -> int; - // Use this to destroy any named widget (even those in containers). - void DeleteWidget(ui_v1::Widget* widget); - void SetUIInputDevice(InputDevice* input_device); // Returns the input-device that currently owns the menu; otherwise nullptr. diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 0334ca80..f4a5813d 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21041; +const int kEngineBuildNumber = 21042; const char* kEngineVersion = "1.7.20"; auto MonolithicMain(const core::CoreConfig& core_config) -> int { diff --git a/src/ballistica/ui_v1/ui_v1.cc b/src/ballistica/ui_v1/ui_v1.cc index e1e289fa..0b0121e7 100644 --- a/src/ballistica/ui_v1/ui_v1.cc +++ b/src/ballistica/ui_v1/ui_v1.cc @@ -2,6 +2,7 @@ #include "ballistica/ui_v1/ui_v1.h" +#include "ballistica/base/app/app_config.h" #include "ballistica/base/app/app_mode.h" #include "ballistica/base/graphics/component/empty_component.h" #include "ballistica/base/graphics/graphics.h" @@ -12,6 +13,7 @@ #include "ballistica/ui_v1/widget/container_widget.h" #include "ballistica/ui_v1/widget/root_widget.h" #include "ballistica/ui_v1/widget/stack_widget.h" +#include "ballistica/ui_v1/widget/text_widget.h" namespace ballistica::ui_v1 { @@ -74,7 +76,7 @@ void UIV1FeatureSet::DoHandleDeviceMenuPress(base::InputDevice* device) { void UIV1FeatureSet::DoShowURL(const std::string& url) { python->ShowURL(url); } void UIV1FeatureSet::DoQuitWindow() { - g_ui_v1->python->objs().Get(ui_v1::UIV1Python::ObjID::kQuitWindowCall).Call(); + g_ui_v1->python->objs().Get(UIV1Python::ObjID::kQuitWindowCall).Call(); } RootUI* UIV1FeatureSet::NewRootUI() { return new RootUI(); } @@ -195,7 +197,7 @@ void UIV1FeatureSet::Reset() { screen_root_widget_.Clear(); // (Re)create our screen-root widget. - auto sw(Object::New()); + auto sw(Object::New()); sw->set_is_main_window_stack(true); sw->SetWidth(g_base->graphics->screen_virtual_width()); sw->SetHeight(g_base->graphics->screen_virtual_height()); @@ -203,7 +205,7 @@ void UIV1FeatureSet::Reset() { screen_root_widget_ = sw; // (Re)create our screen-overlay widget. - auto ow(Object::New()); + auto ow(Object::New()); ow->set_is_overlay_window_stack(true); ow->SetWidth(g_base->graphics->screen_virtual_width()); ow->SetHeight(g_base->graphics->screen_virtual_height()); @@ -211,7 +213,7 @@ void UIV1FeatureSet::Reset() { overlay_root_widget_ = ow; // (Re)create our abs-root widget. - auto rw(Object::New()); + auto rw(Object::New()); root_widget_ = rw; rw->SetWidth(g_base->graphics->screen_virtual_width()); rw->SetHeight(g_base->graphics->screen_virtual_height()); @@ -267,4 +269,19 @@ auto UIV1FeatureSet::SendWidgetMessage(const base::WidgetMessage& m) -> int { return root_widget_->HandleMessage(m); } +void UIV1FeatureSet::DeleteWidget(Widget* widget) { + assert(widget); + if (widget) { + ContainerWidget* parent = widget->parent_widget(); + if (parent) { + parent->DeleteWidget(widget); + } + } +} + +void UIV1FeatureSet::ApplyAppConfig() { + TextWidget::set_always_use_internal_keyboard(g_base->app_config->Resolve( + base::AppConfig::BoolID::kAlwaysUseInternalKeyboard)); +} + } // namespace ballistica::ui_v1 diff --git a/src/ballistica/ui_v1/ui_v1.h b/src/ballistica/ui_v1/ui_v1.h index f33fabe1..67503861 100644 --- a/src/ballistica/ui_v1/ui_v1.h +++ b/src/ballistica/ui_v1/ui_v1.h @@ -95,10 +95,13 @@ class UIV1FeatureSet : public FeatureSetNativeComponent, // If a parent is provided, the widget is added to it; otherwise it is added // to the root widget. void AddWidget(Widget* w, ContainerWidget* to); + void DeleteWidget(Widget* widget); + void OnScreenSizeChange() override; void OnLanguageChange() override; auto GetRootWidget() -> ui_v1::Widget* override; auto SendWidgetMessage(const base::WidgetMessage& m) -> int override; + void ApplyAppConfig() override; private: UIV1FeatureSet(); diff --git a/src/ballistica/ui_v1/widget/container_widget.cc b/src/ballistica/ui_v1/widget/container_widget.cc index 03b61057..19d11fbe 100644 --- a/src/ballistica/ui_v1/widget/container_widget.cc +++ b/src/ballistica/ui_v1/widget/container_widget.cc @@ -820,7 +820,9 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) { Object::WeakRef weakref(this); g_base->logic->event_loop()->PushCall([weakref] { Widget* w = weakref.Get(); - if (w) g_base->ui->DeleteWidget(w); + if (w) { + g_ui_v1->DeleteWidget(w); + } }); return; } @@ -875,7 +877,9 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) { Object::WeakRef weakref(this); g_base->logic->event_loop()->PushCall([weakref] { Widget* w = weakref.Get(); - if (w) g_base->ui->DeleteWidget(w); + if (w) { + g_ui_v1->DeleteWidget(w); + } }); return; } diff --git a/tools/batools/assetsmakefile.py b/tools/batools/assetsmakefile.py index 1bde1a61..5d325c69 100755 --- a/tools/batools/assetsmakefile.py +++ b/tools/batools/assetsmakefile.py @@ -57,6 +57,7 @@ def _get_targets( def _get_py_targets( projroot: str, meta_manifests: dict[str, str], + explicit_sources: set[str], src: str, dst: str, py_targets: list[str], @@ -66,14 +67,17 @@ def _get_py_targets( ) -> None: # pylint: disable=too-many-branches # pylint: disable=too-many-locals + # pylint: disable=too-many-statements py_generated_root = f'{ASSETS_SRC}/ba_data/python/babase/_mgen' - def _do_get_targets(proot: str, fnames: list[str]) -> None: + def _do_get_targets( + proot: str, fnames: list[str], is_explicit: bool = False + ) -> None: # Special case: ignore temp py files in data src. if proot == f'{ASSETS_SRC}/ba_data/data/maps': return - assert proot.startswith(src) + assert proot.startswith(src), f'{proot} does not start with {src}' assert dst.startswith(BUILD_DIR) dstrootvar = ( '$(BUILD_DIR)' @@ -90,6 +94,16 @@ def _get_py_targets( ): continue + # Ignore any files in the list of explicit sources we got; + # we explicitly add those at the end and don't want to do it + # twice (since we don't know if this one will always exist + # anyway). + if ( + os.path.join(proot, fname) in explicit_sources + and not is_explicit + ): + continue + if proot.startswith(f'{ASSETS_SRC}/ba_data/python-site-packages'): in_subset = 'private-common' elif proot.startswith(f'{ASSETS_SRC}/ba_data') or proot.startswith( @@ -122,7 +136,9 @@ def _get_py_targets( # gamedata pass includes only data; otherwise do all else # .py: - all_targets.add(os.path.join(dstfin, fname)) + targetpath = os.path.join(dstfin, fname) + assert targetpath not in all_targets + all_targets.add(targetpath) py_targets.append(os.path.join(dstrootvar, fname)) # and .pyc: @@ -173,14 +189,25 @@ def _get_py_targets( proot=os.path.dirname(target), fnames=[os.path.basename(target)] ) + # Now create targets for any explicitly passed paths. + for expsrc in explicit_sources: + if expsrc.startswith(f'{src}/'): + _do_get_targets( + proot=os.path.dirname(expsrc), + fnames=[os.path.basename(expsrc)], + is_explicit=True, + ) + def _get_py_targets_subset( projroot: str, meta_manifests: dict[str, str], + explicit_sources: set[str], all_targets: set[str], subset: str, suffix: str, ) -> str: + # pylint: disable=too-many-locals if subset == 'public_tools': src = 'tools' dst = f'{BUILD_DIR}/ba_data/python' @@ -197,6 +224,7 @@ def _get_py_targets_subset( _get_py_targets( projroot, meta_manifests, + explicit_sources, src, dst, py_targets, @@ -368,6 +396,7 @@ def generate_assets_makefile( fname: str, existing_data: str, meta_manifests: dict[str, str], + explicit_sources: set[str], ) -> dict[str, str]: """Main script entry point.""" # pylint: disable=too-many-locals @@ -377,8 +406,6 @@ def generate_assets_makefile( public = getconfig(Path(projroot))['public'] assert isinstance(public, bool) - # with open(fname, encoding='utf-8') as infile: - # original = infile.read() original = existing_data lines = original.splitlines() @@ -395,6 +422,7 @@ def generate_assets_makefile( _get_py_targets_subset( projroot, meta_manifests, + explicit_sources, all_targets_public, subset='public', suffix='_PUBLIC', @@ -402,6 +430,7 @@ def generate_assets_makefile( _get_py_targets_subset( projroot, meta_manifests, + explicit_sources, all_targets_public, subset='public_tools', suffix='_PUBLIC_TOOLS', @@ -416,6 +445,7 @@ def generate_assets_makefile( _get_py_targets_subset( projroot, meta_manifests, + explicit_sources, all_targets_private, subset='private-apple', suffix='_PRIVATE_APPLE', @@ -423,6 +453,7 @@ def generate_assets_makefile( _get_py_targets_subset( projroot, meta_manifests, + explicit_sources, all_targets_private, subset='private-android', suffix='_PRIVATE_ANDROID', @@ -430,6 +461,7 @@ def generate_assets_makefile( _get_py_targets_subset( projroot, meta_manifests, + explicit_sources, all_targets_private, subset='private-common', suffix='_PRIVATE_COMMON', @@ -437,6 +469,7 @@ def generate_assets_makefile( _get_py_targets_subset( projroot, meta_manifests, + explicit_sources, all_targets_private, subset='private-windows-Win32', suffix='_PRIVATE_WIN_WIN32', @@ -444,6 +477,7 @@ def generate_assets_makefile( _get_py_targets_subset( projroot, meta_manifests, + explicit_sources, all_targets_private, subset='private-windows-x64', suffix='_PRIVATE_WIN_X64', @@ -470,7 +504,11 @@ def generate_assets_makefile( all_targets_private, ), _get_targets( - projroot, 'PEM_TARGETS', '.pem', '.pem', all_targets_private + projroot, + 'PEM_TARGETS', + '.pem', + '.pem', + all_targets_private, ), _get_targets( projroot, @@ -481,7 +519,11 @@ def generate_assets_makefile( limit_to_prefix='ba_data/data', ), _get_targets( - projroot, 'AUDIO_TARGETS', '.wav', '.ogg', all_targets_private + projroot, + 'AUDIO_TARGETS', + '.wav', + '.ogg', + all_targets_private, ), _get_targets( projroot, diff --git a/tools/batools/featureset.py b/tools/batools/featureset.py index c1d13e8b..e63d4afe 100644 --- a/tools/batools/featureset.py +++ b/tools/batools/featureset.py @@ -28,49 +28,50 @@ class FeatureSet: _active_feature_set: FeatureSet | None = None - @classmethod - def get_all_for_project(cls, project_root: str) -> list[FeatureSet]: - """Return all feature-sets for the current project.""" - project_root_abs = os.path.abspath(project_root) - - # Only do this once per project. - if project_root_abs not in _g_feature_sets: - _g_feature_sets[project_root_abs] = _build_feature_set_list( - project_root_abs - ) - return _g_feature_sets[project_root_abs] - - @classmethod - def resolve_requirements( - cls, featuresets: list[FeatureSet], reqs: set[str] - ) -> set[str]: - """Resolve all required feature-sets based on a given set of them. - - Throws descriptive CleanErrors if any are missing. - """ - fsets = {f.name: f for f in featuresets} - reqs_out = set[str]() - for req in reqs: - cls._resolve_requirements(fsets, reqs_out, req) - return reqs_out - - @classmethod - def _resolve_requirements( - cls, featuresets: dict[str, FeatureSet], reqs_out: set[str], req: str - ) -> None: - if req in reqs_out: - return - featureset = featuresets.get(req) - if featureset is None: - raise CleanError(f"Required featureset '{req}' not found.") - reqs_out.add(req) - for sub_req in featureset.requirements: - cls._resolve_requirements(featuresets, reqs_out, sub_req) - def __init__(self, name: str): - self.requirements = set[str]() + # (internal; don't set this) self.internal = False + + # Other feature-sets this one requires. Any spinoff project this + # feature-set is included in will implicitly include these as + # well. + self.requirements = set[str]() + + # Feature-sets this one can use but can survive without. Note + # that each of these requirements must have + # 'allow_as_soft_requirement' enabled. While it is possible to + # programmatically check for the presence of *any* feature-set, + # officially listing soft-requirements ensures that any expected + # python-app-subsystems are in place even for feature-sets not + # included in the spinoff project (though be aware their type + # annotations will be 'Any | None' in that case instead of the + # usual 'FooBarSubsystem | None' due to 'FooBarSubsystem' not + # actually existing). + self.soft_requirements = set[str]() + + # Whether this featureset defines a native Python module within + # its C++ code. The build process will try to create dummy + # modules for all native modules you must tell it if you don't + # have one. self.has_native_python_module = True + + # If True, for feature-set 'foo_bar', the build system will + # define a 'babase.app.foo_bar' attr which points to a lazy + # loaded instance of type 'bafoobar.FooBarSubsystem'. + self.has_python_app_subsystem = False + + # If True, feature-set 'foo_bar', will be allowed to be listed + # as a soft-requirement of other feature sets and its + # python-app-subsystem will be annotated as type + # 'bafoobar.FooBarSubsystem | None' instead of simply + # 'bafoobar.FooBarSubsystem'. This forces type-checked code to + # account for the possibility that it will not be present. Note + # that this currently requires has_python_app_subsystem to be + # True (because if a soft-required feature-set is missing we + # must assume that is the case anyway because there's no way to + # know). + self.allow_as_soft_requirement = False + self.validate_name(name) # Paths of files we should disable c++ namespace checks for. @@ -228,6 +229,45 @@ class FeatureSet: assert type(self)._active_feature_set is self type(self)._active_feature_set = None + @classmethod + def get_all_for_project(cls, project_root: str) -> list[FeatureSet]: + """Return all feature-sets for the current project.""" + project_root_abs = os.path.abspath(project_root) + + # Only do this once per project. + if project_root_abs not in _g_feature_sets: + _g_feature_sets[project_root_abs] = _build_feature_set_list( + project_root_abs + ) + return _g_feature_sets[project_root_abs] + + @classmethod + def resolve_requirements( + cls, featuresets: list[FeatureSet], reqs: set[str] + ) -> set[str]: + """Resolve all required feature-sets based on a given set of them. + + Throws descriptive CleanErrors if any are missing. + """ + fsets = {f.name: f for f in featuresets} + reqs_out = set[str]() + for req in reqs: + cls._resolve_requirements(fsets, reqs_out, req) + return reqs_out + + @classmethod + def _resolve_requirements( + cls, featuresets: dict[str, FeatureSet], reqs_out: set[str], req: str + ) -> None: + if req in reqs_out: + return + featureset = featuresets.get(req) + if featureset is None: + raise CleanError(f"Required featureset '{req}' not found.") + reqs_out.add(req) + for sub_req in featureset.requirements: + cls._resolve_requirements(featuresets, reqs_out, sub_req) + def _build_feature_set_list(project_root: str) -> list[FeatureSet]: featuresets: list[FeatureSet] = [] @@ -246,28 +286,56 @@ def _build_feature_set_list(project_root: str) -> list[FeatureSet]: featureset.apply_config(os.path.join(fsdir, filename)) featuresets.append(featureset) - # Run some sanity checks to make sure our featuresets don't have - # clashing names/etc. (for instance, foo_v1 and foov_1 would resolve - # to the same foov1 py module name). + # Run some sanity checks to make sure our full set of featuresets + # don't have clashing names/etc. (for instance, foo_v1 and foov_1 + # would resolve to the same foov1 py module name). - fsnames = {f.name for f in featuresets} - assert len(fsnames) == len(featuresets) + featuresets_by_name = {f.name: f for f in featuresets} + assert len(featuresets_by_name) == len(featuresets) assert len({f.name_compact for f in featuresets}) == len(featuresets) assert len({f.name_compact for f in featuresets}) == len(featuresets) for featureset in featuresets: + # Require soft-req-enabled feature-sets to have app subsystems + # enabled (see above for explanation). + if featureset.allow_as_soft_requirement: + if not featureset.has_python_app_subsystem: + raise CleanError( + f"Feature-set '{featureset.name}'" + " has 'allow_as_soft_requirement' set to True but" + " 'has_python_app_subsystem' set to False;" + ' soft-requireable feature-sets currently MUST' + ' provide a subsystem.' + ) + for req in featureset.requirements: if req == featureset.name: raise CleanError( f"Feature-set '{featureset.name}'" f' lists itself as a requirement; this is not allowed.' ) - if req not in fsnames: + if req not in featuresets_by_name: raise CleanError( f"Undefined feature-set '{req}'" f' listed as a requirement of feature-set' f" '{featureset.name}'." ) + for req in featureset.soft_requirements: + if req == featureset.name: + raise CleanError( + f"Feature-set '{featureset.name}'" + f' lists itself as a soft-requirement; this is not allowed.' + ) + if ( + req in featuresets_by_name + and not featuresets_by_name[req].allow_as_soft_requirement + ): + raise CleanError( + f"Feature-set '{req}'" + f' is listed as a soft-requirement of feature-set' + f" '{featureset.name}' but is not allowed to be soft" + ' required.' + ) return featuresets diff --git a/tools/batools/project/_updater.py b/tools/batools/project/_updater.py index 353f0d3e..3a756b2b 100755 --- a/tools/batools/project/_updater.py +++ b/tools/batools/project/_updater.py @@ -111,6 +111,7 @@ class ProjectUpdater: self._update_cmake_files() self._update_visual_studio_projects() self._update_xcode_projects() + self._update_app_module() @property def source_files(self) -> list[str]: @@ -401,6 +402,8 @@ class ProjectUpdater: self._generate_resources_makefile(path, existing_data) elif path == 'src/meta/Makefile': self._generate_meta_makefile(existing_data) + elif path == 'src/assets/ba_data/python/babase/_app.py': + self._generate_app_module(path, existing_data) elif path.startswith('src/meta/.meta_manifest_'): # These are always generated as a side-effect of the # meta Makefile. @@ -414,6 +417,9 @@ class ProjectUpdater: assert path in self._generated_files return self._generated_files[path] + def _update_app_module(self) -> None: + self.enqueue_update('src/assets/ba_data/python/babase/_app.py') + def _update_xcode_projects(self) -> None: # from batools.xcode import update_xcode_project @@ -652,7 +658,7 @@ class ProjectUpdater: def _generate_assets_makefile(self, path: str, existing_data: str) -> None: from batools.assetsmakefile import generate_assets_makefile - # We need to now what files meta will be creating (since they + # We need to know what files meta will be creating (since they # can be asset sources). meta_manifests: dict[str, str] = {} for mantype in ['public', 'private']: @@ -661,8 +667,17 @@ class ProjectUpdater: manifest_file_name ) + # Special case; the app module file in the base feature set + # is created/updated here as a project file. It may or may not + # exist on disk, but we want to ignore it if it does and add it + # explicitly similarly to meta-manifests. + if 'base' in self.feature_sets: + explicit_sources = {'src/assets/ba_data/python/babase/_app.py'} + else: + explicit_sources = set() + outfiles = generate_assets_makefile( - self.projroot, path, existing_data, meta_manifests + self.projroot, path, existing_data, meta_manifests, explicit_sources ) for out_path, out_contents in outfiles.items(): @@ -680,6 +695,120 @@ class ProjectUpdater: self.projroot, existing_data ) + def _generate_app_module(self, path: str, existing_data: str) -> None: + # pylint: disable=too-many-locals + import textwrap + + from efrotools import replace_section + + fsets = self.feature_sets + + out = existing_data + + info = '# This section autogenerated by project-update.' + indent = ' ' + + # Import modules we need for feature-set subsystems. + contents = '' + for _fsname, fset in sorted(fsets.items()): + if fset.has_python_app_subsystem: + modname = fset.name_python_package + classname = f'{fset.name_camel}Subsystem' + contents += f'from {modname} import {classname}\n' + out = replace_section( + out, + f'{indent}# __FEATURESET_APP_SUBSYSTEM_IMPORTS_BEGIN__\n', + f'{indent}# __FEATURESET_APP_SUBSYSTEM_IMPORTS_END__\n', + textwrap.indent(f'{info}\n\n{contents}\n', indent), + keep_markers=True, + ) + + # Calc which feature-sets are soft-required by any of the ones here + # but not included here. For those we'll expose app-subsystems that + # always return None. + missing_soft_fset_names = set[str]() + + for fset in fsets.values(): + for softreq in fset.soft_requirements: + if softreq not in fsets: + missing_soft_fset_names.add(softreq) + + all_fset_names = missing_soft_fset_names | fsets.keys() + + # Add properties to instantiate feature-set subsystems. + contents = '' + + for fsetname in sorted(all_fset_names): + # for _fsname, fset in sorted(fsets.items()): + if fsetname in missing_soft_fset_names: + contents += ( + f'\n' + f'@cached_property\n' + f'def {fsetname}(self) -> Any | None:\n' + f' """Our {fsetname} subsystem (not available)."""\n' + f'\n' + f' return None\n' + ) + else: + fset = fsets[fsetname] + if fset.has_python_app_subsystem: + if not fset.allow_as_soft_requirement: + raise CleanError( + f'allow_as_soft_requirement is False for' + f' feature-set {fset.name};' + f' this is not yet supported.' + ) + modname = fset.name_python_package + classname = f'{fset.name_camel}Subsystem' + contents += ( + f'\n' + f'@cached_property\n' + f'def {fset.name}(self) -> {classname} | None:\n' + f' """Our {fset.name} subsystem (if available)."""\n' + f'\n' + f' try:\n' + f' from {modname} import {classname}\n' + f'\n' + f' return {classname}()\n' + f' except ImportError:\n' + f' return None\n' + f' except Exception:\n' + f" logging.exception('Error importing" + f" {modname}.')\n" + f' return None\n' + ) + out = replace_section( + out, + f'{indent}# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_BEGIN__\n', + f'{indent}# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_END__\n', + textwrap.indent(f'{info}\n{contents}\n', indent), + keep_markers=True, + ) + + # Set default app-mode-selection logic. + + # TEMP - fill this out with proper logic/options. + if 'scene_v1' in fsets: + contents = 'import bascenev1\n\nreturn bascenev1.SceneV1AppMode\n' + else: + contents = "raise RuntimeError('FIXME: unimplemented.')\n" + + indent = ' ' + out = replace_section( + out, + f'{indent}# __DEFAULT_APP_MODE_SELECTION_BEGIN__\n', + f'{indent}# __DEFAULT_APP_MODE_SELECTION_END__\n', + textwrap.indent(f'{info}\n\n{contents}\n', indent), + keep_markers=True, + ) + + # Note: we *should* format this string, but because this code + # runs with every project update I'm just gonna try to keep the + # formatting correct manually for now to save a bit of time. + # (project update time jumps from 0.3 to 0.5 seconds if I format + # thie one file). + self._generated_files[path] = out + def _update_meta_makefile(self) -> None: self.enqueue_update('src/meta/Makefile') diff --git a/tools/batools/spinoff/_context.py b/tools/batools/spinoff/_context.py index ed9e8751..d21880d1 100644 --- a/tools/batools/spinoff/_context.py +++ b/tools/batools/spinoff/_context.py @@ -129,20 +129,10 @@ class SpinoffContext: self._execution_error = False - self._project_file_names = { - 'Makefile', - 'CMakeLists.txt', - '.meta_manifest_public.json', - '.meta_manifest_private.json', - '.asset_manifest_public.json', - '.asset_manifest_private.json', - } + self.project_file_paths = set[str]() + self.project_file_names = set[str]() + self.project_file_suffixes = set[str]() - self._project_file_suffixes = { - '.vcxproj', - '.vcxproj.filters', - '.pbxproj', - } # Set of files/symlinks in src. self._src_entities: dict[str, SrcEntity] = {} @@ -1300,8 +1290,10 @@ class SpinoffContext: if path.startswith('tools/') or path.startswith('src/external'): return False bname = os.path.basename(path) - return bname in self._project_file_names or any( - bname.endswith(s) for s in self._project_file_suffixes + return ( + path in self.project_file_paths + or bname in self.project_file_names + or any(bname.endswith(s) for s in self.project_file_suffixes) ) def _update(self) -> None: @@ -1401,6 +1393,7 @@ class SpinoffContext: print_individual_updates: bool, is_project_file: bool = False, ) -> None: + # pylint: disable=too-many-locals src_entity = self._src_entities[src_path] dst_path = src_entity.dst src_path_full = os.path.join(self._src_root, src_path) @@ -1465,10 +1458,12 @@ class SpinoffContext: f'{Clr.RED}Error removing failed dst file: {exc2}{Clr.RST}' ) self._execution_error = True + verbose_note = ( + '' if self._verbose else ' (use --verbose for full traceback)' + ) print( f'{Clr.RED}Error copying/filtering file:' - f" '{src_path_full}'{Clr.RST}: {exc}" - ' (use --verbose for full traceback)', + f" '{src_path_full}'{Clr.RST}: {exc}{verbose_note}", file=sys.stderr, ) if self._verbose: diff --git a/tools/efrotools/__init__.py b/tools/efrotools/__init__.py index e5f040b0..28462e0a 100644 --- a/tools/efrotools/__init__.py +++ b/tools/efrotools/__init__.py @@ -136,6 +136,7 @@ def replace_section( begin_marker: str, end_marker: str, replace_text: str = '', + keep_markers: bool = False, error_if_missing: bool = True, ) -> str: """Replace all text between two marker strings (including the markers).""" @@ -157,7 +158,9 @@ def replace_section( f'; found {text.count(end_marker)}.' ) _before_end, after_end = splits - return before_begin + replace_text + after_end + if keep_markers: + replace_text = f'{begin_marker}{replace_text}{end_marker}' + return f'{before_begin}{replace_text}{after_end}' def get_public_license(style: str) -> str: