more apple version work and cleaning up keyboard input

This commit is contained in:
Eric 2023-10-26 10:11:20 -07:00
parent a2ff9ab872
commit 2ec08d706e
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
66 changed files with 1112 additions and 1055 deletions

88
.efrocachemap generated
View File

@ -4056,50 +4056,50 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "ad13d636bcb25150044a7644846b8a09", "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "c98fdb967f44411171c3d7fde1c74471",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "7c5df955611590ef491bf614fbd60179", "build/prefab/full/linux_arm64_gui/release/ballisticakit": "5514854ae459adab968120a361692cd5",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "ae38bd212ae64b51482a2ccb9c1cbfd3", "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "83bcfb3af22b61e5c35972d989b3f7f8",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "d0bcee2dd5567719aa35667c5206dffc", "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "a63758734d93eb969fe482b56c8d1ed2",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "eb3cd4f86175afcf8ffa2749afa32fa3", "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "7e68a605645c70fca06e4f4d5155bb0c",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "6b86ba36c3719773008feaa6cdc0d0f8", "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "0598aa59718bb8404c3b007c22123c75",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "877c9ae4532fef809a3dcbd8ffea343c", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "9c86d900fc1bc2d6f3bdb4c3e26149da",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "7df313c48c87460f56fa837502965088", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "37f5996151f983b45925ad15dce2d0e9",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "bc40bb549d26437fb8679c1e9d088272", "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "b8560855bf772b3e74f2a8f190f54885",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "47aa08cb9f5e660023f0f3c0e4ffd65e", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "55c777fa3afd720089223fb949478502",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "bbb0a8383d6ce1ca887190ea49223f4f", "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "81a41ddca9da9060d5d5034a84845674",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "7dd91e3407d49981c1c975d4f01ac205", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "536825d983f4a3bcec018b0295005bdb",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "c87883aa2f832e792e945fd9208d712a", "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "1733a9610de15f77dd8df3effcca1516",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "fea8fd84d8c060f2f82f402902b8c54e", "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "f7434d47c371f0baaae676d288847351",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "0a454a8be47f37231655761d15e3f7e5", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "d16ea39c50f41117697789dafb249200",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "4c79db3a882eb0b8b225a8df0339b1cc", "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "55d18233c5b887af17afcb266a8c5597",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "43b9ef321f8e80da29ddb19a760dbd77", "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "c4df3b76ef890daf48e8181cffd4cd82",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "6f891004f2f07c452dea29bd53f29d30", "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "eb609d02a0897c671e40519faad3365b",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "bf7a1ce0e7a2015d538406c6f6df761c", "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "0c0d303a440e852d0112c3a5aa75ef36",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "cf213dce81901a67c9970b3befdaa320", "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "bdd6b2c15d6718a6c99ee287d965f022",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "473e7e6c0cf90b9e6ac653552b18f68d", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "becf7a70c7c0d7bb5bfe731dde5e0249",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "4e11b895cbf2e1339cf34bc06c54a4ea", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "c9ef036408f0832cd068a34365485e0b",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "473e7e6c0cf90b9e6ac653552b18f68d", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "becf7a70c7c0d7bb5bfe731dde5e0249",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "4e11b895cbf2e1339cf34bc06c54a4ea", "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "c9ef036408f0832cd068a34365485e0b",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "d9af1a429cff9346e0cad6fcea017e5b", "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "5bd8bcd68e03939501d192b7cda55d64",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "ae5f87286947575463c386cfe1c443e4", "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "a8545c0c985eef9219c351773c5c6127",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "d9af1a429cff9346e0cad6fcea017e5b", "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "5bd8bcd68e03939501d192b7cda55d64",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "ae5f87286947575463c386cfe1c443e4", "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "a8545c0c985eef9219c351773c5c6127",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "110eef3dc285a35a1899510e368c73b1", "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "1e2c088713c47ff47d9aa312ebb0bd1a",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "2692dc69f7cb2501f0aaa8675f559987", "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "e6bbe6c564c65cb498edc58daec1e084",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "110eef3dc285a35a1899510e368c73b1", "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "1e2c088713c47ff47d9aa312ebb0bd1a",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "2692dc69f7cb2501f0aaa8675f559987", "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "e6bbe6c564c65cb498edc58daec1e084",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "344954c4f788d7d9b4d7035ebb6131d8", "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "98df65aba607e74eb5c0c7305903ac29",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "48c4873dae2344c1d4092a1d85dab424", "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "2b57cf28eeadf43d09b1d780a5db1423",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "abcede4e60fa8877f18e66e086fb7387", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "3314d791a9ab37ea81be824460c63d14",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "48c4873dae2344c1d4092a1d85dab424", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "2b57cf28eeadf43d09b1d780a5db1423",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "6149911c660a9864b651cc1a8e50eec1", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "bd63402d48fce829f16d59c6c1f87977",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "57cef68ab703ba819bd0fbe9e4b1c331", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "de24c4e6f661f6201b933af3343084cc",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "a47bab28b86c7cefce891b8e5c8b687a", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "84904f537e435d09c06b4b6c10abea7d",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "d68ebb1139363d711b044de65e17b204", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "22a32d161e85baa6c6459412a368bf82",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ea1349137f64f3d662b9a95278ca4c02", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "f43972d496e1953fdc30ff094a22a0d1",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "c8731ff226716cee3d1e46027ead1cfe", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "c069093fb4773f3feac13236d474ecf1",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "4ee6b633a99c5bcbea4f5dee5bda186e", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "4d745c03bbeab02c5f69bed1ae376933",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "2c3bd4952b30d88247229ad309f73092", "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/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101", "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "6df0f34207346d89a72924249ddd4706", "src/ballistica/base/mgen/pyembed/binding_base.inc": "6df0f34207346d89a72924249ddd4706",

View File

@ -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, - 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 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 seems pretty awesome these days. It should support most stuff SDL does and
with less configuring involved. Please holler if you come across something with less configuring involved. Please holler if you come across something
that doesn't work. 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) ### 1.7.27 (build 21282, api 8, 2023-08-30)

View File

@ -442,6 +442,8 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/base/support/huffman.cc ${BA_SRC_ROOT}/ballistica/base/support/huffman.cc
${BA_SRC_ROOT}/ballistica/base/support/huffman.h ${BA_SRC_ROOT}/ballistica/base/support/huffman.h
${BA_SRC_ROOT}/ballistica/base/support/plus_soft.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.cc
${BA_SRC_ROOT}/ballistica/base/support/stdio_console.h ${BA_SRC_ROOT}/ballistica/base/support/stdio_console.h
${BA_SRC_ROOT}/ballistica/base/support/stress_test.cc ${BA_SRC_ROOT}/ballistica/base/support/stress_test.cc

View File

@ -434,6 +434,8 @@
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" /> <ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\huffman.h" /> <ClInclude Include="..\..\src\ballistica\base\support\huffman.h" />
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h" /> <ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc" /> <ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" /> <ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" /> <ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />

View File

@ -736,6 +736,12 @@
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h"> <ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h">
<Filter>ballistica\base\support</Filter> <Filter>ballistica\base\support</Filter>
</ClInclude> </ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc"> <ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc">
<Filter>ballistica\base\support</Filter> <Filter>ballistica\base\support</Filter>
</ClCompile> </ClCompile>

View File

@ -429,6 +429,8 @@
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" /> <ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\huffman.h" /> <ClInclude Include="..\..\src\ballistica\base\support\huffman.h" />
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h" /> <ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc" /> <ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" /> <ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" /> <ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />

View File

@ -736,6 +736,12 @@
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h"> <ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h">
<Filter>ballistica\base\support</Filter> <Filter>ballistica\base\support</Filter>
</ClInclude> </ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc"> <ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc">
<Filter>ballistica\base\support</Filter> <Filter>ballistica\base\support</Filter>
</ClCompile> </ClCompile>

View File

@ -915,6 +915,12 @@ class App:
_babase.lifecyclelog('fade-and-shutdown-graphics begin') _babase.lifecyclelog('fade-and-shutdown-graphics begin')
_babase.fade_screen(False, time=0.15) _babase.fade_screen(False, time=0.15)
await asyncio.sleep(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') _babase.lifecyclelog('fade-and-shutdown-graphics end')
async def _fade_and_shutdown_audio(self) -> None: async def _fade_and_shutdown_audio(self) -> None:

View File

@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be # Build number and version of the ballistica binary we expect to be
# using. # using.
TARGET_BALLISTICA_BUILD = 21491 TARGET_BALLISTICA_BUILD = 21510
TARGET_BALLISTICA_VERSION = '1.7.28' TARGET_BALLISTICA_VERSION = '1.7.28'

View File

@ -315,4 +315,7 @@ auto AppAdapter::GetGraphicsClientContext() -> GraphicsClientContext* {
return new GraphicsClientContext(); return new GraphicsClientContext();
} }
auto AppAdapter::GetKeyRepeatDelay() -> float { return 0.3f; }
auto AppAdapter::GetKeyRepeatInterval() -> float { return 0.08f; }
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -189,6 +189,13 @@ class AppAdapter {
/// changing (it may be preferable to rely on dialogs for non-english /// changing (it may be preferable to rely on dialogs for non-english
/// languages/etc.). Default implementation returns false. This function /// languages/etc.). Default implementation returns false. This function
/// should be callable from any thread. /// 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; virtual auto HasDirectKeyboardInput() -> bool;
/// Called in the graphics context to apply new settings coming in from /// Called in the graphics context to apply new settings coming in from
@ -197,6 +204,9 @@ class AppAdapter {
/// settings coming in. /// settings coming in.
virtual void ApplyGraphicsSettings(const GraphicsSettings* settings); virtual void ApplyGraphicsSettings(const GraphicsSettings* settings);
virtual auto GetKeyRepeatDelay() -> float;
virtual auto GetKeyRepeatInterval() -> float;
protected: protected:
AppAdapter(); AppAdapter();
virtual ~AppAdapter(); virtual ~AppAdapter();

View File

@ -3,16 +3,21 @@
#include "ballistica/base/app_adapter/app_adapter_apple.h" #include "ballistica/base/app_adapter/app_adapter_apple.h"
#include <BallisticaKit-Swift.h>
#include "ballistica/base/graphics/gl/renderer_gl.h" #include "ballistica/base/graphics/gl/renderer_gl.h"
#include "ballistica/base/graphics/graphics.h" #include "ballistica/base/graphics/graphics.h"
#include "ballistica/base/graphics/graphics_server.h" #include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/logic/logic.h" #include "ballistica/base/logic/logic.h"
#include "ballistica/base/platform/apple/from_swift.h"
#include "ballistica/base/support/app_config.h" #include "ballistica/base/support/app_config.h"
#include "ballistica/shared/ballistica.h" #include "ballistica/shared/ballistica.h"
#include "ballistica/shared/foundation/event_loop.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 <BallisticaKit-Swift.h>
// clang-format on
namespace ballistica::base { namespace ballistica::base {
/// RAII-friendly way to mark the thread and calls we're allowed to run graphics /// 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) { void AppAdapterApple::DoPushMainThreadRunnable(Runnable* runnable) {
// Kick this along to swift. // Kick this along to swift.
BallisticaKit::FromCppPushRawRunnableToMain(runnable); BallisticaKit::FromCpp::PushRawRunnableToMain(runnable);
} }
void AppAdapterApple::DoApplyAppConfig() { assert(g_base->InLogicThread()); } 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). // matches what we have (or until we try for too long or fail at drawing).
seconds_t start_time = g_core->GetAppTimeSeconds(); seconds_t start_time = g_core->GetAppTimeSeconds();
for (int i = 0; i < 5; ++i) { 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()) - g_base->graphics_server->screen_pixel_width())
> 0.01f) > 0.01f)
|| (std::abs(resize_target_resolution_.y || (std::abs(resize_target_resolution_.y
- g_base->graphics_server->screen_pixel_height()) - g_base->graphics_server->screen_pixel_height())
> 0.01f)) > 0.01f));
&& g_core->GetAppTimeSeconds() - start_time < 0.1 && result) { if (size_differs && g_core->GetAppTimeSeconds() - start_time < 0.1
&& result) {
result = g_base->graphics_server->TryRender(); result = g_base->graphics_server->TryRender();
} else {
break;
} }
} }
} }
@ -182,13 +187,13 @@ void AppAdapterApple::SetHardwareCursorVisible(bool visible) {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
#if BA_OSTYPE_MACOS #if BA_OSTYPE_MACOS
BallisticaKit::CocoaFromCppSetCursorVisible(visible); BallisticaKit::CocoaFromCpp::SetCursorVisible(visible);
#endif #endif
} }
void AppAdapterApple::TerminateApp() { void AppAdapterApple::TerminateApp() {
#if BA_OSTYPE_MACOS #if BA_OSTYPE_MACOS
BallisticaKit::CocoaFromCppTerminateApp(); BallisticaKit::CocoaFromCpp::TerminateApp();
#else #else
AppAdapter::TerminateApp(); AppAdapter::TerminateApp();
#endif #endif
@ -205,7 +210,7 @@ auto AppAdapterApple::FullscreenControlAvailable() const -> bool {
auto AppAdapterApple::FullscreenControlGet() const -> bool { auto AppAdapterApple::FullscreenControlGet() const -> bool {
#if BA_OSTYPE_MACOS #if BA_OSTYPE_MACOS
return BallisticaKit::CocoaFromCppGetMainWindowIsFullscreen(); return BallisticaKit::CocoaFromCpp::GetMainWindowIsFullscreen();
#else #else
return false; return false;
#endif #endif
@ -213,7 +218,7 @@ auto AppAdapterApple::FullscreenControlGet() const -> bool {
void AppAdapterApple::FullscreenControlSet(bool fullscreen) { void AppAdapterApple::FullscreenControlSet(bool fullscreen) {
#if BA_OSTYPE_MACOS #if BA_OSTYPE_MACOS
return BallisticaKit::CocoaFromCppSetMainWindowFullscreen(fullscreen); return BallisticaKit::CocoaFromCpp::SetMainWindowFullscreen(fullscreen);
#endif #endif
} }
@ -224,6 +229,22 @@ auto AppAdapterApple::FullscreenControlKeyShortcut() const
auto AppAdapterApple::HasDirectKeyboardInput() -> bool { return true; }; 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 } // namespace ballistica::base
#endif // BA_XCODE_BUILD #endif // BA_XCODE_BUILD

View File

@ -41,6 +41,9 @@ class AppAdapterApple : public AppAdapter {
auto HasDirectKeyboardInput() -> bool override; auto HasDirectKeyboardInput() -> bool override;
void EnableResizeFriendlyMode(int width, int height); void EnableResizeFriendlyMode(int width, int height);
auto GetKeyRepeatDelay() -> float override;
auto GetKeyRepeatInterval() -> float override;
protected: protected:
void DoPushMainThreadRunnable(Runnable* runnable) override; void DoPushMainThreadRunnable(Runnable* runnable) override;
void DoPushGraphicsContextRunnable(Runnable* runnable) override; void DoPushGraphicsContextRunnable(Runnable* runnable) override;

View File

@ -415,7 +415,9 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) {
} }
case SDL_KEYDOWN: { case SDL_KEYDOWN: {
g_base->input->PushKeyPressEvent(event.key.keysym); if (!event.key.repeat) {
g_base->input->PushKeyPressEvent(event.key.keysym);
}
break; break;
} }

View File

@ -26,7 +26,7 @@ void AssetsServer::OnAppStartInThread() {
// Ask our thread to give us periodic processing time (close to but // Ask our thread to give us periodic processing time (close to but
// not *exactly* one second; try to avoid aliasing with similar updates). // not *exactly* one second; try to avoid aliasing with similar updates).
process_timer_ = event_loop()->NewTimer( process_timer_ = event_loop()->NewTimer(
987, true, NewLambdaRunnable([this] { Process(); })); 987, true, NewLambdaRunnable([this] { Process(); }).Get());
} }
void AssetsServer::PushPendingPreload(Object::Ref<Asset>* asset_ref_ptr) { void AssetsServer::PushPendingPreload(Object::Ref<Asset>* asset_ref_ptr) {

View File

@ -15,7 +15,7 @@
namespace ballistica::base { 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. // Compute the actual number of pixel elements in the buffer.
size_t cpel = cb / 4; size_t cpel = cb / 4;
auto* psrc = src; auto* psrc = src;
@ -157,7 +157,7 @@ void TextureAsset::DoPreload() {
auto* buffer = static_cast<uint8_t*>(malloc(buffer_size)); auto* buffer = static_cast<uint8_t*>(malloc(buffer_size));
preload_datas_[0].buffers[0] = buffer; preload_datas_[0].buffers[0] = buffer;
memcpy(buffer, pixels, buffer_size); 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].widths[0] = width;
preload_datas_[0].heights[0] = height; preload_datas_[0].heights[0] = height;
preload_datas_[0].formats[0] = TextureFormat::kRGBA_8888; preload_datas_[0].formats[0] = TextureFormat::kRGBA_8888;

View File

@ -161,7 +161,7 @@ void AudioServer::OnAppStartInThread_() {
// Get our thread to give us periodic processing time. // Get our thread to give us periodic processing time.
process_timer_ = process_timer_ =
event_loop()->NewTimer(kAudioProcessIntervalNormal, true, event_loop()->NewTimer(kAudioProcessIntervalNormal, true,
NewLambdaRunnable([this] { Process_(); })); NewLambdaRunnable([this] { Process_(); }).Get());
#if BA_ENABLE_AUDIO #if BA_ENABLE_AUDIO

View File

@ -99,6 +99,7 @@ class RenderPass;
class RenderTarget; class RenderTarget;
class RemoteAppServer; class RemoteAppServer;
class RemoteControlInput; class RemoteControlInput;
class Repeater;
class ScoreToBeat; class ScoreToBeat;
class AppAdapterSDL; class AppAdapterSDL;
class SDLContext; class SDLContext;

View File

@ -182,7 +182,7 @@ void Graphics::UpdateInitialGraphicsSettingsSend_() {
void Graphics::StepDisplayTime() { assert(g_base->InLogicThread()); } void Graphics::StepDisplayTime() { assert(g_base->InLogicThread()); }
void Graphics::AddCleanFrameCommand(const Object::Ref<PythonContextCall>& c) { void Graphics::AddCleanFrameCommand(const Object::Ref<PythonContextCall>& c) {
BA_PRECONDITION(g_base->InLogicThread()); assert(g_base->InLogicThread());
clean_frame_commands_.push_back(c); clean_frame_commands_.push_back(c);
} }
@ -1058,7 +1058,7 @@ void Graphics::ClearFrameDefDeleteList() {
} }
void Graphics::FadeScreen(bool to, millisecs_t time, PyObject* endcall) { 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. // If there's an ourstanding fade-end command, go ahead and run it.
// (otherwise, overlapping fades can cause things to get lost) // (otherwise, overlapping fades can cause things to get lost)
if (fade_end_call_.Exists()) { if (fade_end_call_.Exists()) {
@ -1186,7 +1186,7 @@ void Graphics::DrawDevUI(FrameDef* frame_def) {
void Graphics::BuildAndPushFrameDef() { void Graphics::BuildAndPushFrameDef() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
BA_PRECONDITION_FATAL(g_base->logic->app_bootstrapping_complete()); assert(g_base->logic->app_bootstrapping_complete());
assert(camera_.Exists()); assert(camera_.Exists());
assert(!g_core->HeadlessMode()); assert(!g_core->HeadlessMode());

View File

@ -141,8 +141,8 @@ class Graphics {
static void DrawRadialMeter(MeshIndexedSimpleFull* m, float amt); static void DrawRadialMeter(MeshIndexedSimpleFull* m, float amt);
// Ways to add a few simple component types quickly. // Ways to add a few simple component types quickly (uses particle
// (uses particle rendering for efficient batches). // rendering for efficient batches).
void DrawBlotch(const Vector3f& pos, float size, float r, float g, float b, void DrawBlotch(const Vector3f& pos, float size, float r, float g, float b,
float a) { float a) {
DoDrawBlotch(&blotch_indices_, &blotch_verts_, pos, size, r, g, b, a); DoDrawBlotch(&blotch_indices_, &blotch_verts_, pos, size, r, g, b, a);
@ -240,13 +240,8 @@ class Graphics {
float upper_top); float upper_top);
void ReleaseFadeEndCommand(); void ReleaseFadeEndCommand();
// auto tv_border() const { // Nodes that draw flat stuff into the overlay pass should query this z
// assert(g_base->InLogicThread()); // value for where to draw in z.
// return tv_border_;
// }
// 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() { auto overlay_node_z_depth() {
fetched_overlay_node_z_depth_ = true; fetched_overlay_node_z_depth_ = true;
return overlay_node_z_depth_; return overlay_node_z_depth_;
@ -296,8 +291,8 @@ class Graphics {
void AddMeshDataCreate(MeshData* d); void AddMeshDataCreate(MeshData* d);
void AddMeshDataDestroy(MeshData* d); void AddMeshDataDestroy(MeshData* d);
// For debugging: ensures that only transparent or opaque components // For debugging: ensures that only transparent or opaque components are
// are submitted while enabled. // submitted while enabled.
auto drawing_transparent_only() const { return drawing_transparent_only_; } auto drawing_transparent_only() const { return drawing_transparent_only_; }
void set_drawing_transparent_only(bool val) { void set_drawing_transparent_only(bool val) {
drawing_transparent_only_ = val; drawing_transparent_only_ = val;
@ -362,8 +357,8 @@ class Graphics {
} }
/// For temporary use in arbitrary threads. This should be removed when /// For temporary use in arbitrary threads. This should be removed when
/// possible and replaced with proper safe thread-specific access /// possible and replaced with proper safe thread-specific access patterns
/// patterns (so we can support switching renderers/etc.). /// (so we can support switching renderers/etc.).
auto placeholder_client_context() const -> const GraphicsClientContext* { auto placeholder_client_context() const -> const GraphicsClientContext* {
// Using this from arbitrary threads is currently ok currently since // Using this from arbitrary threads is currently ok currently since
// context never changes once set. Will need to kill this call once that // 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_lower_top_{4.0f};
float shadow_upper_bottom_{30.0f}; float shadow_upper_bottom_{30.0f};
float shadow_upper_top_{40.0f}; float shadow_upper_top_{40.0f};
seconds_t last_cursor_visibility_event_time_{};
millisecs_t fade_start_{}; millisecs_t fade_start_{};
millisecs_t fade_time_{}; millisecs_t fade_time_{};
millisecs_t next_stat_update_time_{}; millisecs_t next_stat_update_time_{};
millisecs_t progress_bar_end_time_{-9999}; millisecs_t progress_bar_end_time_{-9999};
millisecs_t last_progress_bar_draw_time_{}; millisecs_t last_progress_bar_draw_time_{};
millisecs_t last_progress_bar_start_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_create_frame_def_time_millisecs_{};
millisecs_t last_jitter_update_time_{}; 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<ImageMesh> screen_mesh_; Object::Ref<ImageMesh> screen_mesh_;
Object::Ref<ImageMesh> progress_bar_bottom_mesh_; Object::Ref<ImageMesh> progress_bar_bottom_mesh_;
Object::Ref<ImageMesh> progress_bar_top_mesh_; Object::Ref<ImageMesh> progress_bar_top_mesh_;

View File

@ -137,14 +137,10 @@ auto GraphicsServer::TryRender() -> bool {
auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* { auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* {
assert(g_base->app_adapter->InGraphicsContext()); assert(g_base->app_adapter->InGraphicsContext());
millisecs_t app_time = g_core->GetAppTimeMillisecs(); millisecs_t start_time = g_core->GetAppTimeMillisecs();
if (!renderer_) { // Don't bother waiting if we can't/shouldn't render anyway.
return nullptr; if (!renderer_ || shutting_down_ || g_base->app_adapter->app_suspended()) {
}
// If the app is paused, never render.
if (g_base->app_adapter->app_suspended()) {
return nullptr; return nullptr;
} }
@ -173,12 +169,12 @@ auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* {
// if we've been waiting for too long, give up. On some platforms such // 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 // 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. // 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 (t >= 1000) {
if (g_buildconfig.debug_build()) { if (g_buildconfig.debug_build()) {
Log(LogLevel::kWarning, Log(LogLevel::kWarning,
"GraphicsServer: aborting GetRenderFrameDef after " "GraphicsServer: timed out at " + std::to_string(t)
+ std::to_string(t) + "ms."); + "ms waiting for logic thread to send us a FrameDef.");
} }
break; // Fail. break; // Fail.
} }
@ -336,38 +332,6 @@ void GraphicsServer::LoadRenderer() {
texture_quality_ = Graphics::TextureQualityFromRequest( texture_quality_ = Graphics::TextureQualityFromRequest(
texture_quality_requested_, renderer_->GetAutoTextureQuality()); 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<int>(texture_quality_requested_)));
// texture_quality_ = TextureQuality::kLow;
// }
// texture_quality_set_ = true;
// Ok we've got our qualities figured out; now load/update the renderer. // Ok we've got our qualities figured out; now load/update the renderer.
renderer_->Load(); renderer_->Load();
@ -657,4 +621,15 @@ auto GraphicsServer::InGraphicsContext_() const -> bool {
return g_base->app_adapter->InGraphicsContext(); 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 } // namespace ballistica::base

View File

@ -231,14 +231,6 @@ class GraphicsServer {
return tv_border_; 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 { auto SupportsTextureCompressionType(TextureCompressionType t) const -> bool {
assert(InGraphicsContext_()); assert(InGraphicsContext_());
assert(texture_compression_types_set_); assert(texture_compression_types_set_);
@ -249,10 +241,6 @@ class GraphicsServer {
void SetTextureCompressionTypes( void SetTextureCompressionTypes(
const std::list<TextureCompressionType>& types); const std::list<TextureCompressionType>& types);
// auto texture_compression_types_are_set() const {
// return texture_compression_types_set_;
// }
void set_renderer_context_lost(bool lost) { renderer_context_lost_ = lost; } void set_renderer_context_lost(bool lost) { renderer_context_lost_ = lost; }
auto renderer_context_lost() const { return renderer_context_lost_; } auto renderer_context_lost() const { return renderer_context_lost_; }
@ -289,6 +277,11 @@ class GraphicsServer {
return texture_compression_types_; return texture_compression_types_;
} }
/// Start spinning down the graphics server/etc.
void Shutdown();
auto shutdown_completed() const { return shutdown_completed_; }
private: private:
/// Pass a freshly allocated GraphicsContext instance, which the graphics /// Pass a freshly allocated GraphicsContext instance, which the graphics
/// system will take ownership of. /// 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 renderer_loaded_ : 1 {};
bool model_view_projection_matrix_dirty_ : 1 {true}; bool model_view_projection_matrix_dirty_ : 1 {true};
bool model_world_matrix_dirty_ : 1 {true}; bool model_world_matrix_dirty_ : 1 {true};
@ -331,11 +328,9 @@ class GraphicsServer {
bool renderer_context_lost_ : 1 {}; bool renderer_context_lost_ : 1 {};
bool texture_compression_types_set_ : 1 {}; bool texture_compression_types_set_ : 1 {};
bool cam_orient_matrix_dirty_ : 1 {true}; bool cam_orient_matrix_dirty_ : 1 {true};
bool shutting_down_ : 1 {};
bool shutdown_completed_ : 1 {};
Snapshot<GraphicsClientContext>* client_context_{}; Snapshot<GraphicsClientContext>* client_context_{};
TextureQualityRequest texture_quality_requested_{};
TextureQuality texture_quality_{};
GraphicsQualityRequest graphics_quality_requested_{};
GraphicsQuality graphics_quality_{};
float res_x_{}; float res_x_{};
float res_y_{}; float res_y_{};
float res_x_virtual_{}; float res_x_virtual_{};

View File

@ -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. // compile it and add its final spans to our mesh.
if (packer) { if (packer) {
std::vector<TextPacker::Span> spans; std::vector<TextPacker::Span> spans;
packer->compile(); packer->Compile();
// DEBUGGING - add a single quad above our first // DEBUGGING - add a single quad above our first
// span showing the entire texture for debugging purposes // span showing the entire texture for debugging purposes

View File

@ -12,25 +12,25 @@
#include "ballistica/base/graphics/text/text_graphics.h" #include "ballistica/base/graphics/text/text_graphics.h"
namespace ballistica::base { 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 #define BA_GLYPH_PAGE_COUNT 8
// the total number of glyphs we have // The total number of glyphs we have.
const int kGlyphCount = 1280; 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, uint32_t g_glyph_page_start_index_map[8] = {0, 258, 416, 546,
698, 981, 1138, 1276}; 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}; 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, TextGraphics::Glyph* g_glyph_pages[8] = {nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr}; nullptr, nullptr, nullptr, nullptr};
// the page index for each glyph // The page index for each glyph.
uint16_t g_glyph_map[kGlyphCount] = { 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, 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,

View File

@ -116,8 +116,7 @@ TextGraphics::TextGraphics() {
} }
} }
// init glyph values for our big font page // Init glyph values for our big font page (a 8x8 array).
// (a 8x8 array)
{ {
float x_offs = 0.009f; float x_offs = 0.009f;
float y_offs = 0.0059f; float y_offs = 0.0059f;
@ -284,7 +283,7 @@ TextGraphics::TextGraphics() {
(1.0f / 8.0f) * static_cast<float>(x + 1) + x_offs + scale_extra; (1.0f / 8.0f) * static_cast<float>(x + 1) + x_offs + scale_extra;
g.tex_max_y = (1.0f / 8.0f) * static_cast<float>(y) + y_offs; g.tex_max_y = (1.0f / 8.0f) * static_cast<float>(y) + y_offs;
// just scooted letters over.. account for that // Just scooted letters over; account for that.
float foo_x = 0.0183f; float foo_x = 0.0183f;
float foo_y = 0.000f; float foo_y = 0.000f;
g.tex_min_x += foo_x; g.tex_min_x += foo_x;
@ -292,12 +291,12 @@ TextGraphics::TextGraphics() {
g.tex_min_y += foo_y; g.tex_min_y += foo_y;
g.tex_max_y += foo_y; g.tex_max_y += foo_y;
// clamp based on char width // Clamp based on char width.
float scale = w * 1.32f; float scale = w * 1.32f;
g.x_size *= scale; g.x_size *= scale;
g.tex_max_x = g.tex_min_x + (g.tex_max_x - g.tex_min_x) * 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) { if (bot_offset != 0.0f) {
g.tex_min_y = g.tex_max_y g.tex_min_y = g.tex_max_y
+ (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.pen_offset_y -= bot_offset;
g.y_size += bot_offset; g.y_size += bot_offset;
} }
// add left offset // Add left offset.
if (left_offset != 0.0f) { if (left_offset != 0.0f) {
g.tex_min_x = g.tex_max_x g.tex_min_x = g.tex_max_x
+ (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.pen_offset_x -= left_offset;
g.x_size += left_offset; g.x_size += left_offset;
} }
// add right offset // Add right offset.
if (right_offset != 0.0f) { if (right_offset != 0.0f) {
g.tex_max_x = g.tex_min_x g.tex_max_x = g.tex_min_x
+ (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) / g.x_size);
g.x_size += right_offset; g.x_size += right_offset;
} }
// add top offset // Add top offset.
if (top_offset != 0.0f) { if (top_offset != 0.0f) {
g.tex_max_y = g.tex_min_y g.tex_max_y = g.tex_min_y
+ (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: // Our special pages:
switch (page) { switch (page) {
case static_cast<int>(FontPage::kOSRendered): { case static_cast<int>(FontPage::kOSRendered): {
// we allow the OS to render anything not in one of our glyph textures // 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 // (technically this overlaps the private-use range which we use our
// textures for, but that's handled as a special-case by // own textures for, but that's handled as a special-case by
// TextGroup::setText // TextGroup::SetText.
(*first_char) = kGlyphCount; (*first_char) = kGlyphCount;
(*last_char) = kTextMaxUnicodeVal; // hmm what's the max unicode value we // hmm what's the max unicode value we should ever see?..
// should ever see?.. (*last_char) = kTextMaxUnicodeVal;
break; break;
} }
case static_cast<int>(FontPage::kExtras1): { case static_cast<int>(FontPage::kExtras1): {
@ -887,52 +886,57 @@ void TextGraphics::GetFontPagesForText(const std::string& text,
int last_page = -1; int last_page = -1;
std::vector<uint32_t> unicode = Utils::UnicodeFromUTF8(text, "c03853"); std::vector<uint32_t> unicode = Utils::UnicodeFromUTF8(text, "c03853");
for (uint32_t val : unicode) { for (uint32_t val : unicode) {
int page; int page{-1};
// Hack: allow showing euro even if we don't support unicode font rendering. // Hack: allow showing euro even if we don't support unicode font
if (g_buildconfig.enable_os_font_rendering()) { // rendering.
if (val == 8364) { // if (g_buildconfig.enable_os_font_rendering()) {
val = 0xE000; // if (val == 8364) {
} // val = 0xE000;
} // }
// }
// For values in the custom-char range (U+E000U+F8FF) we point at our own bool covered{};
// custom page(s)
// For values in the custom-char range (U+E000U+F8FF) we point at our
// own custom page(s)
if (val >= 0xE000 && val <= 0xF8FF) { if (val >= 0xE000 && val <= 0xF8FF) {
// The 25 chars after this are in our fontExtras sheet. // The 25 chars after this are in our fontExtras sheet.
if (val < 0xE000 + 25) { if (val < 0xE000 + 25) {
// Special value denoting our custom font page. // Special value denoting our custom font page.
page = static_cast<int>(FontPage::kExtras1); page = static_cast<int>(FontPage::kExtras1);
covered = true;
} else if (val < 0xE000 + 50) { } else if (val < 0xE000 + 50) {
// Special value denoting our custom font page. // Special value denoting our custom font page.
page = static_cast<int>(FontPage::kExtras2); page = static_cast<int>(FontPage::kExtras2);
covered = true;
} else if (val < 0xE000 + 75) { } else if (val < 0xE000 + 75) {
// Special value denoting our custom font page. // Special value denoting our custom font page.
page = static_cast<int>(FontPage::kExtras3); page = static_cast<int>(FontPage::kExtras3);
covered = true;
} else if (val < 0xE000 + 100) { } else if (val < 0xE000 + 100) {
// Special value denoting our custom font page. // Special value denoting our custom font page.
page = static_cast<int>(FontPage::kExtras4); page = static_cast<int>(FontPage::kExtras4);
} else { covered = true;
// We dont cover this.. just go with '?'
val = '?';
page = g_glyph_map[val];
} }
} else if (val >= kGlyphCount) { } else if (val < kGlyphCount) {
// Otherwise if its outside of our texture-coverage area. page = g_glyph_map[val];
covered = true;
}
if (!covered) {
if (g_buildconfig.enable_os_font_rendering()) { if (g_buildconfig.enable_os_font_rendering()) {
page = static_cast<int>(FontPage::kOSRendered); page = static_cast<int>(FontPage::kOSRendered);
} else { } else {
val = '?'; val = '?';
page = g_glyph_map[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) { if (page != last_page) {
(*font_pages).insert(page); font_pages->insert(page);
last_page = 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. // Send this entry to the back of the list since we used it.
text_span_bounds_cache_.erase(entry->list_iterator_); 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_ = entry->list_iterator_ =
text_span_bounds_cache_.insert(text_span_bounds_cache_.end(), entry); text_span_bounds_cache_.insert(text_span_bounds_cache_.end(), entry);
#pragma clang diagnostic pop
return; return;
} }
auto entry(Object::New<TextSpanBoundsCacheEntry>()); auto entry(Object::New<TextSpanBoundsCacheEntry>());
@ -1068,7 +1068,9 @@ auto TextGraphics::GetStringWidth(const char* text, bool big) -> float {
line_length += GetOSTextSpanWidth(s); line_length += GetOSTextSpanWidth(s);
os_span.clear(); 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; line_length = 0;
t++; t++;
} else { } else {
@ -1145,7 +1147,9 @@ void TextGraphics::BreakUpString(const char* text, float width,
s_begin = t; s_begin = t;
} }
} else { } else {
if (*t == 0) throw Exception(); if (*t == 0) {
throw Exception();
}
uint32_t val = Utils::GetUTF8Value(t); uint32_t val = Utils::GetUTF8Value(t);
Utils::AdvanceUTF8(&t); Utils::AdvanceUTF8(&t);

View File

@ -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 // 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 // bounds at one size and then scale that linearly when trying to fit
// into the texture. However, fonts don't always scale linearly (and even when // things into the texture. However, fonts don't always scale linearly (and
// that's an option it can be expensive). // even when that's an option it can be expensive).
void TextPacker::compile() { void TextPacker::Compile() {
assert(!compiled_); assert(!compiled_);
if (spans_.empty()) { if (spans_.empty()) {
compiled_ = true; compiled_ = true;
@ -57,24 +57,23 @@ void TextPacker::compile() {
width *= 2; width *= 2;
} }
// Alternately, if we're too big, crank our scale down so that our widest span // Alternately, if we're too big, crank our scale down so that our widest
// fits. // span fits.
if (widest_unscaled_span_width * scale > width * 0.9f) { if (widest_unscaled_span_width * scale > width * 0.9f) {
scale *= ((width * 0.9f) / (widest_unscaled_span_width * scale)); scale *= ((width * 0.9f) / (widest_unscaled_span_width * scale));
} }
float start_height = height; float start_height = height;
int mini_shrink_tries = 0; int mini_shrink_tries = 0;
// Ok; we've now locked in a width and scale. // Ok; we've now locked in a width and scale. Now we go through and
// Now we go through and position our spans. // position our spans. We may need to do this more than once if our height
// We may need to do this more than once if our height comes out too big. // comes out too big. (hopefully this will never be a problem in practice)
// (hopefully this will never be a problem in practice)
while (true) { while (true) {
height = start_height; height = start_height;
// We currently just lay out left-to-right, top-to-bottom. // We currently just lay out left-to-right, top-to-bottom. This could be
// This could be somewhat wasteful in particular configurations. // somewhat wasteful in particular configurations. (leaving half-filled
// (leaving half-filled lines, etc) so it might be worth improving later. // lines, etc) so it might be worth improving later.
float widest_fill_right = 0.0f; float widest_fill_right = 0.0f;
float fill_right = 0.0f; float fill_right = 0.0f;
float fill_bottom = 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. // Start a new line if this would put us past the end.
if (fill_right + span_width > width) { if (fill_right + span_width > width) {
if (fill_right > widest_fill_right) { 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_right = 0.0f;
fill_bottom += line_height; fill_bottom += line_height;
line_height = 0.0f; line_height = 0.0f;
} }
// Position x such that x + left bound - buffer lines up with our current // Position x such that x + left bound - buffer lines up with our
// right point. // current right point.
float to_left = (i.bounds.l - span_buffer) * scale; float to_left = (i.bounds.l - span_buffer) * scale;
i.tex_x = fill_right - to_left; i.tex_x = fill_right - to_left;
fill_right += span_width; fill_right += span_width;
// Position y such that y - top bound - buffer lines up with our current // Position y such that y - top bound - buffer lines up with our
// bottom point. // current bottom point.
float to_top = (-i.bounds.t - span_buffer) * scale; float to_top = (-i.bounds.t - span_buffer) * scale;
i.tex_y = fill_bottom - to_top; i.tex_y = fill_bottom - to_top;
// If our total height is greater than the current line height, expand the // If our total height is greater than the current line height, expand
// line's. // the line's.
if (span_height > line_height) { if (span_height > line_height) {
line_height = span_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. // 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 // Dropping our scale has a disproportional effect on the final height
// (since it opens up more relative horizontal space). // (since it opens up more relative horizontal space). I'm not sure
// I'm not sure how to figure out how much to drop by other than // how to figure out how much to drop by other than incrementally
// incrementally dropping values until we fit. // dropping values until we fit.
scale *= 0.75f; scale *= 0.75f;
} else if (((widest_fill_right < (width * mini_shrink_threshold_h) } else if (((widest_fill_right < (width * mini_shrink_threshold_h)
@ -135,15 +135,15 @@ void TextPacker::compile() {
|| fill_bottom + line_height || fill_bottom + line_height
< (height * mini_shrink_threshold_v)) < (height * mini_shrink_threshold_v))
&& mini_shrink_tries < 3) { && mini_shrink_tries < 3) {
// If we're here it means we *barely* use more than half of the texture in // If we're here it means we *barely* use more than half of the
// one direction or the other; let's shrink just a tiny bit and we should // texture in one direction or the other; let's shrink just a tiny bit
// be able to chop our texture size in half // and we should be able to chop our texture size in half
if (widest_fill_right < width * mini_shrink_threshold_h && width > 16) { if (widest_fill_right < width * mini_shrink_threshold_h && width > 16) {
float scale_val = 0.99f * (((width * 0.5f) / widest_fill_right)); float scale_val = 0.99f * (((width * 0.5f) / widest_fill_right));
if (scale_val < 1.0f) { if (scale_val < 1.0f) {
// FIXME - should think about a fixed multiplier here; // FIXME - should think about a fixed multiplier here; under the
// under the hood the system might be caching glyphs based on scale // hood the system might be caching glyphs based on scale and
// and this would leave us with fewer different scales in the end and // this would leave us with fewer different scales in the end and
// thus better caching performance // thus better caching performance
scale *= scale_val; scale *= scale_val;
} }
@ -151,9 +151,9 @@ void TextPacker::compile() {
} else { } else {
float scale_val = 0.99f * (height * 0.5f) / (fill_bottom + line_height); float scale_val = 0.99f * (height * 0.5f) / (fill_bottom + line_height);
if (scale_val < 1.0f) { if (scale_val < 1.0f) {
// FIXME - should think about a fixed multiplier here; // FIXME - should think about a fixed multiplier here; under the
// under the hood the system might be caching glyphs based on scale // hood the system might be caching glyphs based on scale and
// and this would leave us with fewer different scales in the end and // this would leave us with fewer different scales in the end and
// thus better caching performance // thus better caching performance
scale *= scale_val; scale *= scale_val;
} }
@ -165,8 +165,8 @@ void TextPacker::compile() {
} }
} }
// Lastly, now that our texture width and height are completely finalized, we // Lastly, now that our texture width and height are completely finalized,
// can calculate UVs. // we can calculate UVs.
for (auto&& i : spans_) { for (auto&& i : spans_) {
// Now store uv coords for this span; they should include the buffer. // 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; 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 // 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. // configuration; we'll use that as a key for the texture we'll
// ..this way multiple meshes can share the same generated texture. // generate/use. ..this way multiple meshes can share the same generated
// *technically* we could calculate this hash and check for an existing // texture. *technically* we could calculate this hash and check for an
// texture before we bother laying out our spans, but that might not save us // existing texture before we bother laying out our spans, but that might
// much time and would complicate things. // not save us much time and would complicate things.
hash_ = std::to_string(resolution_scale_); hash_ = std::to_string(resolution_scale_);
for (auto&& i : spans_) { for (auto&& i : spans_) {
char buffer[64]; char buffer[64];

View File

@ -21,7 +21,7 @@ class TextPacker : public Object {
// outside of here anyway so might as well recycle. // outside of here anyway so might as well recycle.
void AddSpan(const std::string& text, float x, float y, const Rect& bounds); 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_); assert(compiled_);
return hash_; return hash_;
} }
@ -51,32 +51,32 @@ class TextPacker : public Object {
// Once done adding spans, call this to calculate final span UV values, // Once done adding spans, call this to calculate final span UV values,
// texture configuration, and hash. // texture configuration, and hash.
void compile(); void Compile();
auto spans() const -> const std::list<Span>& { return spans_; } const auto& spans() const { return spans_; }
auto texture_width() const -> int { auto texture_width() const {
assert(compiled_); assert(compiled_);
return texture_width_; return texture_width_;
} }
auto texture_height() const -> int { auto texture_height() const {
assert(compiled_); assert(compiled_);
return texture_height_; return texture_height_;
} }
auto text_scale() const -> float { auto text_scale() const {
assert(compiled_); assert(compiled_);
return text_scale_; return text_scale_;
} }
private: private:
bool compiled_{false};
float resolution_scale_; float resolution_scale_;
float text_scale_{};
int texture_width_{}; int texture_width_{};
int texture_height_{}; int texture_height_{};
float text_scale_{};
std::string hash_; std::string hash_;
bool compiled_{false};
std::list<Span> spans_; std::list<Span> spans_;
}; };

View File

@ -9,6 +9,7 @@
#include "ballistica/base/input/input.h" #include "ballistica/base/input/input.h"
#include "ballistica/base/python/base_python.h" #include "ballistica/base/python/base_python.h"
#include "ballistica/base/support/classic_soft.h" #include "ballistica/base/support/classic_soft.h"
#include "ballistica/base/support/repeater.h"
#include "ballistica/base/ui/ui.h" #include "ballistica/base/ui/ui.h"
#include "ballistica/core/core.h" #include "ballistica/core/core.h"
#include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/foundation/event_loop.h"
@ -17,10 +18,6 @@
namespace ballistica::base { namespace ballistica::base {
const char* kMFiControllerName = "iOS/Mac Controller";
const int kJoystickRepeatDelay{500};
// Joy values below this are candidates for calibration. // Joy values below this are candidates for calibration.
const float kJoystickCalibrationThreshold{6000.0f}; 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_.size() <= 22) {
raw_sdl_joystick_name_ = "XInput Controller"; 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 #else // BA_ENABLE_SDL_JOYSTICKS
throw Exception(); // Shouldn't happen. throw Exception(); // Shouldn't happen.
@ -101,8 +84,6 @@ JoystickInput::JoystickInput(int sdl_joystick_id,
// Its a manual joystick. // Its a manual joystick.
sdl_joystick_ = nullptr; sdl_joystick_ = nullptr;
is_mfi_controller_ = (custom_device_name_ == kMFiControllerName);
// Hard code a few remote controls. // Hard code a few remote controls.
// The newer way to do this is just set 'UI-Only' on the device config // The newer way to do this is just set 'UI-Only' on the device config
is_remote_control_ = ((custom_device_name_ == "Amazon Remote") is_remote_control_ = ((custom_device_name_ == "Amazon Remote")
@ -179,6 +160,7 @@ auto JoystickInput::GetButtonName(int index) -> std::string {
break; break;
} }
} }
if (g_buildconfig.ostype_android()) { if (g_buildconfig.ostype_android()) {
// Special case: if this is a samsung controller, return the dice // Special case: if this is a samsung controller, return the dice
// button icons. // 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<millisecs_t>(static_cast<float>(repeat_delay) * 0.8f);
}
}
}
} }
void JoystickInput::SetStandardExtendedButtons() { void JoystickInput::SetStandardExtendedButtons() {
@ -508,6 +450,8 @@ void JoystickInput::ResetHeldStates() {
SDL_Event e; SDL_Event e;
dpad_right_held_ = dpad_left_held_ = dpad_up_held_ = dpad_down_held_ = false; dpad_right_held_ = dpad_left_held_ = dpad_up_held_ = dpad_down_held_ = false;
ui_repeater_.Clear();
run_buttons_held_.clear(); run_buttons_held_.clear();
run_trigger1_value_ = run_trigger2_value_ = 0.0f; run_trigger1_value_ = run_trigger2_value_ = 0.0f;
UpdateRunningState(); UpdateRunningState();
@ -594,28 +538,28 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|| dpad_down_held_)) || dpad_down_held_))
return; 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 // 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). // rely on hold position to keep from doing stuff usually).
if (e->type == SDL_JOYBUTTONDOWN if (e->type == SDL_JOYBUTTONDOWN
&& e->jbutton.button == hold_position_button_) { && e->jbutton.button == hold_position_button_) {
need_to_send_held_state_ = true; need_to_send_held_state_ = true;
hold_position_held_ = true; hold_position_held_ = true;
isHoldPositionEvent = true; is_hold_position_event = true;
} }
if (e->type == SDL_JOYBUTTONUP if (e->type == SDL_JOYBUTTONUP
&& e->jbutton.button == hold_position_button_) { && e->jbutton.button == hold_position_button_) {
need_to_send_held_state_ = true; need_to_send_held_state_ = true;
hold_position_held_ = false; hold_position_held_ = false;
isHoldPositionEvent = true; is_hold_position_event = true;
} }
// Let's ignore events for just a moment after we're created. // Let's ignore events for just a moment after we're created.
// (some joysticks seem to spit out erroneous button-pressed events when // (some joysticks seem to spit out erroneous button-pressed events when
// first plugged in ). // first plugged in ).
if (time - creation_time_ < 250 && !isHoldPositionEvent) { if (time - creation_time_ < 250 && !is_hold_position_event) {
return; 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) if ((e->type == SDL_JOYBUTTONDOWN || e->type == SDL_JOYBUTTONUP)
&& (e->jbutton.button == ignored_button_ && (e->jbutton.button == ignored_button_
|| e->jbutton.button == ignored_button2_ || e->jbutton.button == ignored_button2_
@ -709,49 +653,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
return; 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<int>(e->jaxis.axis),
static_cast<int>(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. // A few high level button press interceptions.
if (e->type == SDL_JOYBUTTONDOWN) { if (e->type == SDL_JOYBUTTONDOWN) {
if (e->jbutton.button == start_button_ 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. // If we're in the ui, send ui events.
// We keep track of special x/y values for dialog usage. // We keep track of special x/y values for ui usage.
// These are formed as combinations of the actual joy value // These are formed as combinations of the actual joy value
// and the hold-position state. // and the hold-position state.
// Think of hold-position as somewhat of a 'magnitude' to the joy event's // 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 // 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). // 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->type == SDL_JOYAXISMOTION) {
if (e->jaxis.axis == analog_lr_) { if (e->jaxis.axis == analog_lr_) {
dialog_jaxis_x_ = e->jaxis.value; dialog_jaxis_x_ = e->jaxis.value;
isAnalogStickJAxisEvent = true; is_analog_stick_jaxis_event = true;
} else if (e->jaxis.axis == analog_ud_) { } else if (e->jaxis.axis == analog_ud_) {
dialog_jaxis_y_ = e->jaxis.value; 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_) { 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_) { 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 // We might not wanna grab at the UI if we're a axis-motion event
// below our 'pressed' threshold.. Otherwise fuzzy analog joystick // below our 'pressed' threshold.. Otherwise fuzzy analog joystick
// readings would cause rampant UI stealing even if no events are being sent. // 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; 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. // 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; left_held_ = false;
ui_repeater_.Clear();
} }
if (right_held_ && dialogJaxisX <= kJoystickDiscreteThreshold) { if (right_held_ && ui_jaxis_x <= kJoystickDiscreteThreshold) {
right_held_ = false; right_held_ = false;
ui_repeater_.Clear();
} }
if (up_held_ && dialogJaxisY >= -kJoystickDiscreteThreshold) { if (up_held_ && ui_jaxis_y >= -kJoystickDiscreteThreshold) {
up_held_ = false; up_held_ = false;
ui_repeater_.Clear();
} }
if (down_held_ && dialogJaxisY <= kJoystickDiscreteThreshold) { if (down_held_ && ui_jaxis_y <= kJoystickDiscreteThreshold) {
down_held_ = false; 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_) } else if ((e->type == SDL_JOYHATMOTION && e->jhat.hat == hat_)
|| (e->type == SDL_JOYBUTTONDOWN || (e->type == SDL_JOYBUTTONDOWN
&& e->jbutton.button != hold_position_button_)) { && e->jbutton.button != hold_position_button_)) {
// Other button-downs and hat motions always go. // Other button-downs and hat motions always go.
would_go_to_dialog = true; would_go_to_ui = true;
} }
// Resets always circumvent dialogs. // 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 // Anything that would go to ui also counts to mark us as 'recently-used'.
// 'recently-used'. if (would_go_to_ui) {
if (would_go_to_dialog) {
UpdateLastInputTime(); UpdateLastInputTime();
} }
if (would_go_to_dialog && g_base->ui->GetWidgetForInput(this)) { if (would_go_to_ui && g_base->ui->GetWidgetForInput(this)) {
bool pass = false; bool pass{};
// Special case.. either joy-axis-motion or hold-position events trigger // Special case.. either joy-axis-motion or hold-position events trigger
// these. // these.
if (isAnalogStickJAxisEvent || isHoldPositionEvent) { if (is_analog_stick_jaxis_event || is_hold_position_event) {
if (dialogJaxisX > kJoystickDiscreteThreshold) { if (ui_jaxis_x > kJoystickDiscreteThreshold) {
// To the right.
if (!right_held_ && !up_held_ && !down_held_) { if (!right_held_ && !up_held_ && !down_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
right_held_ = true; right_held_ = true;
pass = true;
wm = WidgetMessage::Type::kMoveRight; wm = WidgetMessage::Type::kMoveRight;
pass = true;
} }
} else if (dialogJaxisX < -kJoystickDiscreteThreshold) { } else if (ui_jaxis_x < -kJoystickDiscreteThreshold) {
if (!left_held_ && !up_held_ && !down_held_) { if (!left_held_ && !up_held_ && !down_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveLeft;
pass = true;
left_held_ = 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_) { if (!down_held_ && !left_held_ && !right_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveDown;
pass = true;
down_held_ = 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; pass = true;
wm = WidgetMessage::Type::kMoveDown;
}
} else if (ui_jaxis_y < -kJoystickDiscreteThreshold) {
if (!up_held_ && !left_held_ && !right_held_) {
up_held_ = true; up_held_ = true;
pass = true;
wm = WidgetMessage::Type::kMoveUp;
} }
} }
} }
@ -924,7 +830,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
switch (e->jhat.value) { switch (e->jhat.value) {
case SDL_HAT_LEFT: { case SDL_HAT_LEFT: {
if (!left_held_) { if (!left_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveLeft; wm = WidgetMessage::Type::kMoveLeft;
pass = true; pass = true;
left_held_ = true; left_held_ = true;
@ -935,7 +840,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
case SDL_HAT_RIGHT: { case SDL_HAT_RIGHT: {
if (!right_held_) { if (!right_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveRight; wm = WidgetMessage::Type::kMoveRight;
pass = true; pass = true;
right_held_ = true; right_held_ = true;
@ -945,7 +849,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
} }
case SDL_HAT_UP: { case SDL_HAT_UP: {
if (!up_held_) { if (!up_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveUp; wm = WidgetMessage::Type::kMoveUp;
pass = true; pass = true;
up_held_ = true; up_held_ = true;
@ -955,7 +858,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
} }
case SDL_HAT_DOWN: { case SDL_HAT_DOWN: {
if (!down_held_) { if (!down_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveDown; wm = WidgetMessage::Type::kMoveDown;
pass = true; pass = true;
down_held_ = true; down_held_ = true;
@ -968,6 +870,7 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
down_held_ = false; down_held_ = false;
left_held_ = false; left_held_ = false;
right_held_ = false; right_held_ = false;
ui_repeater_.Clear();
} }
default: default:
break; break;
@ -1007,7 +910,20 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
break; break;
} }
if (pass) { 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; return;
} }
@ -1502,22 +1418,7 @@ auto JoystickInput::GetRawDeviceName() -> std::string {
auto JoystickInput::GetDeviceExtraDescription() -> std::string { auto JoystickInput::GetDeviceExtraDescription() -> std::string {
std::string s; std::string s;
// On mac, PS3 controllers can connect via USB or bluetooth, // (no longer being used).
// 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)";
}
}
}
return s; return s;
} }

View File

@ -16,7 +16,7 @@ namespace ballistica::base {
const int kJoystickDiscreteThreshold{15000}; const int kJoystickDiscreteThreshold{15000};
const float kJoystickDiscreteThresholdFloat{0.46f}; const float kJoystickDiscreteThresholdFloat{0.46f};
const int kJoystickAnalogCalibrationDivisions{20}; const int kJoystickAnalogCalibrationDivisions{20};
extern const char* kMFiControllerName; // extern const char* kMFiControllerName;
/// A physical game controller. /// A physical game controller.
class JoystickInput : public InputDevice { class JoystickInput : public InputDevice {
@ -87,48 +87,48 @@ class JoystickInput : public InputDevice {
void UpdateRunningState(); void UpdateRunningState();
auto GetCalibratedValue(float raw, float neutral) const -> int32_t; 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* child_joy_stick_{};
JoystickInput* parent_joy_stick_{}; JoystickInput* parent_joy_stick_{};
millisecs_t last_ui_only_print_time_{}; 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_{}; millisecs_t creation_time_{};
bool did_initial_reset_{};
// FIXME - should take this out and replace it with a bool // FIXME - should take this out and replace it with a bool
// (we never actually access the sdl joystick directly outside of our // (we never actually access the sdl joystick directly outside of our
// constructor) // constructor)
SDL_Joystick* sdl_joystick_{}; SDL_Joystick* sdl_joystick_{};
bool is_test_input_{}; bool ui_only_ : 1 {};
bool is_remote_control_{}; bool unassigned_buttons_run_ : 1 {true};
bool is_remote_app_{}; bool start_button_activates_default_widget_ : 1 {true};
bool is_mfi_controller_{}; bool auto_recalibrate_analog_stick_ : 1 {};
bool is_mac_ps3_controller_{}; bool did_initial_reset_ : 1 {};
bool is_test_input_ : 1 {};
millisecs_t ps3_last_joy_press_time_{-10000}; bool is_remote_control_ : 1 {};
bool is_remote_app_ : 1 {};
bool is_mfi_controller_ : 1 {};
// For dialogs. // For dialogs.
bool left_held_{}; bool left_held_ : 1 {};
bool right_held_{}; bool right_held_ : 1 {};
bool up_held_{}; bool up_held_ : 1 {};
bool down_held_{}; bool down_held_ : 1 {};
bool hold_position_held_{}; bool hold_position_held_ : 1 {};
bool need_to_send_held_state_{}; 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 hat_{};
int analog_lr_{}; int analog_lr_{};
int analog_ud_{1}; 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. // Mappings of ba buttons to SDL buttons.
int jump_button_{}; 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 // 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). // the rest we allow. (all devices are treated as one and the same there).
int remote_enter_button_{-1}; int remote_enter_button_{-1};
bool ignore_completely_{};
int ignored_button_{-1}; int ignored_button_{-1};
int ignored_button2_{-1}; int ignored_button2_{-1};
int ignored_button3_{-1}; int ignored_button3_{-1};
@ -153,12 +152,6 @@ class JoystickInput : public InputDevice {
int run_trigger1_{-1}; int run_trigger1_{-1};
int run_trigger2_{-1}; int run_trigger2_{-1};
int vr_reorient_button_{-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 left_button_{-1};
int right_button_{-1}; int right_button_{-1};
int up_button_{-1}; int up_button_{-1};
@ -167,15 +160,19 @@ class JoystickInput : public InputDevice {
int right_button2_{-1}; int right_button2_{-1};
int up_button2_{-1}; int up_button2_{-1};
int down_button2_{-1}; int down_button2_{-1};
std::set<int> run_buttons_held_;
int sdl_joystick_id_{}; int sdl_joystick_id_{};
bool ps3_jaxis1_pressed_{}; float run_value_{};
bool ps3_jaxis2_pressed_{}; 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_threshold_{};
float calibration_break_threshold_{}; float calibration_break_threshold_{};
float analog_calibration_vals_[kJoystickAnalogCalibrationDivisions]{}; float analog_calibration_vals_[kJoystickAnalogCalibrationDivisions]{};
std::string custom_device_name_; float calibrated_neutral_x_{};
bool can_configure_{}; float calibrated_neutral_y_{};
int32_t dialog_jaxis_x_{}; int32_t dialog_jaxis_x_{};
int32_t dialog_jaxis_y_{}; int32_t dialog_jaxis_y_{};
int32_t jaxis_raw_x_{}; int32_t jaxis_raw_x_{};
@ -183,11 +180,12 @@ class JoystickInput : public InputDevice {
int32_t jaxis_x_{}; int32_t jaxis_x_{};
int32_t jaxis_y_{}; int32_t jaxis_y_{};
millisecs_t calibration_start_time_x_{}; millisecs_t calibration_start_time_x_{};
float calibrated_neutral_x_{};
millisecs_t calibration_start_time_y_{}; millisecs_t calibration_start_time_y_{};
float calibrated_neutral_y_{}; std::set<int> run_buttons_held_;
bool resetting_{}; std::string custom_device_name_;
bool calibrate_{}; std::string raw_sdl_joystick_name_;
std::string raw_sdl_joystick_identifier_;
Object::Ref<Repeater> ui_repeater_;
BA_DISALLOW_CLASS_COPIES(JoystickInput); BA_DISALLOW_CLASS_COPIES(JoystickInput);
}; };

View File

@ -2,8 +2,10 @@
#include "ballistica/base/input/device/keyboard_input.h" #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/platform/base_platform.h"
#include "ballistica/base/support/classic_soft.h" #include "ballistica/base/support/classic_soft.h"
#include "ballistica/base/support/repeater.h"
#include "ballistica/base/ui/ui.h" #include "ballistica/base/ui/ui.h"
namespace ballistica::base { namespace ballistica::base {
@ -42,10 +44,12 @@ KeyboardInput::KeyboardInput(KeyboardInput* parent_keyboard_input_in) {
KeyboardInput::~KeyboardInput() = default; KeyboardInput::~KeyboardInput() = default;
auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down) auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool down) -> bool {
-> bool {
// Only allow the *main* keyboard to talk to the UI // Only allow the *main* keyboard to talk to the UI
if (parent_keyboard_input_ == nullptr) { if (parent_keyboard_input_ == nullptr) {
// Any new event coming in cancels repeats.
ui_repeater_.Clear();
if (g_base->ui->GetWidgetForInput(this)) { if (g_base->ui->GetWidgetForInput(this)) {
bool pass = false; bool pass = false;
auto c = WidgetMessage::Type::kEmptyMessage; 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_SPACE:
case SDLK_KP_ENTER: case SDLK_KP_ENTER:
case SDLK_RETURN: case SDLK_RETURN:
if (!repeat) { c = WidgetMessage::Type::kActivate;
c = WidgetMessage::Type::kActivate; pass = true;
pass = true;
}
break; break;
case SDLK_ESCAPE: case SDLK_ESCAPE:
// (limit to kb1 so we don't get double-beeps on failure) // (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) { 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); return (pass);
} }
} }
// Bring up menu if start is pressed. // 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); g_base->ui->PushMainMenuPressCall(this);
return true; 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. // 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_ && enable_child_) {
if (child_keyboard_input_->HandleKey(keysym, repeat, down)) { if (child_keyboard_input_->HandleKey(keysym, down)) {
return true; return true;
} }
} }
#pragma clang diagnostic pop
if (!AttachedToPlayer()) { if (!AttachedToPlayer()) {
if (down if (down
&& ((keysym->sym == jump_key_) || (keysym->sym == punch_key_) && ((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 // (removing init values from input_type and input_type_2 gives a
// 'possibly uninited value used' warning but leaving them gives a // 'possibly uninited value used' warning but leaving them gives a
// 'values unused' warning. Grumble.) // 'values unused' warning. Grumble.)
explicit_bool(input_type // explicit_bool(input_type
== (explicit_bool(false) ? input_type_2 : InputType::kLast)); // == (explicit_bool(false) ? input_type_2 :
// InputType::kLast));
if (!repeat) { // Keyboard 1 supports assigned keys plus arrow keys if they're unused.
// Keyboard 1 supports assigned keys plus arrow keys if they're unused. if (keysym->sym == left_key_
if (keysym->sym == left_key_ || (device_number() == 1 && keysym->sym == SDLK_LEFT
|| (device_number() == 1 && keysym->sym == SDLK_LEFT && !left_key_assigned())) {
&& !left_key_assigned())) { player_input = true;
player_input = true; input_type = InputType::kLeftRight;
input_type = InputType::kLeftRight; left_held_ = down;
left_held_ = down; if (down) {
if (down) { if (right_held_) {
if (right_held_) { input_value = 0;
input_value = 0;
} else {
input_value = -32767;
}
} else { } else {
if (right_held_) { input_value = -32767;
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 { } else {
// Any other keys get processed as run keys. if (right_held_) {
// keypad keys go to player 2 - anything else to player 1. input_value = 32767;
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;
} }
} }
} 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) { 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()); bool was_held = (!keys_held_.empty());
if (down) { if (down) {
keys_held_.insert(key); keys_held_.insert(key);
@ -407,51 +420,51 @@ void KeyboardInput::UpdateMapping() {
int val = cl ? cl->GetControllerValue(this, "buttonJump") : -1; int val = cl ? cl->GetControllerValue(this, "buttonJump") : -1;
jump_key_ = (val == -1) ? jump_key_default : (SDL_Keycode)val; jump_key_ = (val == -1) ? jump_key_default : (SDL_Keycode)val;
UpdateArrowKeys(jump_key_); UpdateArrowKeys_(jump_key_);
val = cl ? cl->GetControllerValue(this, "buttonPunch") : -1; val = cl ? cl->GetControllerValue(this, "buttonPunch") : -1;
punch_key_ = (val == -1) ? punch_key_default : (SDL_Keycode)val; punch_key_ = (val == -1) ? punch_key_default : (SDL_Keycode)val;
UpdateArrowKeys(punch_key_); UpdateArrowKeys_(punch_key_);
val = cl ? cl->GetControllerValue(this, "buttonBomb") : -1; val = cl ? cl->GetControllerValue(this, "buttonBomb") : -1;
bomb_key_ = (val == -1) ? bomb_key_default : (SDL_Keycode)val; bomb_key_ = (val == -1) ? bomb_key_default : (SDL_Keycode)val;
UpdateArrowKeys(bomb_key_); UpdateArrowKeys_(bomb_key_);
val = cl ? cl->GetControllerValue(this, "buttonPickUp") : -1; val = cl ? cl->GetControllerValue(this, "buttonPickUp") : -1;
pick_up_key_ = (val == -1) ? pick_up_key_default : (SDL_Keycode)val; 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; val = cl ? cl->GetControllerValue(this, "buttonHoldPosition") : -1;
hold_position_key_ = hold_position_key_ =
(val == -1) ? hold_position_key_default : (SDL_Keycode)val; (val == -1) ? hold_position_key_default : (SDL_Keycode)val;
UpdateArrowKeys(hold_position_key_); UpdateArrowKeys_(hold_position_key_);
val = cl ? cl->GetControllerValue(this, "buttonStart") : -1; val = cl ? cl->GetControllerValue(this, "buttonStart") : -1;
start_key_ = (val == -1) ? start_key_default : (SDL_Keycode)val; start_key_ = (val == -1) ? start_key_default : (SDL_Keycode)val;
UpdateArrowKeys(start_key_); UpdateArrowKeys_(start_key_);
val = cl ? cl->GetControllerValue(this, "buttonUp") : -1; val = cl ? cl->GetControllerValue(this, "buttonUp") : -1;
up_key_ = (val == -1) ? up_key_default : (SDL_Keycode)val; up_key_ = (val == -1) ? up_key_default : (SDL_Keycode)val;
UpdateArrowKeys(up_key_); UpdateArrowKeys_(up_key_);
val = cl ? cl->GetControllerValue(this, "buttonDown") : -1; val = cl ? cl->GetControllerValue(this, "buttonDown") : -1;
down_key_ = (val == -1) ? down_key_default : (SDL_Keycode)val; down_key_ = (val == -1) ? down_key_default : (SDL_Keycode)val;
UpdateArrowKeys(down_key_); UpdateArrowKeys_(down_key_);
val = cl ? cl->GetControllerValue(this, "buttonLeft") : -1; val = cl ? cl->GetControllerValue(this, "buttonLeft") : -1;
left_key_ = (val == -1) ? left_key_default : (SDL_Keycode)val; left_key_ = (val == -1) ? left_key_default : (SDL_Keycode)val;
UpdateArrowKeys(left_key_); UpdateArrowKeys_(left_key_);
val = cl ? cl->GetControllerValue(this, "buttonRight") : -1; val = cl ? cl->GetControllerValue(this, "buttonRight") : -1;
right_key_ = (val == -1) ? right_key_default : (SDL_Keycode)val; right_key_ = (val == -1) ? right_key_default : (SDL_Keycode)val;
UpdateArrowKeys(right_key_); UpdateArrowKeys_(right_key_);
enable_child_ = true; enable_child_ = true;
up_held_ = down_held_ = left_held_ = right_held_ = false; 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) { if (key == SDLK_UP) {
up_key_assigned_ = true; up_key_assigned_ = true;
} else if (key == SDLK_DOWN) { } else if (key == SDLK_DOWN) {
@ -465,7 +478,6 @@ void KeyboardInput::UpdateArrowKeys(SDL_Keycode key) {
auto KeyboardInput::GetButtonName(int index) -> std::string { auto KeyboardInput::GetButtonName(int index) -> std::string {
return g_base->platform->GetKeyName(index); return g_base->platform->GetKeyName(index);
// return InputDevice::GetButtonName(index);
} }
auto KeyboardInput::GetRawDeviceName() -> std::string { return "Keyboard"; } auto KeyboardInput::GetRawDeviceName() -> std::string { return "Keyboard"; }

View File

@ -8,6 +8,7 @@
#include "ballistica/base/input/device/input_device.h" #include "ballistica/base/input/device/input_device.h"
#include "ballistica/core/platform/support/min_sdl.h" #include "ballistica/core/platform/support/min_sdl.h"
#include "ballistica/shared/foundation/object.h"
namespace ballistica::base { namespace ballistica::base {
@ -15,7 +16,7 @@ class KeyboardInput : public InputDevice {
public: public:
explicit KeyboardInput(KeyboardInput* parent); explicit KeyboardInput(KeyboardInput* parent);
~KeyboardInput() override; ~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; void UpdateMapping() override;
auto GetRawDeviceName() -> std::string override; auto GetRawDeviceName() -> std::string override;
void ResetHeldStates() override; void ResetHeldStates() override;
@ -29,8 +30,17 @@ class KeyboardInput : public InputDevice {
auto GetButtonName(int index) -> std::string override; auto GetButtonName(int index) -> std::string override;
private: private:
void UpdateArrowKeys(SDL_Keycode key); void UpdateArrowKeys_(SDL_Keycode key);
void UpdateRun(SDL_Keycode key, bool down); 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 up_key_{};
SDL_Keycode down_key_{}; SDL_Keycode down_key_{};
SDL_Keycode left_key_{}; SDL_Keycode left_key_{};
@ -41,18 +51,10 @@ class KeyboardInput : public InputDevice {
SDL_Keycode pick_up_key_{}; SDL_Keycode pick_up_key_{};
SDL_Keycode hold_position_key_{}; SDL_Keycode hold_position_key_{};
SDL_Keycode start_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* parent_keyboard_input_{};
KeyboardInput* child_keyboard_input_{}; KeyboardInput* child_keyboard_input_{};
std::set<int> keys_held_; std::set<int> keys_held_;
Object::Ref<Repeater> ui_repeater_;
}; };
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -159,12 +159,11 @@ void Input::AnnounceConnects_() {
// For the first announcement just say "X controllers detected" and don't // For the first announcement just say "X controllers detected" and don't
// have a sound. // have a sound.
if (first_print && g_core->GetAppTimeSeconds() < 5.0) { if (first_print && g_core->GetAppTimeSeconds() < 2.0) {
first_print = false; first_print = false;
// Disabling this completely on Android for now; we often get large // Disabling this completely on Android for now; we often get large
// numbers of devices there that aren't actually devices. // numbers of devices there that aren't actually devices.
bool do_print_initial_counts{!g_buildconfig.ostype_android()}; bool do_print_initial_counts{!g_buildconfig.ostype_android()};
// If there's been several connected, just give a number. // 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_); g_base->logic->DeleteAppTimer(connect_print_timer_id_);
} }
connect_print_timer_id_ = g_base->logic->NewAppTimer( connect_print_timer_id_ = g_base->logic->NewAppTimer(
250, false, NewLambdaRunnable([this] { AnnounceConnects_(); })); 250, false, NewLambdaRunnable([this] { AnnounceConnects_(); }).Get());
} }
void Input::ShowStandardInputDeviceDisconnectedMessage_(InputDevice* j) { void Input::ShowStandardInputDeviceDisconnectedMessage_(InputDevice* j) {
@ -258,7 +257,7 @@ void Input::ShowStandardInputDeviceDisconnectedMessage_(InputDevice* j) {
g_base->logic->DeleteAppTimer(disconnect_print_timer_id_); g_base->logic->DeleteAppTimer(disconnect_print_timer_id_);
} }
disconnect_print_timer_id_ = g_base->logic->NewAppTimer( 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, void Input::PushAddInputDeviceCall(InputDevice* input_device,
@ -802,6 +801,7 @@ void Input::ProcessStressTesting(int player_count) {
(*test_input).Reset(); (*test_input).Reset();
} }
} }
while (stress_test_time_ < time) { while (stress_test_time_ < time) {
stress_test_time_++; stress_test_time_++;
for (auto& test_input : test_inputs_) { 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] { g_base->logic->event_loop()->PushCall([this, text] {
MarkInputActive(); 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()) { if (!g_base->app_adapter->HasDirectKeyboardInput()) {
return; return;
} }
@ -848,6 +848,7 @@ void Input::PushTextInputEvent(const std::string& text) {
&& g_base->ui->dev_console()->HandleTextEditing(text)) { && g_base->ui->dev_console()->HandleTextEditing(text)) {
return; return;
} }
g_base->ui->SendWidgetMessage(WidgetMessage( g_base->ui->SendWidgetMessage(WidgetMessage(
WidgetMessage::Type::kTextInput, nullptr, 0, 0, 0, 0, text.c_str())); WidgetMessage::Type::kTextInput, nullptr, 0, 0, 0, 0, text.c_str()));
}); });
@ -986,6 +987,34 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) {
return; 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 someone is capturing these events, give them a crack at it.
if (keyboard_input_capture_press_) { if (keyboard_input_capture_press_) {
if (keyboard_input_capture_press_(keysym)) { 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) // ideally we should use the modifiers bundled with the key presses)
UpdateModKeyStates_(&keysym, true); 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. // Mobile-specific stuff.
if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) { // if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) {
switch (keysym.sym) { // switch (keysym.sym) {
// FIXME: See if this stuff is still necessary. Was this perhaps // // FIXME: See if this stuff is still necessary. Was this perhaps
// specifically to support the console? // // specifically to support the console?
case SDLK_DELETE: // case SDLK_DELETE:
case SDLK_RETURN: // case SDLK_RETURN:
case SDLK_KP_ENTER: // case SDLK_KP_ENTER:
case SDLK_BACKSPACE: { // case SDLK_BACKSPACE: {
// FIXME: I don't remember what this was put here for, but now that // // FIXME: I don't remember what this was put here for, but now that
// we have hardware keyboards it crashes text fields by sending // // we have hardware keyboards it crashes text fields by sending
// them a TEXT_INPUT message with no string.. I made them resistant // // them a TEXT_INPUT message with no string.. I made them resistant
// to that case but wondering if we can take this out? // // to that case but wondering if we can take this out?
g_base->ui->SendWidgetMessage( // g_base->ui->SendWidgetMessage(
WidgetMessage(WidgetMessage::Type::kTextInput, &keysym)); // WidgetMessage(WidgetMessage::Type::kTextInput, &keysym));
break; // break;
} // }
default: // default:
break; // break;
} // }
} // }
// Explicitly handle fullscreen-toggles in some cases. // Explicitly handle fullscreen-toggles in some cases.
if (g_base->app_adapter->FullscreenControlAvailable()) { if (g_base->app_adapter->FullscreenControlAvailable()) {
bool do_toggle{}; bool do_toggle{};
// On our Mac SDL builds we support ctrl+F for toggling fullscreen. // On our Mac SDL builds we support ctrl+F for toggling fullscreen.
// On our nice Cocoa build, fullscreening happens magically through the // 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 (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; do_toggle = true;
} }
} }
// On Windows we support both F11 and Alt+Enter for toggling fullscreen. // On Windows and Linux we support both F11 and Alt+Enter for toggling
if (g_buildconfig.ostype_windows()) { // fullscreen.
if (!repeat_press if (g_buildconfig.ostype_windows() || g_buildconfig.ostype_linux()) {
&& (keysym.sym == SDLK_F11 if ((keysym.sym == SDLK_F11
|| (keysym.sym == SDLK_RETURN && ((keysym.mod & KMOD_ALT))))) { || (keysym.sym == SDLK_RETURN && ((keysym.mod & KMOD_ALT))))) {
do_toggle = true; 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 // Dev Console.
// 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.
if (auto* console = g_base->ui->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)) { if (console->HandleKeyPress(&keysym)) {
return; return;
} }
} }
// Ctrl-V or Cmd-V sends paste commands to any interested text fields. // Ctrl-V or Cmd-V sends paste commands to any interested text fields.
// Command-Q or Control-Q quits. if (keysym.sym == SDLK_v
if (!repeat_press && keysym.sym == SDLK_v
&& ((keysym.mod & KMOD_CTRL) || (keysym.mod & KMOD_GUI))) { && ((keysym.mod & KMOD_CTRL) || (keysym.mod & KMOD_GUI))) {
g_base->ui->SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kPaste)); g_base->ui->SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kPaste));
return; return;
@ -1082,93 +1098,89 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) {
bool handled = false; bool handled = false;
// None of the following stuff accepts key repeats. switch (keysym.sym) {
if (!repeat_press) { // Menu button on android/etc. pops up the menu.
switch (keysym.sym) { case SDLK_MENU: {
// Menu button on android/etc. pops up the menu. if (!g_base->ui->MainMenuVisible()) {
case SDLK_MENU: { g_base->ui->PushMainMenuPressCall(touch_input_);
if (!g_base->ui->MainMenuVisible()) {
g_base->ui->PushMainMenuPressCall(touch_input_);
}
handled = true;
break;
} }
handled = true;
case SDLK_EQUALS: break;
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;
} }
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 (!handled) {
if (keyboard_input_) { 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 // 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 reset was run, it deals out key releases, but then the
// keyboard driver issues them as well). // keyboard driver issues them as well).
if (keys_held_.count(keysym.sym) == 0) { if (keys_held_.find(keysym.sym) == keys_held_.end()) {
return; return;
} }
@ -1205,7 +1217,7 @@ void Input::HandleKeyRelease_(const SDL_Keysym& keysym) {
} }
if (keyboard_input_) { if (keyboard_input_) {
keyboard_input_->HandleKey(&keysym, false, false); keyboard_input_->HandleKey(&keysym, false);
} }
} }

View File

@ -100,7 +100,7 @@ void Logic::OnGraphicsReady() {
// variety of rates anyway. NOTE: This length is currently milliseconds. // variety of rates anyway. NOTE: This length is currently milliseconds.
headless_display_time_step_timer_ = event_loop()->NewTimer( headless_display_time_step_timer_ = event_loop()->NewTimer(
kHeadlessMinDisplayTimeStep / 1000, true, kHeadlessMinDisplayTimeStep / 1000, true,
NewLambdaRunnable([this] { StepDisplayTime_(); })); NewLambdaRunnable([this] { StepDisplayTime_(); }).Get());
} else { } else {
// In gui mode, push an initial frame to the graphics server. From this // 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 // point it will be self-sustaining, sending us a frame request each
@ -133,9 +133,9 @@ void Logic::CompleteAppBootstrapping_() {
// Set up our timers. // Set up our timers.
process_pending_work_timer_ = event_loop()->NewTimer( process_pending_work_timer_ = event_loop()->NewTimer(
0, true, NewLambdaRunnable([this] { ProcessPendingWork_(); })); 0, true, NewLambdaRunnable([this] { ProcessPendingWork_(); }).Get());
asset_prune_timer_ = event_loop()->NewTimer( 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. // Let our initial dummy app-mode know it has become active.
g_base->app_mode()->OnActivate(); g_base->app_mode()->OnActivate();
@ -635,8 +635,8 @@ void Logic::NotifyOfPendingAssetLoads() {
UpdatePendingWorkTimer_(); UpdatePendingWorkTimer_();
} }
auto Logic::NewAppTimer(millisecs_t length, bool repeat, auto Logic::NewAppTimer(millisecs_t length, bool repeat, Runnable* runnable)
const Object::Ref<Runnable>& runnable) -> int { -> int {
// App-Timers simply get injected into our loop and run alongside our own // App-Timers simply get injected into our loop and run alongside our own
// stuff. // stuff.
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
@ -667,7 +667,7 @@ auto Logic::NewDisplayTimer(microsecs_t length, bool repeat,
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
int offset = 0; int offset = 0;
Timer* t = display_timers_->NewTimer(g_core->GetAppTimeMicrosecs(), length, Timer* t = display_timers_->NewTimer(g_core->GetAppTimeMicrosecs(), length,
offset, repeat ? -1 : 0, runnable); offset, repeat ? -1 : 0, runnable.Get());
return t->id(); return t->id();
} }

View File

@ -89,8 +89,7 @@ class Logic {
void HandleInterruptSignal(); void HandleInterruptSignal();
void HandleTerminateSignal(); void HandleTerminateSignal();
auto NewAppTimer(millisecs_t length, bool repeat, auto NewAppTimer(millisecs_t length, bool repeat, Runnable* runnable) -> int;
const Object::Ref<Runnable>& runnable) -> int;
void DeleteAppTimer(int timer_id); void DeleteAppTimer(int timer_id);
void SetAppTimerLength(int timer_id, millisecs_t length); void SetAppTimerLength(int timer_id, millisecs_t length);

View File

@ -4,11 +4,14 @@
#include "ballistica/base/platform/apple/base_platform_apple.h" #include "ballistica/base/platform/apple/base_platform_apple.h"
#if BA_XCODE_BUILD #if BA_XCODE_BUILD
#include <BallisticaKit-Swift.h> #include "ballistica/base/platform/apple/apple_utils.h"
#include "ballistica/base/platform/apple/from_swift.h"
#endif #endif
#if BA_XCODE_BUILD #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 <BallisticaKit-Swift.h>
#endif #endif
namespace ballistica::base { namespace ballistica::base {
@ -48,9 +51,9 @@ void BasePlatformApple::PurchaseAck(const std::string& purchase,
void BasePlatformApple::DoOpenURL(const std::string& url) { void BasePlatformApple::DoOpenURL(const std::string& url) {
#if BA_XCODE_BUILD #if BA_XCODE_BUILD
#if BA_OSTYPE_MACOS #if BA_OSTYPE_MACOS
BallisticaKit::CocoaFromCppOpenURL(url); BallisticaKit::CocoaFromCpp::OpenURL(url);
#else #else
BallisticaKit::UIKitFromCppOpenURL(url); BallisticaKit::UIKitFromCpp::OpenURL(url);
#endif // BA_OSTYPE_MACOS #endif // BA_OSTYPE_MACOS
#else #else

View File

@ -95,7 +95,7 @@ auto PythonClassAppTimer::tp_new(PyTypeObject* type, PyObject* args,
self->timer_id_ = g_base->logic->NewAppTimer( self->timer_id_ = g_base->logic->NewAppTimer(
static_cast<millisecs_t>(length * 1000.0), repeat, static_cast<millisecs_t>(length * 1000.0), repeat,
Object::New<Runnable, PythonContextCallRunnable>(call_obj)); Object::New<Runnable, PythonContextCallRunnable>(call_obj).Get());
self->have_timer_ = true; self->have_timer_ = true;

View File

@ -366,7 +366,7 @@ static auto PyAppTimer(PyObject* self, PyObject* args, PyObject* keywds)
} }
g_base->logic->NewAppTimer( g_base->logic->NewAppTimer(
static_cast<millisecs_t>(length * 1000.0), false, static_cast<millisecs_t>(length * 1000.0), false,
Object::New<Runnable, PythonContextCallRunnable>(call_obj)); Object::New<Runnable, PythonContextCallRunnable>(call_obj).Get());
Py_RETURN_NONE; Py_RETURN_NONE;
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
} }
@ -1645,6 +1645,52 @@ static PyMethodDef PyAudioShutdownIsCompleteDef = {
"(internal)\n", "(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<PyMethodDef> { auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
@ -1702,6 +1748,8 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
PyDevConsoleInputAdapterFinishDef, PyDevConsoleInputAdapterFinishDef,
PyAudioShutdownBeginDef, PyAudioShutdownBeginDef,
PyAudioShutdownIsCompleteDef, PyAudioShutdownIsCompleteDef,
PyGraphicsShutdownBeginDef,
PyGraphicsShutdownIsCompleteDef,
}; };
} }

View File

@ -536,6 +536,7 @@ static auto PyFadeScreen(PyObject* self, PyObject* args, PyObject* keywds)
&endcall)) { &endcall)) {
return nullptr; return nullptr;
} }
BA_PRECONDITION(g_base->InLogicThread());
g_base->graphics->FadeScreen(static_cast<bool>(fade), g_base->graphics->FadeScreen(static_cast<bool>(fade),
static_cast<int>(1000.0f * time), endcall); static_cast<int>(1000.0f * time), endcall);
Py_RETURN_NONE; Py_RETURN_NONE;

View File

@ -13,12 +13,17 @@ namespace ballistica::base {
class AppTimer : public Object { class AppTimer : public Object {
public: public:
AppTimer(millisecs_t length, bool repeat, AppTimer(millisecs_t length, bool repeat, Runnable* runnable) {
const Object::Ref<Runnable>& runnable) {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
timer_id_ = base::g_base->logic->NewAppTimer(length, repeat, runnable); timer_id_ = base::g_base->logic->NewAppTimer(length, repeat, runnable);
} }
template <typename F>
static auto New(millisecs_t length, bool repeat, const F& lambda) {
return Object::New<AppTimer>(length, repeat,
NewLambdaRunnable<F>(lambda).Get());
}
void SetLength(millisecs_t length) { void SetLength(millisecs_t length) {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
base::g_base->logic->SetAppTimerLength(timer_id_, length); base::g_base->logic->SetAppTimerLength(timer_id_, length);
@ -32,13 +37,6 @@ class AppTimer : public Object {
int timer_id_; int timer_id_;
}; };
/// Create a AppTimer from a raw lambda.
template <typename F>
auto NewAppTimer(millisecs_t length, bool repeat, const F& lambda)
-> Object::Ref<AppTimer> {
return Object::New<AppTimer>(length, repeat, NewLambdaRunnable<F>(lambda));
}
} // namespace ballistica::base } // namespace ballistica::base
#endif // BALLISTICA_BASE_SUPPORT_APP_TIMER_H_ #endif // BALLISTICA_BASE_SUPPORT_APP_TIMER_H_

View File

@ -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<Repeater>(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<millisecs_t>(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<millisecs_t>(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

View File

@ -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 <typename F>
static auto New(seconds_t initial_delay, seconds_t repeat_delay,
const F& lambda) {
return Object::New<Repeater>(initial_delay, repeat_delay,
NewLambdaRunnable<F>(lambda).Get());
}
private:
seconds_t initial_delay_;
seconds_t repeat_delay_;
Object::Ref<AppTimer> timer_;
Object::Ref<Runnable> runnable_;
};
} // namespace ballistica::base
#endif // BALLISTICA_BASE_SUPPORT_REPEATER_H_

View File

@ -2,6 +2,7 @@
#include "ballistica/base/ui/dev_console.h" #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/app_mode/app_mode.h"
#include "ballistica/base/audio/audio.h" #include "ballistica/base/audio/audio.h"
#include "ballistica/base/graphics/component/simple_component.h" #include "ballistica/base/graphics/component/simple_component.h"
@ -11,6 +12,7 @@
#include "ballistica/base/logic/logic.h" #include "ballistica/base/logic/logic.h"
#include "ballistica/base/platform/base_platform.h" #include "ballistica/base/platform/base_platform.h"
#include "ballistica/base/python/base_python.h" #include "ballistica/base/python/base_python.h"
#include "ballistica/base/support/repeater.h"
#include "ballistica/base/ui/ui.h" #include "ballistica/base/ui/ui.h"
#include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/generic/utils.h" #include "ballistica/shared/generic/utils.h"
@ -20,14 +22,12 @@
namespace ballistica::base { namespace ballistica::base {
// How much of the screen the console covers when it is at full size. // How much of the screen the console covers when it is at full size.
const float kDevConsoleSize = 0.9f; const float kDevConsoleSize{0.9f};
const int kDevConsoleLineLimit = 80; const int kDevConsoleLineLimit{80};
const int kDevConsoleStringBreakUpSize = 1950; const int kDevConsoleStringBreakUpSize{1950};
const int kDevConsoleActivateKey1 = SDLK_BACKQUOTE;
const int kDevConsoleActivateKey2 = SDLK_F2;
const float kDevConsoleTabButtonCornerRadius{16.0f}; const float kDevConsoleTabButtonCornerRadius{16.0f};
const double kTransitionSeconds = 0.15; const double kTransitionSeconds{0.15};
enum class DevConsoleHAnchor_ { kLeft, kCenter, kRight }; enum class DevConsoleHAnchor_ { kLeft, kCenter, kRight };
enum class DevButtonStyle_ { kNormal, kDark }; enum class DevButtonStyle_ { kNormal, kDark };
@ -171,11 +171,7 @@ class DevConsole::Text_ : public DevConsole::Widget_ {
} }
void Draw(RenderPass* pass, float bottom) override { void Draw(RenderPass* pass, float bottom) override {
Vector3f fgcolor; auto fgcolor = Vector3f{0.8f, 0.7f, 0.8f};
Vector3f bgcolor;
fgcolor = Vector3f{0.8f, 0.7f, 0.8f};
bgcolor = Vector3f{0.25, 0.2f, 0.3f};
DrawText(pass, &text_group, scale, bottom, x + XOffs(h_attach), y, fgcolor); 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 { void Draw(RenderPass* pass, float bottom) override {
DrawRect(pass, &mesh, bottom, x + XOffs(attach), y, width, height, DrawRect(pass, &mesh, bottom, x + XOffs(attach), y, width, height,
pressed ? Vector3f{0.5f, 0.2f, 1.0f} pressed ? Vector3f{0.5f, 0.2f, 1.0f}
: on ? Vector3f{0.5f, 0.4f, 0.6f} : on ? Vector3f{0.5f, 0.4f, 0.6f}
: Vector3f{0.25, 0.2f, 0.3f}); : Vector3f{0.25, 0.2f, 0.3f});
@ -448,14 +443,11 @@ DevConsole::DevConsole() {
if (g_buildconfig.test_build()) { if (g_buildconfig.test_build()) {
title += " (test)"; title += " (test)";
} }
title_text_group_.SetText(title); title_text_group_.SetText(title);
built_text_group_.SetText("Built: " __DATE__ " " __TIME__); built_text_group_.SetText("Built: " __DATE__ " " __TIME__);
prompt_text_group_.SetText(">"); prompt_text_group_.SetText(">");
} }
DevConsole::~DevConsole() = default;
void DevConsole::RefreshTabButtons_() { void DevConsole::RefreshTabButtons_() {
// IMPORTANT: This code should always be run in its own top level call and // 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 // 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 { auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
// Any presses or releases cancels repeat actions.
key_repeater_.Clear();
// Handle our toggle buttons no matter whether we're active. // Handle our toggle buttons no matter whether we're active.
switch (keysym->sym) { // switch (keysym->sym) {
case kDevConsoleActivateKey1: // case kDevConsoleActivateKey1:
case kDevConsoleActivateKey2: { // case kDevConsoleActivateKey2: {
if (!g_buildconfig.demo_build() && !g_buildconfig.arcade_build()) { // if (!g_buildconfig.demo_build() && !g_buildconfig.arcade_build()) {
// (reset input so characters don't continue walking and stuff) // // (reset input so characters don't continue walking and stuff)
g_base->input->ResetHoldStates(); // g_base->input->ResetHoldStates();
if (auto console = g_base->ui->dev_console()) { // if (auto console = g_base->ui->dev_console()) {
console->ToggleState(); // console->ToggleState();
} // }
} // }
return true; // return true;
} // }
default: // default:
break; // break;
} // }
if (state_ == State_::kInactive) { if (state_ == State_::kInactive) {
return false; return false;
@ -725,18 +720,21 @@ auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
break; break;
} }
// Handle some stuff only with the Python terminal visible. // If we support direct keyboard input, and python terminal is showing,
if (python_terminal_visible_) { // handle some keys directly.
if (python_terminal_visible_ && g_base->ui->UIHasDirectKeyboardInput()) {
switch (keysym->sym) { switch (keysym->sym) {
case SDLK_BACKSPACE: case SDLK_BACKSPACE: {
case SDLK_DELETE: { key_repeater_ = Repeater::New(
std::vector<uint32_t> unichars = g_base->app_adapter->GetKeyRepeatDelay(),
Utils::UnicodeFromUTF8(input_string_, "fjco38"); g_base->app_adapter->GetKeyRepeatInterval(), [this] {
if (!unichars.empty()) { auto unichars = Utils::UnicodeFromUTF8(input_string_, "fjco38");
unichars.resize(unichars.size() - 1); if (!unichars.empty()) {
input_string_ = Utils::UTF8FromUnicode(unichars); unichars.resize(unichars.size() - 1);
input_text_dirty_ = true; input_string_ = Utils::UTF8FromUnicode(unichars);
} input_text_dirty_ = true;
}
});
break; break;
} }
case SDLK_UP: case SDLK_UP:
@ -772,8 +770,20 @@ auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
break; 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() { void DevConsole::Exec() {
@ -879,17 +889,6 @@ auto DevConsole::HandleTextEditing(const std::string& text) -> bool {
return true; 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) { void DevConsole::Print(const std::string& s_in) {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
std::string s = Utils::GetValidUTF8(s_in.c_str(), "cspr"); std::string s = Utils::GetValidUTF8(s_in.c_str(), "cspr");

View File

@ -20,7 +20,6 @@ const float kDevConsoleZDepth = 0.0f;
class DevConsole { class DevConsole {
public: public:
DevConsole(); DevConsole();
~DevConsole();
auto IsActive() const -> bool { return (state_ != State_::kInactive); } auto IsActive() const -> bool { return (state_ != State_::kInactive); }
auto HandleTextEditing(const std::string& text) -> bool; auto HandleTextEditing(const std::string& text) -> bool;
auto HandleKeyPress(const SDL_Keysym* keysym) -> bool; auto HandleKeyPress(const SDL_Keysym* keysym) -> bool;
@ -82,18 +81,18 @@ class DevConsole {
void RefreshTabButtons_(); void RefreshTabButtons_();
void RefreshTabContents_(); void RefreshTabContents_();
bool input_text_dirty_{true}; int input_history_position_{};
bool input_enabled_{}; int ui_lock_count_ : 1 {};
bool last_line_mesh_dirty_{true}; bool input_text_dirty_ : 1 {true};
bool python_terminal_visible_{}; bool input_enabled_ : 1 {};
bool python_terminal_pressed_{}; bool last_line_mesh_dirty_ : 1 {true};
bool refresh_pending_{}; bool python_terminal_visible_ : 1 {};
int ui_lock_count_{}; bool python_terminal_pressed_ : 1 {};
bool refresh_pending_ : 1 {};
double transition_start_{};
State_ state_{State_::kInactive}; State_ state_{State_::kInactive};
State_ state_prev_{State_::kInactive}; State_ state_prev_{State_::kInactive};
millisecs_t last_input_text_change_time_{}; millisecs_t last_input_text_change_time_{};
double transition_start_{};
int input_history_position_{};
ImageMesh bg_mesh_; ImageMesh bg_mesh_;
ImageMesh stripe_mesh_; ImageMesh stripe_mesh_;
ImageMesh border_mesh_; ImageMesh border_mesh_;
@ -103,15 +102,15 @@ class DevConsole {
TextGroup input_text_group_; TextGroup input_text_group_;
std::string last_line_; std::string last_line_;
std::string input_string_; std::string input_string_;
std::list<std::string> tabs_{"Python", "AppModes", "Logging", "Graphics", std::list<std::string> tabs_;
"UI"}; std::string active_tab_;
std::string active_tab_{"Python"};
PythonRef string_edit_adapter_; PythonRef string_edit_adapter_;
Object::Ref<TextGroup> last_line_mesh_group_;
std::list<std::string> input_history_; std::list<std::string> input_history_;
std::list<OutputLine_> output_lines_; std::list<OutputLine_> output_lines_;
std::vector<std::unique_ptr<Widget_> > widgets_; std::vector<std::unique_ptr<Widget_> > widgets_;
std::vector<std::unique_ptr<Widget_> > tab_buttons_; std::vector<std::unique_ptr<Widget_> > tab_buttons_;
Object::Ref<TextGroup> last_line_mesh_group_;
Object::Ref<Repeater> key_repeater_;
}; };
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -194,7 +194,7 @@ auto UI::UIHasDirectKeyboardInput() const -> bool {
// we'll probably want to pop up a controller-centric on-screen-keyboard // we'll probably want to pop up a controller-centric on-screen-keyboard
// thingie instead. // thingie instead.
auto* ui_input_device = g_base->ui->GetUIInputDevice(); 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) { if (ui_input_device == keyboard || ui_input_device == nullptr) {
return true; return true;
} }
@ -532,7 +532,7 @@ void UI::OnAssetsAvailable() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
// Spin up the dev console. // Spin up the dev console.
if (!g_core->HeadlessMode()) { if (!g_core->HeadlessMode() && !g_buildconfig.demo_build()) {
assert(dev_console_ == nullptr); assert(dev_console_ == nullptr);
dev_console_ = new DevConsole(); dev_console_ = new DevConsole();

View File

@ -17,6 +17,14 @@ class Widget;
namespace ballistica::base { 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 // Our global UI subsystem. This acts as a manager/wrapper for individual UI
// feature-sets that provide specific UI functionality. // feature-sets that provide specific UI functionality.
class UI { class UI {
@ -84,10 +92,10 @@ class UI {
auto GetUIInputDevice() const -> InputDevice*; auto GetUIInputDevice() const -> InputDevice*;
/// Return true if there is a full desktop-style hardware keyboard /// 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 /// also may take language or user preferences into account. Editable text
/// elements can use this to opt in to accepting key events directly /// 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; auto UIHasDirectKeyboardInput() const -> bool;
/// Schedule a back button press. Can be called from any thread. /// Schedule a back button press. Can be called from any thread.

View File

@ -4,7 +4,6 @@
#include "ballistica/core/platform/apple/core_platform_apple.h" #include "ballistica/core/platform/apple/core_platform_apple.h"
#if BA_XCODE_BUILD #if BA_XCODE_BUILD
#include <BallisticaKit-Swift.h>
#include <unistd.h> #include <unistd.h>
#endif #endif
@ -12,6 +11,14 @@
#if BA_XCODE_BUILD #if BA_XCODE_BUILD
#include "ballistica/base/platform/apple/apple_utils.h" #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 <BallisticaKit-Swift.h>
#endif #endif
namespace ballistica::core { namespace ballistica::core {
@ -31,7 +38,7 @@ auto CorePlatformApple::GetDeviceV1AccountUUIDPrefix() -> std::string {
// Legacy for device-accounts; don't modify this code. // Legacy for device-accounts; don't modify this code.
auto CorePlatformApple::GetRealLegacyDeviceUUID(std::string* uuid) -> bool { auto CorePlatformApple::GetRealLegacyDeviceUUID(std::string* uuid) -> bool {
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD #if BA_OSTYPE_MACOS && BA_XCODE_BUILD
*uuid = base::AppleUtils::GetMacUUID(); *uuid = std::string(BallisticaKit::CocoaFromCpp::GetLegacyDeviceUUID());
return true; return true;
#endif #endif
#if BA_OSTYPE_IOS_TVOS #if BA_OSTYPE_IOS_TVOS
@ -69,7 +76,8 @@ auto CorePlatformApple::GetDeviceUUIDInputs() -> std::list<std::string> {
std::list<std::string> out; std::list<std::string> out;
#if BA_OSTYPE_MACOS #if BA_OSTYPE_MACOS
#if BA_XCODE_BUILD #if BA_XCODE_BUILD
out.push_back(base::AppleUtils::GetMacUUID()); out.push_back(
std::string(BallisticaKit::CocoaFromCpp::GetLegacyDeviceUUID()));
#else // BA_XCODE_BUILD #else // BA_XCODE_BUILD
out.push_back(GetMacUUIDFallback()); out.push_back(GetMacUUIDFallback());
#endif // BA_XCODE_BUILD #endif // BA_XCODE_BUILD
@ -96,28 +104,13 @@ auto CorePlatformApple::DoGetConfigDirectoryMonolithicDefault()
printf("FIXME: get proper default-config-dir\n"); printf("FIXME: get proper default-config-dir\n");
return std::string(getenv("HOME")) + "/Library"; return std::string(getenv("HOME")) + "/Library";
#elif BA_OSTYPE_MACOS && BA_XCODE_BUILD #elif BA_OSTYPE_MACOS && BA_XCODE_BUILD
return base::AppleUtils::GetApplicationSupportPath() + "/BallisticaKit"; return std::string(BallisticaKit::CocoaFromCpp::GetApplicationSupportPath())
+ "/BallisticaKit";
#else #else
return CorePlatform::DoGetConfigDirectoryMonolithicDefault(); return CorePlatform::DoGetConfigDirectoryMonolithicDefault();
#endif #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 { auto CorePlatformApple::DoHasTouchScreen() -> bool {
#if BA_OSTYPE_IOS #if BA_OSTYPE_IOS
return true; return true;
@ -134,7 +127,7 @@ auto CorePlatformApple::GetDefaultUIScale() -> UIScale {
return UIScale::kSmall; return UIScale::kSmall;
} }
#else #else
// Default case handles mac & tvos. // The default case handles mac & tvos.
return CorePlatform::GetDefaultUIScale(); return CorePlatform::GetDefaultUIScale();
#endif #endif
} }
@ -163,37 +156,37 @@ void CorePlatformApple::EmitPlatformLog(const std::string& name, LogLevel level,
auto CorePlatformApple::DoGetDataDirectoryMonolithicDefault() -> std::string { auto CorePlatformApple::DoGetDataDirectoryMonolithicDefault() -> std::string {
#if BA_XCODE_BUILD #if BA_XCODE_BUILD
return BallisticaKit::FromCppGetResourcesPath(); return BallisticaKit::FromCpp::GetResourcesPath();
#else #else
// Fall back to default. // Fall back to default.
return CorePlatform::DoGetDataDirectoryMonolithicDefault(); return CorePlatform::DoGetDataDirectoryMonolithicDefault();
#endif #endif
} }
void CorePlatformApple::GetTextBoundsAndWidth(const std::string& text, Rect* r, #if BA_XCODE_BUILD
float* width) { class TextTextureWrapper_ {
#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD public:
base::AppleUtils::GetTextBoundsAndWidth(text, r, width); TextTextureWrapper_(int width, int height,
#else const std::vector<std::string>& strings,
CorePlatform::GetTextBoundsAndWidth(text, r, width); const std::vector<float>& positions,
const std::vector<float>& widths, float scale)
: data{BallisticaKit::TextTextureData::init(width, height, strings,
positions, widths, scale)} {}
BallisticaKit::TextTextureData data;
};
#endif #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( auto CorePlatformApple::CreateTextTexture(
int width, int height, const std::vector<std::string>& strings, int width, int height, const std::vector<std::string>& strings,
const std::vector<float>& positions, const std::vector<float>& widths, const std::vector<float>& positions, const std::vector<float>& widths,
float scale) -> void* { float scale) -> void* {
#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD #if BA_XCODE_BUILD && !BA_HEADLESS_BUILD
return base::AppleUtils::CreateTextTexture(width, height, strings, positions, auto* wrapper =
widths, scale); new TextTextureWrapper_(width, height, strings, positions, widths, scale);
// wrapper->old = base::AppleUtils::CreateTextTexture(width, height, strings,
// positions, widths,
// scale);
return wrapper;
#else #else
return CorePlatform::CreateTextTexture(width, height, strings, positions, return CorePlatform::CreateTextTexture(width, height, strings, positions,
widths, scale); widths, scale);
@ -202,12 +195,45 @@ auto CorePlatformApple::CreateTextTexture(
auto CorePlatformApple::GetTextTextureData(void* tex) -> uint8_t* { auto CorePlatformApple::GetTextTextureData(void* tex) -> uint8_t* {
#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD #if BA_XCODE_BUILD && !BA_HEADLESS_BUILD
return base::AppleUtils::GetTextTextureData(tex); auto* wrapper = static_cast<TextTextureWrapper_*>(tex);
return static_cast<uint8_t*>(wrapper->data.getTextTextureData());
// return base::AppleUtils::GetTextTextureData(wrapper->old);
#else #else
return CorePlatform::GetTextTextureData(tex); return CorePlatform::GetTextTextureData(tex);
#endif #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<TextTextureWrapper_*>(tex);
// base::AppleUtils::FreeTextTexture(wrapper->old);
delete wrapper;
#else
CorePlatform::FreeTextTexture(tex);
#endif
}
void CorePlatformApple::SubmitScore(const std::string& game, void CorePlatformApple::SubmitScore(const std::string& game,
const std::string& version, int64_t score) { const std::string& version, int64_t score) {
#if BA_USE_GAME_CENTER #if BA_USE_GAME_CENTER
@ -278,15 +304,18 @@ void CorePlatformApple::GameCenterLogin() {
auto CorePlatformApple::IsOSPlayingMusic() -> bool { auto CorePlatformApple::IsOSPlayingMusic() -> bool {
#if BA_XCODE_BUILD #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 #else
return CorePlatform::IsOSPlayingMusic(); return CorePlatform::IsOSPlayingMusic();
#endif #endif
} }
void CorePlatformApple::OpenFileExternally(const std::string& path) { void CorePlatformApple::OpenFileExternally(const std::string& path) {
#if BA_XCODE_BUILD #if BA_OSTYPE_MACOS && BA_XCODE_BUILD
base::AppleUtils::EditTextFile(path.c_str()); BallisticaKit::CocoaFromCpp::OpenFileExternally(path);
#else #else
CorePlatform::OpenFileExternally(path); CorePlatform::OpenFileExternally(path);
#endif #endif
@ -294,7 +323,7 @@ void CorePlatformApple::OpenFileExternally(const std::string& path) {
void CorePlatformApple::OpenDirExternally(const std::string& path) { void CorePlatformApple::OpenDirExternally(const std::string& path) {
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD #if BA_OSTYPE_MACOS && BA_XCODE_BUILD
BallisticaKit::CocoaFromCppOpenDirExternally(path); BallisticaKit::CocoaFromCpp::OpenDirExternally(path);
#else #else
CorePlatform::OpenDirExternally(path); CorePlatform::OpenDirExternally(path);
#endif #endif

View File

@ -21,8 +21,8 @@ class CorePlatformApple : public CorePlatform {
auto GenerateUUID() -> std::string override; auto GenerateUUID() -> std::string override;
auto DoGetConfigDirectoryMonolithicDefault() auto DoGetConfigDirectoryMonolithicDefault()
-> std::optional<std::string> override; -> std::optional<std::string> override;
auto GetLocale() -> std::string override; // auto GetLocale() -> std::string override;
auto DoGetDeviceName() -> std::string override; // auto DoGetDeviceName() -> std::string override;
auto DoHasTouchScreen() -> bool override; auto DoHasTouchScreen() -> bool override;
auto GetDefaultUIScale() -> UIScale override; auto GetDefaultUIScale() -> UIScale override;
auto IsRunningOnDesktop() -> bool override; auto IsRunningOnDesktop() -> bool override;

View File

@ -328,7 +328,7 @@ typedef enum {
#define SDLK_SCANCODE_MASK (1 << 30) #define SDLK_SCANCODE_MASK (1 << 30)
#define SDL_SCANCODE_TO_KEYCODE(X) (X | SDLK_SCANCODE_MASK) #define SDL_SCANCODE_TO_KEYCODE(X) (X | SDLK_SCANCODE_MASK)
enum { enum SDL_KeycodeEnum {
SDLK_UNKNOWN = 0, SDLK_UNKNOWN = 0,
SDLK_RETURN = '\r', SDLK_RETURN = '\r',

View File

@ -94,7 +94,7 @@ auto PythonClassBaseTimer::tp_new(PyTypeObject* type, PyObject* args,
self->timer_id_ = SceneV1Context::Current().NewTimer( self->timer_id_ = SceneV1Context::Current().NewTimer(
TimeType::kBase, static_cast<millisecs_t>(length * 1000.0), TimeType::kBase, static_cast<millisecs_t>(length * 1000.0),
static_cast<bool>(repeat), static_cast<bool>(repeat),
Object::New<Runnable, base::PythonContextCallRunnable>(call_obj)); Object::New<Runnable, base::PythonContextCallRunnable>(call_obj).Get());
self->have_timer_ = true; self->have_timer_ = true;
return reinterpret_cast<PyObject*>(self); return reinterpret_cast<PyObject*>(self);

View File

@ -100,7 +100,7 @@ auto PythonClassSceneTimer::tp_new(PyTypeObject* type, PyObject* args,
self->timer_id_ = SceneV1Context::Current().NewTimer( self->timer_id_ = SceneV1Context::Current().NewTimer(
TimeType::kSim, static_cast<millisecs_t>(length * 1000.0), TimeType::kSim, static_cast<millisecs_t>(length * 1000.0),
static_cast<bool>(repeat), static_cast<bool>(repeat),
Object::New<Runnable, base::PythonContextCallRunnable>(call_obj)); Object::New<Runnable, base::PythonContextCallRunnable>(call_obj).Get());
self->have_timer_ = true; self->have_timer_ = true;
return reinterpret_cast<PyObject*>(self); return reinterpret_cast<PyObject*>(self);

View File

@ -98,7 +98,7 @@ static auto PyTimer(PyObject* self, PyObject* args, PyObject* keywds)
SceneV1Context::Current().NewTimer( SceneV1Context::Current().NewTimer(
TimeType::kSim, static_cast<millisecs_t>(length * 1000.0), TimeType::kSim, static_cast<millisecs_t>(length * 1000.0),
static_cast<bool>(repeat), static_cast<bool>(repeat),
Object::New<Runnable, base::PythonContextCallRunnable>(call_obj)); Object::New<Runnable, base::PythonContextCallRunnable>(call_obj).Get());
Py_RETURN_NONE; Py_RETURN_NONE;
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
@ -207,7 +207,7 @@ static auto PyBaseTimer(PyObject* self, PyObject* args, PyObject* keywds)
SceneV1Context::Current().NewTimer( SceneV1Context::Current().NewTimer(
TimeType::kBase, static_cast<millisecs_t>(length * 1000.0), TimeType::kBase, static_cast<millisecs_t>(length * 1000.0),
static_cast<bool>(repeat), static_cast<bool>(repeat),
Object::New<Runnable, base::PythonContextCallRunnable>(call_obj)); Object::New<Runnable, base::PythonContextCallRunnable>(call_obj).Get());
Py_RETURN_NONE; Py_RETURN_NONE;
BA_PYTHON_CATCH; BA_PYTHON_CATCH;

View File

@ -178,7 +178,7 @@ void HostActivity::Start() {
// Create our step timer - gets called whenever scene should step. // Create our step timer - gets called whenever scene should step.
step_scene_timer_id_ = step_scene_timer_id_ =
host_session->NewTimer(TimeType::kBase, kGameStepMilliseconds, true, host_session->NewTimer(TimeType::kBase, kGameStepMilliseconds, true,
NewLambdaRunnable([this] { StepScene(); })); NewLambdaRunnable([this] { StepScene(); }).Get());
session_base_timer_ids_.push_back(step_scene_timer_id_); session_base_timer_ids_.push_back(step_scene_timer_id_);
UpdateStepTimerLength(); UpdateStepTimerLength();
} }
@ -363,7 +363,7 @@ auto HostActivity::globals_node() const -> GlobalsNode* {
} }
auto HostActivity::NewSimTimer(millisecs_t length, bool repeat, auto HostActivity::NewSimTimer(millisecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int { Runnable* runnable) -> int {
if (shutting_down_) { if (shutting_down_) {
BA_LOG_PYTHON_TRACE_ONCE( BA_LOG_PYTHON_TRACE_ONCE(
"WARNING: Creating game timer during host-activity shutdown"); "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, auto HostActivity::NewBaseTimer(millisecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int { Runnable* runnable) -> int {
if (shutting_down_) { if (shutting_down_) {
BA_LOG_PYTHON_TRACE_ONCE( BA_LOG_PYTHON_TRACE_ONCE(
"WARNING: Creating session-time timer during host-activity shutdown"); "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, auto HostActivity::NewTimer(TimeType timetype, TimerMedium length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int { Runnable* runnable) -> int {
// Make sure the runnable passed in is reference-managed already. // Make sure the runnable passed in is reference-managed already.
// (we may not add an initial reference ourself) // (we may not add an initial reference ourself)
assert(runnable.IsValidManagedObject()); assert(Object::IsValidManagedObject(runnable));
// We currently support game and base timers. // We currently support game and base timers.
switch (timetype) { switch (timetype) {

View File

@ -25,7 +25,7 @@ class HostActivity : public SceneV1Context {
// ContextTarget time/timer support. // ContextTarget time/timer support.
auto NewTimer(TimeType timetype, TimerMedium length, bool repeat, auto NewTimer(TimeType timetype, TimerMedium length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int override; Runnable* runnable) -> int override;
void DeleteTimer(TimeType timetype, int timer_id) override; void DeleteTimer(TimeType timetype, int timer_id) override;
auto GetTime(TimeType timetype) -> millisecs_t override; auto GetTime(TimeType timetype) -> millisecs_t override;
@ -77,11 +77,9 @@ class HostActivity : public SceneV1Context {
private: private:
void HandleOutOfBoundsNodes(); void HandleOutOfBoundsNodes();
auto NewSimTimer(millisecs_t length, bool repeat, auto NewSimTimer(millisecs_t length, bool repeat, Runnable* runnable) -> int;
const Object::Ref<Runnable>& runnable) -> int;
void DeleteSimTimer(int timer_id); void DeleteSimTimer(int timer_id);
auto NewBaseTimer(millisecs_t length, bool repeat, auto NewBaseTimer(millisecs_t length, bool repeat, Runnable* runnable) -> int;
const Object::Ref<Runnable>& runnable) -> int;
void DeleteBaseTimer(int timer_id); void DeleteBaseTimer(int timer_id);
void UpdateStepTimerLength(); void UpdateStepTimerLength();
void StepScene(); void StepScene();

View File

@ -40,7 +40,7 @@ HostSession::HostSession(PyObject* session_type_obj)
// Create a timer to step our session scene. // Create a timer to step our session scene.
step_scene_timer_ = step_scene_timer_ =
base_timers_.NewTimer(base_time_millisecs_, kGameStepMilliseconds, 0, -1, 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. // 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 // 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, auto HostSession::NewTimer(TimeType timetype, TimerMedium length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int { Runnable* runnable) -> int {
assert(runnable.IsValidManagedObject()); assert(Object::IsValidManagedObject(runnable));
// We currently support game and base timers. // We currently support game and base timers.
switch (timetype) { switch (timetype) {

View File

@ -38,7 +38,7 @@ class HostSession : public Session {
// ContextTarget time/timer support // ContextTarget time/timer support
auto NewTimer(TimeType timetype, TimerMedium length, bool repeat, auto NewTimer(TimeType timetype, TimerMedium length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int override; Runnable* runnable) -> int override;
void DeleteTimer(TimeType timetype, int timer_id) override; void DeleteTimer(TimeType timetype, int timer_id) override;
auto GetTime(TimeType timetype) -> millisecs_t override; auto GetTime(TimeType timetype) -> millisecs_t override;

View File

@ -55,12 +55,10 @@ auto SceneV1Context::GetAsHostActivity() -> HostActivity* { return nullptr; }
auto SceneV1Context::GetMutableScene() -> Scene* { return nullptr; } auto SceneV1Context::GetMutableScene() -> Scene* { return nullptr; }
auto SceneV1Context::NewTimer(TimeType timetype, TimerMedium length, auto SceneV1Context::NewTimer(TimeType timetype, TimerMedium length,
bool repeat, bool repeat, Runnable* runnable) -> int {
const Object::Ref<Runnable>& runnable) -> int {
// Make sure the passed runnable has a ref-count already // Make sure the passed runnable has a ref-count already
// (don't want them to rely on us to create initial one). // (don't want them to rely on us to create initial one).
assert(runnable.Exists()); assert(Object::IsValidManagedObject(runnable));
assert(Object::IsValidManagedObject(runnable.Get()));
switch (timetype) { switch (timetype) {
case TimeType::kSim: case TimeType::kSim:

View File

@ -61,7 +61,7 @@ class SceneV1Context : public base::Context {
// Default NewTimer implementation throws a descriptive error, so it can // Default NewTimer implementation throws a descriptive error, so it can
// be useful to fall back on for unsupported cases. // be useful to fall back on for unsupported cases.
virtual auto NewTimer(TimeType timetype, TimerMedium length, bool repeat, virtual auto NewTimer(TimeType timetype, TimerMedium length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int; Runnable* runnable) -> int;
virtual void DeleteTimer(TimeType timetype, int timer_id); virtual void DeleteTimer(TimeType timetype, int timer_id);
virtual auto GetTexture(const std::string& name) -> Object::Ref<SceneTexture>; virtual auto GetTexture(const std::string& name) -> Object::Ref<SceneTexture>;

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't modify them here. // 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 char* kEngineVersion = "1.7.28";
const int kEngineApiVersion = 8; const int kEngineApiVersion = 8;

View File

@ -607,11 +607,11 @@ auto EventLoop::AreEventLoopsSuspended() -> bool {
return g_core->event_loops_suspended; return g_core->event_loops_suspended;
} }
auto EventLoop::NewTimer(millisecs_t length, bool repeat, auto EventLoop::NewTimer(millisecs_t length, bool repeat, Runnable* runnable)
const Object::Ref<Runnable>& runnable) -> Timer* { -> Timer* {
assert(g_core); assert(g_core);
assert(ThreadIsCurrent()); assert(ThreadIsCurrent());
assert(runnable.Exists()); assert(Object::IsValidManagedObject(runnable));
return timers_.NewTimer(g_core->GetAppTimeMillisecs(), length, 0, return timers_.NewTimer(g_core->GetAppTimeMillisecs(), length, 0,
repeat ? -1 : 0, runnable); repeat ? -1 : 0, runnable);
} }

View File

@ -56,8 +56,7 @@ class EventLoop {
auto identifier() const -> EventLoopID { return identifier_; } auto identifier() const -> EventLoopID { return identifier_; }
// Register a timer to run on the thread. // Register a timer to run on the thread.
auto NewTimer(millisecs_t length, bool repeat, auto NewTimer(millisecs_t length, bool repeat, Runnable* runnable) -> Timer*;
const Object::Ref<Runnable>& runnable) -> Timer*;
Timer* GetTimer(int id); Timer* GetTimer(int id);
void DeleteTimer(int id); void DeleteTimer(int id);

View File

@ -160,10 +160,11 @@ auto TimerList::GetExpiredTimer(TimerMedium target_time) -> Timer* {
auto TimerList::NewTimer(TimerMedium current_time, TimerMedium length, auto TimerList::NewTimer(TimerMedium current_time, TimerMedium length,
TimerMedium offset, int repeat_count, TimerMedium offset, int repeat_count,
const Object::Ref<Runnable>& runnable) -> Timer* { Runnable* runnable) -> Timer* {
assert(!are_clearing_); assert(!are_clearing_);
auto* t = new Timer(this, next_timer_id_++, current_time, length, offset, auto* t = new Timer(this, next_timer_id_++, current_time, length, offset,
repeat_count); repeat_count);
assert(Object::IsValidManagedObject(runnable));
t->runnable_ = runnable; t->runnable_ = runnable;
// Clion (correctly) points out that t may get deallocated in this call, // Clion (correctly) points out that t may get deallocated in this call,

View File

@ -22,8 +22,8 @@ class TimerList {
// Create a timer with provided runnable. // Create a timer with provided runnable.
auto NewTimer(TimerMedium current_time, TimerMedium length, auto NewTimer(TimerMedium current_time, TimerMedium length,
TimerMedium offset, int repeat_count, TimerMedium offset, int repeat_count, Runnable* runnable)
const Object::Ref<Runnable>& runnable) -> Timer*; -> Timer*;
// Return a timer by its id, or nullptr if the timer no longer exists. // Return a timer by its id, or nullptr if the timer no longer exists.
auto GetTimer(int id) -> Timer*; auto GetTimer(int id) -> Timer*;

View File

@ -485,8 +485,8 @@ auto ButtonWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
pressed_ = true; pressed_ = true;
if (repeat_) { if (repeat_) {
repeat_timer_ = repeat_timer_ = base::AppTimer::New(
base::NewAppTimer(300, true, [this] { OnRepeatTimerExpired(); }); 300, true, [this] { OnRepeatTimerExpired(); });
// If we're a repeat button we trigger immediately. // If we're a repeat button we trigger immediately.
// (waiting till mouse up sort of defeats the purpose here) // (waiting till mouse up sort of defeats the purpose here)

View File

@ -405,7 +405,7 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
// Top level touches eventually get passed as mouse-downs if no // Top level touches eventually get passed as mouse-downs if no
// scrolling has started. // scrolling has started.
if (static_cast<int>(m.type)) { if (static_cast<int>(m.type)) {
touch_delay_timer_ = base::NewAppTimer( touch_delay_timer_ = base::AppTimer::New(
150, false, [this] { OnTouchDelayTimerExpired(); }); 150, false, [this] { OnTouchDelayTimerExpired(); });
} }

View File

@ -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 // 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. // click if it hasn't turned into a scroll or a child scroll.
if (!child_is_scrolling_) { if (!child_is_scrolling_) {
touch_delay_timer_ = base::NewAppTimer( touch_delay_timer_ = base::AppTimer::New(
150, false, [this] { OnTouchDelayTimerExpired(); }); 150, false, [this] { OnTouchDelayTimerExpired(); });
} }
} }

View File

@ -659,7 +659,6 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
if (m.has_keysym && !ShouldUseStringEditor_()) { if (m.has_keysym && !ShouldUseStringEditor_()) {
last_carat_change_time_millisecs_ = last_carat_change_time_millisecs_ =
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0); static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
text_group_dirty_ = true; text_group_dirty_ = true;
bool claimed = false; bool claimed = false;
switch (m.keysym.sym) { switch (m.keysym.sym) {
@ -724,112 +723,29 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
break; break;
} }
if (!claimed) { if (!claimed) {
// Pop in a char. // Direct text edits come through as seperate events, but we still
if (editable()) { // want to claim key down events here; otherwise they'll do weird
claimed = true; // stuff like navigate to other widgets.
claimed = true;
// #if BA_SDL2_BUILD || BA_MINSDL_BUILD
// // On SDL2, chars come through as TEXT_INPUT messages;
// // can ignore this.
// #else
// std::vector<uint32_t> unichars =
// Utils::UnicodeFromUTF8(text_raw_, "2jf987");
// int len = static_cast<int>(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
}
} }
return claimed; return claimed;
} }
switch (m.type) { switch (m.type) {
case base::WidgetMessage::Type::kTextInput: { case base::WidgetMessage::Type::kTextInput: {
// If we're using an edit dialog, any attempted text input just kicks us if (editable()) {
// over to that. if (ShouldUseStringEditor_()) {
if (editable() && ShouldUseStringEditor_()) { // Normally we shouldn't be getting direct text input events in
InvokeStringEditor_(); // situations where we're using string editors, but it still might
} else { // be possible; for instance if a game controller is driving the
// Otherwise apply the text directly. // ui when a key is typed. We simply ignore the event in that case
if (editable() && m.sval != nullptr) { // because otherwise the text input would be fighting with the
AddCharsToText_(*m.sval); // string-editor.
return true; } else {
// Apply text directly.
if (m.sval != nullptr) {
AddCharsToText_(*m.sval);
return true;
}
} }
} }
break; break;