diff --git a/.efrocachemap b/.efrocachemap index b7a571d2..b24be883 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4056,50 +4056,50 @@ "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "ad13d636bcb25150044a7644846b8a09", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "7c5df955611590ef491bf614fbd60179", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "ae38bd212ae64b51482a2ccb9c1cbfd3", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "d0bcee2dd5567719aa35667c5206dffc", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "eb3cd4f86175afcf8ffa2749afa32fa3", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "6b86ba36c3719773008feaa6cdc0d0f8", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "877c9ae4532fef809a3dcbd8ffea343c", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "7df313c48c87460f56fa837502965088", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "bc40bb549d26437fb8679c1e9d088272", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "47aa08cb9f5e660023f0f3c0e4ffd65e", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "bbb0a8383d6ce1ca887190ea49223f4f", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "7dd91e3407d49981c1c975d4f01ac205", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "c87883aa2f832e792e945fd9208d712a", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "fea8fd84d8c060f2f82f402902b8c54e", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "0a454a8be47f37231655761d15e3f7e5", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "4c79db3a882eb0b8b225a8df0339b1cc", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "43b9ef321f8e80da29ddb19a760dbd77", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "6f891004f2f07c452dea29bd53f29d30", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "bf7a1ce0e7a2015d538406c6f6df761c", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "cf213dce81901a67c9970b3befdaa320", - "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "473e7e6c0cf90b9e6ac653552b18f68d", - "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "4e11b895cbf2e1339cf34bc06c54a4ea", - "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "473e7e6c0cf90b9e6ac653552b18f68d", - "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "4e11b895cbf2e1339cf34bc06c54a4ea", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "d9af1a429cff9346e0cad6fcea017e5b", - "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "ae5f87286947575463c386cfe1c443e4", - "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "d9af1a429cff9346e0cad6fcea017e5b", - "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "ae5f87286947575463c386cfe1c443e4", - "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "110eef3dc285a35a1899510e368c73b1", - "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "2692dc69f7cb2501f0aaa8675f559987", - "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "110eef3dc285a35a1899510e368c73b1", - "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "2692dc69f7cb2501f0aaa8675f559987", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "344954c4f788d7d9b4d7035ebb6131d8", - "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "48c4873dae2344c1d4092a1d85dab424", - "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "abcede4e60fa8877f18e66e086fb7387", - "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "48c4873dae2344c1d4092a1d85dab424", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "6149911c660a9864b651cc1a8e50eec1", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "57cef68ab703ba819bd0fbe9e4b1c331", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "a47bab28b86c7cefce891b8e5c8b687a", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "d68ebb1139363d711b044de65e17b204", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ea1349137f64f3d662b9a95278ca4c02", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "c8731ff226716cee3d1e46027ead1cfe", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "4ee6b633a99c5bcbea4f5dee5bda186e", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "2c3bd4952b30d88247229ad309f73092", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "c98fdb967f44411171c3d7fde1c74471", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "5514854ae459adab968120a361692cd5", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "83bcfb3af22b61e5c35972d989b3f7f8", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "a63758734d93eb969fe482b56c8d1ed2", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "7e68a605645c70fca06e4f4d5155bb0c", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "0598aa59718bb8404c3b007c22123c75", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "9c86d900fc1bc2d6f3bdb4c3e26149da", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "37f5996151f983b45925ad15dce2d0e9", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "b8560855bf772b3e74f2a8f190f54885", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "55c777fa3afd720089223fb949478502", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "81a41ddca9da9060d5d5034a84845674", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "536825d983f4a3bcec018b0295005bdb", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "1733a9610de15f77dd8df3effcca1516", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "f7434d47c371f0baaae676d288847351", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "d16ea39c50f41117697789dafb249200", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "55d18233c5b887af17afcb266a8c5597", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "c4df3b76ef890daf48e8181cffd4cd82", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "eb609d02a0897c671e40519faad3365b", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "0c0d303a440e852d0112c3a5aa75ef36", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "bdd6b2c15d6718a6c99ee287d965f022", + "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "becf7a70c7c0d7bb5bfe731dde5e0249", + "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "c9ef036408f0832cd068a34365485e0b", + "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "becf7a70c7c0d7bb5bfe731dde5e0249", + "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "c9ef036408f0832cd068a34365485e0b", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "5bd8bcd68e03939501d192b7cda55d64", + "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "a8545c0c985eef9219c351773c5c6127", + "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "5bd8bcd68e03939501d192b7cda55d64", + "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "a8545c0c985eef9219c351773c5c6127", + "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "1e2c088713c47ff47d9aa312ebb0bd1a", + "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "e6bbe6c564c65cb498edc58daec1e084", + "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "1e2c088713c47ff47d9aa312ebb0bd1a", + "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "e6bbe6c564c65cb498edc58daec1e084", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "98df65aba607e74eb5c0c7305903ac29", + "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "2b57cf28eeadf43d09b1d780a5db1423", + "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "3314d791a9ab37ea81be824460c63d14", + "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "2b57cf28eeadf43d09b1d780a5db1423", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "bd63402d48fce829f16d59c6c1f87977", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "de24c4e6f661f6201b933af3343084cc", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "84904f537e435d09c06b4b6c10abea7d", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "22a32d161e85baa6c6459412a368bf82", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "f43972d496e1953fdc30ff094a22a0d1", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "c069093fb4773f3feac13236d474ecf1", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "4d745c03bbeab02c5f69bed1ae376933", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "c1e1321bb0d5bb74211377e0f5cae45c", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101", "src/ballistica/base/mgen/pyembed/binding_base.inc": "6df0f34207346d89a72924249ddd4706", diff --git a/CHANGELOG.md b/CHANGELOG.md index 46e6c34d..9d37890f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.28 (build 21491, api 8, 2023-10-22) +### 1.7.28 (build 21510, api 8, 2023-10-26) - Massively cleaned up code related to rendering and window systems (OpenGL, SDL, etc). This code had been growing into a nasty tangle for 15 years @@ -154,6 +154,16 @@ seems pretty awesome these days. It should support most stuff SDL does and with less configuring involved. Please holler if you come across something that doesn't work. +- Mac build is also now using the Game Controller Framework to handle keyboard + events. This should better handle things like modifier keys and also will + allow us to use that exact same code on the iPad/iPhone version. +- OS key repeat events are no longer passed through the engine. This means that + any time we want repeating behavior, such as holding an arrow key to move + through UI elements, we will need to wire it up ourselves. We already do this + for things like game controllers however, so this is more consistent in a way. +- Dev console no longer claims key events unless the Python tab is showing and + there is a hardware keyboard attached. This allows showing dev console tabs + above gameplay without interfering with it. ### 1.7.27 (build 21282, api 8, 2023-08-30) diff --git a/ballisticakit-cmake/CMakeLists.txt b/ballisticakit-cmake/CMakeLists.txt index 297cfe0b..13214011 100644 --- a/ballisticakit-cmake/CMakeLists.txt +++ b/ballisticakit-cmake/CMakeLists.txt @@ -442,6 +442,8 @@ set(BALLISTICA_SOURCES ${BA_SRC_ROOT}/ballistica/base/support/huffman.cc ${BA_SRC_ROOT}/ballistica/base/support/huffman.h ${BA_SRC_ROOT}/ballistica/base/support/plus_soft.h + ${BA_SRC_ROOT}/ballistica/base/support/repeater.cc + ${BA_SRC_ROOT}/ballistica/base/support/repeater.h ${BA_SRC_ROOT}/ballistica/base/support/stdio_console.cc ${BA_SRC_ROOT}/ballistica/base/support/stdio_console.h ${BA_SRC_ROOT}/ballistica/base/support/stress_test.cc diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj index affcd239..68e39f6f 100644 --- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj +++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj @@ -434,6 +434,8 @@ + + diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters index b3e5ebdf..761cf81f 100644 --- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters +++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters @@ -736,6 +736,12 @@ ballistica\base\support + + ballistica\base\support + + + ballistica\base\support + ballistica\base\support diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj index 82d010b3..02f49c4b 100644 --- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj +++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj @@ -429,6 +429,8 @@ + + diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters index b3e5ebdf..761cf81f 100644 --- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters +++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters @@ -736,6 +736,12 @@ ballistica\base\support + + ballistica\base\support + + + ballistica\base\support + ballistica\base\support diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py index ec0e0d8b..c22aee9f 100644 --- a/src/assets/ba_data/python/babase/_app.py +++ b/src/assets/ba_data/python/babase/_app.py @@ -915,6 +915,12 @@ class App: _babase.lifecyclelog('fade-and-shutdown-graphics begin') _babase.fade_screen(False, time=0.15) await asyncio.sleep(0.15) + + # Now tell the graphics system to go down and wait until + # it has done so. + _babase.graphics_shutdown_begin() + while not _babase.graphics_shutdown_is_complete(): + await asyncio.sleep(0.01) _babase.lifecyclelog('fade-and-shutdown-graphics end') async def _fade_and_shutdown_audio(self) -> None: diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 0d19c817..d2bc7bfe 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21491 +TARGET_BALLISTICA_BUILD = 21510 TARGET_BALLISTICA_VERSION = '1.7.28' diff --git a/src/ballistica/base/app_adapter/app_adapter.cc b/src/ballistica/base/app_adapter/app_adapter.cc index a7f1cf89..ee84b319 100644 --- a/src/ballistica/base/app_adapter/app_adapter.cc +++ b/src/ballistica/base/app_adapter/app_adapter.cc @@ -315,4 +315,7 @@ auto AppAdapter::GetGraphicsClientContext() -> GraphicsClientContext* { return new GraphicsClientContext(); } +auto AppAdapter::GetKeyRepeatDelay() -> float { return 0.3f; } +auto AppAdapter::GetKeyRepeatInterval() -> float { return 0.08f; } + } // namespace ballistica::base diff --git a/src/ballistica/base/app_adapter/app_adapter.h b/src/ballistica/base/app_adapter/app_adapter.h index 25fb83de..829c236d 100644 --- a/src/ballistica/base/app_adapter/app_adapter.h +++ b/src/ballistica/base/app_adapter/app_adapter.h @@ -189,6 +189,13 @@ class AppAdapter { /// changing (it may be preferable to rely on dialogs for non-english /// languages/etc.). Default implementation returns false. This function /// should be callable from any thread. + /// + /// Note that UI elements wanting to accept direct keyboard input should + /// not call this directly, but instead should call + /// UI::UIHasDirectKeyboardInput, as that takes into account other factors + /// such as which device is currently controlling the UI (Someone + /// navigating the UI with a game controller may still get an on-screen + /// keyboard even if there is a physical keyboard attached). virtual auto HasDirectKeyboardInput() -> bool; /// Called in the graphics context to apply new settings coming in from @@ -197,6 +204,9 @@ class AppAdapter { /// settings coming in. virtual void ApplyGraphicsSettings(const GraphicsSettings* settings); + virtual auto GetKeyRepeatDelay() -> float; + virtual auto GetKeyRepeatInterval() -> float; + protected: AppAdapter(); virtual ~AppAdapter(); diff --git a/src/ballistica/base/app_adapter/app_adapter_apple.cc b/src/ballistica/base/app_adapter/app_adapter_apple.cc index aaefa65e..737b89bf 100644 --- a/src/ballistica/base/app_adapter/app_adapter_apple.cc +++ b/src/ballistica/base/app_adapter/app_adapter_apple.cc @@ -3,16 +3,21 @@ #include "ballistica/base/app_adapter/app_adapter_apple.h" -#include - #include "ballistica/base/graphics/gl/renderer_gl.h" #include "ballistica/base/graphics/graphics.h" #include "ballistica/base/graphics/graphics_server.h" #include "ballistica/base/logic/logic.h" +#include "ballistica/base/platform/apple/from_swift.h" #include "ballistica/base/support/app_config.h" #include "ballistica/shared/ballistica.h" #include "ballistica/shared/foundation/event_loop.h" +// clang-format off +// This needs to be below ballistica headers since it relies on +// some types in them but does not include headers itself. +#include +// clang-format on + namespace ballistica::base { /// RAII-friendly way to mark the thread and calls we're allowed to run graphics @@ -41,7 +46,7 @@ auto AppAdapterApple::ManagesMainThreadEventLoop() const -> bool { void AppAdapterApple::DoPushMainThreadRunnable(Runnable* runnable) { // Kick this along to swift. - BallisticaKit::FromCppPushRawRunnableToMain(runnable); + BallisticaKit::FromCpp::PushRawRunnableToMain(runnable); } void AppAdapterApple::DoApplyAppConfig() { assert(g_base->InLogicThread()); } @@ -122,16 +127,16 @@ auto AppAdapterApple::TryRender() -> bool { // matches what we have (or until we try for too long or fail at drawing). seconds_t start_time = g_core->GetAppTimeSeconds(); for (int i = 0; i < 5; ++i) { - if (((std::abs(resize_target_resolution_.x + bool size_differs = + ((std::abs(resize_target_resolution_.x - g_base->graphics_server->screen_pixel_width()) > 0.01f) || (std::abs(resize_target_resolution_.y - g_base->graphics_server->screen_pixel_height()) - > 0.01f)) - && g_core->GetAppTimeSeconds() - start_time < 0.1 && result) { + > 0.01f)); + if (size_differs && g_core->GetAppTimeSeconds() - start_time < 0.1 + && result) { result = g_base->graphics_server->TryRender(); - } else { - break; } } } @@ -182,13 +187,13 @@ void AppAdapterApple::SetHardwareCursorVisible(bool visible) { assert(g_core->InMainThread()); #if BA_OSTYPE_MACOS - BallisticaKit::CocoaFromCppSetCursorVisible(visible); + BallisticaKit::CocoaFromCpp::SetCursorVisible(visible); #endif } void AppAdapterApple::TerminateApp() { #if BA_OSTYPE_MACOS - BallisticaKit::CocoaFromCppTerminateApp(); + BallisticaKit::CocoaFromCpp::TerminateApp(); #else AppAdapter::TerminateApp(); #endif @@ -205,7 +210,7 @@ auto AppAdapterApple::FullscreenControlAvailable() const -> bool { auto AppAdapterApple::FullscreenControlGet() const -> bool { #if BA_OSTYPE_MACOS - return BallisticaKit::CocoaFromCppGetMainWindowIsFullscreen(); + return BallisticaKit::CocoaFromCpp::GetMainWindowIsFullscreen(); #else return false; #endif @@ -213,7 +218,7 @@ auto AppAdapterApple::FullscreenControlGet() const -> bool { void AppAdapterApple::FullscreenControlSet(bool fullscreen) { #if BA_OSTYPE_MACOS - return BallisticaKit::CocoaFromCppSetMainWindowFullscreen(fullscreen); + return BallisticaKit::CocoaFromCpp::SetMainWindowFullscreen(fullscreen); #endif } @@ -224,6 +229,22 @@ auto AppAdapterApple::FullscreenControlKeyShortcut() const auto AppAdapterApple::HasDirectKeyboardInput() -> bool { return true; }; +auto AppAdapterApple::GetKeyRepeatDelay() -> float { +#if BA_OSTYPE_MACOS + return BallisticaKit::CocoaFromCpp::GetKeyRepeatDelay(); +#else + return AppAdapter::GetKeyRepeatDelay(); +#endif +} + +auto AppAdapterApple::GetKeyRepeatInterval() -> float { +#if BA_OSTYPE_MACOS + return BallisticaKit::CocoaFromCpp::GetKeyRepeatInterval(); +#else + return AppAdapter::GetKeyRepeatDelay(); +#endif +} + } // namespace ballistica::base #endif // BA_XCODE_BUILD diff --git a/src/ballistica/base/app_adapter/app_adapter_apple.h b/src/ballistica/base/app_adapter/app_adapter_apple.h index 9217299b..d1c9823d 100644 --- a/src/ballistica/base/app_adapter/app_adapter_apple.h +++ b/src/ballistica/base/app_adapter/app_adapter_apple.h @@ -41,6 +41,9 @@ class AppAdapterApple : public AppAdapter { auto HasDirectKeyboardInput() -> bool override; void EnableResizeFriendlyMode(int width, int height); + auto GetKeyRepeatDelay() -> float override; + auto GetKeyRepeatInterval() -> float override; + protected: void DoPushMainThreadRunnable(Runnable* runnable) override; void DoPushGraphicsContextRunnable(Runnable* runnable) override; diff --git a/src/ballistica/base/app_adapter/app_adapter_sdl.cc b/src/ballistica/base/app_adapter/app_adapter_sdl.cc index b43f8397..6def0c9f 100644 --- a/src/ballistica/base/app_adapter/app_adapter_sdl.cc +++ b/src/ballistica/base/app_adapter/app_adapter_sdl.cc @@ -415,7 +415,9 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) { } case SDL_KEYDOWN: { - g_base->input->PushKeyPressEvent(event.key.keysym); + if (!event.key.repeat) { + g_base->input->PushKeyPressEvent(event.key.keysym); + } break; } diff --git a/src/ballistica/base/assets/assets_server.cc b/src/ballistica/base/assets/assets_server.cc index 0fddc71f..102c6027 100644 --- a/src/ballistica/base/assets/assets_server.cc +++ b/src/ballistica/base/assets/assets_server.cc @@ -26,7 +26,7 @@ void AssetsServer::OnAppStartInThread() { // Ask our thread to give us periodic processing time (close to but // not *exactly* one second; try to avoid aliasing with similar updates). process_timer_ = event_loop()->NewTimer( - 987, true, NewLambdaRunnable([this] { Process(); })); + 987, true, NewLambdaRunnable([this] { Process(); }).Get()); } void AssetsServer::PushPendingPreload(Object::Ref* asset_ref_ptr) { diff --git a/src/ballistica/base/assets/texture_asset.cc b/src/ballistica/base/assets/texture_asset.cc index aa757cb4..8198a4d9 100644 --- a/src/ballistica/base/assets/texture_asset.cc +++ b/src/ballistica/base/assets/texture_asset.cc @@ -15,7 +15,7 @@ namespace ballistica::base { -static void rgba8888_unpremultiply_in_place(uint8_t* src, size_t cb) { +static void Rgba8888UnpremultiplyInPlace_(uint8_t* src, size_t cb) { // Compute the actual number of pixel elements in the buffer. size_t cpel = cb / 4; auto* psrc = src; @@ -157,7 +157,7 @@ void TextureAsset::DoPreload() { auto* buffer = static_cast(malloc(buffer_size)); preload_datas_[0].buffers[0] = buffer; memcpy(buffer, pixels, buffer_size); - rgba8888_unpremultiply_in_place(buffer, buffer_size); + Rgba8888UnpremultiplyInPlace_(buffer, buffer_size); preload_datas_[0].widths[0] = width; preload_datas_[0].heights[0] = height; preload_datas_[0].formats[0] = TextureFormat::kRGBA_8888; diff --git a/src/ballistica/base/audio/audio_server.cc b/src/ballistica/base/audio/audio_server.cc index 1e873026..adcdee40 100644 --- a/src/ballistica/base/audio/audio_server.cc +++ b/src/ballistica/base/audio/audio_server.cc @@ -161,7 +161,7 @@ void AudioServer::OnAppStartInThread_() { // Get our thread to give us periodic processing time. process_timer_ = event_loop()->NewTimer(kAudioProcessIntervalNormal, true, - NewLambdaRunnable([this] { Process_(); })); + NewLambdaRunnable([this] { Process_(); }).Get()); #if BA_ENABLE_AUDIO diff --git a/src/ballistica/base/base.h b/src/ballistica/base/base.h index 473e5edf..07ab3f2e 100644 --- a/src/ballistica/base/base.h +++ b/src/ballistica/base/base.h @@ -99,6 +99,7 @@ class RenderPass; class RenderTarget; class RemoteAppServer; class RemoteControlInput; +class Repeater; class ScoreToBeat; class AppAdapterSDL; class SDLContext; diff --git a/src/ballistica/base/graphics/graphics.cc b/src/ballistica/base/graphics/graphics.cc index cc01e49f..7f405a9d 100644 --- a/src/ballistica/base/graphics/graphics.cc +++ b/src/ballistica/base/graphics/graphics.cc @@ -182,7 +182,7 @@ void Graphics::UpdateInitialGraphicsSettingsSend_() { void Graphics::StepDisplayTime() { assert(g_base->InLogicThread()); } void Graphics::AddCleanFrameCommand(const Object::Ref& c) { - BA_PRECONDITION(g_base->InLogicThread()); + assert(g_base->InLogicThread()); clean_frame_commands_.push_back(c); } @@ -1058,7 +1058,7 @@ void Graphics::ClearFrameDefDeleteList() { } void Graphics::FadeScreen(bool to, millisecs_t time, PyObject* endcall) { - BA_PRECONDITION(g_base->InLogicThread()); + assert(g_base->InLogicThread()); // If there's an ourstanding fade-end command, go ahead and run it. // (otherwise, overlapping fades can cause things to get lost) if (fade_end_call_.Exists()) { @@ -1186,7 +1186,7 @@ void Graphics::DrawDevUI(FrameDef* frame_def) { void Graphics::BuildAndPushFrameDef() { assert(g_base->InLogicThread()); - BA_PRECONDITION_FATAL(g_base->logic->app_bootstrapping_complete()); + assert(g_base->logic->app_bootstrapping_complete()); assert(camera_.Exists()); assert(!g_core->HeadlessMode()); diff --git a/src/ballistica/base/graphics/graphics.h b/src/ballistica/base/graphics/graphics.h index f04ff79c..4c244dd3 100644 --- a/src/ballistica/base/graphics/graphics.h +++ b/src/ballistica/base/graphics/graphics.h @@ -141,8 +141,8 @@ class Graphics { static void DrawRadialMeter(MeshIndexedSimpleFull* m, float amt); - // Ways to add a few simple component types quickly. - // (uses particle rendering for efficient batches). + // Ways to add a few simple component types quickly (uses particle + // rendering for efficient batches). void DrawBlotch(const Vector3f& pos, float size, float r, float g, float b, float a) { DoDrawBlotch(&blotch_indices_, &blotch_verts_, pos, size, r, g, b, a); @@ -240,13 +240,8 @@ class Graphics { float upper_top); void ReleaseFadeEndCommand(); - // auto tv_border() const { - // assert(g_base->InLogicThread()); - // return tv_border_; - // } - - // Nodes that draw flat stuff into the overlay pass should query this z value - // for where to draw in z. + // Nodes that draw flat stuff into the overlay pass should query this z + // value for where to draw in z. auto overlay_node_z_depth() { fetched_overlay_node_z_depth_ = true; return overlay_node_z_depth_; @@ -296,8 +291,8 @@ class Graphics { void AddMeshDataCreate(MeshData* d); void AddMeshDataDestroy(MeshData* d); - // For debugging: ensures that only transparent or opaque components - // are submitted while enabled. + // For debugging: ensures that only transparent or opaque components are + // submitted while enabled. auto drawing_transparent_only() const { return drawing_transparent_only_; } void set_drawing_transparent_only(bool val) { drawing_transparent_only_ = val; @@ -362,8 +357,8 @@ class Graphics { } /// For temporary use in arbitrary threads. This should be removed when - /// possible and replaced with proper safe thread-specific access - /// patterns (so we can support switching renderers/etc.). + /// possible and replaced with proper safe thread-specific access patterns + /// (so we can support switching renderers/etc.). auto placeholder_client_context() const -> const GraphicsClientContext* { // Using this from arbitrary threads is currently ok currently since // context never changes once set. Will need to kill this call once that @@ -478,18 +473,18 @@ class Graphics { float shadow_lower_top_{4.0f}; float shadow_upper_bottom_{30.0f}; float shadow_upper_top_{40.0f}; + seconds_t last_cursor_visibility_event_time_{}; millisecs_t fade_start_{}; millisecs_t fade_time_{}; millisecs_t next_stat_update_time_{}; millisecs_t progress_bar_end_time_{-9999}; millisecs_t last_progress_bar_draw_time_{}; millisecs_t last_progress_bar_start_time_{}; - microsecs_t last_suppress_gyro_time_{}; - seconds_t last_cursor_visibility_event_time_{}; - microsecs_t next_frame_number_filtered_increment_time_{}; - microsecs_t last_create_frame_def_time_microsecs_{}; millisecs_t last_create_frame_def_time_millisecs_{}; millisecs_t last_jitter_update_time_{}; + microsecs_t last_suppress_gyro_time_{}; + microsecs_t next_frame_number_filtered_increment_time_{}; + microsecs_t last_create_frame_def_time_microsecs_{}; Object::Ref screen_mesh_; Object::Ref progress_bar_bottom_mesh_; Object::Ref progress_bar_top_mesh_; diff --git a/src/ballistica/base/graphics/graphics_server.cc b/src/ballistica/base/graphics/graphics_server.cc index 98351f47..949efced 100644 --- a/src/ballistica/base/graphics/graphics_server.cc +++ b/src/ballistica/base/graphics/graphics_server.cc @@ -137,14 +137,10 @@ auto GraphicsServer::TryRender() -> bool { auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* { assert(g_base->app_adapter->InGraphicsContext()); - millisecs_t app_time = g_core->GetAppTimeMillisecs(); + millisecs_t start_time = g_core->GetAppTimeMillisecs(); - if (!renderer_) { - return nullptr; - } - - // If the app is paused, never render. - if (g_base->app_adapter->app_suspended()) { + // Don't bother waiting if we can't/shouldn't render anyway. + if (!renderer_ || shutting_down_ || g_base->app_adapter->app_suspended()) { return nullptr; } @@ -173,12 +169,12 @@ auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* { // if we've been waiting for too long, give up. On some platforms such // as Android, this frame will still get flipped whether we draw in it // or not, so we *really* want to not skip drawing if we can help it. - millisecs_t t = g_core->GetAppTimeMillisecs() - app_time; + millisecs_t t = g_core->GetAppTimeMillisecs() - start_time; if (t >= 1000) { if (g_buildconfig.debug_build()) { Log(LogLevel::kWarning, - "GraphicsServer: aborting GetRenderFrameDef after " - + std::to_string(t) + "ms."); + "GraphicsServer: timed out at " + std::to_string(t) + + "ms waiting for logic thread to send us a FrameDef."); } break; // Fail. } @@ -336,38 +332,6 @@ void GraphicsServer::LoadRenderer() { texture_quality_ = Graphics::TextureQualityFromRequest( texture_quality_requested_, renderer_->GetAutoTextureQuality()); - // If we don't support high quality graphics, make sure we're no higher than - // medium. - // BA_PRECONDITION(g_base->graphics->has_supports_high_quality_graphics_value()); - // if (!g_base->graphics->supports_high_quality_graphics() - // && graphics_quality_ > GraphicsQuality::kMedium) { - // graphics_quality_ = GraphicsQuality::kMedium; - // } - // graphics_quality_set_ = true; - - // Update texture quality based on request. - // switch (texture_quality_requested_) { - // case TextureQualityRequest::kLow: - // texture_quality_ = TextureQuality::kLow; - // break; - // case TextureQualityRequest::kMedium: - // texture_quality_ = TextureQuality::kMedium; - // break; - // case TextureQualityRequest::kHigh: - // texture_quality_ = TextureQuality::kHigh; - // break; - // case TextureQualityRequest::kAuto: - // texture_quality_ = renderer_->GetAutoTextureQuality(); - // break; - // default: - // Log(LogLevel::kError, - // "Unhandled TextureQualityRequest value: " - // + - // std::to_string(static_cast(texture_quality_requested_))); - // texture_quality_ = TextureQuality::kLow; - // } - // texture_quality_set_ = true; - // Ok we've got our qualities figured out; now load/update the renderer. renderer_->Load(); @@ -657,4 +621,15 @@ auto GraphicsServer::InGraphicsContext_() const -> bool { return g_base->app_adapter->InGraphicsContext(); } +void GraphicsServer::Shutdown() { + BA_PRECONDITION(!shutting_down_); + BA_PRECONDITION(g_base->InGraphicsContext()); + shutting_down_ = true; + + // We don't actually do anything here currently; just take note + // that we're shutting down so we no longer wait for frames to come + // in from the main thread. + shutdown_completed_ = true; +} + } // namespace ballistica::base diff --git a/src/ballistica/base/graphics/graphics_server.h b/src/ballistica/base/graphics/graphics_server.h index ed2cbd5b..dc3df09e 100644 --- a/src/ballistica/base/graphics/graphics_server.h +++ b/src/ballistica/base/graphics/graphics_server.h @@ -231,14 +231,6 @@ class GraphicsServer { return tv_border_; } - // auto graphics_quality_set() const { - // return graphics_quality_ != GraphicsQuality::kUnset; - // } - - // auto texture_quality_set() const { - // return texture_quality_ != TextureQuality::kUnset; - // } - auto SupportsTextureCompressionType(TextureCompressionType t) const -> bool { assert(InGraphicsContext_()); assert(texture_compression_types_set_); @@ -249,10 +241,6 @@ class GraphicsServer { void SetTextureCompressionTypes( const std::list& types); - // auto texture_compression_types_are_set() const { - // return texture_compression_types_set_; - // } - void set_renderer_context_lost(bool lost) { renderer_context_lost_ = lost; } auto renderer_context_lost() const { return renderer_context_lost_; } @@ -289,6 +277,11 @@ class GraphicsServer { return texture_compression_types_; } + /// Start spinning down the graphics server/etc. + void Shutdown(); + + auto shutdown_completed() const { return shutdown_completed_; } + private: /// Pass a freshly allocated GraphicsContext instance, which the graphics /// system will take ownership of. @@ -324,6 +317,10 @@ class GraphicsServer { } } + TextureQualityRequest texture_quality_requested_{}; + TextureQuality texture_quality_{}; + GraphicsQualityRequest graphics_quality_requested_{}; + GraphicsQuality graphics_quality_{}; bool renderer_loaded_ : 1 {}; bool model_view_projection_matrix_dirty_ : 1 {true}; bool model_world_matrix_dirty_ : 1 {true}; @@ -331,11 +328,9 @@ class GraphicsServer { bool renderer_context_lost_ : 1 {}; bool texture_compression_types_set_ : 1 {}; bool cam_orient_matrix_dirty_ : 1 {true}; + bool shutting_down_ : 1 {}; + bool shutdown_completed_ : 1 {}; Snapshot* client_context_{}; - TextureQualityRequest texture_quality_requested_{}; - TextureQuality texture_quality_{}; - GraphicsQualityRequest graphics_quality_requested_{}; - GraphicsQuality graphics_quality_{}; float res_x_{}; float res_y_{}; float res_x_virtual_{}; diff --git a/src/ballistica/base/graphics/mesh/text_mesh.cc b/src/ballistica/base/graphics/mesh/text_mesh.cc index f2c7e378..6b735175 100644 --- a/src/ballistica/base/graphics/mesh/text_mesh.cc +++ b/src/ballistica/base/graphics/mesh/text_mesh.cc @@ -360,7 +360,7 @@ void TextMesh::SetText(const std::string& text_in, HAlign alignment_h, // compile it and add its final spans to our mesh. if (packer) { std::vector spans; - packer->compile(); + packer->Compile(); // DEBUGGING - add a single quad above our first // span showing the entire texture for debugging purposes diff --git a/src/ballistica/base/graphics/text/font_page_map_data.h b/src/ballistica/base/graphics/text/font_page_map_data.h index 10ff01c3..60ea0aa8 100644 --- a/src/ballistica/base/graphics/text/font_page_map_data.h +++ b/src/ballistica/base/graphics/text/font_page_map_data.h @@ -12,25 +12,25 @@ #include "ballistica/base/graphics/text/text_graphics.h" namespace ballistica::base { -// the total number of glyph pages we have +// The total number of glyph pages we have. #define BA_GLYPH_PAGE_COUNT 8 -// the total number of glyphs we have +// The total number of glyphs we have. const int kGlyphCount = 1280; -// the starting glyph index for each page +// The starting glyph index for each page. uint32_t g_glyph_page_start_index_map[8] = {0, 258, 416, 546, 698, 981, 1138, 1276}; -// the number of glyphs on each page +// The number of glyphs on each page. uint32_t g_glyph_page_glyph_counts[8] = {258, 158, 130, 152, 283, 157, 138, 4}; -// our dynamically-loaded glyph structs for each page +// Our dynamically-loaded glyph structs for each page. TextGraphics::Glyph* g_glyph_pages[8] = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; -// the page index for each glyph -uint16_t g_glyph_map[kGlyphCount] = { +// The page index for each glyph. +uint8_t g_glyph_map[kGlyphCount] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/src/ballistica/base/graphics/text/text_graphics.cc b/src/ballistica/base/graphics/text/text_graphics.cc index 5a5cd8de..fae81787 100644 --- a/src/ballistica/base/graphics/text/text_graphics.cc +++ b/src/ballistica/base/graphics/text/text_graphics.cc @@ -116,8 +116,7 @@ TextGraphics::TextGraphics() { } } - // init glyph values for our big font page - // (a 8x8 array) + // Init glyph values for our big font page (a 8x8 array). { float x_offs = 0.009f; float y_offs = 0.0059f; @@ -284,7 +283,7 @@ TextGraphics::TextGraphics() { (1.0f / 8.0f) * static_cast(x + 1) + x_offs + scale_extra; g.tex_max_y = (1.0f / 8.0f) * static_cast(y) + y_offs; - // just scooted letters over.. account for that + // Just scooted letters over; account for that. float foo_x = 0.0183f; float foo_y = 0.000f; g.tex_min_x += foo_x; @@ -292,12 +291,12 @@ TextGraphics::TextGraphics() { g.tex_min_y += foo_y; g.tex_max_y += foo_y; - // clamp based on char width + // Clamp based on char width. float scale = w * 1.32f; g.x_size *= scale; g.tex_max_x = g.tex_min_x + (g.tex_max_x - g.tex_min_x) * scale; - // add bot offset + // Add bot offset. if (bot_offset != 0.0f) { g.tex_min_y = g.tex_max_y + (g.tex_min_y - g.tex_max_y) @@ -305,7 +304,7 @@ TextGraphics::TextGraphics() { g.pen_offset_y -= bot_offset; g.y_size += bot_offset; } - // add left offset + // Add left offset. if (left_offset != 0.0f) { g.tex_min_x = g.tex_max_x + (g.tex_min_x - g.tex_max_x) @@ -313,14 +312,14 @@ TextGraphics::TextGraphics() { g.pen_offset_x -= left_offset; g.x_size += left_offset; } - // add right offset + // Add right offset. if (right_offset != 0.0f) { g.tex_max_x = g.tex_min_x + (g.tex_max_x - g.tex_min_x) * ((g.x_size + right_offset) / g.x_size); g.x_size += right_offset; } - // add top offset + // Add top offset. if (top_offset != 0.0f) { g.tex_max_y = g.tex_min_y + (g.tex_max_y - g.tex_min_y) @@ -844,13 +843,13 @@ void TextGraphics::GetFontPageCharRange(int page, uint32_t* first_char, // Our special pages: switch (page) { case static_cast(FontPage::kOSRendered): { - // we allow the OS to render anything not in one of our glyph textures - // (technically this overlaps the private-use range which we use our own - // textures for, but that's handled as a special-case by - // TextGroup::setText + // We allow the OS to render anything not in one of our glyph textures + // (technically this overlaps the private-use range which we use our + // own textures for, but that's handled as a special-case by + // TextGroup::SetText. (*first_char) = kGlyphCount; - (*last_char) = kTextMaxUnicodeVal; // hmm what's the max unicode value we - // should ever see?.. + // hmm what's the max unicode value we should ever see?.. + (*last_char) = kTextMaxUnicodeVal; break; } case static_cast(FontPage::kExtras1): { @@ -887,52 +886,57 @@ void TextGraphics::GetFontPagesForText(const std::string& text, int last_page = -1; std::vector unicode = Utils::UnicodeFromUTF8(text, "c03853"); for (uint32_t val : unicode) { - int page; + int page{-1}; - // Hack: allow showing euro even if we don't support unicode font rendering. - if (g_buildconfig.enable_os_font_rendering()) { - if (val == 8364) { - val = 0xE000; - } - } + // Hack: allow showing euro even if we don't support unicode font + // rendering. + // if (g_buildconfig.enable_os_font_rendering()) { + // if (val == 8364) { + // val = 0xE000; + // } + // } - // For values in the custom-char range (U+E000–U+F8FF) we point at our own - // custom page(s) + bool covered{}; + + // For values in the custom-char range (U+E000–U+F8FF) we point at our + // own custom page(s) if (val >= 0xE000 && val <= 0xF8FF) { // The 25 chars after this are in our fontExtras sheet. if (val < 0xE000 + 25) { // Special value denoting our custom font page. page = static_cast(FontPage::kExtras1); + covered = true; } else if (val < 0xE000 + 50) { // Special value denoting our custom font page. page = static_cast(FontPage::kExtras2); + covered = true; } else if (val < 0xE000 + 75) { // Special value denoting our custom font page. page = static_cast(FontPage::kExtras3); + covered = true; } else if (val < 0xE000 + 100) { // Special value denoting our custom font page. page = static_cast(FontPage::kExtras4); - } else { - // We dont cover this.. just go with '?' - val = '?'; - page = g_glyph_map[val]; + covered = true; } - } else if (val >= kGlyphCount) { - // Otherwise if its outside of our texture-coverage area. + } else if (val < kGlyphCount) { + page = g_glyph_map[val]; + covered = true; + } + + if (!covered) { if (g_buildconfig.enable_os_font_rendering()) { page = static_cast(FontPage::kOSRendered); } else { val = '?'; page = g_glyph_map[val]; } - } else { - // yay we cover it! - page = g_glyph_map[val]; } - // compare to lastPage to avoid doing a set insert for *everything* since - // most will be the same + + // Compare to last_page to avoid doing a set insert for *everything* + // since most will be the same. if (page != last_page) { - (*font_pages).insert(page); + font_pages->insert(page); last_page = page; } } @@ -1009,12 +1013,8 @@ void TextGraphics::GetOSTextSpanBoundsAndWidth(const std::string& s, Rect* r, // Send this entry to the back of the list since we used it. text_span_bounds_cache_.erase(entry->list_iterator_); - // I guess inspection doesn't realize entry lives on after this?... -#pragma clang diagnostic push -#pragma ide diagnostic ignored "UnusedValue" entry->list_iterator_ = text_span_bounds_cache_.insert(text_span_bounds_cache_.end(), entry); -#pragma clang diagnostic pop return; } auto entry(Object::New()); @@ -1068,7 +1068,9 @@ auto TextGraphics::GetStringWidth(const char* text, bool big) -> float { line_length += GetOSTextSpanWidth(s); os_span.clear(); } - if (line_length > max_line_length) max_line_length = line_length; + if (line_length > max_line_length) { + max_line_length = line_length; + } line_length = 0; t++; } else { @@ -1145,7 +1147,9 @@ void TextGraphics::BreakUpString(const char* text, float width, s_begin = t; } } else { - if (*t == 0) throw Exception(); + if (*t == 0) { + throw Exception(); + } uint32_t val = Utils::GetUTF8Value(t); Utils::AdvanceUTF8(&t); diff --git a/src/ballistica/base/graphics/text/text_packer.cc b/src/ballistica/base/graphics/text/text_packer.cc index 2f9ea250..fffd74b1 100644 --- a/src/ballistica/base/graphics/text/text_packer.cc +++ b/src/ballistica/base/graphics/text/text_packer.cc @@ -24,11 +24,11 @@ void TextPacker::AddSpan(const std::string& text, float x, float y, } // FIXME - we currently run into minor problems because we measure our text -// bounds at one size and then scale that linearly when trying to fit things -// into the texture. However, fonts don't always scale linearly (and even when -// that's an option it can be expensive). +// bounds at one size and then scale that linearly when trying to fit +// things into the texture. However, fonts don't always scale linearly (and +// even when that's an option it can be expensive). -void TextPacker::compile() { +void TextPacker::Compile() { assert(!compiled_); if (spans_.empty()) { compiled_ = true; @@ -57,24 +57,23 @@ void TextPacker::compile() { width *= 2; } - // Alternately, if we're too big, crank our scale down so that our widest span - // fits. + // Alternately, if we're too big, crank our scale down so that our widest + // span fits. if (widest_unscaled_span_width * scale > width * 0.9f) { scale *= ((width * 0.9f) / (widest_unscaled_span_width * scale)); } float start_height = height; int mini_shrink_tries = 0; - // Ok; we've now locked in a width and scale. - // Now we go through and position our spans. - // We may need to do this more than once if our height comes out too big. - // (hopefully this will never be a problem in practice) + // Ok; we've now locked in a width and scale. Now we go through and + // position our spans. We may need to do this more than once if our height + // comes out too big. (hopefully this will never be a problem in practice) while (true) { height = start_height; - // We currently just lay out left-to-right, top-to-bottom. - // This could be somewhat wasteful in particular configurations. - // (leaving half-filled lines, etc) so it might be worth improving later. + // We currently just lay out left-to-right, top-to-bottom. This could be + // somewhat wasteful in particular configurations. (leaving half-filled + // lines, etc) so it might be worth improving later. float widest_fill_right = 0.0f; float fill_right = 0.0f; float fill_bottom = 0.0f; @@ -87,26 +86,27 @@ void TextPacker::compile() { // Start a new line if this would put us past the end. if (fill_right + span_width > width) { if (fill_right > widest_fill_right) { - widest_fill_right = fill_right; // Keep track of how far over we go. + // Keep track of how far over we go. + widest_fill_right = fill_right; } fill_right = 0.0f; fill_bottom += line_height; line_height = 0.0f; } - // Position x such that x + left bound - buffer lines up with our current - // right point. + // Position x such that x + left bound - buffer lines up with our + // current right point. float to_left = (i.bounds.l - span_buffer) * scale; i.tex_x = fill_right - to_left; fill_right += span_width; - // Position y such that y - top bound - buffer lines up with our current - // bottom point. + // Position y such that y - top bound - buffer lines up with our + // current bottom point. float to_top = (-i.bounds.t - span_buffer) * scale; i.tex_y = fill_bottom - to_top; - // If our total height is greater than the current line height, expand the - // line's. + // If our total height is greater than the current line height, expand + // the line's. if (span_height > line_height) { line_height = span_height; } @@ -125,9 +125,9 @@ void TextPacker::compile() { // If it doesn't fit, repeat again with a smaller scale until it does. // Dropping our scale has a disproportional effect on the final height - // (since it opens up more relative horizontal space). - // I'm not sure how to figure out how much to drop by other than - // incrementally dropping values until we fit. + // (since it opens up more relative horizontal space). I'm not sure + // how to figure out how much to drop by other than incrementally + // dropping values until we fit. scale *= 0.75f; } else if (((widest_fill_right < (width * mini_shrink_threshold_h) @@ -135,15 +135,15 @@ void TextPacker::compile() { || fill_bottom + line_height < (height * mini_shrink_threshold_v)) && mini_shrink_tries < 3) { - // If we're here it means we *barely* use more than half of the texture in - // one direction or the other; let's shrink just a tiny bit and we should - // be able to chop our texture size in half + // If we're here it means we *barely* use more than half of the + // texture in one direction or the other; let's shrink just a tiny bit + // and we should be able to chop our texture size in half if (widest_fill_right < width * mini_shrink_threshold_h && width > 16) { float scale_val = 0.99f * (((width * 0.5f) / widest_fill_right)); if (scale_val < 1.0f) { - // FIXME - should think about a fixed multiplier here; - // under the hood the system might be caching glyphs based on scale - // and this would leave us with fewer different scales in the end and + // FIXME - should think about a fixed multiplier here; under the + // hood the system might be caching glyphs based on scale and + // this would leave us with fewer different scales in the end and // thus better caching performance scale *= scale_val; } @@ -151,9 +151,9 @@ void TextPacker::compile() { } else { float scale_val = 0.99f * (height * 0.5f) / (fill_bottom + line_height); if (scale_val < 1.0f) { - // FIXME - should think about a fixed multiplier here; - // under the hood the system might be caching glyphs based on scale - // and this would leave us with fewer different scales in the end and + // FIXME - should think about a fixed multiplier here; under the + // hood the system might be caching glyphs based on scale and + // this would leave us with fewer different scales in the end and // thus better caching performance scale *= scale_val; } @@ -165,8 +165,8 @@ void TextPacker::compile() { } } - // Lastly, now that our texture width and height are completely finalized, we - // can calculate UVs. + // Lastly, now that our texture width and height are completely finalized, + // we can calculate UVs. for (auto&& i : spans_) { // Now store uv coords for this span; they should include the buffer. i.u_min = (i.tex_x + (i.bounds.l - span_buffer) * scale) / width; @@ -182,11 +182,11 @@ void TextPacker::compile() { } // TODO(ericf): now we calculate a hash that's unique to this text - // configuration; we'll use that as a key for the texture we'll generate/use. - // ..this way multiple meshes can share the same generated texture. - // *technically* we could calculate this hash and check for an existing - // texture before we bother laying out our spans, but that might not save us - // much time and would complicate things. + // configuration; we'll use that as a key for the texture we'll + // generate/use. ..this way multiple meshes can share the same generated + // texture. *technically* we could calculate this hash and check for an + // existing texture before we bother laying out our spans, but that might + // not save us much time and would complicate things. hash_ = std::to_string(resolution_scale_); for (auto&& i : spans_) { char buffer[64]; diff --git a/src/ballistica/base/graphics/text/text_packer.h b/src/ballistica/base/graphics/text/text_packer.h index d1889ba8..e2c40310 100644 --- a/src/ballistica/base/graphics/text/text_packer.h +++ b/src/ballistica/base/graphics/text/text_packer.h @@ -21,7 +21,7 @@ class TextPacker : public Object { // outside of here anyway so might as well recycle. void AddSpan(const std::string& text, float x, float y, const Rect& bounds); - auto hash() const -> const std::string& { + const auto& hash() const { assert(compiled_); return hash_; } @@ -51,32 +51,32 @@ class TextPacker : public Object { // Once done adding spans, call this to calculate final span UV values, // texture configuration, and hash. - void compile(); + void Compile(); - auto spans() const -> const std::list& { return spans_; } + const auto& spans() const { return spans_; } - auto texture_width() const -> int { + auto texture_width() const { assert(compiled_); return texture_width_; } - auto texture_height() const -> int { + auto texture_height() const { assert(compiled_); return texture_height_; } - auto text_scale() const -> float { + auto text_scale() const { assert(compiled_); return text_scale_; } private: + bool compiled_{false}; float resolution_scale_; + float text_scale_{}; int texture_width_{}; int texture_height_{}; - float text_scale_{}; std::string hash_; - bool compiled_{false}; std::list spans_; }; diff --git a/src/ballistica/base/input/device/joystick_input.cc b/src/ballistica/base/input/device/joystick_input.cc index 7d6df160..08ad4148 100644 --- a/src/ballistica/base/input/device/joystick_input.cc +++ b/src/ballistica/base/input/device/joystick_input.cc @@ -9,6 +9,7 @@ #include "ballistica/base/input/input.h" #include "ballistica/base/python/base_python.h" #include "ballistica/base/support/classic_soft.h" +#include "ballistica/base/support/repeater.h" #include "ballistica/base/ui/ui.h" #include "ballistica/core/core.h" #include "ballistica/shared/foundation/event_loop.h" @@ -17,10 +18,6 @@ namespace ballistica::base { -const char* kMFiControllerName = "iOS/Mac Controller"; - -const int kJoystickRepeatDelay{500}; - // Joy values below this are candidates for calibration. const float kJoystickCalibrationThreshold{6000.0f}; @@ -78,20 +75,6 @@ JoystickInput::JoystickInput(int sdl_joystick_id, && raw_sdl_joystick_name_.size() <= 22) { raw_sdl_joystick_name_ = "XInput Controller"; } - // #else - // raw_sdl_joystick_name_ = SDL_JoystickName(sdl_joystick_id_); - // #endif // BA_SDL2_BUILD - - // If its an SDL joystick and we're using our custom sdl 1.2 build, ask it. - // #if BA_XCODE_BUILD && BA_OSTYPE_MACOS && !BA_SDL2_BUILD - // raw_sdl_joystick_identifier_ = - // SDL_JoystickIdentifier(sdl_joystick_id_); - // #endif - - // Some special-cases on mac. - if (strstr(raw_sdl_joystick_name_.c_str(), "PLAYSTATION") != nullptr) { - is_mac_ps3_controller_ = true; - } #else // BA_ENABLE_SDL_JOYSTICKS throw Exception(); // Shouldn't happen. @@ -101,8 +84,6 @@ JoystickInput::JoystickInput(int sdl_joystick_id, // Its a manual joystick. sdl_joystick_ = nullptr; - is_mfi_controller_ = (custom_device_name_ == kMFiControllerName); - // Hard code a few remote controls. // The newer way to do this is just set 'UI-Only' on the device config is_remote_control_ = ((custom_device_name_ == "Amazon Remote") @@ -179,6 +160,7 @@ auto JoystickInput::GetButtonName(int index) -> std::string { break; } } + if (g_buildconfig.ostype_android()) { // Special case: if this is a samsung controller, return the dice // button icons. @@ -446,46 +428,6 @@ void JoystickInput::Update() { } } } - - // If a button's being held, potentially pass repeats along. - if (up_held_ || down_held_ || left_held_ || right_held_) { - // Don't ask for the widget unless we have something held. - // (otherwise we prevent other inputs from getting at it) - if (g_base->ui->GetWidgetForInput(this)) { - millisecs_t repeat_delay = kJoystickRepeatDelay; - - millisecs_t t = g_core->GetAppTimeMillisecs(); - auto c = WidgetMessage::Type::kEmptyMessage; - if (t - last_hold_time_ < repeat_delay) { - return; - } - - if (t - last_hold_time_ >= repeat_delay) { - bool pass = false; - if (up_held_) { - pass = true; - c = WidgetMessage::Type::kMoveUp; - } else if (down_held_) { - pass = true; - c = WidgetMessage::Type::kMoveDown; - } else if (left_held_) { - pass = true; - c = WidgetMessage::Type::kMoveLeft; - } else if (right_held_) { - pass = true; - c = WidgetMessage::Type::kMoveRight; - } - if (pass) { - g_base->ui->SendWidgetMessage(WidgetMessage(c)); - } - - // Set another repeat to happen sooner. - last_hold_time_ = - t - - static_cast(static_cast(repeat_delay) * 0.8f); - } - } - } } void JoystickInput::SetStandardExtendedButtons() { @@ -508,6 +450,8 @@ void JoystickInput::ResetHeldStates() { SDL_Event e; dpad_right_held_ = dpad_left_held_ = dpad_up_held_ = dpad_down_held_ = false; + ui_repeater_.Clear(); + run_buttons_held_.clear(); run_trigger1_value_ = run_trigger2_value_ = 0.0f; UpdateRunningState(); @@ -594,28 +538,28 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) { || dpad_down_held_)) return; - bool isHoldPositionEvent = false; + bool is_hold_position_event = false; // Keep track of whether hold-position is being held. If so, we don't send - // window events. (some joysticks always give us significant axis values but + // window events (some joysticks always give us significant axis values but // rely on hold position to keep from doing stuff usually). if (e->type == SDL_JOYBUTTONDOWN && e->jbutton.button == hold_position_button_) { need_to_send_held_state_ = true; hold_position_held_ = true; - isHoldPositionEvent = true; + is_hold_position_event = true; } if (e->type == SDL_JOYBUTTONUP && e->jbutton.button == hold_position_button_) { need_to_send_held_state_ = true; hold_position_held_ = false; - isHoldPositionEvent = true; + is_hold_position_event = true; } // Let's ignore events for just a moment after we're created. // (some joysticks seem to spit out erroneous button-pressed events when // first plugged in ). - if (time - creation_time_ < 250 && !isHoldPositionEvent) { + if (time - creation_time_ < 250 && !is_hold_position_event) { return; } @@ -700,7 +644,7 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) { } } - // If its the ignore button, ignore it. + // If its an ignored button, ignore it. if ((e->type == SDL_JOYBUTTONDOWN || e->type == SDL_JOYBUTTONUP) && (e->jbutton.button == ignored_button_ || e->jbutton.button == ignored_button2_ @@ -709,49 +653,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) { return; } - // A little pre-filtering on mac PS3 gamepads. (try to filter out some noise - // we're seeing, etc). - if (g_buildconfig.ostype_macos() && is_mac_ps3_controller_) { - switch (e->type) { - case SDL_JOYAXISMOTION: { - // On my ps3 controller, I seem to be seeing occasional joy-axis-events - // coming in with values of -32768 when nothing is being touched. - // Filtering those out here.. Should look into this more and see if its - // SDL's fault or else forward a bug to apple. - if ((e->jaxis.axis == 0 || e->jaxis.axis == 1) - && e->jaxis.value == -32768 - && (time - ps3_last_joy_press_time_ > 2000) && !ps3_jaxis1_pressed_ - && !ps3_jaxis2_pressed_) { - printf( - "BAJoyStick notice: filtering out errand PS3 axis %d value of " - "%d\n", - static_cast(e->jaxis.axis), - static_cast(e->jaxis.value)); - fflush(stdout); - - // std::cout << "BSJoyStick notice: filtering out errant PS3 axis " << - // int(e->jaxis.axis) << " value of " << e->jaxis.value << std::endl; - return; - } - - if (abs(e->jaxis.value) >= kJoystickDiscreteThreshold) { - ps3_last_joy_press_time_ = time; - } - - // Keep track of whether its pressed for next time. - if (e->jaxis.axis == 0) { - ps3_jaxis1_pressed_ = (abs(e->jaxis.value) > 3000); - } else if (e->jaxis.axis == 1) { - ps3_jaxis2_pressed_ = (abs(e->jaxis.value) > 3000); - } - - break; - } - default: - break; - } - } - // A few high level button press interceptions. if (e->type == SDL_JOYBUTTONDOWN) { if (e->jbutton.button == start_button_ @@ -806,111 +707,116 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) { } } - // If we're in a dialog, send dialog events. - // We keep track of special x/y values for dialog usage. + // If we're in the ui, send ui events. + // We keep track of special x/y values for ui usage. // These are formed as combinations of the actual joy value // and the hold-position state. // Think of hold-position as somewhat of a 'magnitude' to the joy event's // direction. They're really one and the same event. (we just need to store // their states ourselves since they don't both come through at once). - bool isAnalogStickJAxisEvent = false; + // FIXME: Ugh need to rip out this old hold-position stuff. + bool is_analog_stick_jaxis_event = false; if (e->type == SDL_JOYAXISMOTION) { if (e->jaxis.axis == analog_lr_) { dialog_jaxis_x_ = e->jaxis.value; - isAnalogStickJAxisEvent = true; + is_analog_stick_jaxis_event = true; } else if (e->jaxis.axis == analog_ud_) { dialog_jaxis_y_ = e->jaxis.value; - isAnalogStickJAxisEvent = true; + is_analog_stick_jaxis_event = true; } } - int dialogJaxisX = dialog_jaxis_x_; + int ui_jaxis_x = dialog_jaxis_x_; if (hold_position_held_) { - dialogJaxisX = 0; // Throttle is off. + ui_jaxis_x = 0; // Throttle is off. } - int dialogJaxisY = dialog_jaxis_y_; + int ui_jaxis_y = dialog_jaxis_y_; if (hold_position_held_) { - dialogJaxisY = 0; // Throttle is off. + ui_jaxis_y = 0; // Throttle is off. } // We might not wanna grab at the UI if we're a axis-motion event // below our 'pressed' threshold.. Otherwise fuzzy analog joystick // readings would cause rampant UI stealing even if no events are being sent. - bool would_go_to_dialog = false; + bool would_go_to_ui = false; auto wm = WidgetMessage::Type::kEmptyMessage; - if (isAnalogStickJAxisEvent || isHoldPositionEvent) { + if (is_analog_stick_jaxis_event || is_hold_position_event) { // Even when we're not sending, clear out some 'held' states. - if (left_held_ && dialogJaxisX >= -kJoystickDiscreteThreshold) { + if (left_held_ && ui_jaxis_x >= -kJoystickDiscreteThreshold) { left_held_ = false; + ui_repeater_.Clear(); } - if (right_held_ && dialogJaxisX <= kJoystickDiscreteThreshold) { + if (right_held_ && ui_jaxis_x <= kJoystickDiscreteThreshold) { right_held_ = false; + ui_repeater_.Clear(); } - if (up_held_ && dialogJaxisY >= -kJoystickDiscreteThreshold) { + if (up_held_ && ui_jaxis_y >= -kJoystickDiscreteThreshold) { up_held_ = false; + ui_repeater_.Clear(); } - if (down_held_ && dialogJaxisY <= kJoystickDiscreteThreshold) { + if (down_held_ && ui_jaxis_y <= kJoystickDiscreteThreshold) { down_held_ = false; + ui_repeater_.Clear(); + } + if ((!right_held_) && ui_jaxis_x > kJoystickDiscreteThreshold) { + would_go_to_ui = true; + } + if ((!left_held_) && ui_jaxis_x < -kJoystickDiscreteThreshold) { + would_go_to_ui = true; + } + if ((!up_held_) && ui_jaxis_y < -kJoystickDiscreteThreshold) { + would_go_to_ui = true; + } + if ((!down_held_) && ui_jaxis_y > kJoystickDiscreteThreshold) { + would_go_to_ui = true; } - if ((!right_held_) && dialogJaxisX > kJoystickDiscreteThreshold) - would_go_to_dialog = true; - if ((!left_held_) && dialogJaxisX < -kJoystickDiscreteThreshold) - would_go_to_dialog = true; - if ((!up_held_) && dialogJaxisY < -kJoystickDiscreteThreshold) - would_go_to_dialog = true; - if ((!down_held_) && dialogJaxisY > kJoystickDiscreteThreshold) - would_go_to_dialog = true; } else if ((e->type == SDL_JOYHATMOTION && e->jhat.hat == hat_) || (e->type == SDL_JOYBUTTONDOWN && e->jbutton.button != hold_position_button_)) { // Other button-downs and hat motions always go. - would_go_to_dialog = true; + would_go_to_ui = true; } // Resets always circumvent dialogs. - if (resetting_) would_go_to_dialog = false; + if (resetting_) { + would_go_to_ui = false; + } - // Anything that would go to a dialog also counts to mark us as - // 'recently-used'. - if (would_go_to_dialog) { + // Anything that would go to ui also counts to mark us as 'recently-used'. + if (would_go_to_ui) { UpdateLastInputTime(); } - if (would_go_to_dialog && g_base->ui->GetWidgetForInput(this)) { - bool pass = false; + if (would_go_to_ui && g_base->ui->GetWidgetForInput(this)) { + bool pass{}; // Special case.. either joy-axis-motion or hold-position events trigger // these. - if (isAnalogStickJAxisEvent || isHoldPositionEvent) { - if (dialogJaxisX > kJoystickDiscreteThreshold) { - // To the right. + if (is_analog_stick_jaxis_event || is_hold_position_event) { + if (ui_jaxis_x > kJoystickDiscreteThreshold) { if (!right_held_ && !up_held_ && !down_held_) { - last_hold_time_ = g_core->GetAppTimeMillisecs(); right_held_ = true; + pass = true; wm = WidgetMessage::Type::kMoveRight; - pass = true; } - } else if (dialogJaxisX < -kJoystickDiscreteThreshold) { + } else if (ui_jaxis_x < -kJoystickDiscreteThreshold) { if (!left_held_ && !up_held_ && !down_held_) { - last_hold_time_ = g_core->GetAppTimeMillisecs(); - wm = WidgetMessage::Type::kMoveLeft; - pass = true; left_held_ = true; + pass = true; + wm = WidgetMessage::Type::kMoveLeft; } } - if (dialogJaxisY > kJoystickDiscreteThreshold) { + if (ui_jaxis_y > kJoystickDiscreteThreshold) { if (!down_held_ && !left_held_ && !right_held_) { - last_hold_time_ = g_core->GetAppTimeMillisecs(); - wm = WidgetMessage::Type::kMoveDown; - pass = true; down_held_ = true; - } - } else if (dialogJaxisY < -kJoystickDiscreteThreshold) { - if (!up_held_ && !left_held_ && !right_held_) { - last_hold_time_ = g_core->GetAppTimeMillisecs(); - wm = WidgetMessage::Type::kMoveUp; pass = true; + wm = WidgetMessage::Type::kMoveDown; + } + } else if (ui_jaxis_y < -kJoystickDiscreteThreshold) { + if (!up_held_ && !left_held_ && !right_held_) { up_held_ = true; + pass = true; + wm = WidgetMessage::Type::kMoveUp; } } } @@ -924,7 +830,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) { switch (e->jhat.value) { case SDL_HAT_LEFT: { if (!left_held_) { - last_hold_time_ = g_core->GetAppTimeMillisecs(); wm = WidgetMessage::Type::kMoveLeft; pass = true; left_held_ = true; @@ -935,7 +840,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) { case SDL_HAT_RIGHT: { if (!right_held_) { - last_hold_time_ = g_core->GetAppTimeMillisecs(); wm = WidgetMessage::Type::kMoveRight; pass = true; right_held_ = true; @@ -945,7 +849,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) { } case SDL_HAT_UP: { if (!up_held_) { - last_hold_time_ = g_core->GetAppTimeMillisecs(); wm = WidgetMessage::Type::kMoveUp; pass = true; up_held_ = true; @@ -955,7 +858,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) { } case SDL_HAT_DOWN: { if (!down_held_) { - last_hold_time_ = g_core->GetAppTimeMillisecs(); wm = WidgetMessage::Type::kMoveDown; pass = true; down_held_ = true; @@ -968,6 +870,7 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) { down_held_ = false; left_held_ = false; right_held_ = false; + ui_repeater_.Clear(); } default: break; @@ -1007,7 +910,20 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) { break; } if (pass) { - g_base->ui->SendWidgetMessage(WidgetMessage(wm)); + switch (wm) { + case WidgetMessage::Type::kMoveUp: + case WidgetMessage::Type::kMoveDown: + case WidgetMessage::Type::kMoveLeft: + case WidgetMessage::Type::kMoveRight: + // For UI movement, set up a repeater so we can hold the button. + ui_repeater_ = Repeater::New( + kUINavigationRepeatDelay, kUINavigationRepeatInterval, + [wm] { g_base->ui->SendWidgetMessage(WidgetMessage(wm)); }); + break; + default: + // Other messages are just one-shots. + g_base->ui->SendWidgetMessage(WidgetMessage(wm)); + } } return; } @@ -1502,22 +1418,7 @@ auto JoystickInput::GetRawDeviceName() -> std::string { auto JoystickInput::GetDeviceExtraDescription() -> std::string { std::string s; - // On mac, PS3 controllers can connect via USB or bluetooth, - // and it can be confusing if one is doing both, - // so lets specify here. - if (GetDeviceName() == "PLAYSTATION(R)3 Controller") { - // For bluetooth we get a serial in the form "04-76-6e-d1-17-90" while - // on USB we get a simple int (the usb location id): "-9340234" - // so lets consider it wireless if its got a dash not at the beginning. - s = " (USB)"; - - auto dname = GetDeviceIdentifier(); - for (const char* tst = dname.c_str(); *tst; tst++) { - if (*tst == '-' && tst != dname) { - s = " (Bluetooth)"; - } - } - } + // (no longer being used). return s; } diff --git a/src/ballistica/base/input/device/joystick_input.h b/src/ballistica/base/input/device/joystick_input.h index e6133154..5748f702 100644 --- a/src/ballistica/base/input/device/joystick_input.h +++ b/src/ballistica/base/input/device/joystick_input.h @@ -16,7 +16,7 @@ namespace ballistica::base { const int kJoystickDiscreteThreshold{15000}; const float kJoystickDiscreteThresholdFloat{0.46f}; const int kJoystickAnalogCalibrationDivisions{20}; -extern const char* kMFiControllerName; +// extern const char* kMFiControllerName; /// A physical game controller. class JoystickInput : public InputDevice { @@ -87,48 +87,48 @@ class JoystickInput : public InputDevice { void UpdateRunningState(); auto GetCalibratedValue(float raw, float neutral) const -> int32_t; - std::string raw_sdl_joystick_name_; - std::string raw_sdl_joystick_identifier_; - float run_value_{}; JoystickInput* child_joy_stick_{}; JoystickInput* parent_joy_stick_{}; millisecs_t last_ui_only_print_time_{}; - bool ui_only_{}; - bool unassigned_buttons_run_{true}; - bool start_button_activates_default_widget_{true}; - bool auto_recalibrate_analog_stick_{}; millisecs_t creation_time_{}; - bool did_initial_reset_{}; // FIXME - should take this out and replace it with a bool // (we never actually access the sdl joystick directly outside of our // constructor) SDL_Joystick* sdl_joystick_{}; - bool is_test_input_{}; - bool is_remote_control_{}; - bool is_remote_app_{}; - bool is_mfi_controller_{}; - bool is_mac_ps3_controller_{}; - - millisecs_t ps3_last_joy_press_time_{-10000}; + bool ui_only_ : 1 {}; + bool unassigned_buttons_run_ : 1 {true}; + bool start_button_activates_default_widget_ : 1 {true}; + bool auto_recalibrate_analog_stick_ : 1 {}; + bool did_initial_reset_ : 1 {}; + bool is_test_input_ : 1 {}; + bool is_remote_control_ : 1 {}; + bool is_remote_app_ : 1 {}; + bool is_mfi_controller_ : 1 {}; // For dialogs. - bool left_held_{}; - bool right_held_{}; - bool up_held_{}; - bool down_held_{}; - bool hold_position_held_{}; - bool need_to_send_held_state_{}; + bool left_held_ : 1 {}; + bool right_held_ : 1 {}; + bool up_held_ : 1 {}; + bool down_held_ : 1 {}; + bool hold_position_held_ : 1 {}; + bool need_to_send_held_state_ : 1 {}; + + bool hat_held_ : 1 {}; + bool dpad_right_held_ : 1 {}; + bool dpad_left_held_ : 1 {}; + bool dpad_up_held_ : 1 {}; + bool dpad_down_held_ : 1 {}; + + bool ignore_completely_ : 1 {}; + bool resetting_ : 1 {}; + bool calibrate_ : 1 {}; + bool can_configure_ : 1 {}; + int hat_{}; int analog_lr_{}; int analog_ud_{1}; - millisecs_t last_hold_time_{}; - bool hat_held_{}; - bool dpad_right_held_{}; - bool dpad_left_held_{}; - bool dpad_up_held_{}; - bool dpad_down_held_{}; // Mappings of ba buttons to SDL buttons. int jump_button_{}; @@ -143,7 +143,6 @@ class JoystickInput : public InputDevice { // Used on rift build; we have one button which we disallow from joining but // the rest we allow. (all devices are treated as one and the same there). int remote_enter_button_{-1}; - bool ignore_completely_{}; int ignored_button_{-1}; int ignored_button2_{-1}; int ignored_button3_{-1}; @@ -153,12 +152,6 @@ class JoystickInput : public InputDevice { int run_trigger1_{-1}; int run_trigger2_{-1}; int vr_reorient_button_{-1}; - float run_trigger1_min_{}; - float run_trigger1_max_{}; - float run_trigger2_min_{}; - float run_trigger2_max_{}; - float run_trigger1_value_{}; - float run_trigger2_value_{}; int left_button_{-1}; int right_button_{-1}; int up_button_{-1}; @@ -167,15 +160,19 @@ class JoystickInput : public InputDevice { int right_button2_{-1}; int up_button2_{-1}; int down_button2_{-1}; - std::set run_buttons_held_; int sdl_joystick_id_{}; - bool ps3_jaxis1_pressed_{}; - bool ps3_jaxis2_pressed_{}; + float run_value_{}; + float run_trigger1_min_{}; + float run_trigger1_max_{}; + float run_trigger2_min_{}; + float run_trigger2_max_{}; + float run_trigger1_value_{}; + float run_trigger2_value_{}; float calibration_threshold_{}; float calibration_break_threshold_{}; float analog_calibration_vals_[kJoystickAnalogCalibrationDivisions]{}; - std::string custom_device_name_; - bool can_configure_{}; + float calibrated_neutral_x_{}; + float calibrated_neutral_y_{}; int32_t dialog_jaxis_x_{}; int32_t dialog_jaxis_y_{}; int32_t jaxis_raw_x_{}; @@ -183,11 +180,12 @@ class JoystickInput : public InputDevice { int32_t jaxis_x_{}; int32_t jaxis_y_{}; millisecs_t calibration_start_time_x_{}; - float calibrated_neutral_x_{}; millisecs_t calibration_start_time_y_{}; - float calibrated_neutral_y_{}; - bool resetting_{}; - bool calibrate_{}; + std::set run_buttons_held_; + std::string custom_device_name_; + std::string raw_sdl_joystick_name_; + std::string raw_sdl_joystick_identifier_; + Object::Ref ui_repeater_; BA_DISALLOW_CLASS_COPIES(JoystickInput); }; diff --git a/src/ballistica/base/input/device/keyboard_input.cc b/src/ballistica/base/input/device/keyboard_input.cc index 4a1908d4..ba9b3cab 100644 --- a/src/ballistica/base/input/device/keyboard_input.cc +++ b/src/ballistica/base/input/device/keyboard_input.cc @@ -2,8 +2,10 @@ #include "ballistica/base/input/device/keyboard_input.h" +#include "ballistica/base/app_adapter/app_adapter.h" #include "ballistica/base/platform/base_platform.h" #include "ballistica/base/support/classic_soft.h" +#include "ballistica/base/support/repeater.h" #include "ballistica/base/ui/ui.h" namespace ballistica::base { @@ -42,10 +44,12 @@ KeyboardInput::KeyboardInput(KeyboardInput* parent_keyboard_input_in) { KeyboardInput::~KeyboardInput() = default; -auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down) - -> bool { +auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool down) -> bool { // Only allow the *main* keyboard to talk to the UI if (parent_keyboard_input_ == nullptr) { + // Any new event coming in cancels repeats. + ui_repeater_.Clear(); + if (g_base->ui->GetWidgetForInput(this)) { bool pass = false; auto c = WidgetMessage::Type::kEmptyMessage; @@ -78,10 +82,8 @@ auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down) case SDLK_SPACE: case SDLK_KP_ENTER: case SDLK_RETURN: - if (!repeat) { - c = WidgetMessage::Type::kActivate; - pass = true; - } + c = WidgetMessage::Type::kActivate; + pass = true; break; case SDLK_ESCAPE: // (limit to kb1 so we don't get double-beeps on failure) @@ -123,32 +125,44 @@ auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down) } } if (pass) { - g_base->ui->SendWidgetMessage(WidgetMessage(c, keysym)); + // For movement and key press widget events, set up repeats. + // Otherwise run a single time immediately. + switch (c) { + case WidgetMessage::Type::kMoveUp: + case WidgetMessage::Type::kMoveDown: + case WidgetMessage::Type::kMoveLeft: + case WidgetMessage::Type::kMoveRight: + case WidgetMessage::Type::kKey: + // Note: Need to pass keysym along as a value; not a pointer. + ui_repeater_ = Repeater::New( + g_base->app_adapter->GetKeyRepeatDelay(), + g_base->app_adapter->GetKeyRepeatInterval(), + [c, keysym = *keysym] { + g_base->ui->SendWidgetMessage(WidgetMessage(c, &keysym)); + }); + break; + default: + g_base->ui->SendWidgetMessage(WidgetMessage(c, keysym)); + break; + } } return (pass); } } // Bring up menu if start is pressed. - if (keysym->sym == start_key_ && !repeat && !g_base->ui->MainMenuVisible()) { + if (keysym->sym == start_key_ && !g_base->ui->MainMenuVisible()) { g_base->ui->PushMainMenuPressCall(this); return true; } -// Clion seems to think child_keyboard_input_ will never be set here (it will). -#pragma clang diagnostic push -#pragma ide diagnostic ignored "UnreachableCode" -#pragma ide diagnostic ignored "ConstantConditionsOC" - // At this point, if we have a child input, let it try to handle things. if (child_keyboard_input_ && enable_child_) { - if (child_keyboard_input_->HandleKey(keysym, repeat, down)) { + if (child_keyboard_input_->HandleKey(keysym, down)) { return true; } } -#pragma clang diagnostic pop - if (!AttachedToPlayer()) { if (down && ((keysym->sym == jump_key_) || (keysym->sym == punch_key_) @@ -173,152 +187,151 @@ auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down) // (removing init values from input_type and input_type_2 gives a // 'possibly uninited value used' warning but leaving them gives a // 'values unused' warning. Grumble.) - explicit_bool(input_type - == (explicit_bool(false) ? input_type_2 : InputType::kLast)); + // explicit_bool(input_type + // == (explicit_bool(false) ? input_type_2 : + // InputType::kLast)); - if (!repeat) { - // Keyboard 1 supports assigned keys plus arrow keys if they're unused. - if (keysym->sym == left_key_ - || (device_number() == 1 && keysym->sym == SDLK_LEFT - && !left_key_assigned())) { - player_input = true; - input_type = InputType::kLeftRight; - left_held_ = down; - if (down) { - if (right_held_) { - input_value = 0; - } else { - input_value = -32767; - } + // Keyboard 1 supports assigned keys plus arrow keys if they're unused. + if (keysym->sym == left_key_ + || (device_number() == 1 && keysym->sym == SDLK_LEFT + && !left_key_assigned())) { + player_input = true; + input_type = InputType::kLeftRight; + left_held_ = down; + if (down) { + if (right_held_) { + input_value = 0; } else { - if (right_held_) { - input_value = 32767; - } - } - } else if (keysym->sym == right_key_ - || (device_number() == 1 && keysym->sym == SDLK_RIGHT - && !right_key_assigned())) { - // Keyboard 1 supports assigned keys plus arrow keys if they're unused. - player_input = true; - input_type = InputType::kLeftRight; - right_held_ = down; - if (down) { - if (left_held_) { - input_value = 0; - } else { - input_value = 32767; - } - } else { - if (left_held_) { - input_value = -32767; - } - } - } else if (keysym->sym == up_key_ - || (device_number() == 1 && keysym->sym == SDLK_UP - && !up_key_assigned())) { - player_input = true; - input_type = InputType::kUpDown; - up_held_ = down; - if (down) { - if (down_held_) { - input_value = 0; - } else { - input_value = 32767; - } - } else { - if (down_held_) input_value = -32767; - } - } else if (keysym->sym == down_key_ - || (device_number() == 1 && keysym->sym == SDLK_DOWN - && !down_key_assigned())) { - player_input = true; - input_type = InputType::kUpDown; - down_held_ = down; - if (down) { - if (up_held_) { - input_value = 0; - } else { - input_value = -32767; - } - } else { - if (up_held_) input_value = 32767; - } - } else if (keysym->sym == punch_key_) { - player_input = true; - UpdateRun(keysym->sym, down); - if (down) { - input_type = InputType::kPunchPress; - } else { - input_type = InputType::kPunchRelease; - } - } else if (keysym->sym == bomb_key_) { - player_input = true; - UpdateRun(keysym->sym, down); - if (down) - input_type = InputType::kBombPress; - else - input_type = InputType::kBombRelease; - } else if (keysym->sym == hold_position_key_) { - player_input = true; - if (down) { - input_type = InputType::kHoldPositionPress; - } else { - input_type = InputType::kHoldPositionRelease; - } - } else if (keysym->sym == pick_up_key_) { - player_input = true; - UpdateRun(keysym->sym, down); - if (down) { - input_type = InputType::kPickUpPress; - } else { - input_type = InputType::kPickUpRelease; - } - } else if ((device_number() == 1 && keysym->sym == SDLK_RETURN) - || (device_number() == 1 && keysym->sym == SDLK_KP_ENTER) - || keysym->sym == jump_key_) { - // Keyboard 1 claims certain keys if they are otherwise unclaimed - // (arrow keys, enter/return, etc). - player_input = true; - UpdateRun(keysym->sym, down); - if (down) { - input_type = InputType::kJumpPress; - have_input_2 = true; - input_type_2 = InputType::kFlyPress; - } else { - input_type = InputType::kJumpRelease; - have_input_2 = true; - input_type_2 = InputType::kFlyRelease; + input_value = -32767; } } else { - // Any other keys get processed as run keys. - // keypad keys go to player 2 - anything else to player 1. - switch (keysym->sym) { - case SDLK_KP_0: - case SDLK_KP_1: - case SDLK_KP_2: - case SDLK_KP_3: - case SDLK_KP_4: - case SDLK_KP_5: - case SDLK_KP_6: - case SDLK_KP_7: - case SDLK_KP_8: - case SDLK_KP_9: - case SDLK_KP_PLUS: - case SDLK_KP_MINUS: - case SDLK_KP_ENTER: - if (device_number() == 2) { - UpdateRun(keysym->sym, down); - return true; - } - break; - default: - if (device_number() == 1) { - UpdateRun(keysym->sym, down); - return true; - } - break; + if (right_held_) { + input_value = 32767; } } + } else if (keysym->sym == right_key_ + || (device_number() == 1 && keysym->sym == SDLK_RIGHT + && !right_key_assigned())) { + // Keyboard 1 supports assigned keys plus arrow keys if they're unused. + player_input = true; + input_type = InputType::kLeftRight; + right_held_ = down; + if (down) { + if (left_held_) { + input_value = 0; + } else { + input_value = 32767; + } + } else { + if (left_held_) { + input_value = -32767; + } + } + } else if (keysym->sym == up_key_ + || (device_number() == 1 && keysym->sym == SDLK_UP + && !up_key_assigned())) { + player_input = true; + input_type = InputType::kUpDown; + up_held_ = down; + if (down) { + if (down_held_) { + input_value = 0; + } else { + input_value = 32767; + } + } else { + if (down_held_) input_value = -32767; + } + } else if (keysym->sym == down_key_ + || (device_number() == 1 && keysym->sym == SDLK_DOWN + && !down_key_assigned())) { + player_input = true; + input_type = InputType::kUpDown; + down_held_ = down; + if (down) { + if (up_held_) { + input_value = 0; + } else { + input_value = -32767; + } + } else { + if (up_held_) input_value = 32767; + } + } else if (keysym->sym == punch_key_) { + player_input = true; + UpdateRun_(keysym->sym, down); + if (down) { + input_type = InputType::kPunchPress; + } else { + input_type = InputType::kPunchRelease; + } + } else if (keysym->sym == bomb_key_) { + player_input = true; + UpdateRun_(keysym->sym, down); + if (down) + input_type = InputType::kBombPress; + else + input_type = InputType::kBombRelease; + } else if (keysym->sym == hold_position_key_) { + player_input = true; + if (down) { + input_type = InputType::kHoldPositionPress; + } else { + input_type = InputType::kHoldPositionRelease; + } + } else if (keysym->sym == pick_up_key_) { + player_input = true; + UpdateRun_(keysym->sym, down); + if (down) { + input_type = InputType::kPickUpPress; + } else { + input_type = InputType::kPickUpRelease; + } + } else if ((device_number() == 1 && keysym->sym == SDLK_RETURN) + || (device_number() == 1 && keysym->sym == SDLK_KP_ENTER) + || keysym->sym == jump_key_) { + // Keyboard 1 claims certain keys if they are otherwise unclaimed + // (arrow keys, enter/return, etc). + player_input = true; + UpdateRun_(keysym->sym, down); + if (down) { + input_type = InputType::kJumpPress; + have_input_2 = true; + input_type_2 = InputType::kFlyPress; + } else { + input_type = InputType::kJumpRelease; + have_input_2 = true; + input_type_2 = InputType::kFlyRelease; + } + } else { + // Any other keys get processed as run keys. + // keypad keys go to player 2 - anything else to player 1. + switch (keysym->sym) { + case SDLK_KP_0: + case SDLK_KP_1: + case SDLK_KP_2: + case SDLK_KP_3: + case SDLK_KP_4: + case SDLK_KP_5: + case SDLK_KP_6: + case SDLK_KP_7: + case SDLK_KP_8: + case SDLK_KP_9: + case SDLK_KP_PLUS: + case SDLK_KP_MINUS: + case SDLK_KP_ENTER: + if (device_number() == 2) { + UpdateRun_(keysym->sym, down); + return true; + } + break; + default: + if (device_number() == 1) { + UpdateRun_(keysym->sym, down); + return true; + } + break; + } } if (player_input) { @@ -344,7 +357,7 @@ void KeyboardInput::ResetHeldStates() { } } -void KeyboardInput::UpdateRun(SDL_Keycode key, bool down) { +void KeyboardInput::UpdateRun_(SDL_Keycode key, bool down) { bool was_held = (!keys_held_.empty()); if (down) { keys_held_.insert(key); @@ -407,51 +420,51 @@ void KeyboardInput::UpdateMapping() { int val = cl ? cl->GetControllerValue(this, "buttonJump") : -1; jump_key_ = (val == -1) ? jump_key_default : (SDL_Keycode)val; - UpdateArrowKeys(jump_key_); + UpdateArrowKeys_(jump_key_); val = cl ? cl->GetControllerValue(this, "buttonPunch") : -1; punch_key_ = (val == -1) ? punch_key_default : (SDL_Keycode)val; - UpdateArrowKeys(punch_key_); + UpdateArrowKeys_(punch_key_); val = cl ? cl->GetControllerValue(this, "buttonBomb") : -1; bomb_key_ = (val == -1) ? bomb_key_default : (SDL_Keycode)val; - UpdateArrowKeys(bomb_key_); + UpdateArrowKeys_(bomb_key_); val = cl ? cl->GetControllerValue(this, "buttonPickUp") : -1; pick_up_key_ = (val == -1) ? pick_up_key_default : (SDL_Keycode)val; - UpdateArrowKeys(pick_up_key_); + UpdateArrowKeys_(pick_up_key_); val = cl ? cl->GetControllerValue(this, "buttonHoldPosition") : -1; hold_position_key_ = (val == -1) ? hold_position_key_default : (SDL_Keycode)val; - UpdateArrowKeys(hold_position_key_); + UpdateArrowKeys_(hold_position_key_); val = cl ? cl->GetControllerValue(this, "buttonStart") : -1; start_key_ = (val == -1) ? start_key_default : (SDL_Keycode)val; - UpdateArrowKeys(start_key_); + UpdateArrowKeys_(start_key_); val = cl ? cl->GetControllerValue(this, "buttonUp") : -1; up_key_ = (val == -1) ? up_key_default : (SDL_Keycode)val; - UpdateArrowKeys(up_key_); + UpdateArrowKeys_(up_key_); val = cl ? cl->GetControllerValue(this, "buttonDown") : -1; down_key_ = (val == -1) ? down_key_default : (SDL_Keycode)val; - UpdateArrowKeys(down_key_); + UpdateArrowKeys_(down_key_); val = cl ? cl->GetControllerValue(this, "buttonLeft") : -1; left_key_ = (val == -1) ? left_key_default : (SDL_Keycode)val; - UpdateArrowKeys(left_key_); + UpdateArrowKeys_(left_key_); val = cl ? cl->GetControllerValue(this, "buttonRight") : -1; right_key_ = (val == -1) ? right_key_default : (SDL_Keycode)val; - UpdateArrowKeys(right_key_); + UpdateArrowKeys_(right_key_); enable_child_ = true; up_held_ = down_held_ = left_held_ = right_held_ = false; } -void KeyboardInput::UpdateArrowKeys(SDL_Keycode key) { +void KeyboardInput::UpdateArrowKeys_(SDL_Keycode key) { if (key == SDLK_UP) { up_key_assigned_ = true; } else if (key == SDLK_DOWN) { @@ -465,7 +478,6 @@ void KeyboardInput::UpdateArrowKeys(SDL_Keycode key) { auto KeyboardInput::GetButtonName(int index) -> std::string { return g_base->platform->GetKeyName(index); - // return InputDevice::GetButtonName(index); } auto KeyboardInput::GetRawDeviceName() -> std::string { return "Keyboard"; } diff --git a/src/ballistica/base/input/device/keyboard_input.h b/src/ballistica/base/input/device/keyboard_input.h index 6d790e9c..8138b5c6 100644 --- a/src/ballistica/base/input/device/keyboard_input.h +++ b/src/ballistica/base/input/device/keyboard_input.h @@ -8,6 +8,7 @@ #include "ballistica/base/input/device/input_device.h" #include "ballistica/core/platform/support/min_sdl.h" +#include "ballistica/shared/foundation/object.h" namespace ballistica::base { @@ -15,7 +16,7 @@ class KeyboardInput : public InputDevice { public: explicit KeyboardInput(KeyboardInput* parent); ~KeyboardInput() override; - auto HandleKey(const SDL_Keysym* keysym, bool repeat, bool down) -> bool; + auto HandleKey(const SDL_Keysym* keysym, bool down) -> bool; void UpdateMapping() override; auto GetRawDeviceName() -> std::string override; void ResetHeldStates() override; @@ -29,8 +30,17 @@ class KeyboardInput : public InputDevice { auto GetButtonName(int index) -> std::string override; private: - void UpdateArrowKeys(SDL_Keycode key); - void UpdateRun(SDL_Keycode key, bool down); + void UpdateArrowKeys_(SDL_Keycode key); + void UpdateRun_(SDL_Keycode key, bool down); + bool down_held_ : 1 {}; + bool up_held_ : 1 {}; + bool left_held_ : 1 {}; + bool right_held_ : 1 {}; + bool enable_child_ : 1 {}; + bool left_key_assigned_ : 1 {}; + bool right_key_assigned_ : 1 {}; + bool up_key_assigned_ : 1 {}; + bool down_key_assigned_ : 1 {}; SDL_Keycode up_key_{}; SDL_Keycode down_key_{}; SDL_Keycode left_key_{}; @@ -41,18 +51,10 @@ class KeyboardInput : public InputDevice { SDL_Keycode pick_up_key_{}; SDL_Keycode hold_position_key_{}; SDL_Keycode start_key_{}; - bool down_held_{}; - bool up_held_{}; - bool left_held_{}; - bool right_held_{}; - bool enable_child_{}; - bool left_key_assigned_{}; - bool right_key_assigned_{}; - bool up_key_assigned_{}; - bool down_key_assigned_{}; KeyboardInput* parent_keyboard_input_{}; KeyboardInput* child_keyboard_input_{}; std::set keys_held_; + Object::Ref ui_repeater_; }; } // namespace ballistica::base diff --git a/src/ballistica/base/input/input.cc b/src/ballistica/base/input/input.cc index acefa84b..dfca9fd0 100644 --- a/src/ballistica/base/input/input.cc +++ b/src/ballistica/base/input/input.cc @@ -159,12 +159,11 @@ void Input::AnnounceConnects_() { // For the first announcement just say "X controllers detected" and don't // have a sound. - if (first_print && g_core->GetAppTimeSeconds() < 5.0) { + if (first_print && g_core->GetAppTimeSeconds() < 2.0) { first_print = false; // Disabling this completely on Android for now; we often get large // numbers of devices there that aren't actually devices. - bool do_print_initial_counts{!g_buildconfig.ostype_android()}; // If there's been several connected, just give a number. @@ -243,7 +242,7 @@ void Input::ShowStandardInputDeviceConnectedMessage_(InputDevice* j) { g_base->logic->DeleteAppTimer(connect_print_timer_id_); } connect_print_timer_id_ = g_base->logic->NewAppTimer( - 250, false, NewLambdaRunnable([this] { AnnounceConnects_(); })); + 250, false, NewLambdaRunnable([this] { AnnounceConnects_(); }).Get()); } void Input::ShowStandardInputDeviceDisconnectedMessage_(InputDevice* j) { @@ -258,7 +257,7 @@ void Input::ShowStandardInputDeviceDisconnectedMessage_(InputDevice* j) { g_base->logic->DeleteAppTimer(disconnect_print_timer_id_); } disconnect_print_timer_id_ = g_base->logic->NewAppTimer( - 250, false, NewLambdaRunnable([this] { AnnounceDisconnects_(); })); + 250, false, NewLambdaRunnable([this] { AnnounceDisconnects_(); }).Get()); } void Input::PushAddInputDeviceCall(InputDevice* input_device, @@ -802,6 +801,7 @@ void Input::ProcessStressTesting(int player_count) { (*test_input).Reset(); } } + while (stress_test_time_ < time) { stress_test_time_++; for (auto& test_input : test_inputs_) { @@ -815,7 +815,7 @@ void Input::PushTextInputEvent(const std::string& text) { g_base->logic->event_loop()->PushCall([this, text] { MarkInputActive(); - // If if the app doesn't want direct text input right now. + // If the app doesn't want direct text input right now, ignore. if (!g_base->app_adapter->HasDirectKeyboardInput()) { return; } @@ -848,6 +848,7 @@ void Input::PushTextInputEvent(const std::string& text) { && g_base->ui->dev_console()->HandleTextEditing(text)) { return; } + g_base->ui->SendWidgetMessage(WidgetMessage( WidgetMessage::Type::kTextInput, nullptr, 0, 0, 0, 0, text.c_str())); }); @@ -986,6 +987,34 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) { return; } + // Nowadays we don't want the OS to deliver repeat events to us, + // so filter out any that we get and make noise that they should stop. We + // explicitly handle repeats for UI purposes at the InputDevice or Widget + // level now. + if (keys_held_.find(keysym.sym) != keys_held_.end()) { + // Look out for several repeats coming in within the span of a few + // seconds and complain if it happens. This should allow for the random + // fluke repeat key press event due to funky OS circumstances. + static int count{}; + static seconds_t last_count_reset_time{}; + auto now = g_core->GetAppTimeSeconds(); + if (now - last_count_reset_time > 2.0) { + count = 0; + last_count_reset_time = now; + } else { + count++; + if (count > 10) { + BA_LOG_ONCE( + LogLevel::kWarning, + "Input::HandleKeyPress_ seems to be getting passed repeat key" + " press events. Only initial press events should be passed."); + } + } + return; + } + + keys_held_.insert(keysym.sym); + // If someone is capturing these events, give them a crack at it. if (keyboard_input_capture_press_) { if (keyboard_input_capture_press_(keysym)) { @@ -998,52 +1027,44 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) { // ideally we should use the modifiers bundled with the key presses) UpdateModKeyStates_(&keysym, true); - bool repeat_press; - if (keys_held_.count(keysym.sym) != 0) { - repeat_press = true; - } else { - repeat_press = false; - keys_held_.insert(keysym.sym); - } - // Mobile-specific stuff. - if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) { - switch (keysym.sym) { - // FIXME: See if this stuff is still necessary. Was this perhaps - // specifically to support the console? - case SDLK_DELETE: - case SDLK_RETURN: - case SDLK_KP_ENTER: - case SDLK_BACKSPACE: { - // FIXME: I don't remember what this was put here for, but now that - // we have hardware keyboards it crashes text fields by sending - // them a TEXT_INPUT message with no string.. I made them resistant - // to that case but wondering if we can take this out? - g_base->ui->SendWidgetMessage( - WidgetMessage(WidgetMessage::Type::kTextInput, &keysym)); - break; - } - default: - break; - } - } + // if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) { + // switch (keysym.sym) { + // // FIXME: See if this stuff is still necessary. Was this perhaps + // // specifically to support the console? + // case SDLK_DELETE: + // case SDLK_RETURN: + // case SDLK_KP_ENTER: + // case SDLK_BACKSPACE: { + // // FIXME: I don't remember what this was put here for, but now that + // // we have hardware keyboards it crashes text fields by sending + // // them a TEXT_INPUT message with no string.. I made them resistant + // // to that case but wondering if we can take this out? + // g_base->ui->SendWidgetMessage( + // WidgetMessage(WidgetMessage::Type::kTextInput, &keysym)); + // break; + // } + // default: + // break; + // } + // } // Explicitly handle fullscreen-toggles in some cases. if (g_base->app_adapter->FullscreenControlAvailable()) { bool do_toggle{}; // On our Mac SDL builds we support ctrl+F for toggling fullscreen. // On our nice Cocoa build, fullscreening happens magically through the - // view menu fullscreen controls. + // view menu fullscreen control's shortcut. if (g_buildconfig.ostype_macos() && !g_buildconfig.xcode_build()) { - if (!repeat_press && keysym.sym == SDLK_f && ((keysym.mod & KMOD_CTRL))) { + if (keysym.sym == SDLK_f && ((keysym.mod & KMOD_CTRL))) { do_toggle = true; } } - // On Windows we support both F11 and Alt+Enter for toggling fullscreen. - if (g_buildconfig.ostype_windows()) { - if (!repeat_press - && (keysym.sym == SDLK_F11 - || (keysym.sym == SDLK_RETURN && ((keysym.mod & KMOD_ALT))))) { + // On Windows and Linux we support both F11 and Alt+Enter for toggling + // fullscreen. + if (g_buildconfig.ostype_windows() || g_buildconfig.ostype_linux()) { + if ((keysym.sym == SDLK_F11 + || (keysym.sym == SDLK_RETURN && ((keysym.mod & KMOD_ALT))))) { do_toggle = true; } } @@ -1055,26 +1076,21 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) { } } - // Control-Q quits. On Mac, the usual Cmd-Q gets handled implicitly by the - // app-adapter. - // UPDATE: Disabling this for now. Looks like standard OS shortcuts like - // Alt+F4 on windows or Cmd-Q on Mac are doing the right thing with SDL - // builds these days so these are not needed. - // if (!repeat_press && keysym.sym == SDLK_q && (keysym.mod & KMOD_CTRL)) { - // g_base->QuitApp(true); - // return; - // } - - // Let the console intercept stuff if it wants at this point. + // Dev Console. if (auto* console = g_base->ui->dev_console()) { + if (keysym.sym == SDLK_BACKQUOTE || keysym.sym == SDLK_F2) { + // (reset input so characters don't continue walking and stuff) + g_base->input->ResetHoldStates(); + console->ToggleState(); + return; + } if (console->HandleKeyPress(&keysym)) { return; } } // Ctrl-V or Cmd-V sends paste commands to any interested text fields. - // Command-Q or Control-Q quits. - if (!repeat_press && keysym.sym == SDLK_v + if (keysym.sym == SDLK_v && ((keysym.mod & KMOD_CTRL) || (keysym.mod & KMOD_GUI))) { g_base->ui->SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kPaste)); return; @@ -1082,93 +1098,89 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) { bool handled = false; - // None of the following stuff accepts key repeats. - if (!repeat_press) { - switch (keysym.sym) { - // Menu button on android/etc. pops up the menu. - case SDLK_MENU: { - if (!g_base->ui->MainMenuVisible()) { - g_base->ui->PushMainMenuPressCall(touch_input_); - } - handled = true; - break; + switch (keysym.sym) { + // Menu button on android/etc. pops up the menu. + case SDLK_MENU: { + if (!g_base->ui->MainMenuVisible()) { + g_base->ui->PushMainMenuPressCall(touch_input_); } - - case SDLK_EQUALS: - case SDLK_PLUS: - if (keysym.mod & KMOD_CTRL) { - g_base->app_mode()->ChangeGameSpeed(1); - handled = true; - } - break; - - case SDLK_MINUS: - if (keysym.mod & KMOD_CTRL) { - g_base->app_mode()->ChangeGameSpeed(-1); - handled = true; - } - break; - - case SDLK_F5: { - if (g_base->ui->PartyIconVisible()) { - g_base->ui->ActivatePartyIcon(); - } - handled = true; - break; - } - - case SDLK_F7: - assert(g_base->logic->event_loop()); - g_base->logic->event_loop()->PushCall( - [] { g_base->graphics->ToggleManualCamera(); }); - handled = true; - break; - - case SDLK_F8: - assert(g_base->logic->event_loop()); - g_base->logic->event_loop()->PushCall( - [] { g_base->graphics->ToggleNetworkDebugDisplay(); }); - handled = true; - break; - - case SDLK_F9: - g_base->python->objs().PushCall( - BasePython::ObjID::kLanguageTestToggleCall); - handled = true; - break; - - case SDLK_F10: - assert(g_base->logic->event_loop()); - g_base->logic->event_loop()->PushCall( - [] { g_base->graphics->ToggleDebugDraw(); }); - handled = true; - break; - - case SDLK_ESCAPE: - - if (!g_base->ui->MainMenuVisible()) { - // There's no main menu up. Ask for one. - - // Note: keyboard_input_ may be nullptr but escape key should - // still function for menus; it just won't claim ownership. - g_base->ui->PushMainMenuPressCall(keyboard_input_); - } else { - // Ok there *is* a main menu up. Send it a cancel message. - g_base->ui->SendWidgetMessage( - WidgetMessage(WidgetMessage::Type::kCancel)); - } - handled = true; - break; - - default: - break; + handled = true; + break; } + + case SDLK_EQUALS: + case SDLK_PLUS: + if (keysym.mod & KMOD_CTRL) { + g_base->app_mode()->ChangeGameSpeed(1); + handled = true; + } + break; + + case SDLK_MINUS: + if (keysym.mod & KMOD_CTRL) { + g_base->app_mode()->ChangeGameSpeed(-1); + handled = true; + } + break; + + case SDLK_F5: { + if (g_base->ui->PartyIconVisible()) { + g_base->ui->ActivatePartyIcon(); + } + handled = true; + break; + } + + case SDLK_F7: + assert(g_base->logic->event_loop()); + g_base->logic->event_loop()->PushCall( + [] { g_base->graphics->ToggleManualCamera(); }); + handled = true; + break; + + case SDLK_F8: + assert(g_base->logic->event_loop()); + g_base->logic->event_loop()->PushCall( + [] { g_base->graphics->ToggleNetworkDebugDisplay(); }); + handled = true; + break; + + case SDLK_F9: + g_base->python->objs().PushCall( + BasePython::ObjID::kLanguageTestToggleCall); + handled = true; + break; + + case SDLK_F10: + assert(g_base->logic->event_loop()); + g_base->logic->event_loop()->PushCall( + [] { g_base->graphics->ToggleDebugDraw(); }); + handled = true; + break; + + case SDLK_ESCAPE: + if (!g_base->ui->MainMenuVisible()) { + // There's no main menu up. Ask for one. + + // Note: keyboard_input_ may be nullptr but escape key should + // still function for menus; it just won't claim ownership. + g_base->ui->PushMainMenuPressCall(keyboard_input_); + } else { + // Ok there *is* a main menu up. Send it a cancel message. + g_base->ui->SendWidgetMessage( + WidgetMessage(WidgetMessage::Type::kCancel)); + } + handled = true; + break; + + default: + break; } - // If we haven't claimed it, pass it along as potential player/widget input. + // If we haven't handled this, pass it along as potential player/widget input. if (!handled) { if (keyboard_input_) { - keyboard_input_->HandleKey(&keysym, repeat_press, true); + keyboard_input_->HandleKey(&keysym, true); } } } @@ -1184,7 +1196,7 @@ void Input::HandleKeyRelease_(const SDL_Keysym& keysym) { // In some cases we may receive duplicate key-release events (if a // keyboard reset was run, it deals out key releases, but then the // keyboard driver issues them as well). - if (keys_held_.count(keysym.sym) == 0) { + if (keys_held_.find(keysym.sym) == keys_held_.end()) { return; } @@ -1205,7 +1217,7 @@ void Input::HandleKeyRelease_(const SDL_Keysym& keysym) { } if (keyboard_input_) { - keyboard_input_->HandleKey(&keysym, false, false); + keyboard_input_->HandleKey(&keysym, false); } } diff --git a/src/ballistica/base/logic/logic.cc b/src/ballistica/base/logic/logic.cc index 67a05cf2..030229c2 100644 --- a/src/ballistica/base/logic/logic.cc +++ b/src/ballistica/base/logic/logic.cc @@ -100,7 +100,7 @@ void Logic::OnGraphicsReady() { // variety of rates anyway. NOTE: This length is currently milliseconds. headless_display_time_step_timer_ = event_loop()->NewTimer( kHeadlessMinDisplayTimeStep / 1000, true, - NewLambdaRunnable([this] { StepDisplayTime_(); })); + NewLambdaRunnable([this] { StepDisplayTime_(); }).Get()); } else { // In gui mode, push an initial frame to the graphics server. From this // point it will be self-sustaining, sending us a frame request each @@ -133,9 +133,9 @@ void Logic::CompleteAppBootstrapping_() { // Set up our timers. process_pending_work_timer_ = event_loop()->NewTimer( - 0, true, NewLambdaRunnable([this] { ProcessPendingWork_(); })); + 0, true, NewLambdaRunnable([this] { ProcessPendingWork_(); }).Get()); asset_prune_timer_ = event_loop()->NewTimer( - 2345, true, NewLambdaRunnable([] { g_base->assets->Prune(); })); + 2345, true, NewLambdaRunnable([] { g_base->assets->Prune(); }).Get()); // Let our initial dummy app-mode know it has become active. g_base->app_mode()->OnActivate(); @@ -635,8 +635,8 @@ void Logic::NotifyOfPendingAssetLoads() { UpdatePendingWorkTimer_(); } -auto Logic::NewAppTimer(millisecs_t length, bool repeat, - const Object::Ref& runnable) -> int { +auto Logic::NewAppTimer(millisecs_t length, bool repeat, Runnable* runnable) + -> int { // App-Timers simply get injected into our loop and run alongside our own // stuff. assert(g_base->InLogicThread()); @@ -667,7 +667,7 @@ auto Logic::NewDisplayTimer(microsecs_t length, bool repeat, assert(g_base->InLogicThread()); int offset = 0; Timer* t = display_timers_->NewTimer(g_core->GetAppTimeMicrosecs(), length, - offset, repeat ? -1 : 0, runnable); + offset, repeat ? -1 : 0, runnable.Get()); return t->id(); } diff --git a/src/ballistica/base/logic/logic.h b/src/ballistica/base/logic/logic.h index b215be32..3974392b 100644 --- a/src/ballistica/base/logic/logic.h +++ b/src/ballistica/base/logic/logic.h @@ -89,8 +89,7 @@ class Logic { void HandleInterruptSignal(); void HandleTerminateSignal(); - auto NewAppTimer(millisecs_t length, bool repeat, - const Object::Ref& runnable) -> int; + auto NewAppTimer(millisecs_t length, bool repeat, Runnable* runnable) -> int; void DeleteAppTimer(int timer_id); void SetAppTimerLength(int timer_id, millisecs_t length); diff --git a/src/ballistica/base/platform/apple/base_platform_apple.cc b/src/ballistica/base/platform/apple/base_platform_apple.cc index 55226812..833d8003 100644 --- a/src/ballistica/base/platform/apple/base_platform_apple.cc +++ b/src/ballistica/base/platform/apple/base_platform_apple.cc @@ -4,11 +4,14 @@ #include "ballistica/base/platform/apple/base_platform_apple.h" #if BA_XCODE_BUILD -#include +#include "ballistica/base/platform/apple/apple_utils.h" +#include "ballistica/base/platform/apple/from_swift.h" #endif #if BA_XCODE_BUILD -#include "ballistica/base/platform/apple/apple_utils.h" +// This needs to be below ballistica headers since it relies on +// some types in them but does not include headers itself. +#include #endif namespace ballistica::base { @@ -48,9 +51,9 @@ void BasePlatformApple::PurchaseAck(const std::string& purchase, void BasePlatformApple::DoOpenURL(const std::string& url) { #if BA_XCODE_BUILD #if BA_OSTYPE_MACOS - BallisticaKit::CocoaFromCppOpenURL(url); + BallisticaKit::CocoaFromCpp::OpenURL(url); #else - BallisticaKit::UIKitFromCppOpenURL(url); + BallisticaKit::UIKitFromCpp::OpenURL(url); #endif // BA_OSTYPE_MACOS #else diff --git a/src/ballistica/base/python/class/python_class_app_timer.cc b/src/ballistica/base/python/class/python_class_app_timer.cc index dd75823f..0202a91d 100644 --- a/src/ballistica/base/python/class/python_class_app_timer.cc +++ b/src/ballistica/base/python/class/python_class_app_timer.cc @@ -95,7 +95,7 @@ auto PythonClassAppTimer::tp_new(PyTypeObject* type, PyObject* args, self->timer_id_ = g_base->logic->NewAppTimer( static_cast(length * 1000.0), repeat, - Object::New(call_obj)); + Object::New(call_obj).Get()); self->have_timer_ = true; diff --git a/src/ballistica/base/python/methods/python_methods_app.cc b/src/ballistica/base/python/methods/python_methods_app.cc index befd17d3..471c6c08 100644 --- a/src/ballistica/base/python/methods/python_methods_app.cc +++ b/src/ballistica/base/python/methods/python_methods_app.cc @@ -366,7 +366,7 @@ static auto PyAppTimer(PyObject* self, PyObject* args, PyObject* keywds) } g_base->logic->NewAppTimer( static_cast(length * 1000.0), false, - Object::New(call_obj)); + Object::New(call_obj).Get()); Py_RETURN_NONE; BA_PYTHON_CATCH; } @@ -1645,6 +1645,52 @@ static PyMethodDef PyAudioShutdownIsCompleteDef = { "(internal)\n", }; +// ----------------------- graphics_shutdown_begin ----------------------------- + +static auto PyGraphicsShutdownBegin(PyObject* self) -> PyObject* { + BA_PYTHON_TRY; + + g_base->app_adapter->PushGraphicsContextCall( + [] { g_base->graphics_server->Shutdown(); }); + + Py_RETURN_NONE; + + BA_PYTHON_CATCH; +} + +static PyMethodDef PyGraphicsShutdownBeginDef = { + "graphics_shutdown_begin", // name + (PyCFunction)PyGraphicsShutdownBegin, // method + METH_NOARGS, // flags + + "graphics_shutdown_begin() -> None\n" + "\n" + "(internal)\n", +}; + +// -------------------- graphics_shutdown_is_complete -------------------------- + +static auto PyGraphicsShutdownIsComplete(PyObject* self) -> PyObject* { + BA_PYTHON_TRY; + + if (g_base->graphics_server->shutdown_completed()) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; + + BA_PYTHON_CATCH; +} + +static PyMethodDef PyGraphicsShutdownIsCompleteDef = { + "graphics_shutdown_is_complete", // name + (PyCFunction)PyGraphicsShutdownIsComplete, // method + METH_NOARGS, // flags + + "graphics_shutdown_is_complete() -> bool\n" + "\n" + "(internal)\n", +}; + // ----------------------------------------------------------------------------- auto PythonMethodsApp::GetMethods() -> std::vector { @@ -1702,6 +1748,8 @@ auto PythonMethodsApp::GetMethods() -> std::vector { PyDevConsoleInputAdapterFinishDef, PyAudioShutdownBeginDef, PyAudioShutdownIsCompleteDef, + PyGraphicsShutdownBeginDef, + PyGraphicsShutdownIsCompleteDef, }; } diff --git a/src/ballistica/base/python/methods/python_methods_graphics.cc b/src/ballistica/base/python/methods/python_methods_graphics.cc index 63eac46f..1e489461 100644 --- a/src/ballistica/base/python/methods/python_methods_graphics.cc +++ b/src/ballistica/base/python/methods/python_methods_graphics.cc @@ -536,6 +536,7 @@ static auto PyFadeScreen(PyObject* self, PyObject* args, PyObject* keywds) &endcall)) { return nullptr; } + BA_PRECONDITION(g_base->InLogicThread()); g_base->graphics->FadeScreen(static_cast(fade), static_cast(1000.0f * time), endcall); Py_RETURN_NONE; diff --git a/src/ballistica/base/support/app_timer.h b/src/ballistica/base/support/app_timer.h index e116c26a..966ef01b 100644 --- a/src/ballistica/base/support/app_timer.h +++ b/src/ballistica/base/support/app_timer.h @@ -13,12 +13,17 @@ namespace ballistica::base { class AppTimer : public Object { public: - AppTimer(millisecs_t length, bool repeat, - const Object::Ref& runnable) { + AppTimer(millisecs_t length, bool repeat, Runnable* runnable) { assert(g_base->InLogicThread()); timer_id_ = base::g_base->logic->NewAppTimer(length, repeat, runnable); } + template + static auto New(millisecs_t length, bool repeat, const F& lambda) { + return Object::New(length, repeat, + NewLambdaRunnable(lambda).Get()); + } + void SetLength(millisecs_t length) { assert(g_base->InLogicThread()); base::g_base->logic->SetAppTimerLength(timer_id_, length); @@ -32,13 +37,6 @@ class AppTimer : public Object { int timer_id_; }; -/// Create a AppTimer from a raw lambda. -template -auto NewAppTimer(millisecs_t length, bool repeat, const F& lambda) - -> Object::Ref { - return Object::New(length, repeat, NewLambdaRunnable(lambda)); -} - } // namespace ballistica::base #endif // BALLISTICA_BASE_SUPPORT_APP_TIMER_H_ diff --git a/src/ballistica/base/support/repeater.cc b/src/ballistica/base/support/repeater.cc new file mode 100644 index 00000000..1593da15 --- /dev/null +++ b/src/ballistica/base/support/repeater.cc @@ -0,0 +1,58 @@ +// Released under the MIT License. See LICENSE for details. + +#include "ballistica/base/support/repeater.h" + +#include "ballistica/base/app_adapter/app_adapter.h" +#include "ballistica/base/support/app_timer.h" +#include "ballistica/shared/foundation/event_loop.h" + +namespace ballistica::base { +Repeater::Repeater(seconds_t initial_delay, seconds_t repeat_delay, + Runnable* runnable) + : initial_delay_(initial_delay), + repeat_delay_(repeat_delay), + runnable_(runnable) { + assert(g_base->InLogicThread()); + assert(initial_delay >= 0.0); + assert(repeat_delay >= 0.0); + + // Let's go ahead and run our initial time in a deferred call; + // this is generally safer than running in the middle of whatever UI + // code set this up. + auto weak_this = Object::WeakRef(this); + g_base->logic->event_loop()->PushCall([weak_this] { + if (weak_this.Exists()) { + weak_this->runnable_->RunAndLogErrors(); + if (!weak_this.Exists()) { + // Runnable might have killed us. + return; + } + // Kick off our initial delay timer (generally the longer one). + weak_this->timer_ = AppTimer::New( + static_cast(weak_this->initial_delay_ * 1000.0), false, + [weak_this] { + // Timer should not have fired if we died. + assert(weak_this.Exists()); + weak_this->runnable_->RunAndLogErrors(); + if (!weak_this.Exists()) { + // Runnable might have killed us. + return; + } + // Kick off our repeat timer (generally the short one). + weak_this->timer_ = AppTimer::New( + static_cast(weak_this->repeat_delay_ * 1000.0), + true, [weak_this] { + // Timer should not have fired if we died. + assert(weak_this.Exists()); + weak_this->runnable_->RunAndLogErrors(); + // Doesn't matter if Runnable killed us since we don't + // touch anything for the remainder of this function. + }); + }); + } + }); +} + +Repeater::~Repeater() { assert(g_base->InLogicThread()); } + +} // namespace ballistica::base diff --git a/src/ballistica/base/support/repeater.h b/src/ballistica/base/support/repeater.h new file mode 100644 index 00000000..19355a38 --- /dev/null +++ b/src/ballistica/base/support/repeater.h @@ -0,0 +1,35 @@ +// Released under the MIT License. See LICENSE for details. + +#ifndef BALLISTICA_BASE_SUPPORT_REPEATER_H_ +#define BALLISTICA_BASE_SUPPORT_REPEATER_H_ + +#include "ballistica/base/base.h" +#include "ballistica/shared/foundation/object.h" +#include "ballistica/shared/generic/lambda_runnable.h" + +namespace ballistica::base { + +/// Runs some code immediately and then repeatedly after a delay. Useful for +/// jobs such as selecting ui elements while keys or buttons are held. +class Repeater : public Object { + public: + Repeater(seconds_t initial_delay, seconds_t repeat_delay, Runnable* runnable); + ~Repeater(); + + template + static auto New(seconds_t initial_delay, seconds_t repeat_delay, + const F& lambda) { + return Object::New(initial_delay, repeat_delay, + NewLambdaRunnable(lambda).Get()); + } + + private: + seconds_t initial_delay_; + seconds_t repeat_delay_; + Object::Ref timer_; + Object::Ref runnable_; +}; + +} // namespace ballistica::base + +#endif // BALLISTICA_BASE_SUPPORT_REPEATER_H_ diff --git a/src/ballistica/base/ui/dev_console.cc b/src/ballistica/base/ui/dev_console.cc index 8b7dc05a..f0ac5365 100644 --- a/src/ballistica/base/ui/dev_console.cc +++ b/src/ballistica/base/ui/dev_console.cc @@ -2,6 +2,7 @@ #include "ballistica/base/ui/dev_console.h" +#include "ballistica/base/app_adapter/app_adapter.h" #include "ballistica/base/app_mode/app_mode.h" #include "ballistica/base/audio/audio.h" #include "ballistica/base/graphics/component/simple_component.h" @@ -11,6 +12,7 @@ #include "ballistica/base/logic/logic.h" #include "ballistica/base/platform/base_platform.h" #include "ballistica/base/python/base_python.h" +#include "ballistica/base/support/repeater.h" #include "ballistica/base/ui/ui.h" #include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/generic/utils.h" @@ -20,14 +22,12 @@ namespace ballistica::base { // How much of the screen the console covers when it is at full size. -const float kDevConsoleSize = 0.9f; -const int kDevConsoleLineLimit = 80; -const int kDevConsoleStringBreakUpSize = 1950; -const int kDevConsoleActivateKey1 = SDLK_BACKQUOTE; -const int kDevConsoleActivateKey2 = SDLK_F2; +const float kDevConsoleSize{0.9f}; +const int kDevConsoleLineLimit{80}; +const int kDevConsoleStringBreakUpSize{1950}; const float kDevConsoleTabButtonCornerRadius{16.0f}; -const double kTransitionSeconds = 0.15; +const double kTransitionSeconds{0.15}; enum class DevConsoleHAnchor_ { kLeft, kCenter, kRight }; enum class DevButtonStyle_ { kNormal, kDark }; @@ -171,11 +171,7 @@ class DevConsole::Text_ : public DevConsole::Widget_ { } void Draw(RenderPass* pass, float bottom) override { - Vector3f fgcolor; - Vector3f bgcolor; - fgcolor = Vector3f{0.8f, 0.7f, 0.8f}; - bgcolor = Vector3f{0.25, 0.2f, 0.3f}; - + auto fgcolor = Vector3f{0.8f, 0.7f, 0.8f}; DrawText(pass, &text_group, scale, bottom, x + XOffs(h_attach), y, fgcolor); } }; @@ -327,7 +323,6 @@ class DevConsole::ToggleButton_ : public DevConsole::Widget_ { void Draw(RenderPass* pass, float bottom) override { DrawRect(pass, &mesh, bottom, x + XOffs(attach), y, width, height, - pressed ? Vector3f{0.5f, 0.2f, 1.0f} : on ? Vector3f{0.5f, 0.4f, 0.6f} : Vector3f{0.25, 0.2f, 0.3f}); @@ -448,14 +443,11 @@ DevConsole::DevConsole() { if (g_buildconfig.test_build()) { title += " (test)"; } - title_text_group_.SetText(title); built_text_group_.SetText("Built: " __DATE__ " " __TIME__); prompt_text_group_.SetText(">"); } -DevConsole::~DevConsole() = default; - void DevConsole::RefreshTabButtons_() { // IMPORTANT: This code should always be run in its own top level call and // never directly from user code. Otherwise we can wind up mucking with @@ -695,22 +687,25 @@ void DevConsole::InputAdapterFinish() { auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool { assert(g_base->InLogicThread()); + // Any presses or releases cancels repeat actions. + key_repeater_.Clear(); + // Handle our toggle buttons no matter whether we're active. - switch (keysym->sym) { - case kDevConsoleActivateKey1: - case kDevConsoleActivateKey2: { - if (!g_buildconfig.demo_build() && !g_buildconfig.arcade_build()) { - // (reset input so characters don't continue walking and stuff) - g_base->input->ResetHoldStates(); - if (auto console = g_base->ui->dev_console()) { - console->ToggleState(); - } - } - return true; - } - default: - break; - } + // switch (keysym->sym) { + // case kDevConsoleActivateKey1: + // case kDevConsoleActivateKey2: { + // if (!g_buildconfig.demo_build() && !g_buildconfig.arcade_build()) { + // // (reset input so characters don't continue walking and stuff) + // g_base->input->ResetHoldStates(); + // if (auto console = g_base->ui->dev_console()) { + // console->ToggleState(); + // } + // } + // return true; + // } + // default: + // break; + // } if (state_ == State_::kInactive) { return false; @@ -725,18 +720,21 @@ auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool { break; } - // Handle some stuff only with the Python terminal visible. - if (python_terminal_visible_) { + // If we support direct keyboard input, and python terminal is showing, + // handle some keys directly. + if (python_terminal_visible_ && g_base->ui->UIHasDirectKeyboardInput()) { switch (keysym->sym) { - case SDLK_BACKSPACE: - case SDLK_DELETE: { - std::vector unichars = - Utils::UnicodeFromUTF8(input_string_, "fjco38"); - if (!unichars.empty()) { - unichars.resize(unichars.size() - 1); - input_string_ = Utils::UTF8FromUnicode(unichars); - input_text_dirty_ = true; - } + case SDLK_BACKSPACE: { + key_repeater_ = Repeater::New( + g_base->app_adapter->GetKeyRepeatDelay(), + g_base->app_adapter->GetKeyRepeatInterval(), [this] { + auto unichars = Utils::UnicodeFromUTF8(input_string_, "fjco38"); + if (!unichars.empty()) { + unichars.resize(unichars.size() - 1); + input_string_ = Utils::UTF8FromUnicode(unichars); + input_text_dirty_ = true; + } + }); break; } case SDLK_UP: @@ -772,8 +770,20 @@ auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool { break; } } + return true; } - return true; + + // By default don't claim key events; we want to be able to show the + // console while still playing/navigating normally. + return false; +} + +auto DevConsole::HandleKeyRelease(const SDL_Keysym* keysym) -> bool { + // Any presses or releases cancels repeat actions. + key_repeater_.Clear(); + + // Otherwise absorb *all* key-ups when we're active. + return state_ != State_::kInactive; } void DevConsole::Exec() { @@ -879,17 +889,6 @@ auto DevConsole::HandleTextEditing(const std::string& text) -> bool { return true; } -auto DevConsole::HandleKeyRelease(const SDL_Keysym* keysym) -> bool { - // Always absorb our activate keys. - if (keysym->sym == kDevConsoleActivateKey1 - || keysym->sym == kDevConsoleActivateKey2) { - return true; - } - - // Otherwise absorb *all* key-ups when we're active. - return state_ != State_::kInactive; -} - void DevConsole::Print(const std::string& s_in) { assert(g_base->InLogicThread()); std::string s = Utils::GetValidUTF8(s_in.c_str(), "cspr"); diff --git a/src/ballistica/base/ui/dev_console.h b/src/ballistica/base/ui/dev_console.h index c362ed6d..e7134c70 100644 --- a/src/ballistica/base/ui/dev_console.h +++ b/src/ballistica/base/ui/dev_console.h @@ -20,7 +20,6 @@ const float kDevConsoleZDepth = 0.0f; class DevConsole { public: DevConsole(); - ~DevConsole(); auto IsActive() const -> bool { return (state_ != State_::kInactive); } auto HandleTextEditing(const std::string& text) -> bool; auto HandleKeyPress(const SDL_Keysym* keysym) -> bool; @@ -82,18 +81,18 @@ class DevConsole { void RefreshTabButtons_(); void RefreshTabContents_(); - bool input_text_dirty_{true}; - bool input_enabled_{}; - bool last_line_mesh_dirty_{true}; - bool python_terminal_visible_{}; - bool python_terminal_pressed_{}; - bool refresh_pending_{}; - int ui_lock_count_{}; + int input_history_position_{}; + int ui_lock_count_ : 1 {}; + bool input_text_dirty_ : 1 {true}; + bool input_enabled_ : 1 {}; + bool last_line_mesh_dirty_ : 1 {true}; + bool python_terminal_visible_ : 1 {}; + bool python_terminal_pressed_ : 1 {}; + bool refresh_pending_ : 1 {}; + double transition_start_{}; State_ state_{State_::kInactive}; State_ state_prev_{State_::kInactive}; millisecs_t last_input_text_change_time_{}; - double transition_start_{}; - int input_history_position_{}; ImageMesh bg_mesh_; ImageMesh stripe_mesh_; ImageMesh border_mesh_; @@ -103,15 +102,15 @@ class DevConsole { TextGroup input_text_group_; std::string last_line_; std::string input_string_; - std::list tabs_{"Python", "AppModes", "Logging", "Graphics", - "UI"}; - std::string active_tab_{"Python"}; + std::list tabs_; + std::string active_tab_; PythonRef string_edit_adapter_; - Object::Ref last_line_mesh_group_; std::list input_history_; std::list output_lines_; std::vector > widgets_; std::vector > tab_buttons_; + Object::Ref last_line_mesh_group_; + Object::Ref key_repeater_; }; } // namespace ballistica::base diff --git a/src/ballistica/base/ui/ui.cc b/src/ballistica/base/ui/ui.cc index e9b50093..06930e4c 100644 --- a/src/ballistica/base/ui/ui.cc +++ b/src/ballistica/base/ui/ui.cc @@ -194,7 +194,7 @@ auto UI::UIHasDirectKeyboardInput() const -> bool { // we'll probably want to pop up a controller-centric on-screen-keyboard // thingie instead. auto* ui_input_device = g_base->ui->GetUIInputDevice(); - if (auto* keyboard = g_base->ui->GetUIInputDevice()) { + if (auto* keyboard = g_base->input->keyboard_input()) { if (ui_input_device == keyboard || ui_input_device == nullptr) { return true; } @@ -532,7 +532,7 @@ void UI::OnAssetsAvailable() { assert(g_base->InLogicThread()); // Spin up the dev console. - if (!g_core->HeadlessMode()) { + if (!g_core->HeadlessMode() && !g_buildconfig.demo_build()) { assert(dev_console_ == nullptr); dev_console_ = new DevConsole(); diff --git a/src/ballistica/base/ui/ui.h b/src/ballistica/base/ui/ui.h index 02cecbb7..7ed5dd92 100644 --- a/src/ballistica/base/ui/ui.h +++ b/src/ballistica/base/ui/ui.h @@ -17,6 +17,14 @@ class Widget; namespace ballistica::base { +/// Delay before moving through elements in the UI when a key/button/stick +/// is held +const seconds_t kUINavigationRepeatDelay{0.25}; + +/// Interval after the initial delay when moving through UI elements when a +/// key/button/stick is held. +const seconds_t kUINavigationRepeatInterval{0.1}; + // Our global UI subsystem. This acts as a manager/wrapper for individual UI // feature-sets that provide specific UI functionality. class UI { @@ -84,10 +92,10 @@ class UI { auto GetUIInputDevice() const -> InputDevice*; /// Return true if there is a full desktop-style hardware keyboard - /// attached and the active UI InputDevice is set to it or not set. This + /// attached and no non-keyboard device is currently controlling the UI. This /// also may take language or user preferences into account. Editable text /// elements can use this to opt in to accepting key events directly - /// instead of popping up a string edit dialog. + /// instead of popping up string edit dialogs. auto UIHasDirectKeyboardInput() const -> bool; /// Schedule a back button press. Can be called from any thread. diff --git a/src/ballistica/core/platform/apple/core_platform_apple.cc b/src/ballistica/core/platform/apple/core_platform_apple.cc index 04d79880..c864f3ad 100644 --- a/src/ballistica/core/platform/apple/core_platform_apple.cc +++ b/src/ballistica/core/platform/apple/core_platform_apple.cc @@ -4,7 +4,6 @@ #include "ballistica/core/platform/apple/core_platform_apple.h" #if BA_XCODE_BUILD -#include #include #endif @@ -12,6 +11,14 @@ #if BA_XCODE_BUILD #include "ballistica/base/platform/apple/apple_utils.h" +#include "ballistica/base/platform/apple/from_swift.h" +#include "ballistica/shared/math/rect.h" +#endif + +#if BA_XCODE_BUILD +// This needs to be below ballistica headers since it relies on +// some types in them but does not include headers itself. +#include #endif namespace ballistica::core { @@ -31,7 +38,7 @@ auto CorePlatformApple::GetDeviceV1AccountUUIDPrefix() -> std::string { // Legacy for device-accounts; don't modify this code. auto CorePlatformApple::GetRealLegacyDeviceUUID(std::string* uuid) -> bool { #if BA_OSTYPE_MACOS && BA_XCODE_BUILD - *uuid = base::AppleUtils::GetMacUUID(); + *uuid = std::string(BallisticaKit::CocoaFromCpp::GetLegacyDeviceUUID()); return true; #endif #if BA_OSTYPE_IOS_TVOS @@ -69,7 +76,8 @@ auto CorePlatformApple::GetDeviceUUIDInputs() -> std::list { std::list out; #if BA_OSTYPE_MACOS #if BA_XCODE_BUILD - out.push_back(base::AppleUtils::GetMacUUID()); + out.push_back( + std::string(BallisticaKit::CocoaFromCpp::GetLegacyDeviceUUID())); #else // BA_XCODE_BUILD out.push_back(GetMacUUIDFallback()); #endif // BA_XCODE_BUILD @@ -96,28 +104,13 @@ auto CorePlatformApple::DoGetConfigDirectoryMonolithicDefault() printf("FIXME: get proper default-config-dir\n"); return std::string(getenv("HOME")) + "/Library"; #elif BA_OSTYPE_MACOS && BA_XCODE_BUILD - return base::AppleUtils::GetApplicationSupportPath() + "/BallisticaKit"; + return std::string(BallisticaKit::CocoaFromCpp::GetApplicationSupportPath()) + + "/BallisticaKit"; #else return CorePlatform::DoGetConfigDirectoryMonolithicDefault(); #endif } -auto CorePlatformApple::GetLocale() -> std::string { -#if BA_XCODE_BUILD - return BallisticaKit::FromCppGetLocaleString(); -#else - return CorePlatform::GetLocale(); -#endif -} - -auto CorePlatformApple::DoGetDeviceName() -> std::string { -#if BA_OSTYPE_MACOS && BA_XCODE_BUILD - return base::AppleUtils::GetDeviceName(); -#else - return CorePlatform::DoGetDeviceName(); -#endif -} - auto CorePlatformApple::DoHasTouchScreen() -> bool { #if BA_OSTYPE_IOS return true; @@ -134,7 +127,7 @@ auto CorePlatformApple::GetDefaultUIScale() -> UIScale { return UIScale::kSmall; } #else - // Default case handles mac & tvos. + // The default case handles mac & tvos. return CorePlatform::GetDefaultUIScale(); #endif } @@ -163,37 +156,37 @@ void CorePlatformApple::EmitPlatformLog(const std::string& name, LogLevel level, auto CorePlatformApple::DoGetDataDirectoryMonolithicDefault() -> std::string { #if BA_XCODE_BUILD - return BallisticaKit::FromCppGetResourcesPath(); + return BallisticaKit::FromCpp::GetResourcesPath(); #else // Fall back to default. return CorePlatform::DoGetDataDirectoryMonolithicDefault(); #endif } -void CorePlatformApple::GetTextBoundsAndWidth(const std::string& text, Rect* r, - float* width) { -#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD - base::AppleUtils::GetTextBoundsAndWidth(text, r, width); -#else - CorePlatform::GetTextBoundsAndWidth(text, r, width); +#if BA_XCODE_BUILD +class TextTextureWrapper_ { + public: + TextTextureWrapper_(int width, int height, + const std::vector& strings, + const std::vector& positions, + const std::vector& widths, float scale) + : data{BallisticaKit::TextTextureData::init(width, height, strings, + positions, widths, scale)} {} + BallisticaKit::TextTextureData data; +}; #endif -} - -void CorePlatformApple::FreeTextTexture(void* tex) { -#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD - base::AppleUtils::FreeTextTexture(tex); -#else - CorePlatform::FreeTextTexture(tex); -#endif -} auto CorePlatformApple::CreateTextTexture( int width, int height, const std::vector& strings, const std::vector& positions, const std::vector& widths, float scale) -> void* { #if BA_XCODE_BUILD && !BA_HEADLESS_BUILD - return base::AppleUtils::CreateTextTexture(width, height, strings, positions, - widths, scale); + auto* wrapper = + new TextTextureWrapper_(width, height, strings, positions, widths, scale); + // wrapper->old = base::AppleUtils::CreateTextTexture(width, height, strings, + // positions, widths, + // scale); + return wrapper; #else return CorePlatform::CreateTextTexture(width, height, strings, positions, widths, scale); @@ -202,12 +195,45 @@ auto CorePlatformApple::CreateTextTexture( auto CorePlatformApple::GetTextTextureData(void* tex) -> uint8_t* { #if BA_XCODE_BUILD && !BA_HEADLESS_BUILD - return base::AppleUtils::GetTextTextureData(tex); + auto* wrapper = static_cast(tex); + return static_cast(wrapper->data.getTextTextureData()); + // return base::AppleUtils::GetTextTextureData(wrapper->old); #else return CorePlatform::GetTextTextureData(tex); #endif } +void CorePlatformApple::GetTextBoundsAndWidth(const std::string& text, Rect* r, + float* width) { +#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD + + auto vals = BallisticaKit::TextTextureData::getTextBoundsAndWidth(text); + assert(vals.getCount() == 5); + r->l = vals[0]; + r->r = vals[1]; + r->b = vals[2]; + r->t = vals[3]; + *width = vals[4]; + +// base::AppleUtils::GetTextBoundsAndWidth(text, r, width); +// printf("GOT BOUNDS l=%.2f r=%.2f b=%.2f t=%.2f w=%.2f\n", r->l, r->r, r->b, +// r->t, *width); printf("SWIFT BOUNDS l=%.2f r=%.2f b=%.2f t=%.2f w=%.2f\n", +// vals[0], vals[1], vals[2], vals[3], vals[4]); +#else + CorePlatform::GetTextBoundsAndWidth(text, r, width); +#endif +} + +void CorePlatformApple::FreeTextTexture(void* tex) { +#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD + auto* wrapper = static_cast(tex); + // base::AppleUtils::FreeTextTexture(wrapper->old); + delete wrapper; +#else + CorePlatform::FreeTextTexture(tex); +#endif +} + void CorePlatformApple::SubmitScore(const std::string& game, const std::string& version, int64_t score) { #if BA_USE_GAME_CENTER @@ -278,15 +304,18 @@ void CorePlatformApple::GameCenterLogin() { auto CorePlatformApple::IsOSPlayingMusic() -> bool { #if BA_XCODE_BUILD - return base::AppleUtils::IsMusicPlaying(); + // FIXME - should look into doing this properly these days, or whether + // this is still needed at all. + return false; + // return base::AppleUtils::IsMusicPlaying(); #else return CorePlatform::IsOSPlayingMusic(); #endif } void CorePlatformApple::OpenFileExternally(const std::string& path) { -#if BA_XCODE_BUILD - base::AppleUtils::EditTextFile(path.c_str()); +#if BA_OSTYPE_MACOS && BA_XCODE_BUILD + BallisticaKit::CocoaFromCpp::OpenFileExternally(path); #else CorePlatform::OpenFileExternally(path); #endif @@ -294,7 +323,7 @@ void CorePlatformApple::OpenFileExternally(const std::string& path) { void CorePlatformApple::OpenDirExternally(const std::string& path) { #if BA_OSTYPE_MACOS && BA_XCODE_BUILD - BallisticaKit::CocoaFromCppOpenDirExternally(path); + BallisticaKit::CocoaFromCpp::OpenDirExternally(path); #else CorePlatform::OpenDirExternally(path); #endif diff --git a/src/ballistica/core/platform/apple/core_platform_apple.h b/src/ballistica/core/platform/apple/core_platform_apple.h index 6419215d..93923c0a 100644 --- a/src/ballistica/core/platform/apple/core_platform_apple.h +++ b/src/ballistica/core/platform/apple/core_platform_apple.h @@ -21,8 +21,8 @@ class CorePlatformApple : public CorePlatform { auto GenerateUUID() -> std::string override; auto DoGetConfigDirectoryMonolithicDefault() -> std::optional override; - auto GetLocale() -> std::string override; - auto DoGetDeviceName() -> std::string override; + // auto GetLocale() -> std::string override; + // auto DoGetDeviceName() -> std::string override; auto DoHasTouchScreen() -> bool override; auto GetDefaultUIScale() -> UIScale override; auto IsRunningOnDesktop() -> bool override; diff --git a/src/ballistica/core/platform/support/min_sdl.h b/src/ballistica/core/platform/support/min_sdl.h index b827e46d..e20e4010 100644 --- a/src/ballistica/core/platform/support/min_sdl.h +++ b/src/ballistica/core/platform/support/min_sdl.h @@ -328,7 +328,7 @@ typedef enum { #define SDLK_SCANCODE_MASK (1 << 30) #define SDL_SCANCODE_TO_KEYCODE(X) (X | SDLK_SCANCODE_MASK) -enum { +enum SDL_KeycodeEnum { SDLK_UNKNOWN = 0, SDLK_RETURN = '\r', diff --git a/src/ballistica/scene_v1/python/class/python_class_base_timer.cc b/src/ballistica/scene_v1/python/class/python_class_base_timer.cc index 0e095b7a..faa8f8d6 100644 --- a/src/ballistica/scene_v1/python/class/python_class_base_timer.cc +++ b/src/ballistica/scene_v1/python/class/python_class_base_timer.cc @@ -94,7 +94,7 @@ auto PythonClassBaseTimer::tp_new(PyTypeObject* type, PyObject* args, self->timer_id_ = SceneV1Context::Current().NewTimer( TimeType::kBase, static_cast(length * 1000.0), static_cast(repeat), - Object::New(call_obj)); + Object::New(call_obj).Get()); self->have_timer_ = true; return reinterpret_cast(self); diff --git a/src/ballistica/scene_v1/python/class/python_class_scene_timer.cc b/src/ballistica/scene_v1/python/class/python_class_scene_timer.cc index e505b6ad..f9a8c608 100644 --- a/src/ballistica/scene_v1/python/class/python_class_scene_timer.cc +++ b/src/ballistica/scene_v1/python/class/python_class_scene_timer.cc @@ -100,7 +100,7 @@ auto PythonClassSceneTimer::tp_new(PyTypeObject* type, PyObject* args, self->timer_id_ = SceneV1Context::Current().NewTimer( TimeType::kSim, static_cast(length * 1000.0), static_cast(repeat), - Object::New(call_obj)); + Object::New(call_obj).Get()); self->have_timer_ = true; return reinterpret_cast(self); diff --git a/src/ballistica/scene_v1/python/methods/python_methods_scene.cc b/src/ballistica/scene_v1/python/methods/python_methods_scene.cc index 77b887bb..ab7e2e73 100644 --- a/src/ballistica/scene_v1/python/methods/python_methods_scene.cc +++ b/src/ballistica/scene_v1/python/methods/python_methods_scene.cc @@ -98,7 +98,7 @@ static auto PyTimer(PyObject* self, PyObject* args, PyObject* keywds) SceneV1Context::Current().NewTimer( TimeType::kSim, static_cast(length * 1000.0), static_cast(repeat), - Object::New(call_obj)); + Object::New(call_obj).Get()); Py_RETURN_NONE; BA_PYTHON_CATCH; @@ -207,7 +207,7 @@ static auto PyBaseTimer(PyObject* self, PyObject* args, PyObject* keywds) SceneV1Context::Current().NewTimer( TimeType::kBase, static_cast(length * 1000.0), static_cast(repeat), - Object::New(call_obj)); + Object::New(call_obj).Get()); Py_RETURN_NONE; BA_PYTHON_CATCH; diff --git a/src/ballistica/scene_v1/support/host_activity.cc b/src/ballistica/scene_v1/support/host_activity.cc index b357fbb7..17fe55bd 100644 --- a/src/ballistica/scene_v1/support/host_activity.cc +++ b/src/ballistica/scene_v1/support/host_activity.cc @@ -178,7 +178,7 @@ void HostActivity::Start() { // Create our step timer - gets called whenever scene should step. step_scene_timer_id_ = host_session->NewTimer(TimeType::kBase, kGameStepMilliseconds, true, - NewLambdaRunnable([this] { StepScene(); })); + NewLambdaRunnable([this] { StepScene(); }).Get()); session_base_timer_ids_.push_back(step_scene_timer_id_); UpdateStepTimerLength(); } @@ -363,7 +363,7 @@ auto HostActivity::globals_node() const -> GlobalsNode* { } auto HostActivity::NewSimTimer(millisecs_t length, bool repeat, - const Object::Ref& runnable) -> int { + Runnable* runnable) -> int { if (shutting_down_) { BA_LOG_PYTHON_TRACE_ONCE( "WARNING: Creating game timer during host-activity shutdown"); @@ -384,7 +384,7 @@ auto HostActivity::NewSimTimer(millisecs_t length, bool repeat, } auto HostActivity::NewBaseTimer(millisecs_t length, bool repeat, - const Object::Ref& runnable) -> int { + Runnable* runnable) -> int { if (shutting_down_) { BA_LOG_PYTHON_TRACE_ONCE( "WARNING: Creating session-time timer during host-activity shutdown"); @@ -543,10 +543,10 @@ void HostActivity::DumpFullState(SessionStream* out) { } auto HostActivity::NewTimer(TimeType timetype, TimerMedium length, bool repeat, - const Object::Ref& runnable) -> int { + Runnable* runnable) -> int { // Make sure the runnable passed in is reference-managed already. // (we may not add an initial reference ourself) - assert(runnable.IsValidManagedObject()); + assert(Object::IsValidManagedObject(runnable)); // We currently support game and base timers. switch (timetype) { diff --git a/src/ballistica/scene_v1/support/host_activity.h b/src/ballistica/scene_v1/support/host_activity.h index 5f583ff9..507e75d0 100644 --- a/src/ballistica/scene_v1/support/host_activity.h +++ b/src/ballistica/scene_v1/support/host_activity.h @@ -25,7 +25,7 @@ class HostActivity : public SceneV1Context { // ContextTarget time/timer support. auto NewTimer(TimeType timetype, TimerMedium length, bool repeat, - const Object::Ref& runnable) -> int override; + Runnable* runnable) -> int override; void DeleteTimer(TimeType timetype, int timer_id) override; auto GetTime(TimeType timetype) -> millisecs_t override; @@ -77,11 +77,9 @@ class HostActivity : public SceneV1Context { private: void HandleOutOfBoundsNodes(); - auto NewSimTimer(millisecs_t length, bool repeat, - const Object::Ref& runnable) -> int; + auto NewSimTimer(millisecs_t length, bool repeat, Runnable* runnable) -> int; void DeleteSimTimer(int timer_id); - auto NewBaseTimer(millisecs_t length, bool repeat, - const Object::Ref& runnable) -> int; + auto NewBaseTimer(millisecs_t length, bool repeat, Runnable* runnable) -> int; void DeleteBaseTimer(int timer_id); void UpdateStepTimerLength(); void StepScene(); diff --git a/src/ballistica/scene_v1/support/host_session.cc b/src/ballistica/scene_v1/support/host_session.cc index a5d04506..9c726733 100644 --- a/src/ballistica/scene_v1/support/host_session.cc +++ b/src/ballistica/scene_v1/support/host_session.cc @@ -40,7 +40,7 @@ HostSession::HostSession(PyObject* session_type_obj) // Create a timer to step our session scene. step_scene_timer_ = base_timers_.NewTimer(base_time_millisecs_, kGameStepMilliseconds, 0, -1, - NewLambdaRunnable([this] { StepScene(); })); + NewLambdaRunnable([this] { StepScene(); }).Get()); // Set up our output-stream, which will go to a replay and/or the network. // We don't dump to a replay if we're doing the main menu; that replay @@ -766,8 +766,8 @@ void HostSession::GetCorrectionMessages( } auto HostSession::NewTimer(TimeType timetype, TimerMedium length, bool repeat, - const Object::Ref& runnable) -> int { - assert(runnable.IsValidManagedObject()); + Runnable* runnable) -> int { + assert(Object::IsValidManagedObject(runnable)); // We currently support game and base timers. switch (timetype) { diff --git a/src/ballistica/scene_v1/support/host_session.h b/src/ballistica/scene_v1/support/host_session.h index d17de58d..cf53cccf 100644 --- a/src/ballistica/scene_v1/support/host_session.h +++ b/src/ballistica/scene_v1/support/host_session.h @@ -38,7 +38,7 @@ class HostSession : public Session { // ContextTarget time/timer support auto NewTimer(TimeType timetype, TimerMedium length, bool repeat, - const Object::Ref& runnable) -> int override; + Runnable* runnable) -> int override; void DeleteTimer(TimeType timetype, int timer_id) override; auto GetTime(TimeType timetype) -> millisecs_t override; diff --git a/src/ballistica/scene_v1/support/scene_v1_context.cc b/src/ballistica/scene_v1/support/scene_v1_context.cc index d4f8f9c5..fa3becf2 100644 --- a/src/ballistica/scene_v1/support/scene_v1_context.cc +++ b/src/ballistica/scene_v1/support/scene_v1_context.cc @@ -55,12 +55,10 @@ auto SceneV1Context::GetAsHostActivity() -> HostActivity* { return nullptr; } auto SceneV1Context::GetMutableScene() -> Scene* { return nullptr; } auto SceneV1Context::NewTimer(TimeType timetype, TimerMedium length, - bool repeat, - const Object::Ref& runnable) -> int { + bool repeat, Runnable* runnable) -> int { // Make sure the passed runnable has a ref-count already // (don't want them to rely on us to create initial one). - assert(runnable.Exists()); - assert(Object::IsValidManagedObject(runnable.Get())); + assert(Object::IsValidManagedObject(runnable)); switch (timetype) { case TimeType::kSim: diff --git a/src/ballistica/scene_v1/support/scene_v1_context.h b/src/ballistica/scene_v1/support/scene_v1_context.h index 16414ecd..60e27916 100644 --- a/src/ballistica/scene_v1/support/scene_v1_context.h +++ b/src/ballistica/scene_v1/support/scene_v1_context.h @@ -61,7 +61,7 @@ class SceneV1Context : public base::Context { // Default NewTimer implementation throws a descriptive error, so it can // be useful to fall back on for unsupported cases. virtual auto NewTimer(TimeType timetype, TimerMedium length, bool repeat, - const Object::Ref& runnable) -> int; + Runnable* runnable) -> int; virtual void DeleteTimer(TimeType timetype, int timer_id); virtual auto GetTexture(const std::string& name) -> Object::Ref; diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 8a23ce50..747e6a9a 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 = 21491; +const int kEngineBuildNumber = 21510; const char* kEngineVersion = "1.7.28"; const int kEngineApiVersion = 8; diff --git a/src/ballistica/shared/foundation/event_loop.cc b/src/ballistica/shared/foundation/event_loop.cc index 9133557b..f6e9850d 100644 --- a/src/ballistica/shared/foundation/event_loop.cc +++ b/src/ballistica/shared/foundation/event_loop.cc @@ -607,11 +607,11 @@ auto EventLoop::AreEventLoopsSuspended() -> bool { return g_core->event_loops_suspended; } -auto EventLoop::NewTimer(millisecs_t length, bool repeat, - const Object::Ref& runnable) -> Timer* { +auto EventLoop::NewTimer(millisecs_t length, bool repeat, Runnable* runnable) + -> Timer* { assert(g_core); assert(ThreadIsCurrent()); - assert(runnable.Exists()); + assert(Object::IsValidManagedObject(runnable)); return timers_.NewTimer(g_core->GetAppTimeMillisecs(), length, 0, repeat ? -1 : 0, runnable); } diff --git a/src/ballistica/shared/foundation/event_loop.h b/src/ballistica/shared/foundation/event_loop.h index 40f03631..2551b00a 100644 --- a/src/ballistica/shared/foundation/event_loop.h +++ b/src/ballistica/shared/foundation/event_loop.h @@ -56,8 +56,7 @@ class EventLoop { auto identifier() const -> EventLoopID { return identifier_; } // Register a timer to run on the thread. - auto NewTimer(millisecs_t length, bool repeat, - const Object::Ref& runnable) -> Timer*; + auto NewTimer(millisecs_t length, bool repeat, Runnable* runnable) -> Timer*; Timer* GetTimer(int id); void DeleteTimer(int id); diff --git a/src/ballistica/shared/generic/timer_list.cc b/src/ballistica/shared/generic/timer_list.cc index 84dd97f5..7bcf8b27 100644 --- a/src/ballistica/shared/generic/timer_list.cc +++ b/src/ballistica/shared/generic/timer_list.cc @@ -160,10 +160,11 @@ auto TimerList::GetExpiredTimer(TimerMedium target_time) -> Timer* { auto TimerList::NewTimer(TimerMedium current_time, TimerMedium length, TimerMedium offset, int repeat_count, - const Object::Ref& runnable) -> Timer* { + Runnable* runnable) -> Timer* { assert(!are_clearing_); auto* t = new Timer(this, next_timer_id_++, current_time, length, offset, repeat_count); + assert(Object::IsValidManagedObject(runnable)); t->runnable_ = runnable; // Clion (correctly) points out that t may get deallocated in this call, diff --git a/src/ballistica/shared/generic/timer_list.h b/src/ballistica/shared/generic/timer_list.h index 6c0957e2..a954b759 100644 --- a/src/ballistica/shared/generic/timer_list.h +++ b/src/ballistica/shared/generic/timer_list.h @@ -22,8 +22,8 @@ class TimerList { // Create a timer with provided runnable. auto NewTimer(TimerMedium current_time, TimerMedium length, - TimerMedium offset, int repeat_count, - const Object::Ref& runnable) -> Timer*; + TimerMedium offset, int repeat_count, Runnable* runnable) + -> Timer*; // Return a timer by its id, or nullptr if the timer no longer exists. auto GetTimer(int id) -> Timer*; diff --git a/src/ballistica/ui_v1/widget/button_widget.cc b/src/ballistica/ui_v1/widget/button_widget.cc index 25300269..e8ce6a37 100644 --- a/src/ballistica/ui_v1/widget/button_widget.cc +++ b/src/ballistica/ui_v1/widget/button_widget.cc @@ -485,8 +485,8 @@ auto ButtonWidget::HandleMessage(const base::WidgetMessage& m) -> bool { pressed_ = true; if (repeat_) { - repeat_timer_ = - base::NewAppTimer(300, true, [this] { OnRepeatTimerExpired(); }); + repeat_timer_ = base::AppTimer::New( + 300, true, [this] { OnRepeatTimerExpired(); }); // If we're a repeat button we trigger immediately. // (waiting till mouse up sort of defeats the purpose here) diff --git a/src/ballistica/ui_v1/widget/h_scroll_widget.cc b/src/ballistica/ui_v1/widget/h_scroll_widget.cc index d94c89f8..c8d23371 100644 --- a/src/ballistica/ui_v1/widget/h_scroll_widget.cc +++ b/src/ballistica/ui_v1/widget/h_scroll_widget.cc @@ -405,7 +405,7 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool { // Top level touches eventually get passed as mouse-downs if no // scrolling has started. if (static_cast(m.type)) { - touch_delay_timer_ = base::NewAppTimer( + touch_delay_timer_ = base::AppTimer::New( 150, false, [this] { OnTouchDelayTimerExpired(); }); } diff --git a/src/ballistica/ui_v1/widget/scroll_widget.cc b/src/ballistica/ui_v1/widget/scroll_widget.cc index 6e83222f..de48022c 100644 --- a/src/ballistica/ui_v1/widget/scroll_widget.cc +++ b/src/ballistica/ui_v1/widget/scroll_widget.cc @@ -330,7 +330,7 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool { // After a short delay we go ahead and handle this as a regular // click if it hasn't turned into a scroll or a child scroll. if (!child_is_scrolling_) { - touch_delay_timer_ = base::NewAppTimer( + touch_delay_timer_ = base::AppTimer::New( 150, false, [this] { OnTouchDelayTimerExpired(); }); } } diff --git a/src/ballistica/ui_v1/widget/text_widget.cc b/src/ballistica/ui_v1/widget/text_widget.cc index edeeeaea..f6957b82 100644 --- a/src/ballistica/ui_v1/widget/text_widget.cc +++ b/src/ballistica/ui_v1/widget/text_widget.cc @@ -659,7 +659,6 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool { if (m.has_keysym && !ShouldUseStringEditor_()) { last_carat_change_time_millisecs_ = static_cast(g_base->logic->display_time() * 1000.0); - text_group_dirty_ = true; bool claimed = false; switch (m.keysym.sym) { @@ -724,112 +723,29 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool { break; } if (!claimed) { - // Pop in a char. - if (editable()) { - claimed = true; - - // #if BA_SDL2_BUILD || BA_MINSDL_BUILD - // // On SDL2, chars come through as TEXT_INPUT messages; - // // can ignore this. - // #else - // std::vector unichars = - // Utils::UnicodeFromUTF8(text_raw_, "2jf987"); - // int len = static_cast(unichars.size()); - - // if (len < max_chars_) { - // if ((m.keysym.unicode >= 32) && (m.keysym.sym != SDLK_TAB)) - // { - // claimed = true; - // int pos = carat_position_; - // if (pos > len) pos = len; - // unichars.insert(unichars.begin() + pos, - // m.keysym.unicode); text_raw_ = - // Utils::UTF8FromUnicode(unichars); text_translation_dirty_ - // = true; carat_position_++; - // } else { - // // These don't seem to come through cleanly as unicode: - // // FIXME - should re-check this on SDL2 builds - - // claimed = true; - // std::string s; - // uint32_t pos = carat_position_; - // if (pos > len) pos = len; - // switch (m.keysym.sym) { - // case SDLK_KP0: - // s = '0'; - // break; - // case SDLK_KP1: - // s = '1'; - // break; - // case SDLK_KP2: - // s = '2'; - // break; - // case SDLK_KP3: - // s = '3'; - // break; - // case SDLK_KP4: - // s = '4'; - // break; - // case SDLK_KP5: - // s = '5'; - // break; - // case SDLK_KP6: - // s = '6'; - // break; - // case SDLK_KP7: - // s = '7'; - // break; - // case SDLK_KP8: - // s = '8'; - // break; - // case SDLK_KP9: - // s = '9'; - // break; - // case SDLK_KP_PERIOD: - // s = '.'; - // break; - // case SDLK_KP_DIVIDE: - // s = '/'; - // break; - // case SDLK_KP_MULTIPLY: - // s = '*'; - // break; - // case SDLK_KP_MINUS: - // s = '-'; - // break; - // case SDLK_KP_PLUS: - // s = '+'; - // break; - // case SDLK_KP_EQUALS: - // s = '='; - // break; - // default: - // break; - // } - // if (s.size() > 0) { - // unichars.insert(unichars.begin() + pos, s[0]); - // text_raw_ = Utils::UTF8FromUnicode(unichars); - // text_translation_dirty_ = true; - // carat_position_++; - // } - // } - // } - // #endif // BA_SDL2_BUILD - } + // Direct text edits come through as seperate events, but we still + // want to claim key down events here; otherwise they'll do weird + // stuff like navigate to other widgets. + claimed = true; } return claimed; } switch (m.type) { case base::WidgetMessage::Type::kTextInput: { - // If we're using an edit dialog, any attempted text input just kicks us - // over to that. - if (editable() && ShouldUseStringEditor_()) { - InvokeStringEditor_(); - } else { - // Otherwise apply the text directly. - if (editable() && m.sval != nullptr) { - AddCharsToText_(*m.sval); - return true; + if (editable()) { + if (ShouldUseStringEditor_()) { + // Normally we shouldn't be getting direct text input events in + // situations where we're using string editors, but it still might + // be possible; for instance if a game controller is driving the + // ui when a key is typed. We simply ignore the event in that case + // because otherwise the text input would be fighting with the + // string-editor. + } else { + // Apply text directly. + if (m.sval != nullptr) { + AddCharsToText_(*m.sval); + return true; + } } } break;