mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-06 23:59:18 +08:00
dev console now supports on-screen keyboard input
This commit is contained in:
parent
f23365726b
commit
7579225a3d
92
.efrocachemap
generated
92
.efrocachemap
generated
@ -4064,53 +4064,53 @@
|
|||||||
"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": "44dec65bbb43c2424334cce255b55836",
|
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "e8a40affea4d63bc4ca150adfeb973db",
|
||||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "55489d3d62fd081b83c4df871e40ad27",
|
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "fb00781c9574d0ca777eb5ced699dc18",
|
||||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "21530b0be2f54d1c457a8c2ca5bfb480",
|
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "d53b0c174af88e8a483896fdfd411579",
|
||||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "a3d058738fc7891bc1d0139654b5fc26",
|
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "bb6fd9371937ebe3f25d253d3046269e",
|
||||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "8d4b813a4955b6574b4e0e6b413ad7cf",
|
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "f71e0d97424b7bf4b1ad57876ca4f01f",
|
||||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "7c618d9dac85afc6a7be8c7927693e81",
|
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "6c2ba3735f33f1fd3804bd89a71c75bc",
|
||||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "2b2ddfe86feb7e701d472264c5d7ea83",
|
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "cff1c33bb6725fb06020a256ac8e6b16",
|
||||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "63f59ed473e1f954786284d6988c1a2b",
|
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "d9d6dbe7c5396a3adb8468fbecbf2b8c",
|
||||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "b703788a1e3aef102349db7968b2dd99",
|
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "2dcddcc4a939a290fd8974d575fa3761",
|
||||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "dd4ea149455ddf77db357bbcf92622ac",
|
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "cd7f47495207a86d2f72ea0749b69db8",
|
||||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "d0651d0ed865c44f45a0e86a93fdf46b",
|
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "e223f88277dafee40cd0e631ee4fa02d",
|
||||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "65f43cfa50bf3fb198ccdacb5ac7dfe4",
|
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "1b8a5eac67370bed2a29f81525fbc6eb",
|
||||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "a2b3388c4deec4e980a0268b0757ac3a",
|
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "91debaa22c5ce91586cfdad646a72afe",
|
||||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "a60d7430b9ba3cf71bc9ffe5944026fc",
|
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "84c8c568e4daeef372806cdc96f4ed48",
|
||||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "d08625da75aba13159ea4e649b87eff9",
|
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "ebf0e8e22d53790b85ceb9f5c507db35",
|
||||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "c265fede38b25b8257ae1e6acb1d8036",
|
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "ba136eb04e177e264d459443bcde879f",
|
||||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "74f47c43f480f60732b43a0f9b80f76d",
|
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "fdc1b63de28ebd369b78b2bbeb3c6290",
|
||||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "c9b4d66d5ce318e5cecb7412771fbfba",
|
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "126e2eec53a5f7a00d95039ad506b9b4",
|
||||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "fe6ad3d4cdaadd56326f5e616588b3fb",
|
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "7db3922abdbf3045ef814f235d514cdb",
|
||||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "772769bf8f5c49031782f68eaa49c0d3",
|
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "b5a1ff440599ccf09bd543d64a1d7aba",
|
||||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "a075beb846859a3bee6b4fc1c4d9369b",
|
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "1e786451b0abe1451f17b908c2d8abb3",
|
||||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "208a67fb7e7b942988e8520f9570138e",
|
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "80db3e75458db18efe1657f8ce686996",
|
||||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "a075beb846859a3bee6b4fc1c4d9369b",
|
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "1e786451b0abe1451f17b908c2d8abb3",
|
||||||
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "208a67fb7e7b942988e8520f9570138e",
|
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "80db3e75458db18efe1657f8ce686996",
|
||||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "2a98d808b017ddac714d2f266d443394",
|
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "a29bc1b96b2422dce9154ef8a404a8e6",
|
||||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "24c7f0248a8f59e5349db9c040e6bd4f",
|
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "c36dc72d78f9df240ae9f640dee470c2",
|
||||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "2a98d808b017ddac714d2f266d443394",
|
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "a29bc1b96b2422dce9154ef8a404a8e6",
|
||||||
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "24c7f0248a8f59e5349db9c040e6bd4f",
|
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "c36dc72d78f9df240ae9f640dee470c2",
|
||||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "e8f8be3a0ba00a2ecb8956c2459107ec",
|
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "8f3b213feb6207ab470a7318de8e5cc9",
|
||||||
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "741e277f99d48437a5a1b9dacef107ee",
|
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "1b24bf754c66d424be3f3809b2523c61",
|
||||||
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "e8f8be3a0ba00a2ecb8956c2459107ec",
|
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "8f3b213feb6207ab470a7318de8e5cc9",
|
||||||
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "741e277f99d48437a5a1b9dacef107ee",
|
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "1b24bf754c66d424be3f3809b2523c61",
|
||||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "3db2e9a04f23052f3a14390a0f7ba00e",
|
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "93ff05ad705918cf0c47e5946021a2e5",
|
||||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "15cf0e78e70d952c14c4b5e9ad6ef749",
|
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "61ee5217ce8cfc2d471ffc5d1d8d4ad8",
|
||||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "a0b27fbfca2dd7404a20997fbfa10a7f",
|
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "d57603910e6c8d153b05a5515ec99ab2",
|
||||||
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "15cf0e78e70d952c14c4b5e9ad6ef749",
|
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "61ee5217ce8cfc2d471ffc5d1d8d4ad8",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "25d06d95141284fff10db4a55ed481eb",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "2312b4a091f482d64c22f71ca971efe8",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "708878d75d73b8510b354b2f353da621",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "0d5cb925a29f8185d615d6b954674c6b",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "b8b36fd481e253e83b3cf90734a7d627",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "eb53d93675b81c631d43f2283b59a9ae",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "17cb96c46a7e1763fdfbc6f48199f547",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "f15e465066504b1bfadc6fef97bfa6d4",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ae3b27deef1240beb1b32a17a46b7d90",
|
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "bad42242aa1c98b5e6c7318da8b0fa01",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "0e2c5cec39ac27d42cb5cd635da996bd",
|
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "55366eb80cbd1b22c2636d29b780b6f6",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "6b278874c0c0526494bd94aad4a817ae",
|
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "c6870a6302ae49bac39f53b390797d8f",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "55d3224895c30042caca26e6d77e406b",
|
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "29ff7769db971f92b49917c3d61de3ec",
|
||||||
"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": "f8cd3af311ac63147882590123b78318",
|
"src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318",
|
||||||
"src/ballistica/base/mgen/pyembed/binding_base.inc": "ad347097a38e0d7ede9eb6dec6a80ee9",
|
"src/ballistica/base/mgen/pyembed/binding_base.inc": "c81b2b1f3a14b4cd20a7b93416fe893a",
|
||||||
"src/ballistica/base/mgen/pyembed/binding_base_app.inc": "b67add3e1346f63491bf3450148e60d4",
|
"src/ballistica/base/mgen/pyembed/binding_base_app.inc": "b67add3e1346f63491bf3450148e60d4",
|
||||||
"src/ballistica/classic/mgen/pyembed/binding_classic.inc": "3ceb412513963f0818ab39c58bf292e3",
|
"src/ballistica/classic/mgen/pyembed/binding_classic.inc": "3ceb412513963f0818ab39c58bf292e3",
|
||||||
"src/ballistica/core/mgen/pyembed/binding_core.inc": "9d0a3c9636138e35284923e0c8311c69",
|
"src/ballistica/core/mgen/pyembed/binding_core.inc": "9d0a3c9636138e35284923e0c8311c69",
|
||||||
@ -4118,5 +4118,5 @@
|
|||||||
"src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5",
|
"src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5",
|
||||||
"src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "d80f970053099b3044204bfe29ddefce",
|
"src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "d80f970053099b3044204bfe29ddefce",
|
||||||
"src/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc": "44a45492db057bf7f7158c3b0fa11f0f",
|
"src/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc": "44a45492db057bf7f7158c3b0fa11f0f",
|
||||||
"src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "6e982bd0dda68e8b821f5b5cc18ce1d5"
|
"src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "8f4c2070174bdc2fbf735180394d7b3a"
|
||||||
}
|
}
|
||||||
1
.idea/dictionaries/ericf.xml
generated
1
.idea/dictionaries/ericf.xml
generated
@ -2825,6 +2825,7 @@
|
|||||||
<w>steelseries</w>
|
<w>steelseries</w>
|
||||||
<w>stgdict</w>
|
<w>stgdict</w>
|
||||||
<w>stickman</w>
|
<w>stickman</w>
|
||||||
|
<w>stname</w>
|
||||||
<w>storable</w>
|
<w>storable</w>
|
||||||
<w>storagename</w>
|
<w>storagename</w>
|
||||||
<w>storagenames</w>
|
<w>storagenames</w>
|
||||||
|
|||||||
18
CHANGELOG.md
18
CHANGELOG.md
@ -1,13 +1,21 @@
|
|||||||
### 1.7.28 (build 21329, api 8, 2023-09-10)
|
### 1.7.28 (build 21337, api 8, 2023-09-11)
|
||||||
|
|
||||||
- Renamed Console to DevConsole, and added an option under advanced settings to
|
- Renamed Console to DevConsole, and added an option under advanced settings to
|
||||||
always show an ugly 'dev' button onscreen which can be used to toggle it. The
|
always show a 'dev' button onscreen which can be used to toggle it. The
|
||||||
backtick key still works also for anyone with a keyboard. I plan to add more
|
backtick key still works also for anyone with a keyboard. I plan to add more
|
||||||
functionality besides just the Python console to the dev-console, and perhaps
|
functionality besides just the Python console to the dev-console, and perhaps
|
||||||
improve the Python console a bit too (add support for on-screen keyboards,
|
improve the Python console a bit too (add support for on-screen keyboards,
|
||||||
etc.)
|
etc.)
|
||||||
- The in-app Python console text is now sized up on phone and tablet devices,
|
- The in-app Python console text is now sized up on phone and tablet devices,
|
||||||
and is generally a bit larger everywhere.
|
and is generally a bit larger everywhere.
|
||||||
|
- Cleaned up onscreen keyboard support and generalized it to make it possible to
|
||||||
|
support other things besides widgets and to make it easier to implement on
|
||||||
|
other platforms.
|
||||||
|
- Added onscreen keyboard support to the in-app Python console and added an Exec
|
||||||
|
button to allow execing it without a return key on a keyboard. The cloud
|
||||||
|
console is probably still a better way to go for most people but this makes at
|
||||||
|
least simple things possible without an internet connection for most Android
|
||||||
|
users.
|
||||||
- Added some high level functionality for copying and deleting feature-sets to
|
- Added some high level functionality for copying and deleting feature-sets to
|
||||||
the `spinoff` tool. For example, to create your own `poo` feature-set based on
|
the `spinoff` tool. For example, to create your own `poo` feature-set based on
|
||||||
the existing `template_fs` one, do `tools/spinoff fset-copy template_fs poo`.
|
the existing `template_fs` one, do `tools/spinoff fset-copy template_fs poo`.
|
||||||
@ -24,10 +32,10 @@
|
|||||||
significantly faster & more efficient.
|
significantly faster & more efficient.
|
||||||
- Updated internal Python builds for Apple & iOS to 3.11.5, and updated a few
|
- Updated internal Python builds for Apple & iOS to 3.11.5, and updated a few
|
||||||
dependent libraries as well (OpenSSL bumped from 3.0.8 to 3.0.10, etc.).
|
dependent libraries as well (OpenSSL bumped from 3.0.8 to 3.0.10, etc.).
|
||||||
- Cleaned up the `babase.quit()` mechanism a bit. The default for the 'soft' arg
|
- Cleaned up the `babase.quit()` mechanism. The default for the 'soft' arg is
|
||||||
is now true, so a raw `babase.quit()` should now be a good citizen on mobile
|
now true, so a vanilla `babase.quit()` should now be a good citizen on mobile
|
||||||
platforms. Also added the `g_base->QuitApp()` call which gives the C++ layer
|
platforms. Also added the `g_base->QuitApp()` call which gives the C++ layer
|
||||||
an equivalent to the Python call.
|
a high level equivalent to the Python call.
|
||||||
- (build 21326) Fixed an uninitialized variable that could cause V1 networking
|
- (build 21326) Fixed an uninitialized variable that could cause V1 networking
|
||||||
to fail in some builds/runs (thanks Rikko for the heads-up).
|
to fail in some builds/runs (thanks Rikko for the heads-up).
|
||||||
- (build 21327) Fixed an issue that could cause the app to pause for 3 seconds
|
- (build 21327) Fixed an issue that could cause the app to pause for 3 seconds
|
||||||
|
|||||||
1
ballisticakit-cmake/.idea/dictionaries/ericf.xml
generated
1
ballisticakit-cmake/.idea/dictionaries/ericf.xml
generated
@ -1680,6 +1680,7 @@
|
|||||||
<w>stepnum</w>
|
<w>stepnum</w>
|
||||||
<w>stepsize</w>
|
<w>stepsize</w>
|
||||||
<w>stgdict</w>
|
<w>stgdict</w>
|
||||||
|
<w>stname</w>
|
||||||
<w>storagenames</w>
|
<w>storagenames</w>
|
||||||
<w>storecmd</w>
|
<w>storecmd</w>
|
||||||
<w>stot</w>
|
<w>stot</w>
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
"ba_data/python/babase/__pycache__/_plugin.cpython-311.opt-1.pyc",
|
"ba_data/python/babase/__pycache__/_plugin.cpython-311.opt-1.pyc",
|
||||||
"ba_data/python/babase/__pycache__/_stringedit.cpython-311.opt-1.pyc",
|
"ba_data/python/babase/__pycache__/_stringedit.cpython-311.opt-1.pyc",
|
||||||
"ba_data/python/babase/__pycache__/_text.cpython-311.opt-1.pyc",
|
"ba_data/python/babase/__pycache__/_text.cpython-311.opt-1.pyc",
|
||||||
|
"ba_data/python/babase/__pycache__/_ui.cpython-311.opt-1.pyc",
|
||||||
"ba_data/python/babase/__pycache__/_workspace.cpython-311.opt-1.pyc",
|
"ba_data/python/babase/__pycache__/_workspace.cpython-311.opt-1.pyc",
|
||||||
"ba_data/python/babase/__pycache__/modutils.cpython-311.opt-1.pyc",
|
"ba_data/python/babase/__pycache__/modutils.cpython-311.opt-1.pyc",
|
||||||
"ba_data/python/babase/_accountv2.py",
|
"ba_data/python/babase/_accountv2.py",
|
||||||
@ -60,6 +61,7 @@
|
|||||||
"ba_data/python/babase/_plugin.py",
|
"ba_data/python/babase/_plugin.py",
|
||||||
"ba_data/python/babase/_stringedit.py",
|
"ba_data/python/babase/_stringedit.py",
|
||||||
"ba_data/python/babase/_text.py",
|
"ba_data/python/babase/_text.py",
|
||||||
|
"ba_data/python/babase/_ui.py",
|
||||||
"ba_data/python/babase/_workspace.py",
|
"ba_data/python/babase/_workspace.py",
|
||||||
"ba_data/python/babase/modutils.py",
|
"ba_data/python/babase/modutils.py",
|
||||||
"ba_data/python/baclassic/__init__.py",
|
"ba_data/python/baclassic/__init__.py",
|
||||||
|
|||||||
@ -188,6 +188,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
|
|||||||
$(BUILD_DIR)/ba_data/python/babase/_plugin.py \
|
$(BUILD_DIR)/ba_data/python/babase/_plugin.py \
|
||||||
$(BUILD_DIR)/ba_data/python/babase/_stringedit.py \
|
$(BUILD_DIR)/ba_data/python/babase/_stringedit.py \
|
||||||
$(BUILD_DIR)/ba_data/python/babase/_text.py \
|
$(BUILD_DIR)/ba_data/python/babase/_text.py \
|
||||||
|
$(BUILD_DIR)/ba_data/python/babase/_ui.py \
|
||||||
$(BUILD_DIR)/ba_data/python/babase/_workspace.py \
|
$(BUILD_DIR)/ba_data/python/babase/_workspace.py \
|
||||||
$(BUILD_DIR)/ba_data/python/babase/modutils.py \
|
$(BUILD_DIR)/ba_data/python/babase/modutils.py \
|
||||||
$(BUILD_DIR)/ba_data/python/baclassic/__init__.py \
|
$(BUILD_DIR)/ba_data/python/baclassic/__init__.py \
|
||||||
@ -460,6 +461,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
|
|||||||
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_plugin.cpython-311.opt-1.pyc \
|
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_plugin.cpython-311.opt-1.pyc \
|
||||||
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_stringedit.cpython-311.opt-1.pyc \
|
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_stringedit.cpython-311.opt-1.pyc \
|
||||||
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_text.cpython-311.opt-1.pyc \
|
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_text.cpython-311.opt-1.pyc \
|
||||||
|
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_ui.cpython-311.opt-1.pyc \
|
||||||
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_workspace.cpython-311.opt-1.pyc \
|
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_workspace.cpython-311.opt-1.pyc \
|
||||||
$(BUILD_DIR)/ba_data/python/babase/__pycache__/modutils.cpython-311.opt-1.pyc \
|
$(BUILD_DIR)/ba_data/python/babase/__pycache__/modutils.cpython-311.opt-1.pyc \
|
||||||
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/__init__.cpython-311.opt-1.pyc \
|
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/__init__.cpython-311.opt-1.pyc \
|
||||||
|
|||||||
@ -160,6 +160,7 @@ from babase._math import normalized_color, is_point_in_box, vec3validate
|
|||||||
from babase._meta import MetadataSubsystem
|
from babase._meta import MetadataSubsystem
|
||||||
from babase._net import get_ip_address_type, DEFAULT_REQUEST_TIMEOUT_SECONDS
|
from babase._net import get_ip_address_type, DEFAULT_REQUEST_TIMEOUT_SECONDS
|
||||||
from babase._plugin import PluginSpec, Plugin, PluginSubsystem
|
from babase._plugin import PluginSpec, Plugin, PluginSubsystem
|
||||||
|
from babase._stringedit import StringEditAdapter, StringEditSubsystem
|
||||||
from babase._text import timestring
|
from babase._text import timestring
|
||||||
|
|
||||||
_babase.app = app = App()
|
_babase.app = app = App()
|
||||||
@ -294,6 +295,8 @@ __all__ = [
|
|||||||
'SimpleSound',
|
'SimpleSound',
|
||||||
'SpecialChar',
|
'SpecialChar',
|
||||||
'storagename',
|
'storagename',
|
||||||
|
'StringEditAdapter',
|
||||||
|
'StringEditSubsystem',
|
||||||
'TeamNotFoundError',
|
'TeamNotFoundError',
|
||||||
'timestring',
|
'timestring',
|
||||||
'UIScale',
|
'UIScale',
|
||||||
|
|||||||
@ -20,7 +20,7 @@ from typing import TYPE_CHECKING
|
|||||||
import _babase
|
import _babase
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
pass
|
from babase._stringedit import StringEditAdapter
|
||||||
|
|
||||||
|
|
||||||
def reset_to_main_menu() -> None:
|
def reset_to_main_menu() -> None:
|
||||||
@ -64,14 +64,6 @@ def open_url_with_webbrowser_module(url: str) -> None:
|
|||||||
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||||
|
|
||||||
|
|
||||||
def connecting_to_party_message() -> None:
|
|
||||||
from babase._language import Lstr
|
|
||||||
|
|
||||||
_babase.screenmessage(
|
|
||||||
Lstr(resource='internal.connectingToPartyText'), color=(1, 1, 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def rejecting_invite_already_in_party_message() -> None:
|
def rejecting_invite_already_in_party_message() -> None:
|
||||||
from babase._language import Lstr
|
from babase._language import Lstr
|
||||||
|
|
||||||
@ -372,3 +364,11 @@ def show_client_too_old_error() -> None:
|
|||||||
),
|
),
|
||||||
color=(1, 0, 0),
|
color=(1, 0, 0),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def string_edit_adapter_can_be_replaced(adapter: StringEditAdapter) -> bool:
|
||||||
|
"""Return whether a StringEditAdapter can be replaced."""
|
||||||
|
from babase._stringedit import StringEditAdapter
|
||||||
|
|
||||||
|
assert isinstance(adapter, StringEditAdapter)
|
||||||
|
return adapter.can_be_replaced()
|
||||||
|
|||||||
@ -197,6 +197,17 @@ class PluginSubsystem(AppSubsystem):
|
|||||||
|
|
||||||
_error.print_exception('Error in plugin on_app_shutdown()')
|
_error.print_exception('Error in plugin on_app_shutdown()')
|
||||||
|
|
||||||
|
def on_app_shutdown_complete(self) -> None:
|
||||||
|
for plugin in self.active_plugins:
|
||||||
|
try:
|
||||||
|
plugin.on_app_shutdown_complete()
|
||||||
|
except Exception:
|
||||||
|
from babase import _error
|
||||||
|
|
||||||
|
_error.print_exception(
|
||||||
|
'Error in plugin on_app_shutdown_complete()'
|
||||||
|
)
|
||||||
|
|
||||||
def load_plugins(self) -> None:
|
def load_plugins(self) -> None:
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
|
|
||||||
@ -325,6 +336,9 @@ class Plugin:
|
|||||||
def on_app_shutdown(self) -> None:
|
def on_app_shutdown(self) -> None:
|
||||||
"""Called when the app is beginning the shutdown process."""
|
"""Called when the app is beginning the shutdown process."""
|
||||||
|
|
||||||
|
def on_app_shutdown_complete(self) -> None:
|
||||||
|
"""Called when the app has completed the shutdown process."""
|
||||||
|
|
||||||
def has_settings_ui(self) -> bool:
|
def has_settings_ui(self) -> bool:
|
||||||
"""Called to ask if we have settings UI we can show."""
|
"""Called to ask if we have settings UI we can show."""
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -8,8 +8,13 @@ own ui toolkits.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import weakref
|
||||||
from typing import TYPE_CHECKING, final
|
from typing import TYPE_CHECKING, final
|
||||||
|
|
||||||
|
from efro.util import empty_weakref
|
||||||
|
|
||||||
import _babase
|
import _babase
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -20,29 +25,103 @@ class StringEditSubsystem:
|
|||||||
"""Full string-edit state for the app."""
|
"""Full string-edit state for the app."""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
pass
|
self.active_adapter = empty_weakref(StringEditAdapter)
|
||||||
# print('HELLO FROM STRING EDIT')
|
|
||||||
|
|
||||||
|
|
||||||
class StringEdit:
|
class StringEditAdapter:
|
||||||
"""Represents a string editing operation on some object.
|
"""Represents a string editing operation on some object.
|
||||||
|
|
||||||
Editable objects such as text widgets or in-app-consoles can
|
Editable objects such as text widgets or in-app-consoles can
|
||||||
subclass this to make their contents editable on all platforms.
|
subclass this to make their contents editable on all platforms.
|
||||||
|
|
||||||
|
There can only be one string-edit at a time for the app. New
|
||||||
|
StringEdits will attempt to register themselves as the globally
|
||||||
|
active one in their constructor, but this may not succeed. When
|
||||||
|
creating a StringEditAdapter, always check its 'is_valid()' value after
|
||||||
|
creating it. If this is False, it was not able to set itself as
|
||||||
|
the global active one and should be discarded.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, initial_text: str) -> None:
|
def __init__(
|
||||||
pass
|
self,
|
||||||
|
description: str,
|
||||||
|
initial_text: str,
|
||||||
|
max_length: int | None,
|
||||||
|
screen_space_center: tuple[float, float] | None,
|
||||||
|
) -> None:
|
||||||
|
if not _babase.in_logic_thread():
|
||||||
|
raise RuntimeError('This must be called from the logic thread.')
|
||||||
|
|
||||||
|
self.create_time = time.monotonic()
|
||||||
|
|
||||||
|
# Note: these attr names are hard-coded in C++ code so don't
|
||||||
|
# change them willy-nilly.
|
||||||
|
self.description = description
|
||||||
|
self.initial_text = initial_text
|
||||||
|
self.max_length = max_length
|
||||||
|
self.screen_space_center = screen_space_center
|
||||||
|
|
||||||
|
# Attempt to register ourself as the active edit.
|
||||||
|
subsys = _babase.app.stringedit
|
||||||
|
current_edit = subsys.active_adapter()
|
||||||
|
if current_edit is None or current_edit.can_be_replaced():
|
||||||
|
subsys.active_adapter = weakref.ref(self)
|
||||||
|
|
||||||
|
@final
|
||||||
|
def can_be_replaced(self) -> bool:
|
||||||
|
"""Return whether this adapter can be replaced by a new one.
|
||||||
|
|
||||||
|
This is mainly a safeguard to allow adapters whose drivers have
|
||||||
|
gone away without calling apply or cancel to time out and be
|
||||||
|
replaced with new ones.
|
||||||
|
"""
|
||||||
|
if not _babase.in_logic_thread():
|
||||||
|
raise RuntimeError('This must be called from the logic thread.')
|
||||||
|
|
||||||
|
# Allow ourself to be replaced after a bit.
|
||||||
|
if time.monotonic() - self.create_time > 5.0:
|
||||||
|
if _babase.do_once():
|
||||||
|
logging.warning(
|
||||||
|
'StringEditAdapter can_be_replaced() check for %s'
|
||||||
|
' yielding True due to timeout; ideally this should'
|
||||||
|
' not be possible as the StringEditAdapter driver'
|
||||||
|
' should be blocking anything else from kicking off'
|
||||||
|
' new edits.',
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# We also are always considered replaceable if we're not the
|
||||||
|
# active global adapter.
|
||||||
|
current_edit = _babase.app.stringedit.active_adapter()
|
||||||
|
if current_edit is not self:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
@final
|
@final
|
||||||
def apply(self, new_text: str) -> None:
|
def apply(self, new_text: str) -> None:
|
||||||
"""Should be called by the owner when editing is complete.
|
"""Should be called by the owner when editing is complete.
|
||||||
|
|
||||||
Note that in some cases this call may be a no-op (such as if
|
Note that in some cases this call may be a no-op (such as if
|
||||||
this StringEdit is no longer the globally active one).
|
this StringEditAdapter is no longer the globally active one).
|
||||||
"""
|
"""
|
||||||
if not _babase.in_logic_thread():
|
if not _babase.in_logic_thread():
|
||||||
raise RuntimeError('This must be called from the logic thread.')
|
raise RuntimeError('This must be called from the logic thread.')
|
||||||
|
|
||||||
|
# Make sure whoever is feeding this adapter is honoring max-length.
|
||||||
|
if self.max_length is not None and len(new_text) > self.max_length:
|
||||||
|
logging.warning(
|
||||||
|
'apply() on %s was passed a string of length %d,'
|
||||||
|
' but adapter max_length is %d; this should not happen'
|
||||||
|
' (will truncate).',
|
||||||
|
self,
|
||||||
|
len(new_text),
|
||||||
|
self.max_length,
|
||||||
|
stack_info=True,
|
||||||
|
)
|
||||||
|
new_text = new_text[: self.max_length]
|
||||||
|
|
||||||
self._do_apply(new_text)
|
self._do_apply(new_text)
|
||||||
|
|
||||||
@final
|
@final
|
||||||
|
|||||||
32
src/assets/ba_data/python/babase/_ui.py
Normal file
32
src/assets/ba_data/python/babase/_ui.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Released under the MIT License. See LICENSE for details.
|
||||||
|
#
|
||||||
|
"""UI related bits of babase."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from babase._stringedit import StringEditAdapter
|
||||||
|
import _babase
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DevConsoleStringEditAdapter(StringEditAdapter):
|
||||||
|
"""Allows editing dev-console text."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
description = 'Dev Console Input'
|
||||||
|
initial_text = _babase.get_dev_console_input_text()
|
||||||
|
max_length = None
|
||||||
|
screen_space_center = None
|
||||||
|
super().__init__(
|
||||||
|
description, initial_text, max_length, screen_space_center
|
||||||
|
)
|
||||||
|
|
||||||
|
def _do_apply(self, new_text: str) -> None:
|
||||||
|
_babase.set_dev_console_input_text(new_text)
|
||||||
|
_babase.dev_console_input_adapter_finish()
|
||||||
|
|
||||||
|
def _do_cancel(self) -> None:
|
||||||
|
_babase.dev_console_input_adapter_finish()
|
||||||
@ -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 = 21329
|
TARGET_BALLISTICA_BUILD = 21337
|
||||||
TARGET_BALLISTICA_VERSION = '1.7.28'
|
TARGET_BALLISTICA_VERSION = '1.7.28'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -241,3 +241,35 @@ def ui_upkeep() -> None:
|
|||||||
else:
|
else:
|
||||||
remainingchecks.append(check)
|
remainingchecks.append(check)
|
||||||
ui.cleanupchecks = remainingchecks
|
ui.cleanupchecks = remainingchecks
|
||||||
|
|
||||||
|
|
||||||
|
class TextWidgetStringEditAdapter(babase.StringEditAdapter):
|
||||||
|
"""A StringEditAdapter subclass for editing our text widgets."""
|
||||||
|
|
||||||
|
def __init__(self, text_widget: bauiv1.Widget) -> None:
|
||||||
|
self.widget = text_widget
|
||||||
|
|
||||||
|
# Ugly hacks to pull values from widgets. Really need to clean
|
||||||
|
# up that api.
|
||||||
|
description: Any = _bauiv1.textwidget(query_description=text_widget)
|
||||||
|
assert isinstance(description, str)
|
||||||
|
initial_text: Any = _bauiv1.textwidget(query=text_widget)
|
||||||
|
assert isinstance(initial_text, str)
|
||||||
|
max_length: Any = _bauiv1.textwidget(query_max_chars=text_widget)
|
||||||
|
assert isinstance(max_length, int)
|
||||||
|
|
||||||
|
screen_space_center = text_widget.get_screen_space_center()
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
description, initial_text, max_length, screen_space_center
|
||||||
|
)
|
||||||
|
|
||||||
|
def _do_apply(self, new_text: str) -> None:
|
||||||
|
if self.widget:
|
||||||
|
_bauiv1.textwidget(
|
||||||
|
edit=self.widget, text=new_text, adapter_finished=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def _do_cancel(self) -> None:
|
||||||
|
if self.widget:
|
||||||
|
_bauiv1.textwidget(edit=self.widget, adapter_finished=True)
|
||||||
|
|||||||
@ -15,27 +15,28 @@ import _bauiv1
|
|||||||
from bauiv1._uitypes import Window
|
from bauiv1._uitypes import Window
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from babase import StringEditAdapter
|
||||||
|
|
||||||
import bauiv1 as bui
|
import bauiv1 as bui
|
||||||
|
|
||||||
|
|
||||||
class OnScreenKeyboardWindow(Window):
|
class OnScreenKeyboardWindow(Window):
|
||||||
"""Simple built-in on-screen keyboard."""
|
"""Simple built-in on-screen keyboard."""
|
||||||
|
|
||||||
def __init__(self, textwidget: bui.Widget, label: str, max_chars: int):
|
def __init__(self, adapter: StringEditAdapter):
|
||||||
self._target_text = textwidget
|
self._adapter = adapter
|
||||||
self._width = 700
|
self._width = 700
|
||||||
self._height = 400
|
self._height = 400
|
||||||
assert babase.app.classic is not None
|
assert babase.app.classic is not None
|
||||||
uiscale = babase.app.ui_v1.uiscale
|
uiscale = babase.app.ui_v1.uiscale
|
||||||
top_extra = 20 if uiscale is babase.UIScale.SMALL else 0
|
top_extra = 20 if uiscale is babase.UIScale.SMALL else 0
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
root_widget=_bauiv1.containerwidget(
|
root_widget=_bauiv1.containerwidget(
|
||||||
parent=_bauiv1.get_special_widget('overlay_stack'),
|
parent=_bauiv1.get_special_widget('overlay_stack'),
|
||||||
size=(self._width, self._height + top_extra),
|
size=(self._width, self._height + top_extra),
|
||||||
transition='in_scale',
|
transition='in_scale',
|
||||||
scale_origin_stack_offset=(
|
scale_origin_stack_offset=adapter.screen_space_center,
|
||||||
self._target_text.get_screen_space_center()
|
|
||||||
),
|
|
||||||
scale=(
|
scale=(
|
||||||
2.0
|
2.0
|
||||||
if uiscale is babase.UIScale.SMALL
|
if uiscale is babase.UIScale.SMALL
|
||||||
@ -69,7 +70,7 @@ class OnScreenKeyboardWindow(Window):
|
|||||||
position=(self._width * 0.5, self._height - 41),
|
position=(self._width * 0.5, self._height - 41),
|
||||||
size=(0, 0),
|
size=(0, 0),
|
||||||
scale=0.95,
|
scale=0.95,
|
||||||
text=label,
|
text=adapter.description,
|
||||||
maxwidth=self._width - 140,
|
maxwidth=self._width - 140,
|
||||||
color=babase.app.ui_v1.title_color,
|
color=babase.app.ui_v1.title_color,
|
||||||
h_align='center',
|
h_align='center',
|
||||||
@ -79,8 +80,8 @@ class OnScreenKeyboardWindow(Window):
|
|||||||
self._text_field = _bauiv1.textwidget(
|
self._text_field = _bauiv1.textwidget(
|
||||||
parent=self._root_widget,
|
parent=self._root_widget,
|
||||||
position=(70, self._height - 116),
|
position=(70, self._height - 116),
|
||||||
max_chars=max_chars,
|
max_chars=adapter.max_length,
|
||||||
text=cast(str, _bauiv1.textwidget(query=self._target_text)),
|
text=adapter.initial_text,
|
||||||
on_return_press_call=self._done,
|
on_return_press_call=self._done,
|
||||||
autoselect=True,
|
autoselect=True,
|
||||||
size=(self._width - 140, 55),
|
size=(self._width - 140, 55),
|
||||||
@ -436,13 +437,12 @@ class OnScreenKeyboardWindow(Window):
|
|||||||
self._refresh()
|
self._refresh()
|
||||||
|
|
||||||
def _cancel(self) -> None:
|
def _cancel(self) -> None:
|
||||||
|
self._adapter.cancel()
|
||||||
_bauiv1.getsound('swish').play()
|
_bauiv1.getsound('swish').play()
|
||||||
_bauiv1.containerwidget(edit=self._root_widget, transition='out_scale')
|
_bauiv1.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||||
|
|
||||||
def _done(self) -> None:
|
def _done(self) -> None:
|
||||||
_bauiv1.containerwidget(edit=self._root_widget, transition='out_scale')
|
_bauiv1.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||||
if self._target_text:
|
self._adapter.apply(
|
||||||
_bauiv1.textwidget(
|
cast(str, _bauiv1.textwidget(query=self._text_field))
|
||||||
edit=self._target_text,
|
)
|
||||||
text=cast(str, _bauiv1.textwidget(query=self._text_field)),
|
|
||||||
)
|
|
||||||
|
|||||||
@ -152,18 +152,8 @@ auto BaseFeatureSet::IsBaseCompletelyImported() -> bool {
|
|||||||
|
|
||||||
void BaseFeatureSet::OnAssetsAvailable() {
|
void BaseFeatureSet::OnAssetsAvailable() {
|
||||||
assert(InLogicThread());
|
assert(InLogicThread());
|
||||||
assert(console_ == nullptr);
|
|
||||||
|
|
||||||
// Spin up the in-app console.
|
ui->OnAssetsAvailable();
|
||||||
if (!g_core->HeadlessMode()) {
|
|
||||||
console_ = new DevConsole();
|
|
||||||
|
|
||||||
// Print any messages that have built up.
|
|
||||||
if (!console_startup_messages_.empty()) {
|
|
||||||
console_->Print(console_startup_messages_);
|
|
||||||
console_startup_messages_.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseFeatureSet::StartApp() {
|
void BaseFeatureSet::StartApp() {
|
||||||
@ -576,21 +566,8 @@ void BaseFeatureSet::DoV1CloudLog(const std::string& msg) {
|
|||||||
plus()->DirectSendV1CloudLogs(logprefix, logsuffix, false, nullptr);
|
plus()->DirectSendV1CloudLogs(logprefix, logsuffix, false, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseFeatureSet::PushConsolePrintCall(const std::string& msg) {
|
void BaseFeatureSet::PushDevConsolePrintCall(const std::string& msg) {
|
||||||
// Completely ignore this stuff in headless mode.
|
ui->PushDevConsolePrintCall(msg);
|
||||||
if (g_core->HeadlessMode()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If our event loop AND console are up and running, ship it off to
|
|
||||||
// be printed. Otherwise store it for the console to grab when it's ready.
|
|
||||||
if (auto* event_loop = logic->event_loop()) {
|
|
||||||
if (console_ != nullptr) {
|
|
||||||
event_loop->PushCall([this, msg] { console_->Print(msg); });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Didn't send a print; store for later.
|
|
||||||
console_startup_messages_ += msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* BaseFeatureSet::GetPyExceptionType(PyExcType exctype) {
|
PyObject* BaseFeatureSet::GetPyExceptionType(PyExcType exctype) {
|
||||||
|
|||||||
@ -701,7 +701,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
|||||||
-> PyObject* override;
|
-> PyObject* override;
|
||||||
auto FeatureSetFromData(PyObject* obj) -> FeatureSetNativeComponent* override;
|
auto FeatureSetFromData(PyObject* obj) -> FeatureSetNativeComponent* override;
|
||||||
void DoV1CloudLog(const std::string& msg) override;
|
void DoV1CloudLog(const std::string& msg) override;
|
||||||
void PushConsolePrintCall(const std::string& msg) override;
|
void PushDevConsolePrintCall(const std::string& msg) override;
|
||||||
auto GetPyExceptionType(PyExcType exctype) -> PyObject* override;
|
auto GetPyExceptionType(PyExcType exctype) -> PyObject* override;
|
||||||
auto PrintPythonStackTrace() -> bool override;
|
auto PrintPythonStackTrace() -> bool override;
|
||||||
auto GetPyLString(PyObject* obj) -> std::string override;
|
auto GetPyLString(PyObject* obj) -> std::string override;
|
||||||
@ -754,7 +754,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
|||||||
Utils* const utils;
|
Utils* const utils;
|
||||||
|
|
||||||
// Variable subsystems.
|
// Variable subsystems.
|
||||||
auto* console() const { return console_; }
|
|
||||||
auto* app_mode() const { return app_mode_; }
|
auto* app_mode() const { return app_mode_; }
|
||||||
auto* stress_test() const { return stress_test_; }
|
auto* stress_test() const { return stress_test_; }
|
||||||
void set_app_mode(AppMode* mode);
|
void set_app_mode(AppMode* mode);
|
||||||
@ -779,13 +778,11 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
|||||||
void PrintContextUnavailable_();
|
void PrintContextUnavailable_();
|
||||||
|
|
||||||
AppMode* app_mode_;
|
AppMode* app_mode_;
|
||||||
DevConsole* console_{};
|
|
||||||
PlusSoftInterface* plus_soft_{};
|
PlusSoftInterface* plus_soft_{};
|
||||||
ClassicSoftInterface* classic_soft_{};
|
ClassicSoftInterface* classic_soft_{};
|
||||||
UIV1SoftInterface* ui_v1_soft_{};
|
UIV1SoftInterface* ui_v1_soft_{};
|
||||||
StressTest* stress_test_;
|
StressTest* stress_test_;
|
||||||
|
|
||||||
std::string console_startup_messages_;
|
|
||||||
std::mutex shutdown_suppress_lock_;
|
std::mutex shutdown_suppress_lock_;
|
||||||
bool shutdown_suppress_disallowed_{};
|
bool shutdown_suppress_disallowed_{};
|
||||||
int shutdown_suppress_count_{};
|
int shutdown_suppress_count_{};
|
||||||
|
|||||||
@ -813,8 +813,8 @@ void Input::PushTextInputEvent(const std::string& text) {
|
|||||||
if (IsInputLocked()) {
|
if (IsInputLocked()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (g_base && g_base->console() != nullptr
|
if (g_base && g_base->ui->dev_console() != nullptr
|
||||||
&& g_base->console()->HandleTextEditing(text)) {
|
&& g_base->ui->dev_console()->HandleTextEditing(text)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
g_base->ui->SendWidgetMessage(WidgetMessage(
|
g_base->ui->SendWidgetMessage(WidgetMessage(
|
||||||
@ -966,9 +966,10 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Let the console intercept stuff if it wants at this point.
|
// Let the console intercept stuff if it wants at this point.
|
||||||
if (g_base && g_base->console() != nullptr
|
if (auto* console = g_base->ui->dev_console()) {
|
||||||
&& g_base->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.
|
||||||
@ -1089,8 +1090,8 @@ void Input::HandleKeyRelease(const SDL_Keysym* keysym) {
|
|||||||
|
|
||||||
keys_held_.erase(keysym->sym);
|
keys_held_.erase(keysym->sym);
|
||||||
|
|
||||||
if (g_base->console() != nullptr) {
|
if (g_base->ui->dev_console() != nullptr) {
|
||||||
g_base->console()->HandleKeyRelease(keysym);
|
g_base->ui->dev_console()->HandleKeyRelease(keysym);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyboard_input_) {
|
if (keyboard_input_) {
|
||||||
|
|||||||
@ -164,7 +164,7 @@ void Logic::OnInitialAppModeSet() {
|
|||||||
// We want any sort of raw Python input to only start accepting commands
|
// We want any sort of raw Python input to only start accepting commands
|
||||||
// once we've got an initial app-mode set. Generally said commands will
|
// once we've got an initial app-mode set. Generally said commands will
|
||||||
// assume we're running in that mode and will fail if run before it is set.
|
// assume we're running in that mode and will fail if run before it is set.
|
||||||
if (auto* console = g_base->console()) {
|
if (auto* console = g_base->ui->dev_console()) {
|
||||||
console->EnableInput();
|
console->EnableInput();
|
||||||
}
|
}
|
||||||
if (g_base->stdio_console) {
|
if (g_base->stdio_console) {
|
||||||
|
|||||||
@ -329,4 +329,50 @@ auto BasePlatform::CanBackQuit() -> bool { return false; }
|
|||||||
void BasePlatform::DoBackQuit() {}
|
void BasePlatform::DoBackQuit() {}
|
||||||
void BasePlatform::DoSoftQuit() {}
|
void BasePlatform::DoSoftQuit() {}
|
||||||
|
|
||||||
|
auto BasePlatform::HaveStringEditor() -> bool { return false; }
|
||||||
|
|
||||||
|
void BasePlatform::InvokeStringEditor(PyObject* string_edit_adapter) {
|
||||||
|
BA_PRECONDITION(HaveStringEditor());
|
||||||
|
BA_PRECONDITION(g_base->InLogicThread());
|
||||||
|
|
||||||
|
// We assume there's a single one of these at a time. Hold on to it.
|
||||||
|
string_edit_adapter_.Acquire(string_edit_adapter);
|
||||||
|
|
||||||
|
// Pull values from Python and ship them along to our platform
|
||||||
|
// implementation.
|
||||||
|
auto desc = string_edit_adapter_.GetAttr("description").ValueAsString();
|
||||||
|
auto initial_text =
|
||||||
|
string_edit_adapter_.GetAttr("initial_text").ValueAsString();
|
||||||
|
auto max_length =
|
||||||
|
string_edit_adapter_.GetAttr("max_length").ValueAsOptionalInt();
|
||||||
|
// TODO(ericf): pass along screen_space_center if its ever useful.
|
||||||
|
|
||||||
|
g_base->platform->DoInvokeStringEditor(desc, initial_text, max_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Should be called by platform StringEditor to apply a value.
|
||||||
|
void BasePlatform::StringEditorApply(const std::string& val) {
|
||||||
|
BA_PRECONDITION(HaveStringEditor());
|
||||||
|
BA_PRECONDITION(g_base->InLogicThread());
|
||||||
|
BA_PRECONDITION(string_edit_adapter_.Exists());
|
||||||
|
auto args = PythonRef::Stolen(Py_BuildValue("(s)", val.c_str()));
|
||||||
|
string_edit_adapter_.GetAttr("apply").Call(args);
|
||||||
|
string_edit_adapter_.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Should be called by platform StringEditor to signify a cancel.
|
||||||
|
void BasePlatform::StringEditorCancel() {
|
||||||
|
BA_PRECONDITION(HaveStringEditor());
|
||||||
|
BA_PRECONDITION(g_base->InLogicThread());
|
||||||
|
BA_PRECONDITION(string_edit_adapter_.Exists());
|
||||||
|
string_edit_adapter_.GetAttr("cancel").Call();
|
||||||
|
string_edit_adapter_.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BasePlatform::DoInvokeStringEditor(const std::string& title,
|
||||||
|
const std::string& value,
|
||||||
|
std::optional<int> max_chars) {
|
||||||
|
Log(LogLevel::kError, "FIXME: DoInvokeStringEditor() unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ballistica::base
|
} // namespace ballistica::base
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#define BALLISTICA_BASE_PLATFORM_BASE_PLATFORM_H_
|
#define BALLISTICA_BASE_PLATFORM_BASE_PLATFORM_H_
|
||||||
|
|
||||||
#include "ballistica/base/base.h"
|
#include "ballistica/base/base.h"
|
||||||
|
#include "ballistica/shared/python/python_ref.h"
|
||||||
|
|
||||||
namespace ballistica::base {
|
namespace ballistica::base {
|
||||||
|
|
||||||
@ -104,13 +105,36 @@ class BasePlatform {
|
|||||||
bool active);
|
bool active);
|
||||||
#pragma mark MISC --------------------------------------------------------------
|
#pragma mark MISC --------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Do we define a platform-specific string editor? This is something like
|
||||||
|
/// a text view popup which allows the use of default OS input methods
|
||||||
|
/// such as on-screen-keyboards.
|
||||||
|
virtual auto HaveStringEditor() -> bool;
|
||||||
|
|
||||||
|
/// Trigger a string edit for the provided StringEditAdapter Python obj.
|
||||||
|
/// This should only be called once the edit-adapter has been verified as
|
||||||
|
/// being the globally active one. Must be called from the logic thread.
|
||||||
|
void InvokeStringEditor(PyObject* string_edit_adapter);
|
||||||
|
|
||||||
/// Open the provided URL in a browser or whatnot.
|
/// Open the provided URL in a browser or whatnot.
|
||||||
void OpenURL(const std::string& url);
|
void OpenURL(const std::string& url);
|
||||||
|
|
||||||
/// Get the most up-to-date cursor position.
|
/// Get the most up-to-date cursor position.
|
||||||
void GetCursorPosition(float* x, float* y);
|
void GetCursorPosition(float* x, float* y);
|
||||||
|
|
||||||
|
/// Should be called by platform StringEditor to apply a value.
|
||||||
|
/// Must be called in the logic thread.
|
||||||
|
void StringEditorApply(const std::string& val);
|
||||||
|
|
||||||
|
/// Should be called by platform StringEditor to signify a cancel.
|
||||||
|
/// Must be called in the logic thread.
|
||||||
|
void StringEditorCancel();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/// Pop up a text edit dialog.
|
||||||
|
virtual void DoInvokeStringEditor(const std::string& title,
|
||||||
|
const std::string& value,
|
||||||
|
std::optional<int> max_chars);
|
||||||
|
|
||||||
/// Open the provided URL in a browser or whatnot.
|
/// Open the provided URL in a browser or whatnot.
|
||||||
virtual void DoOpenURL(const std::string& url);
|
virtual void DoOpenURL(const std::string& url);
|
||||||
|
|
||||||
@ -121,11 +145,12 @@ class BasePlatform {
|
|||||||
virtual ~BasePlatform();
|
virtual ~BasePlatform();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Called after our singleton has been instantiated.
|
/// Called after our singleton has been instantiated. Any construction
|
||||||
/// Any construction functionality requiring virtual functions resolving to
|
/// functionality requiring virtual functions resolving to their final
|
||||||
/// their final class versions can go here.
|
/// class versions can go here.
|
||||||
virtual void PostInit();
|
virtual void PostInit();
|
||||||
|
|
||||||
|
PythonRef string_edit_adapter_{};
|
||||||
bool ran_base_post_init_{};
|
bool ran_base_post_init_{};
|
||||||
std::string public_device_uuid_;
|
std::string public_device_uuid_;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -580,4 +580,25 @@ auto BasePython::DoOnce() -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto BasePython::CanPyStringEditAdapterBeReplaced(PyObject* o) -> bool {
|
||||||
|
assert(g_base->InLogicThread());
|
||||||
|
|
||||||
|
auto args = PythonRef::Stolen(Py_BuildValue("(O)", o));
|
||||||
|
auto result = g_base->python->objs()
|
||||||
|
.Get(BasePython::ObjID::kStringEditAdapterCanBeReplacedCall)
|
||||||
|
.Call(args);
|
||||||
|
if (!result.Exists()) {
|
||||||
|
Log(LogLevel::kError, "Error getting StringEdit valid state.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (result.Get() == Py_True) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (result.Get() == Py_False) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log(LogLevel::kError, "Got unexpected value for StringEdit valid.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ballistica::base
|
} // namespace ballistica::base
|
||||||
|
|||||||
@ -41,7 +41,6 @@ class BasePython {
|
|||||||
kSetConfigFullscreenOnCall,
|
kSetConfigFullscreenOnCall,
|
||||||
kSetConfigFullscreenOffCall,
|
kSetConfigFullscreenOffCall,
|
||||||
kNotSignedInScreenMessageCall,
|
kNotSignedInScreenMessageCall,
|
||||||
kConnectingToPartyMessageCall,
|
|
||||||
kRejectingInviteAlreadyInPartyMessageCall,
|
kRejectingInviteAlreadyInPartyMessageCall,
|
||||||
kConnectionFailedMessageCall,
|
kConnectionFailedMessageCall,
|
||||||
kTemporarilyUnavailableMessageCall,
|
kTemporarilyUnavailableMessageCall,
|
||||||
@ -104,6 +103,8 @@ class BasePython {
|
|||||||
kEnvOnNativeModuleImportCall,
|
kEnvOnNativeModuleImportCall,
|
||||||
kOnMainThreadStartAppCall,
|
kOnMainThreadStartAppCall,
|
||||||
kAppPushApplyAppConfigCall,
|
kAppPushApplyAppConfigCall,
|
||||||
|
kStringEditAdapterCanBeReplacedCall,
|
||||||
|
kDevConsoleStringEditAdapterClass,
|
||||||
kLast // Sentinel; must be at end.
|
kLast // Sentinel; must be at end.
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -149,6 +150,8 @@ class BasePython {
|
|||||||
static auto IsPyEnum_InputType(PyObject* obj) -> bool;
|
static auto IsPyEnum_InputType(PyObject* obj) -> bool;
|
||||||
static auto GetPyEnum_InputType(PyObject* obj) -> InputType;
|
static auto GetPyEnum_InputType(PyObject* obj) -> InputType;
|
||||||
|
|
||||||
|
auto CanPyStringEditAdapterBeReplaced(PyObject* o) -> bool;
|
||||||
|
|
||||||
auto IsPyLString(PyObject* o) -> bool;
|
auto IsPyLString(PyObject* o) -> bool;
|
||||||
auto GetPyLString(PyObject* o) -> std::string;
|
auto GetPyLString(PyObject* o) -> std::string;
|
||||||
auto GetPyLStrings(PyObject* o) -> std::vector<std::string>;
|
auto GetPyLStrings(PyObject* o) -> std::vector<std::string>;
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "ballistica/base/python/base_python.h"
|
#include "ballistica/base/python/base_python.h"
|
||||||
#include "ballistica/base/python/support/python_context_call_runnable.h"
|
#include "ballistica/base/python/support/python_context_call_runnable.h"
|
||||||
#include "ballistica/base/support/stress_test.h"
|
#include "ballistica/base/support/stress_test.h"
|
||||||
|
#include "ballistica/base/ui/dev_console.h"
|
||||||
#include "ballistica/base/ui/ui.h"
|
#include "ballistica/base/ui/ui.h"
|
||||||
#include "ballistica/core/platform/core_platform.h"
|
#include "ballistica/core/platform/core_platform.h"
|
||||||
#include "ballistica/shared/foundation/event_loop.h"
|
#include "ballistica/shared/foundation/event_loop.h"
|
||||||
@ -1516,8 +1517,7 @@ static PyMethodDef PyShutdownSuppressEndDef = {
|
|||||||
"(internal)\n",
|
"(internal)\n",
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------ shutdown_suppress_count
|
// ----------------------- shutdown_suppress_count -----------------------------
|
||||||
// ------------------------------
|
|
||||||
|
|
||||||
static auto PyShutdownSuppressCount(PyObject* self) -> PyObject* {
|
static auto PyShutdownSuppressCount(PyObject* self) -> PyObject* {
|
||||||
BA_PYTHON_TRY;
|
BA_PYTHON_TRY;
|
||||||
@ -1537,6 +1537,79 @@ static PyMethodDef PyShutdownSuppressCountDef = {
|
|||||||
"(internal)\n",
|
"(internal)\n",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --------------------- get_dev_console_input_text ----------------------------
|
||||||
|
|
||||||
|
static auto PyGetDevConsoleInputText(PyObject* self) -> PyObject* {
|
||||||
|
BA_PYTHON_TRY;
|
||||||
|
BA_PRECONDITION(g_base->InLogicThread());
|
||||||
|
auto* console = g_base->ui->dev_console();
|
||||||
|
BA_PRECONDITION(console);
|
||||||
|
return PyUnicode_FromString(console->input_string().c_str());
|
||||||
|
BA_PYTHON_CATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef PyGetDevConsoleInputTextDef = {
|
||||||
|
"get_dev_console_input_text", // name
|
||||||
|
(PyCFunction)PyGetDevConsoleInputText, // method
|
||||||
|
METH_NOARGS, // flags
|
||||||
|
|
||||||
|
"get_dev_console_input_text() -> str\n"
|
||||||
|
"\n"
|
||||||
|
"(internal)\n",
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------- set_dev_console_input_text ----------------------------
|
||||||
|
|
||||||
|
static auto PySetDevConsoleInputText(PyObject* self, PyObject* args,
|
||||||
|
PyObject* keywds) -> PyObject* {
|
||||||
|
BA_PYTHON_TRY;
|
||||||
|
BA_PRECONDITION(g_base->InLogicThread());
|
||||||
|
auto* console = g_base->ui->dev_console();
|
||||||
|
BA_PRECONDITION(console);
|
||||||
|
|
||||||
|
const char* val;
|
||||||
|
static const char* kwlist[] = {"val", nullptr};
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
|
||||||
|
const_cast<char**>(kwlist), &val)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
console->set_input_string(val);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
BA_PYTHON_CATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef PySetDevConsoleInputTextDef = {
|
||||||
|
"set_dev_console_input_text", // name
|
||||||
|
(PyCFunction)PySetDevConsoleInputText, // method
|
||||||
|
METH_VARARGS | METH_KEYWORDS, // flags
|
||||||
|
|
||||||
|
"set_dev_console_input_text(val: str) -> None\n"
|
||||||
|
"\n"
|
||||||
|
"(internal)\n",
|
||||||
|
};
|
||||||
|
|
||||||
|
// ------------------ dev_console_input_adapter_finish -------------------------
|
||||||
|
|
||||||
|
static auto PyDevConsoleInputAdapterFinish(PyObject* self) -> PyObject* {
|
||||||
|
BA_PYTHON_TRY;
|
||||||
|
BA_PRECONDITION(g_base->InLogicThread());
|
||||||
|
auto* console = g_base->ui->dev_console();
|
||||||
|
BA_PRECONDITION(console);
|
||||||
|
console->InputAdapterFinish();
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
BA_PYTHON_CATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef PyDevConsoleInputAdapterFinishDef = {
|
||||||
|
"dev_console_input_adapter_finish", // name
|
||||||
|
(PyCFunction)PyDevConsoleInputAdapterFinish, // method
|
||||||
|
METH_NOARGS, // flags
|
||||||
|
|
||||||
|
"dev_console_input_adapter_finish() -> None\n"
|
||||||
|
"\n"
|
||||||
|
"(internal)\n",
|
||||||
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
||||||
@ -1589,6 +1662,9 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
|||||||
PyShutdownSuppressBeginDef,
|
PyShutdownSuppressBeginDef,
|
||||||
PyShutdownSuppressEndDef,
|
PyShutdownSuppressEndDef,
|
||||||
PyShutdownSuppressCountDef,
|
PyShutdownSuppressCountDef,
|
||||||
|
PyGetDevConsoleInputTextDef,
|
||||||
|
PySetDevConsoleInputTextDef,
|
||||||
|
PyDevConsoleInputAdapterFinishDef,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@
|
|||||||
#include "ballistica/base/graphics/text/text_graphics.h"
|
#include "ballistica/base/graphics/text/text_graphics.h"
|
||||||
#include "ballistica/base/input/input.h"
|
#include "ballistica/base/input/input.h"
|
||||||
#include "ballistica/base/logic/logic.h"
|
#include "ballistica/base/logic/logic.h"
|
||||||
|
#include "ballistica/base/platform/base_platform.h"
|
||||||
|
#include "ballistica/base/python/base_python.h"
|
||||||
#include "ballistica/base/support/context.h"
|
#include "ballistica/base/support/context.h"
|
||||||
#include "ballistica/base/ui/ui.h"
|
#include "ballistica/base/ui/ui.h"
|
||||||
#include "ballistica/core/core.h"
|
#include "ballistica/core/core.h"
|
||||||
@ -27,11 +29,117 @@ const int kDevConsoleStringBreakUpSize = 1950;
|
|||||||
const int kDevConsoleActivateKey1 = SDLK_BACKQUOTE;
|
const int kDevConsoleActivateKey1 = SDLK_BACKQUOTE;
|
||||||
const int kDevConsoleActivateKey2 = SDLK_F2;
|
const int kDevConsoleActivateKey2 = SDLK_F2;
|
||||||
|
|
||||||
|
const double kTransitionSeconds = 0.1;
|
||||||
|
|
||||||
|
enum class DevButtonAttach_ { kLeft, kCenter, kRight };
|
||||||
|
|
||||||
|
class DevConsole::Button_ {
|
||||||
|
public:
|
||||||
|
template <typename F>
|
||||||
|
Button_(const std::string& label, float text_scale, DevButtonAttach_ attach,
|
||||||
|
float x, float y, float width, float height, const F& lambda)
|
||||||
|
: label{label},
|
||||||
|
attach{attach},
|
||||||
|
x{x},
|
||||||
|
y{y},
|
||||||
|
width{width},
|
||||||
|
height{height},
|
||||||
|
call{NewLambdaRunnable(lambda)},
|
||||||
|
text_scale{text_scale} {}
|
||||||
|
std::string label;
|
||||||
|
DevButtonAttach_ attach;
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float width;
|
||||||
|
float height;
|
||||||
|
bool pressed{};
|
||||||
|
Object::Ref<Runnable> call;
|
||||||
|
TextGroup text_group;
|
||||||
|
bool text_group_built_{};
|
||||||
|
float text_scale;
|
||||||
|
|
||||||
|
auto InUs(float mx, float my) -> bool {
|
||||||
|
mx -= XOffs();
|
||||||
|
return (mx >= x && mx <= (x + width) && my >= y && my <= (y + height));
|
||||||
|
}
|
||||||
|
auto XOffs() -> float {
|
||||||
|
switch (attach) {
|
||||||
|
case DevButtonAttach_::kLeft:
|
||||||
|
return 0.0f;
|
||||||
|
case DevButtonAttach_::kRight:
|
||||||
|
return g_base->graphics->screen_virtual_width();
|
||||||
|
case DevButtonAttach_::kCenter:
|
||||||
|
return g_base->graphics->screen_virtual_width() * 0.5f;
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto HandleMouseDown(float mx, float my) -> bool {
|
||||||
|
if (InUs(mx, my)) {
|
||||||
|
pressed = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleMouseUp(float mx, float my) {
|
||||||
|
pressed = false;
|
||||||
|
if (InUs(mx, my)) {
|
||||||
|
if (call.Exists()) {
|
||||||
|
call.Get()->Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Draw(RenderPass* pass, float bottom) {
|
||||||
|
if (!text_group_built_) {
|
||||||
|
text_group.set_text(label, TextMesh::HAlign::kCenter,
|
||||||
|
TextMesh::VAlign::kCenter);
|
||||||
|
}
|
||||||
|
SimpleComponent c(pass);
|
||||||
|
c.SetTransparent(true);
|
||||||
|
if (pressed) {
|
||||||
|
c.SetColor(0.8f, 0.7f, 0.8f, 1.0f);
|
||||||
|
} else {
|
||||||
|
c.SetColor(0.25f, 0.2f, 0.3f, 1.0f);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto xf = c.ScopedTransform();
|
||||||
|
c.Translate(x + XOffs() + width * 0.5f, y + bottom + height * 0.5f,
|
||||||
|
kDevConsoleZDepth);
|
||||||
|
// Draw our backing.
|
||||||
|
{
|
||||||
|
auto xf = c.ScopedTransform();
|
||||||
|
c.Scale(width, height);
|
||||||
|
c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
|
||||||
|
}
|
||||||
|
// Draw our text.
|
||||||
|
if (pressed) {
|
||||||
|
c.SetColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
} else {
|
||||||
|
c.SetColor(0.8f, 0.7f, 0.8f, 1.0f);
|
||||||
|
}
|
||||||
|
c.SetFlatness(1.0f);
|
||||||
|
int elem_count = text_group.GetElementCount();
|
||||||
|
for (int e = 0; e < elem_count; e++) {
|
||||||
|
c.SetTexture(text_group.GetElementTexture(e));
|
||||||
|
{
|
||||||
|
auto xf = c.ScopedTransform();
|
||||||
|
float sc{0.6f * text_scale};
|
||||||
|
c.Scale(sc, sc, 1.0f);
|
||||||
|
c.DrawMesh(text_group.GetElementMesh(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Submit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class DevConsole::Line_ {
|
class DevConsole::Line_ {
|
||||||
public:
|
public:
|
||||||
Line_(std::string s_in, millisecs_t c)
|
Line_(std::string s_in, double c) : creation_time(c), s(std::move(s_in)) {}
|
||||||
: creation_time(c), s(std::move(s_in)) {}
|
double creation_time;
|
||||||
millisecs_t creation_time;
|
|
||||||
std::string s;
|
std::string s;
|
||||||
auto GetText() -> TextGroup& {
|
auto GetText() -> TextGroup& {
|
||||||
if (!s_mesh_.Exists()) {
|
if (!s_mesh_.Exists()) {
|
||||||
@ -59,10 +167,123 @@ DevConsole::DevConsole() {
|
|||||||
title_text_group_.set_text(title);
|
title_text_group_.set_text(title);
|
||||||
built_text_group_.set_text("Built: " __DATE__ " " __TIME__);
|
built_text_group_.set_text("Built: " __DATE__ " " __TIME__);
|
||||||
prompt_text_group_.set_text(">");
|
prompt_text_group_.set_text(">");
|
||||||
|
|
||||||
|
// NOTE: Once we can adjust UI scale on the fly we'll have to update
|
||||||
|
// this to recalc accordingly.
|
||||||
|
float bs = PythonConsoleBaseScale_();
|
||||||
|
buttons_.emplace_back("Exec", 0.75f * bs, DevButtonAttach_::kRight,
|
||||||
|
-33.0f * bs, 15.95f * bs, 32.0f * bs, 13.0f * bs,
|
||||||
|
[this] { Exec(); });
|
||||||
|
|
||||||
|
// buttons_.emplace_back("TestButton", 1.0f, DevButtonAttach_::kLeft, 100.0f,
|
||||||
|
// 100.0f, 100.0f, 30.0f, [] { printf("B1 PRESSED!\n");
|
||||||
|
// });
|
||||||
|
|
||||||
|
// buttons_.emplace_back("TestButton2", 1.0f, DevButtonAttach_::kCenter,
|
||||||
|
// -50.0f,
|
||||||
|
// 120.0f, 100.0f, 30.0f, [] { printf("B2 PRESSED!\n");
|
||||||
|
// });
|
||||||
|
|
||||||
|
// buttons_.emplace_back("TestButton3", 0.8f, DevButtonAttach_::kRight,
|
||||||
|
// -200.0f,
|
||||||
|
// 140.0f, 100.0f, 30.0f, [] { printf("B3 PRESSED!\n");
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
DevConsole::~DevConsole() = default;
|
DevConsole::~DevConsole() = default;
|
||||||
|
|
||||||
|
auto DevConsole::HandleMouseDown(int button, float x, float y) -> bool {
|
||||||
|
assert(g_base->InLogicThread());
|
||||||
|
|
||||||
|
if (state_ == State_::kInactive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
float bottom{Bottom_()};
|
||||||
|
|
||||||
|
// Pass to any buttons (in bottom-local space).
|
||||||
|
if (button == 1) {
|
||||||
|
for (auto&& button : buttons_) {
|
||||||
|
if (button.HandleMouseDown(x, y - bottom)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y < bottom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button == 1) {
|
||||||
|
python_console_pressed_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevConsole::HandleMouseUp(int button, float x, float y) {
|
||||||
|
assert(g_base->InLogicThread());
|
||||||
|
float bottom{Bottom_()};
|
||||||
|
|
||||||
|
if (button == 1) {
|
||||||
|
for (auto&& button : buttons_) {
|
||||||
|
button.HandleMouseUp(x, y - bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button == 1 && python_console_pressed_) {
|
||||||
|
python_console_pressed_ = false;
|
||||||
|
if (y > bottom) {
|
||||||
|
// If we're not getting fed keyboard events and have a string editor
|
||||||
|
// available, invoke it.
|
||||||
|
if (!g_base->ui->UIHasDirectKeyboardInput()
|
||||||
|
&& g_base->platform->HaveStringEditor()) {
|
||||||
|
InvokeStringEditor_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevConsole::InvokeStringEditor_() {
|
||||||
|
// If there's already a valid edit-adapter attached to us, do nothing.
|
||||||
|
if (string_edit_adapter_.Exists()
|
||||||
|
&& !g_base->python->CanPyStringEditAdapterBeReplaced(
|
||||||
|
string_edit_adapter_.Get())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a Python StringEditAdapter for this widget, passing ourself as
|
||||||
|
// the sole arg.
|
||||||
|
auto result = g_base->python->objs()
|
||||||
|
.Get(BasePython::ObjID::kDevConsoleStringEditAdapterClass)
|
||||||
|
.Call();
|
||||||
|
if (!result.Exists()) {
|
||||||
|
Log(LogLevel::kError, "Error invoking string edit dialog.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this new one is already marked replacable, it means it wasn't able
|
||||||
|
// to register as the active one, so we can ignore it.
|
||||||
|
if (g_base->python->CanPyStringEditAdapterBeReplaced(result.Get())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok looks like we're good; store the adapter as our active one.
|
||||||
|
string_edit_adapter_ = result;
|
||||||
|
|
||||||
|
g_base->platform->InvokeStringEditor(string_edit_adapter_.Get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevConsole::set_input_string(const std::string& val) {
|
||||||
|
assert(g_base->InLogicThread());
|
||||||
|
input_string_ = val;
|
||||||
|
input_text_dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevConsole::InputAdapterFinish() {
|
||||||
|
assert(g_base->InLogicThread());
|
||||||
|
string_edit_adapter_.Release();
|
||||||
|
}
|
||||||
|
|
||||||
auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
|
auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
|
||||||
assert(g_base->InLogicThread());
|
assert(g_base->InLogicThread());
|
||||||
|
|
||||||
@ -73,7 +294,7 @@ auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
|
|||||||
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->console()) {
|
if (auto console = g_base->ui->dev_console()) {
|
||||||
console->ToggleState();
|
console->ToggleState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +304,7 @@ auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state_ == State::kInactive) {
|
if (state_ == State_::kInactive) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,23 +350,7 @@ auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
|
|||||||
}
|
}
|
||||||
case SDLK_KP_ENTER:
|
case SDLK_KP_ENTER:
|
||||||
case SDLK_RETURN: {
|
case SDLK_RETURN: {
|
||||||
if (!input_enabled_) {
|
Exec();
|
||||||
Log(LogLevel::kWarning, "Console input is not allowed yet.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
input_history_position_ = 0;
|
|
||||||
if (input_string_ == "clear") {
|
|
||||||
last_line_.clear();
|
|
||||||
lines_.clear();
|
|
||||||
} else {
|
|
||||||
SubmitCommand_(input_string_);
|
|
||||||
}
|
|
||||||
input_history_.push_front(input_string_);
|
|
||||||
if (input_history_.size() > 100) {
|
|
||||||
input_history_.pop_back();
|
|
||||||
}
|
|
||||||
input_string_.resize(0);
|
|
||||||
input_text_dirty_ = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -168,9 +373,30 @@ auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DevConsole::Exec() {
|
||||||
|
BA_PRECONDITION(g_base->InLogicThread());
|
||||||
|
if (!input_enabled_) {
|
||||||
|
Log(LogLevel::kWarning, "Console input is not allowed yet.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
input_history_position_ = 0;
|
||||||
|
if (input_string_ == "clear") {
|
||||||
|
last_line_.clear();
|
||||||
|
lines_.clear();
|
||||||
|
} else {
|
||||||
|
SubmitCommand_(input_string_);
|
||||||
|
}
|
||||||
|
input_history_.push_front(input_string_);
|
||||||
|
if (input_history_.size() > 100) {
|
||||||
|
input_history_.pop_back();
|
||||||
|
}
|
||||||
|
input_string_.resize(0);
|
||||||
|
input_text_dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
void DevConsole::SubmitCommand_(const std::string& command) {
|
void DevConsole::SubmitCommand_(const std::string& command) {
|
||||||
assert(g_base);
|
assert(g_base);
|
||||||
g_base->logic->event_loop()->PushCall([command] {
|
g_base->logic->event_loop()->PushCall([command, this] {
|
||||||
// These are always run in whichever context is 'visible'.
|
// These are always run in whichever context is 'visible'.
|
||||||
ScopedSetContext ssc(g_base->app_mode()->GetForegroundContext());
|
ScopedSetContext ssc(g_base->app_mode()->GetForegroundContext());
|
||||||
PythonCommand cmd(command, "<console>");
|
PythonCommand cmd(command, "<console>");
|
||||||
@ -180,7 +406,7 @@ void DevConsole::SubmitCommand_(const std::string& command) {
|
|||||||
if (cmd.CanEval()) {
|
if (cmd.CanEval()) {
|
||||||
auto obj = cmd.Eval(true, nullptr, nullptr);
|
auto obj = cmd.Eval(true, nullptr, nullptr);
|
||||||
if (obj.Exists() && obj.Get() != Py_None) {
|
if (obj.Exists() && obj.Get() != Py_None) {
|
||||||
g_base->console()->Print(obj.Repr() + "\n");
|
Print(obj.Repr() + "\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Not eval-able; just exec it.
|
// Not eval-able; just exec it.
|
||||||
@ -196,36 +422,36 @@ void DevConsole::EnableInput() {
|
|||||||
|
|
||||||
void DevConsole::Dismiss() {
|
void DevConsole::Dismiss() {
|
||||||
assert(g_base->InLogicThread());
|
assert(g_base->InLogicThread());
|
||||||
if (state_ == State::kInactive) {
|
if (state_ == State_::kInactive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state_prev_ = state_;
|
state_prev_ = state_;
|
||||||
state_ = State::kInactive;
|
state_ = State_::kInactive;
|
||||||
transition_start_ = g_core->GetAppTimeMillisecs();
|
transition_start_ = g_base->logic->display_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevConsole::ToggleState() {
|
void DevConsole::ToggleState() {
|
||||||
assert(g_base->InLogicThread());
|
assert(g_base->InLogicThread());
|
||||||
state_prev_ = state_;
|
state_prev_ = state_;
|
||||||
switch (state_) {
|
switch (state_) {
|
||||||
case State::kInactive:
|
case State_::kInactive:
|
||||||
state_ = State::kMini;
|
state_ = State_::kMini;
|
||||||
break;
|
break;
|
||||||
case State::kMini:
|
case State_::kMini:
|
||||||
state_ = State::kFull;
|
state_ = State_::kFull;
|
||||||
break;
|
break;
|
||||||
case State::kFull:
|
case State_::kFull:
|
||||||
state_ = State::kInactive;
|
state_ = State_::kInactive;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
g_base->audio->PlaySound(g_base->assets->SysSound(SysSoundID::kBlip));
|
g_base->audio->PlaySound(g_base->assets->SysSound(SysSoundID::kBlip));
|
||||||
transition_start_ = g_core->GetAppTimeMillisecs();
|
transition_start_ = g_base->logic->display_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto DevConsole::HandleTextEditing(const std::string& text) -> bool {
|
auto DevConsole::HandleTextEditing(const std::string& text) -> bool {
|
||||||
assert(g_base->InLogicThread());
|
assert(g_base->InLogicThread());
|
||||||
if (state_ == State::kInactive) {
|
if (state_ == State_::kInactive) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +472,7 @@ auto DevConsole::HandleKeyRelease(const SDL_Keysym* keysym) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise absorb *all* key-ups when we're active.
|
// Otherwise absorb *all* key-ups when we're active.
|
||||||
return state_ != State::kInactive;
|
return state_ != State_::kInactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevConsole::Print(const std::string& s_in) {
|
void DevConsole::Print(const std::string& s_in) {
|
||||||
@ -259,7 +485,7 @@ void DevConsole::Print(const std::string& s_in) {
|
|||||||
|
|
||||||
// Spit out all completed lines and keep the last one as lastline.
|
// Spit out all completed lines and keep the last one as lastline.
|
||||||
for (size_t i = 0; i < broken_up.size() - 1; i++) {
|
for (size_t i = 0; i < broken_up.size() - 1; i++) {
|
||||||
lines_.emplace_back(broken_up[i], g_core->GetAppTimeMillisecs());
|
lines_.emplace_back(broken_up[i], g_base->logic->display_time());
|
||||||
if (lines_.size() > kDevConsoleLineLimit) {
|
if (lines_.size() > kDevConsoleLineLimit) {
|
||||||
lines_.pop_front();
|
lines_.pop_front();
|
||||||
}
|
}
|
||||||
@ -268,191 +494,209 @@ void DevConsole::Print(const std::string& s_in) {
|
|||||||
last_line_mesh_dirty_ = true;
|
last_line_mesh_dirty_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevConsole::Draw(RenderPass* pass) {
|
auto DevConsole::Bottom_() const -> float {
|
||||||
millisecs_t transition_ticks = 100;
|
float bs = PythonConsoleBaseScale_();
|
||||||
float bs = PythonConsoleBaseScale();
|
float vw = g_base->graphics->screen_virtual_width();
|
||||||
if ((transition_start_ != 0)
|
float vh = g_base->graphics->screen_virtual_height();
|
||||||
&& (state_ != State::kInactive
|
|
||||||
|| ((g_core->GetAppTimeMillisecs() - transition_start_)
|
|
||||||
< transition_ticks))) {
|
|
||||||
float ratio =
|
|
||||||
(static_cast<float>(g_core->GetAppTimeMillisecs() - transition_start_)
|
|
||||||
/ static_cast<float>(transition_ticks));
|
|
||||||
float bottom;
|
|
||||||
float mini_size = 90.0f * bs;
|
|
||||||
if (state_ == State::kMini) {
|
|
||||||
bottom = pass->virtual_height() - mini_size;
|
|
||||||
} else {
|
|
||||||
bottom =
|
|
||||||
pass->virtual_height() - pass->virtual_height() * kDevConsoleSize;
|
|
||||||
}
|
|
||||||
if (g_core->GetAppTimeMillisecs() - transition_start_ < transition_ticks) {
|
|
||||||
float from_height;
|
|
||||||
if (state_prev_ == State::kMini) {
|
|
||||||
from_height = pass->virtual_height() - mini_size;
|
|
||||||
} else if (state_prev_ == State::kFull) {
|
|
||||||
from_height =
|
|
||||||
pass->virtual_height() - pass->virtual_height() * kDevConsoleSize;
|
|
||||||
} else {
|
|
||||||
from_height = pass->virtual_height();
|
|
||||||
}
|
|
||||||
float to_height;
|
|
||||||
if (state_ == State::kMini) {
|
|
||||||
to_height = pass->virtual_height() - mini_size;
|
|
||||||
} else if (state_ == State::kFull) {
|
|
||||||
to_height =
|
|
||||||
pass->virtual_height() - pass->virtual_height() * kDevConsoleSize;
|
|
||||||
} else {
|
|
||||||
to_height = pass->virtual_height();
|
|
||||||
}
|
|
||||||
bottom = to_height * ratio + from_height * (1.0 - ratio);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
bg_mesh_.SetPositionAndSize(0, bottom, kDevConsoleZDepth,
|
|
||||||
pass->virtual_width(),
|
|
||||||
(pass->virtual_height() - bottom));
|
|
||||||
stripe_mesh_.SetPositionAndSize(0, bottom + 15.0f * bs, kDevConsoleZDepth,
|
|
||||||
pass->virtual_width(), 15.0f * bs);
|
|
||||||
shadow_mesh_.SetPositionAndSize(0, bottom - 7.0f * bs, kDevConsoleZDepth,
|
|
||||||
pass->virtual_width(), 7.0f * bs);
|
|
||||||
SimpleComponent c(pass);
|
|
||||||
c.SetTransparent(true);
|
|
||||||
c.SetColor(0, 0, 0.1f, 0.9f);
|
|
||||||
c.DrawMesh(&bg_mesh_);
|
|
||||||
c.Submit();
|
|
||||||
c.SetColor(1.0f, 1.0f, 1.0f, 0.1f);
|
|
||||||
c.DrawMesh(&stripe_mesh_);
|
|
||||||
c.Submit();
|
|
||||||
c.SetColor(0, 0, 0, 0.1f);
|
|
||||||
c.DrawMesh(&shadow_mesh_);
|
|
||||||
c.Submit();
|
|
||||||
}
|
|
||||||
if (input_text_dirty_) {
|
|
||||||
input_text_group_.set_text(input_string_);
|
|
||||||
input_text_dirty_ = false;
|
|
||||||
last_input_text_change_time_ = pass->frame_def()->real_time();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
SimpleComponent c(pass);
|
|
||||||
c.SetTransparent(true);
|
|
||||||
c.SetColor(0.5f, 0.5f, 0.7f, 1.0f);
|
|
||||||
int elem_count = built_text_group_.GetElementCount();
|
|
||||||
for (int e = 0; e < elem_count; e++) {
|
|
||||||
c.SetTexture(built_text_group_.GetElementTexture(e));
|
|
||||||
{
|
|
||||||
auto xf = c.ScopedTransform();
|
|
||||||
c.Translate(pass->virtual_width() - 115.0f * bs, bottom + 4.0f,
|
|
||||||
kDevConsoleZDepth);
|
|
||||||
c.Scale(0.35f * bs, 0.35f * bs, 1.0f);
|
|
||||||
c.DrawMesh(built_text_group_.GetElementMesh(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elem_count = title_text_group_.GetElementCount();
|
|
||||||
for (int e = 0; e < elem_count; e++) {
|
|
||||||
c.SetTexture(title_text_group_.GetElementTexture(e));
|
|
||||||
{
|
|
||||||
auto xf = c.ScopedTransform();
|
|
||||||
c.Translate(10.0f * bs, bottom + 4.0f, kDevConsoleZDepth);
|
|
||||||
c.Scale(0.35f * bs, 0.35f * bs, 1.0f);
|
|
||||||
c.DrawMesh(title_text_group_.GetElementMesh(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elem_count = prompt_text_group_.GetElementCount();
|
|
||||||
for (int e = 0; e < elem_count; e++) {
|
|
||||||
c.SetTexture(prompt_text_group_.GetElementTexture(e));
|
|
||||||
c.SetColor(1, 1, 1, 1);
|
|
||||||
{
|
|
||||||
auto xf = c.ScopedTransform();
|
|
||||||
c.Translate(5.0f * bs, bottom + 14.5f * bs, kDevConsoleZDepth);
|
|
||||||
c.Scale(0.5f * bs, 0.5f * bs, 1.0f);
|
|
||||||
c.DrawMesh(prompt_text_group_.GetElementMesh(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elem_count = input_text_group_.GetElementCount();
|
|
||||||
for (int e = 0; e < elem_count; e++) {
|
|
||||||
c.SetTexture(input_text_group_.GetElementTexture(e));
|
|
||||||
{
|
|
||||||
auto xf = c.ScopedTransform();
|
|
||||||
c.Translate(15.0f * bs, bottom + 14.5f * bs, kDevConsoleZDepth);
|
|
||||||
c.Scale(0.5f * bs, 0.5f * bs, 1.0f);
|
|
||||||
c.DrawMesh(input_text_group_.GetElementMesh(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.Submit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Carat.
|
float ratio =
|
||||||
millisecs_t real_time = pass->frame_def()->real_time();
|
(g_base->logic->display_time() - transition_start_) / kTransitionSeconds;
|
||||||
if (real_time % 200 < 100
|
float bottom;
|
||||||
|| (real_time - last_input_text_change_time_ < 100)) {
|
float mini_size = 90.0f * bs;
|
||||||
SimpleComponent c(pass);
|
if (state_ == State_::kMini) {
|
||||||
c.SetTransparent(true);
|
bottom = vh - mini_size;
|
||||||
c.SetColor(1, 1, 1, 0.7f);
|
} else {
|
||||||
|
bottom = vh - vh * kDevConsoleSize;
|
||||||
|
}
|
||||||
|
if (g_base->logic->display_time() - transition_start_ < kTransitionSeconds) {
|
||||||
|
float from_height;
|
||||||
|
if (state_prev_ == State_::kMini) {
|
||||||
|
from_height = vh - mini_size;
|
||||||
|
} else if (state_prev_ == State_::kFull) {
|
||||||
|
from_height = vh - vh * kDevConsoleSize;
|
||||||
|
} else {
|
||||||
|
from_height = vh;
|
||||||
|
}
|
||||||
|
float to_height;
|
||||||
|
if (state_ == State_::kMini) {
|
||||||
|
to_height = vh - mini_size;
|
||||||
|
} else if (state_ == State_::kFull) {
|
||||||
|
to_height = vh - vh * kDevConsoleSize;
|
||||||
|
} else {
|
||||||
|
to_height = vh;
|
||||||
|
}
|
||||||
|
bottom = to_height * ratio + from_height * (1.0 - ratio);
|
||||||
|
}
|
||||||
|
return bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevConsole::Draw(RenderPass* pass) {
|
||||||
|
float bs = PythonConsoleBaseScale_();
|
||||||
|
|
||||||
|
// If we're not yet transitioning in for the first time OR have
|
||||||
|
// completed transitioning out, do nothing.
|
||||||
|
if (transition_start_ <= 0.0
|
||||||
|
|| state_ == State_::kInactive
|
||||||
|
&& ((g_base->logic->display_time() - transition_start_)
|
||||||
|
>= kTransitionSeconds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float bottom = Bottom_();
|
||||||
|
{
|
||||||
|
bg_mesh_.SetPositionAndSize(0, bottom, kDevConsoleZDepth,
|
||||||
|
pass->virtual_width(),
|
||||||
|
(pass->virtual_height() - bottom));
|
||||||
|
stripe_mesh_.SetPositionAndSize(0, bottom + 15.0f * bs, kDevConsoleZDepth,
|
||||||
|
pass->virtual_width(), 15.0f * bs);
|
||||||
|
shadow_mesh_.SetPositionAndSize(0, bottom - 7.0f * bs, kDevConsoleZDepth,
|
||||||
|
pass->virtual_width(), 7.0f * bs);
|
||||||
|
SimpleComponent c(pass);
|
||||||
|
c.SetTransparent(true);
|
||||||
|
c.SetColor(0, 0, 0.1f, 0.9f);
|
||||||
|
c.DrawMesh(&bg_mesh_);
|
||||||
|
c.Submit();
|
||||||
|
c.SetColor(1.0f, 1.0f, 1.0f, 0.1f);
|
||||||
|
c.DrawMesh(&stripe_mesh_);
|
||||||
|
c.Submit();
|
||||||
|
c.SetColor(0, 0, 0, 0.1f);
|
||||||
|
c.DrawMesh(&shadow_mesh_);
|
||||||
|
c.Submit();
|
||||||
|
}
|
||||||
|
if (input_text_dirty_) {
|
||||||
|
input_text_group_.set_text(input_string_);
|
||||||
|
input_text_dirty_ = false;
|
||||||
|
last_input_text_change_time_ = pass->frame_def()->real_time();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
SimpleComponent c(pass);
|
||||||
|
c.SetFlatness(1.0f);
|
||||||
|
c.SetTransparent(true);
|
||||||
|
c.SetColor(0.5f, 0.5f, 0.7f, 0.8f);
|
||||||
|
int elem_count = built_text_group_.GetElementCount();
|
||||||
|
for (int e = 0; e < elem_count; e++) {
|
||||||
|
c.SetTexture(built_text_group_.GetElementTexture(e));
|
||||||
{
|
{
|
||||||
auto xf = c.ScopedTransform();
|
auto xf = c.ScopedTransform();
|
||||||
c.Translate(
|
c.Translate(pass->virtual_width() - 115.0f * bs, bottom + 4.0f,
|
||||||
(19.0f
|
kDevConsoleZDepth);
|
||||||
+ g_base->text_graphics->GetStringWidth(input_string_) * 0.5f)
|
c.Scale(0.35f * bs, 0.35f * bs, 1.0f);
|
||||||
* bs,
|
c.DrawMesh(built_text_group_.GetElementMesh(e));
|
||||||
bottom + 22.5f * bs, kDevConsoleZDepth);
|
|
||||||
c.Scale(6.0f * bs, 12.0f * bs, 1.0f);
|
|
||||||
c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
|
|
||||||
}
|
}
|
||||||
c.Submit();
|
|
||||||
}
|
}
|
||||||
|
elem_count = title_text_group_.GetElementCount();
|
||||||
// Draw output lines.
|
for (int e = 0; e < elem_count; e++) {
|
||||||
{
|
c.SetTexture(title_text_group_.GetElementTexture(e));
|
||||||
float draw_scale = 0.6f;
|
{
|
||||||
float v_inc = 18.0f;
|
auto xf = c.ScopedTransform();
|
||||||
SimpleComponent c(pass);
|
c.Translate(10.0f * bs, bottom + 4.0f, kDevConsoleZDepth);
|
||||||
c.SetTransparent(true);
|
c.Scale(0.35f * bs, 0.35f * bs, 1.0f);
|
||||||
|
c.DrawMesh(title_text_group_.GetElementMesh(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elem_count = prompt_text_group_.GetElementCount();
|
||||||
|
for (int e = 0; e < elem_count; e++) {
|
||||||
|
c.SetTexture(prompt_text_group_.GetElementTexture(e));
|
||||||
c.SetColor(1, 1, 1, 1);
|
c.SetColor(1, 1, 1, 1);
|
||||||
float h = 0.5f
|
{
|
||||||
* (g_base->graphics->screen_virtual_width()
|
auto xf = c.ScopedTransform();
|
||||||
- (kDevConsoleStringBreakUpSize * draw_scale));
|
c.Translate(5.0f * bs, bottom + 14.5f * bs, kDevConsoleZDepth);
|
||||||
float v = bottom + 32.0f * bs;
|
c.Scale(0.5f * bs, 0.5f * bs, 1.0f);
|
||||||
if (!last_line_.empty()) {
|
c.DrawMesh(prompt_text_group_.GetElementMesh(e));
|
||||||
if (last_line_mesh_dirty_) {
|
|
||||||
if (!last_line_mesh_group_.Exists()) {
|
|
||||||
last_line_mesh_group_ = Object::New<TextGroup>();
|
|
||||||
}
|
|
||||||
last_line_mesh_group_->set_text(last_line_);
|
|
||||||
last_line_mesh_dirty_ = false;
|
|
||||||
}
|
|
||||||
int elem_count = last_line_mesh_group_->GetElementCount();
|
|
||||||
for (int e = 0; e < elem_count; e++) {
|
|
||||||
c.SetTexture(last_line_mesh_group_->GetElementTexture(e));
|
|
||||||
{
|
|
||||||
auto xf = c.ScopedTransform();
|
|
||||||
c.Translate(h, v + 2, kDevConsoleZDepth);
|
|
||||||
c.Scale(draw_scale, draw_scale);
|
|
||||||
c.DrawMesh(last_line_mesh_group_->GetElementMesh(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v += v_inc;
|
|
||||||
}
|
}
|
||||||
for (auto i = lines_.rbegin(); i != lines_.rend(); i++) {
|
}
|
||||||
int elem_count = i->GetText().GetElementCount();
|
elem_count = input_text_group_.GetElementCount();
|
||||||
for (int e = 0; e < elem_count; e++) {
|
for (int e = 0; e < elem_count; e++) {
|
||||||
c.SetTexture(i->GetText().GetElementTexture(e));
|
c.SetTexture(input_text_group_.GetElementTexture(e));
|
||||||
{
|
{
|
||||||
auto xf = c.ScopedTransform();
|
auto xf = c.ScopedTransform();
|
||||||
c.Translate(h, v + 2, kDevConsoleZDepth);
|
c.Translate(15.0f * bs, bottom + 14.5f * bs, kDevConsoleZDepth);
|
||||||
c.Scale(draw_scale, draw_scale);
|
c.Scale(0.5f * bs, 0.5f * bs, 1.0f);
|
||||||
c.DrawMesh(i->GetText().GetElementMesh(e));
|
c.DrawMesh(input_text_group_.GetElementMesh(e));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
c.Submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carat.
|
||||||
|
millisecs_t real_time = pass->frame_def()->real_time();
|
||||||
|
if (real_time % 200 < 100
|
||||||
|
|| (real_time - last_input_text_change_time_ < 100)) {
|
||||||
|
SimpleComponent c(pass);
|
||||||
|
c.SetTransparent(true);
|
||||||
|
c.SetColor(1, 1, 1, 0.7f);
|
||||||
|
{
|
||||||
|
auto xf = c.ScopedTransform();
|
||||||
|
c.Translate(
|
||||||
|
(19.0f + g_base->text_graphics->GetStringWidth(input_string_) * 0.5f)
|
||||||
|
* bs,
|
||||||
|
bottom + 22.5f * bs, kDevConsoleZDepth);
|
||||||
|
c.Scale(6.0f * bs, 12.0f * bs, 1.0f);
|
||||||
|
c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
|
||||||
|
}
|
||||||
|
c.Submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw output lines.
|
||||||
|
{
|
||||||
|
float draw_scale = 0.6f;
|
||||||
|
float v_inc = 18.0f;
|
||||||
|
SimpleComponent c(pass);
|
||||||
|
c.SetTransparent(true);
|
||||||
|
c.SetColor(1, 1, 1, 1);
|
||||||
|
c.SetFlatness(1.0f);
|
||||||
|
float h = 0.5f
|
||||||
|
* (g_base->graphics->screen_virtual_width()
|
||||||
|
- (kDevConsoleStringBreakUpSize * draw_scale));
|
||||||
|
float v = bottom + 32.0f * bs;
|
||||||
|
if (!last_line_.empty()) {
|
||||||
|
if (last_line_mesh_dirty_) {
|
||||||
|
if (!last_line_mesh_group_.Exists()) {
|
||||||
|
last_line_mesh_group_ = Object::New<TextGroup>();
|
||||||
}
|
}
|
||||||
v += v_inc;
|
last_line_mesh_group_->set_text(last_line_);
|
||||||
if (v > pass->virtual_height() + 14) {
|
last_line_mesh_dirty_ = false;
|
||||||
break;
|
}
|
||||||
|
int elem_count = last_line_mesh_group_->GetElementCount();
|
||||||
|
for (int e = 0; e < elem_count; e++) {
|
||||||
|
c.SetTexture(last_line_mesh_group_->GetElementTexture(e));
|
||||||
|
{
|
||||||
|
auto xf = c.ScopedTransform();
|
||||||
|
c.Translate(h, v + 2, kDevConsoleZDepth);
|
||||||
|
c.Scale(draw_scale, draw_scale);
|
||||||
|
c.DrawMesh(last_line_mesh_group_->GetElementMesh(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Submit();
|
v += v_inc;
|
||||||
|
}
|
||||||
|
for (auto i = lines_.rbegin(); i != lines_.rend(); i++) {
|
||||||
|
int elem_count = i->GetText().GetElementCount();
|
||||||
|
for (int e = 0; e < elem_count; e++) {
|
||||||
|
c.SetTexture(i->GetText().GetElementTexture(e));
|
||||||
|
{
|
||||||
|
auto xf = c.ScopedTransform();
|
||||||
|
c.Translate(h, v + 2, kDevConsoleZDepth);
|
||||||
|
c.Scale(draw_scale, draw_scale);
|
||||||
|
c.DrawMesh(i->GetText().GetElementMesh(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v += v_inc;
|
||||||
|
if (v > pass->virtual_height() + v_inc) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons.
|
||||||
|
{
|
||||||
|
for (auto&& button : buttons_) {
|
||||||
|
button.Draw(pass, bottom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto DevConsole::PythonConsoleBaseScale() -> float {
|
|
||||||
|
auto DevConsole::PythonConsoleBaseScale_() const -> float {
|
||||||
switch (g_base->ui->scale()) {
|
switch (g_base->ui->scale()) {
|
||||||
case UIScale::kLarge:
|
case UIScale::kLarge:
|
||||||
return 1.5f;
|
return 1.5f;
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "ballistica/base/graphics/renderer/renderer.h"
|
#include "ballistica/base/graphics/renderer/renderer.h"
|
||||||
#include "ballistica/shared/foundation/object.h"
|
#include "ballistica/shared/foundation/object.h"
|
||||||
|
#include "ballistica/shared/python/python_ref.h"
|
||||||
|
|
||||||
namespace ballistica::base {
|
namespace ballistica::base {
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ class DevConsole {
|
|||||||
public:
|
public:
|
||||||
DevConsole();
|
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;
|
||||||
auto HandleKeyRelease(const SDL_Keysym* keysym) -> bool;
|
auto HandleKeyRelease(const SDL_Keysym* keysym) -> bool;
|
||||||
@ -35,12 +36,26 @@ class DevConsole {
|
|||||||
/// Called when the console should start accepting Python command input.
|
/// Called when the console should start accepting Python command input.
|
||||||
void EnableInput();
|
void EnableInput();
|
||||||
|
|
||||||
private:
|
auto input_string() const {
|
||||||
class Line_;
|
assert(g_base->InLogicThread());
|
||||||
|
return input_string_;
|
||||||
|
}
|
||||||
|
void set_input_string(const std::string& val);
|
||||||
|
|
||||||
auto PythonConsoleBaseScale() -> float;
|
void InputAdapterFinish();
|
||||||
|
|
||||||
|
auto HandleMouseDown(int button, float x, float y) -> bool;
|
||||||
|
void HandleMouseUp(int button, float x, float y);
|
||||||
|
void Exec();
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Button_;
|
||||||
|
class Line_;
|
||||||
|
enum class State_ { kInactive, kMini, kFull };
|
||||||
|
auto Bottom_() const -> float;
|
||||||
|
auto PythonConsoleBaseScale_() const -> float;
|
||||||
void SubmitCommand_(const std::string& command);
|
void SubmitCommand_(const std::string& command);
|
||||||
enum class State { kInactive, kMini, kFull };
|
void InvokeStringEditor_();
|
||||||
ImageMesh bg_mesh_;
|
ImageMesh bg_mesh_;
|
||||||
ImageMesh stripe_mesh_;
|
ImageMesh stripe_mesh_;
|
||||||
ImageMesh shadow_mesh_;
|
ImageMesh shadow_mesh_;
|
||||||
@ -50,9 +65,9 @@ class DevConsole {
|
|||||||
TextGroup input_text_group_;
|
TextGroup input_text_group_;
|
||||||
millisecs_t last_input_text_change_time_{};
|
millisecs_t last_input_text_change_time_{};
|
||||||
bool input_text_dirty_{true};
|
bool input_text_dirty_{true};
|
||||||
millisecs_t transition_start_{};
|
double transition_start_{};
|
||||||
State state_{State::kInactive};
|
State_ state_{State_::kInactive};
|
||||||
State state_prev_{State::kInactive};
|
State_ state_prev_{State_::kInactive};
|
||||||
bool input_enabled_{};
|
bool input_enabled_{};
|
||||||
std::string input_string_;
|
std::string input_string_;
|
||||||
std::list<std::string> input_history_;
|
std::list<std::string> input_history_;
|
||||||
@ -61,6 +76,9 @@ class DevConsole {
|
|||||||
std::string last_line_;
|
std::string last_line_;
|
||||||
Object::Ref<TextGroup> last_line_mesh_group_;
|
Object::Ref<TextGroup> last_line_mesh_group_;
|
||||||
bool last_line_mesh_dirty_{true};
|
bool last_line_mesh_dirty_{true};
|
||||||
|
bool python_console_pressed_{};
|
||||||
|
PythonRef string_edit_adapter_;
|
||||||
|
std::list<Button_> buttons_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ballistica::base
|
} // namespace ballistica::base
|
||||||
|
|||||||
@ -125,14 +125,23 @@ auto UI::HandleMouseDown(int button, float x, float y, bool double_click)
|
|||||||
-> bool {
|
-> bool {
|
||||||
bool handled{};
|
bool handled{};
|
||||||
|
|
||||||
if (show_dev_console_button_ && button == 1) {
|
// Dev console button.
|
||||||
|
if (show_dev_console_button_) {
|
||||||
float vx = g_base->graphics->screen_virtual_width();
|
float vx = g_base->graphics->screen_virtual_width();
|
||||||
float vy = g_base->graphics->screen_virtual_height();
|
float vy = g_base->graphics->screen_virtual_height();
|
||||||
if (InDevConsoleButton_(x, y)) {
|
if (InDevConsoleButton_(x, y)) {
|
||||||
dev_console_button_pressed_ = true;
|
if (button == 1) {
|
||||||
|
dev_console_button_pressed_ = true;
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dev console itself.
|
||||||
|
if (!handled && dev_console_ && dev_console_->IsActive()) {
|
||||||
|
handled = dev_console_->HandleMouseDown(button, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
if (!handled && g_base->HaveUIV1()) {
|
if (!handled && g_base->HaveUIV1()) {
|
||||||
handled = g_base->ui_v1()->HandleLegacyRootUIMouseDown(x, y);
|
handled = g_base->ui_v1()->HandleLegacyRootUIMouseDown(x, y);
|
||||||
}
|
}
|
||||||
@ -151,10 +160,13 @@ void UI::HandleMouseUp(int button, float x, float y) {
|
|||||||
SendWidgetMessage(
|
SendWidgetMessage(
|
||||||
WidgetMessage(WidgetMessage::Type::kMouseUp, nullptr, x, y));
|
WidgetMessage(WidgetMessage::Type::kMouseUp, nullptr, x, y));
|
||||||
|
|
||||||
if (dev_console_button_pressed_) {
|
if (dev_console_) {
|
||||||
|
dev_console_->HandleMouseUp(button, x, y);
|
||||||
|
}
|
||||||
|
if (dev_console_button_pressed_ && button == 1) {
|
||||||
if (InDevConsoleButton_(x, y)) {
|
if (InDevConsoleButton_(x, y)) {
|
||||||
if (auto* console = g_base->console()) {
|
if (dev_console_) {
|
||||||
console->ToggleState();
|
dev_console_->ToggleState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dev_console_button_pressed_ = false;
|
dev_console_button_pressed_ = false;
|
||||||
@ -165,6 +177,26 @@ void UI::HandleMouseUp(int button, float x, float y) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto UI::UIHasDirectKeyboardInput() const -> bool {
|
||||||
|
// Currently limiting this to desktop operating systems, but should
|
||||||
|
// generalize this, as situations such as tablets with hardware keyboards
|
||||||
|
// should behave similarly to desktop PCs. Though perhaps we should make
|
||||||
|
// this optional everywhere (or language dependent) since direct keyboard
|
||||||
|
// input might not work well for some languages even on desktops.
|
||||||
|
if (g_buildconfig.ostype_macos() || g_buildconfig.ostype_windows()
|
||||||
|
|| g_buildconfig.ostype_linux()) {
|
||||||
|
// Return true if we've got a keyboard attached and either it or nothing
|
||||||
|
// is active.
|
||||||
|
auto* ui_input_device = g_base->ui->GetUIInputDevice();
|
||||||
|
if (auto* keyboard = g_base->ui->GetUIInputDevice()) {
|
||||||
|
if (ui_input_device == keyboard || ui_input_device == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void UI::HandleMouseMotion(float x, float y) {
|
void UI::HandleMouseMotion(float x, float y) {
|
||||||
SendWidgetMessage(
|
SendWidgetMessage(
|
||||||
WidgetMessage(WidgetMessage::Type::kMouseMove, nullptr, x, y));
|
WidgetMessage(WidgetMessage::Type::kMouseMove, nullptr, x, y));
|
||||||
@ -353,8 +385,8 @@ void UI::Draw(FrameDef* frame_def) {
|
|||||||
|
|
||||||
void UI::DrawDev(FrameDef* frame_def) {
|
void UI::DrawDev(FrameDef* frame_def) {
|
||||||
// Draw dev console.
|
// Draw dev console.
|
||||||
if (g_base->console()) {
|
if (dev_console_) {
|
||||||
g_base->console()->Draw(frame_def->overlay_pass());
|
dev_console_->Draw(frame_def->overlay_pass());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw dev console button.
|
// Draw dev console button.
|
||||||
@ -440,10 +472,10 @@ void UI::ShowURL(const std::string& url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UI::ConfirmQuit() {
|
void UI::ConfirmQuit() {
|
||||||
g_base->logic->event_loop()->PushCall([] {
|
g_base->logic->event_loop()->PushCall([this] {
|
||||||
// If the in-app console is active, dismiss it.
|
// If the in-app console is active, dismiss it.
|
||||||
if (g_base->console() != nullptr && g_base->console()->IsActive()) {
|
if (dev_console_ != nullptr && dev_console_->IsActive()) {
|
||||||
g_base->console()->Dismiss();
|
dev_console_->Dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(g_base->InLogicThread());
|
assert(g_base->InLogicThread());
|
||||||
@ -467,4 +499,37 @@ void UI::ConfirmQuit() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UI::PushDevConsolePrintCall(const std::string& msg) {
|
||||||
|
// Completely ignore this stuff in headless mode.
|
||||||
|
if (g_core->HeadlessMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If our event loop AND console are up and running, ship it off to
|
||||||
|
// be printed. Otherwise store it for the console to grab when it's ready.
|
||||||
|
if (auto* event_loop = g_base->logic->event_loop()) {
|
||||||
|
if (dev_console_ != nullptr) {
|
||||||
|
event_loop->PushCall([this, msg] { dev_console_->Print(msg); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Didn't send a print; store for later.
|
||||||
|
dev_console_startup_messages_ += msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UI::OnAssetsAvailable() {
|
||||||
|
assert(g_base->InLogicThread());
|
||||||
|
|
||||||
|
// Spin up the dev console.
|
||||||
|
if (!g_core->HeadlessMode()) {
|
||||||
|
assert(dev_console_ == nullptr);
|
||||||
|
dev_console_ = new DevConsole();
|
||||||
|
|
||||||
|
// Print any messages that have built up.
|
||||||
|
if (!dev_console_startup_messages_.empty()) {
|
||||||
|
dev_console_->Print(dev_console_startup_messages_);
|
||||||
|
dev_console_startup_messages_.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ballistica::base
|
} // namespace ballistica::base
|
||||||
|
|||||||
@ -32,6 +32,8 @@ class UI {
|
|||||||
void OnScreenSizeChange();
|
void OnScreenSizeChange();
|
||||||
void StepDisplayTime();
|
void StepDisplayTime();
|
||||||
|
|
||||||
|
void OnAssetsAvailable();
|
||||||
|
|
||||||
void LanguageChanged();
|
void LanguageChanged();
|
||||||
|
|
||||||
/// Reset all UI to a default state. Generally should be called when
|
/// Reset all UI to a default state. Generally should be called when
|
||||||
@ -79,6 +81,13 @@ class UI {
|
|||||||
/// Return the input-device that currently owns the UI; otherwise nullptr.
|
/// Return the input-device that currently owns the UI; otherwise nullptr.
|
||||||
auto GetUIInputDevice() const -> InputDevice*;
|
auto GetUIInputDevice() const -> InputDevice*;
|
||||||
|
|
||||||
|
/// Return true if there is a full desktop-style hardware keyboard
|
||||||
|
/// attached and the active UI InputDevice is set to it or not set. This
|
||||||
|
/// also may take language or user preferences into account. Editable text
|
||||||
|
/// elements can use this to opt in to accepting key events directly
|
||||||
|
/// instead of popping up a string edit dialog.
|
||||||
|
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.
|
||||||
void PushBackButtonCall(InputDevice* input_device);
|
void PushBackButtonCall(InputDevice* input_device);
|
||||||
|
|
||||||
@ -98,11 +107,18 @@ class UI {
|
|||||||
/// device (nullptr to specify none). Can be called from any thread.
|
/// device (nullptr to specify none). Can be called from any thread.
|
||||||
void PushMainMenuPressCall(InputDevice* device);
|
void PushMainMenuPressCall(InputDevice* device);
|
||||||
|
|
||||||
|
auto* dev_console() const { return dev_console_; }
|
||||||
|
|
||||||
|
void PushDevConsolePrintCall(const std::string& msg);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void MainMenuPress_(InputDevice* device);
|
void MainMenuPress_(InputDevice* device);
|
||||||
auto DevConsoleButtonSize_() const -> float;
|
auto DevConsoleButtonSize_() const -> float;
|
||||||
auto InDevConsoleButton_(float x, float y) const -> bool;
|
auto InDevConsoleButton_(float x, float y) const -> bool;
|
||||||
void DrawDevConsoleButton_(FrameDef* frame_def);
|
void DrawDevConsoleButton_(FrameDef* frame_def);
|
||||||
|
|
||||||
|
DevConsole* dev_console_{};
|
||||||
|
std::string dev_console_startup_messages_;
|
||||||
Object::WeakRef<InputDevice> ui_input_device_;
|
Object::WeakRef<InputDevice> ui_input_device_;
|
||||||
millisecs_t last_input_device_use_time_{};
|
millisecs_t last_input_device_use_time_{};
|
||||||
millisecs_t last_widget_input_reject_err_sound_time_{};
|
millisecs_t last_widget_input_reject_err_sound_time_{};
|
||||||
|
|||||||
@ -667,11 +667,6 @@ auto CorePlatform::HaveLeaderboard(const std::string& game,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CorePlatform::EditText(const std::string& title, const std::string& value,
|
|
||||||
int max_chars) {
|
|
||||||
Log(LogLevel::kError, "FIXME: EditText() unimplemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CorePlatform::ShowOnlineScoreUI(const std::string& show,
|
void CorePlatform::ShowOnlineScoreUI(const std::string& show,
|
||||||
const std::string& game,
|
const std::string& game,
|
||||||
const std::string& game_version) {
|
const std::string& game_version) {
|
||||||
|
|||||||
@ -403,10 +403,6 @@ class CorePlatform {
|
|||||||
|
|
||||||
static void SleepMillisecs(millisecs_t ms);
|
static void SleepMillisecs(millisecs_t ms);
|
||||||
|
|
||||||
/// Pop up a text edit dialog.
|
|
||||||
virtual void EditText(const std::string& title, const std::string& value,
|
|
||||||
int max_chars);
|
|
||||||
|
|
||||||
/// Given a C++ symbol, attempt to return a pretty one.
|
/// Given a C++ symbol, attempt to return a pretty one.
|
||||||
virtual auto DemangleCXXSymbol(const std::string& s) -> std::string;
|
virtual auto DemangleCXXSymbol(const std::string& s) -> std::string;
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,7 @@ class BaseSoftInterface {
|
|||||||
virtual auto FeatureSetFromData(PyObject* obj)
|
virtual auto FeatureSetFromData(PyObject* obj)
|
||||||
-> FeatureSetNativeComponent* = 0;
|
-> FeatureSetNativeComponent* = 0;
|
||||||
virtual void DoV1CloudLog(const std::string& msg) = 0;
|
virtual void DoV1CloudLog(const std::string& msg) = 0;
|
||||||
virtual void PushConsolePrintCall(const std::string& msg) = 0;
|
virtual void PushDevConsolePrintCall(const std::string& msg) = 0;
|
||||||
virtual auto GetPyExceptionType(PyExcType exctype) -> PyObject* = 0;
|
virtual auto GetPyExceptionType(PyExcType exctype) -> PyObject* = 0;
|
||||||
virtual auto PrintPythonStackTrace() -> bool = 0;
|
virtual auto PrintPythonStackTrace() -> bool = 0;
|
||||||
virtual auto GetPyLString(PyObject* obj) -> std::string = 0;
|
virtual auto GetPyLString(PyObject* obj) -> std::string = 0;
|
||||||
|
|||||||
@ -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 = 21329;
|
const int kEngineBuildNumber = 21337;
|
||||||
const char* kEngineVersion = "1.7.28";
|
const char* kEngineVersion = "1.7.28";
|
||||||
const int kEngineApiVersion = 8;
|
const int kEngineApiVersion = 8;
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ void Logging::DisplayLog(const std::string& name, LogLevel level,
|
|||||||
const std::string& msg) {
|
const std::string& msg) {
|
||||||
// Print to the in-app console (with a newline added).
|
// Print to the in-app console (with a newline added).
|
||||||
if (g_base_soft) {
|
if (g_base_soft) {
|
||||||
g_base_soft->PushConsolePrintCall(msg + "\n");
|
g_base_soft->PushDevConsolePrintCall(msg + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ship to platform-specific display mechanisms (android log, etc).
|
// Ship to platform-specific display mechanisms (android log, etc).
|
||||||
|
|||||||
@ -162,6 +162,15 @@ auto PythonRef::ValueAsString() const -> std::string {
|
|||||||
return Python::GetPyString(obj_);
|
return Python::GetPyString(obj_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto PythonRef::ValueAsOptionalInt() const -> std::optional<int64_t> {
|
||||||
|
assert(Python::HaveGIL());
|
||||||
|
ThrowIfUnset();
|
||||||
|
if (obj_ == Py_None) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return Python::GetPyInt(obj_);
|
||||||
|
}
|
||||||
|
|
||||||
void PythonRef::ThrowIfUnset() const {
|
void PythonRef::ThrowIfUnset() const {
|
||||||
if (!obj_) {
|
if (!obj_) {
|
||||||
throw Exception("PythonRef is unset.", PyExcType::kValue);
|
throw Exception("PythonRef is unset.", PyExcType::kValue);
|
||||||
|
|||||||
@ -171,6 +171,7 @@ class PythonRef {
|
|||||||
-> std::optional<std::list<std::string>>;
|
-> std::optional<std::list<std::string>>;
|
||||||
|
|
||||||
auto ValueAsInt() const -> int64_t;
|
auto ValueAsInt() const -> int64_t;
|
||||||
|
auto ValueAsOptionalInt() const -> std::optional<int64_t>;
|
||||||
|
|
||||||
/// Returns whether the underlying PyObject is callable.
|
/// Returns whether the underlying PyObject is callable.
|
||||||
/// Throws an exception if unset.
|
/// Throws an exception if unset.
|
||||||
|
|||||||
@ -1869,6 +1869,9 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
PyObject* always_show_carat_obj = Py_None;
|
PyObject* always_show_carat_obj = Py_None;
|
||||||
PyObject* extra_touch_border_scale_obj = Py_None;
|
PyObject* extra_touch_border_scale_obj = Py_None;
|
||||||
PyObject* res_scale_obj = Py_None;
|
PyObject* res_scale_obj = Py_None;
|
||||||
|
PyObject* query_max_chars_obj = Py_None;
|
||||||
|
PyObject* query_description_obj = Py_None;
|
||||||
|
PyObject* adapter_finished_obj = Py_None;
|
||||||
|
|
||||||
static const char* kwlist[] = {"edit",
|
static const char* kwlist[] = {"edit",
|
||||||
"parent",
|
"parent",
|
||||||
@ -1905,9 +1908,12 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
"big",
|
"big",
|
||||||
"extra_touch_border_scale",
|
"extra_touch_border_scale",
|
||||||
"res_scale",
|
"res_scale",
|
||||||
|
"query_max_chars",
|
||||||
|
"query_description",
|
||||||
|
"adapter_finished",
|
||||||
nullptr};
|
nullptr};
|
||||||
if (!PyArg_ParseTupleAndKeywords(
|
if (!PyArg_ParseTupleAndKeywords(
|
||||||
args, keywds, "|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO",
|
args, keywds, "|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO",
|
||||||
const_cast<char**>(kwlist), &edit_obj, &parent_obj, &size_obj,
|
const_cast<char**>(kwlist), &edit_obj, &parent_obj, &size_obj,
|
||||||
&pos_obj, &text_obj, &v_align_obj, &h_align_obj, &editable_obj,
|
&pos_obj, &text_obj, &v_align_obj, &h_align_obj, &editable_obj,
|
||||||
&padding_obj, &on_return_press_call_obj, &on_activate_call_obj,
|
&padding_obj, &on_return_press_call_obj, &on_activate_call_obj,
|
||||||
@ -1917,7 +1923,8 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
&transition_delay_obj, &maxwidth_obj, &max_height_obj, &flatness_obj,
|
&transition_delay_obj, &maxwidth_obj, &max_height_obj, &flatness_obj,
|
||||||
&shadow_obj, &autoselect_obj, &rotate_obj, &enabled_obj,
|
&shadow_obj, &autoselect_obj, &rotate_obj, &enabled_obj,
|
||||||
&force_internal_editing_obj, &always_show_carat_obj, &big_obj,
|
&force_internal_editing_obj, &always_show_carat_obj, &big_obj,
|
||||||
&extra_touch_border_scale_obj, &res_scale_obj))
|
&extra_touch_border_scale_obj, &res_scale_obj, &query_max_chars_obj,
|
||||||
|
&query_description_obj, &adapter_finished_obj))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
if (!g_base->CurrentContext().IsEmpty()) {
|
if (!g_base->CurrentContext().IsEmpty()) {
|
||||||
@ -1934,6 +1941,24 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
}
|
}
|
||||||
return PyUnicode_FromString(widget->text_raw().c_str());
|
return PyUnicode_FromString(widget->text_raw().c_str());
|
||||||
}
|
}
|
||||||
|
if (query_max_chars_obj != Py_None) {
|
||||||
|
widget =
|
||||||
|
dynamic_cast<TextWidget*>(UIV1Python::GetPyWidget(query_max_chars_obj));
|
||||||
|
if (!widget.Exists()) {
|
||||||
|
throw Exception("Invalid or nonexistent widget.",
|
||||||
|
PyExcType::kWidgetNotFound);
|
||||||
|
}
|
||||||
|
return PyLong_FromLong(widget->max_chars());
|
||||||
|
}
|
||||||
|
if (query_description_obj != Py_None) {
|
||||||
|
widget = dynamic_cast<TextWidget*>(
|
||||||
|
UIV1Python::GetPyWidget(query_description_obj));
|
||||||
|
if (!widget.Exists()) {
|
||||||
|
throw Exception("Invalid or nonexistent widget.",
|
||||||
|
PyExcType::kWidgetNotFound);
|
||||||
|
}
|
||||||
|
return PyUnicode_FromString(widget->description().c_str());
|
||||||
|
}
|
||||||
if (edit_obj != Py_None) {
|
if (edit_obj != Py_None) {
|
||||||
widget = dynamic_cast<TextWidget*>(UIV1Python::GetPyWidget(edit_obj));
|
widget = dynamic_cast<TextWidget*>(UIV1Python::GetPyWidget(edit_obj));
|
||||||
if (!widget.Exists()) {
|
if (!widget.Exists()) {
|
||||||
@ -2095,6 +2120,13 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
if (res_scale_obj != Py_None) {
|
if (res_scale_obj != Py_None) {
|
||||||
widget->set_res_scale(Python::GetPyFloat(res_scale_obj));
|
widget->set_res_scale(Python::GetPyFloat(res_scale_obj));
|
||||||
}
|
}
|
||||||
|
if (adapter_finished_obj != Py_None) {
|
||||||
|
if (adapter_finished_obj == Py_True) {
|
||||||
|
widget->AdapterFinished();
|
||||||
|
} else {
|
||||||
|
throw Exception("Unexpected value for adapter_finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if making a new widget add it at the end
|
// if making a new widget add it at the end
|
||||||
if (edit_obj == Py_None) {
|
if (edit_obj == Py_None) {
|
||||||
@ -2144,7 +2176,10 @@ static PyMethodDef PyTextWidgetDef = {
|
|||||||
" always_show_carat: bool | None = None,\n"
|
" always_show_carat: bool | None = None,\n"
|
||||||
" big: bool | None = None,\n"
|
" big: bool | None = None,\n"
|
||||||
" extra_touch_border_scale: float | None = None,\n"
|
" extra_touch_border_scale: float | None = None,\n"
|
||||||
" res_scale: float | None = None)\n"
|
" res_scale: float | None = None,"
|
||||||
|
" query_max_chars: bauiv1.Widget | None = None,\n"
|
||||||
|
" query_description: bauiv1.Widget | None = None,\n"
|
||||||
|
" adapter_finished: bool | None = None)\n"
|
||||||
" -> bauiv1.Widget\n"
|
" -> bauiv1.Widget\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Create or edit a text widget.\n"
|
"Create or edit a text widget.\n"
|
||||||
@ -2757,7 +2792,7 @@ static auto PyConsolePrint(PyObject* self, PyObject* args) -> PyObject* {
|
|||||||
throw Exception();
|
throw Exception();
|
||||||
}
|
}
|
||||||
const char* c = PyUnicode_AsUTF8(str_obj);
|
const char* c = PyUnicode_AsUTF8(str_obj);
|
||||||
g_base->PushConsolePrintCall(c);
|
g_base->PushDevConsolePrintCall(c);
|
||||||
Py_DECREF(str_obj);
|
Py_DECREF(str_obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,7 +101,22 @@ void UIV1Python::HandleDeviceMenuPress(base::InputDevice* device) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIV1Python::LaunchStringEdit(TextWidget* w) {
|
void UIV1Python::InvokeStringEditor(PyObject* string_edit_adapter_instance) {
|
||||||
|
BA_PRECONDITION(g_base->InLogicThread());
|
||||||
|
BA_PRECONDITION(string_edit_adapter_instance);
|
||||||
|
|
||||||
|
base::ScopedSetContext ssc(nullptr);
|
||||||
|
g_base->audio->PlaySound(g_base->assets->SysSound(base::SysSoundID::kSwish));
|
||||||
|
|
||||||
|
// Schedule this in the next cycle to be safe.
|
||||||
|
PythonRef args(Py_BuildValue("(O)", string_edit_adapter_instance),
|
||||||
|
PythonRef::kSteal);
|
||||||
|
Object::New<base::PythonContextCall>(
|
||||||
|
objs().Get(ObjID::kOnScreenKeyboardClass))
|
||||||
|
->Schedule(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIV1Python::LaunchStringEditOld(TextWidget* w) {
|
||||||
assert(g_base->InLogicThread());
|
assert(g_base->InLogicThread());
|
||||||
BA_PRECONDITION(w);
|
BA_PRECONDITION(w);
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,8 @@ class UIV1Python {
|
|||||||
void AddPythonClasses(PyObject* module);
|
void AddPythonClasses(PyObject* module);
|
||||||
void ImportPythonObjs();
|
void ImportPythonObjs();
|
||||||
|
|
||||||
void LaunchStringEdit(TextWidget* w);
|
void LaunchStringEditOld(TextWidget* w);
|
||||||
|
void InvokeStringEditor(PyObject* string_edit_adapter_instance);
|
||||||
void HandleDeviceMenuPress(base::InputDevice* device);
|
void HandleDeviceMenuPress(base::InputDevice* device);
|
||||||
void ShowURL(const std::string& url);
|
void ShowURL(const std::string& url);
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ class UIV1Python {
|
|||||||
kQuitWindowCall,
|
kQuitWindowCall,
|
||||||
kDeviceMenuPressCall,
|
kDeviceMenuPressCall,
|
||||||
kShowURLWindowCall,
|
kShowURLWindowCall,
|
||||||
|
kTextWidgetStringEditAdapterClass,
|
||||||
kLast // Sentinel; must be at end.
|
kLast // Sentinel; must be at end.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -10,12 +10,17 @@
|
|||||||
#include "ballistica/base/input/device/keyboard_input.h"
|
#include "ballistica/base/input/device/keyboard_input.h"
|
||||||
#include "ballistica/base/input/input.h"
|
#include "ballistica/base/input/input.h"
|
||||||
#include "ballistica/base/logic/logic.h"
|
#include "ballistica/base/logic/logic.h"
|
||||||
|
#include "ballistica/base/platform/base_platform.h"
|
||||||
|
#include "ballistica/base/python/base_python.h"
|
||||||
#include "ballistica/base/python/support/python_context_call.h"
|
#include "ballistica/base/python/support/python_context_call.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"
|
||||||
|
#include "ballistica/shared/foundation/logging.h"
|
||||||
|
#include "ballistica/shared/foundation/types.h"
|
||||||
#include "ballistica/shared/generic/utils.h"
|
#include "ballistica/shared/generic/utils.h"
|
||||||
#include "ballistica/shared/python/python.h"
|
#include "ballistica/shared/python/python.h"
|
||||||
|
#include "ballistica/shared/python/python_sys.h"
|
||||||
#include "ballistica/ui_v1/python/ui_v1_python.h"
|
#include "ballistica/ui_v1/python/ui_v1_python.h"
|
||||||
#include "ballistica/ui_v1/widget/container_widget.h"
|
#include "ballistica/ui_v1/widget/container_widget.h"
|
||||||
|
|
||||||
@ -23,25 +28,16 @@ namespace ballistica::ui_v1 {
|
|||||||
|
|
||||||
const float kClearMargin{13.0f};
|
const float kClearMargin{13.0f};
|
||||||
|
|
||||||
// bool TextWidget::always_use_internal_keyboard_{false};
|
|
||||||
|
|
||||||
// FIXME: Move this to g_ui or something; not a global.
|
|
||||||
Object::WeakRef<TextWidget> TextWidget::android_string_edit_widget_;
|
|
||||||
TextWidget* TextWidget::GetAndroidStringEditWidget() {
|
|
||||||
assert(g_base->InLogicThread());
|
|
||||||
return android_string_edit_widget_.Get();
|
|
||||||
}
|
|
||||||
|
|
||||||
TextWidget::TextWidget() {
|
TextWidget::TextWidget() {
|
||||||
// We always show our clear button except for in android when we don't
|
// We always show our clear button except for in android when we don't
|
||||||
// have a touchscreen (android-tv type situations).
|
// have a touchscreen (android-tv type situations).
|
||||||
|
//
|
||||||
// FIXME - should generalize this to any controller-only situation.
|
// FIXME - should generalize this to any controller-only situation.
|
||||||
if (g_buildconfig.ostype_android()) {
|
if (g_buildconfig.ostype_android()) {
|
||||||
if (g_base->input->touch_input() == nullptr) {
|
if (g_base->input->touch_input() == nullptr) {
|
||||||
do_clear_button_ = false;
|
do_clear_button_ = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
birth_time_millisecs_ =
|
birth_time_millisecs_ =
|
||||||
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
|
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
|
||||||
}
|
}
|
||||||
@ -114,8 +110,8 @@ void TextWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
|
|||||||
|
|
||||||
// Center-scale.
|
// Center-scale.
|
||||||
{
|
{
|
||||||
// We should really be scaling our bounds and things,
|
// We should really be scaling our bounds and things, but for now lets
|
||||||
// but for now lets just do a hacky overall scale.
|
// just do a hacky overall scale.
|
||||||
base::EmptyComponent c(pass);
|
base::EmptyComponent c(pass);
|
||||||
c.SetTransparent(true);
|
c.SetTransparent(true);
|
||||||
c.PushTransform();
|
c.PushTransform();
|
||||||
@ -296,7 +292,7 @@ void TextWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply subs/resources to get our actual text if need be.
|
// Apply subs/resources to get our actual text if need be.
|
||||||
UpdateTranslation();
|
UpdateTranslation_();
|
||||||
|
|
||||||
if (!text_group_.Exists()) {
|
if (!text_group_.Exists()) {
|
||||||
text_group_ = Object::New<base::TextGroup>();
|
text_group_ = Object::New<base::TextGroup>();
|
||||||
@ -386,7 +382,7 @@ void TextWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
|
|||||||
// Draw the carat.
|
// Draw the carat.
|
||||||
if (IsHierarchySelected() || always_show_carat_) {
|
if (IsHierarchySelected() || always_show_carat_) {
|
||||||
bool show_cursor = true;
|
bool show_cursor = true;
|
||||||
if (ShouldUseStringEditDialog()) {
|
if (ShouldUseStringEditor_()) {
|
||||||
show_cursor = false;
|
show_cursor = false;
|
||||||
}
|
}
|
||||||
if (show_cursor
|
if (show_cursor
|
||||||
@ -520,70 +516,6 @@ auto TextWidget::GetHeight() -> float {
|
|||||||
return height_;
|
return height_;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto TextWidget::ShouldUseStringEditDialog() const -> bool {
|
|
||||||
if (g_core->HeadlessMode()) {
|
|
||||||
// Shouldn't really get here, but if we do, keep things simple.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (force_internal_editing_) {
|
|
||||||
// Obscure cases such as the text-widget *on* our built-in on-screen
|
|
||||||
// keyboard.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (g_ui_v1->always_use_internal_on_screen_keyboard()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On most platforms we always want to use an edit dialog. On desktop,
|
|
||||||
// however, we use inline editing *if* the current UI input-device is the
|
|
||||||
// mouse or keyboard. For anything else, like game controllers, we bust
|
|
||||||
// out the dialog.
|
|
||||||
if (g_buildconfig.ostype_macos() || g_buildconfig.ostype_windows()
|
|
||||||
|| g_buildconfig.ostype_linux()) {
|
|
||||||
auto* ui_input_device = g_base->ui->GetUIInputDevice();
|
|
||||||
return !(ui_input_device == nullptr
|
|
||||||
|| ui_input_device == g_base->input->keyboard_input());
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextWidget::InvokeStringEditDialog() {
|
|
||||||
bool use_internal_dialog{true};
|
|
||||||
|
|
||||||
// In VR we always use our own dialog.
|
|
||||||
if (g_core->IsVRMode()) {
|
|
||||||
use_internal_dialog = true;
|
|
||||||
} else {
|
|
||||||
// on Android, use the Android keyboard *unless* the user want to use
|
|
||||||
// our built-in one.
|
|
||||||
if (!g_ui_v1->always_use_internal_on_screen_keyboard()) {
|
|
||||||
if (g_buildconfig.ostype_android()) {
|
|
||||||
use_internal_dialog = false;
|
|
||||||
// Store ourself as the current text-widget and kick off an edit.
|
|
||||||
android_string_edit_widget_ = this;
|
|
||||||
g_core->main_event_loop()->PushCall(
|
|
||||||
[name = description_, value = text_raw_, max_chars = max_chars_] {
|
|
||||||
static millisecs_t last_edit_time = 0;
|
|
||||||
millisecs_t t = g_core->GetAppTimeMillisecs();
|
|
||||||
|
|
||||||
// Ignore if too close together (in case second request comes
|
|
||||||
// in before first takes effect).
|
|
||||||
if (t - last_edit_time < 1000) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
last_edit_time = t;
|
|
||||||
assert(g_core->InMainThread());
|
|
||||||
g_core->platform->EditText(name, value, max_chars);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (use_internal_dialog) {
|
|
||||||
g_ui_v1->python->LaunchStringEdit(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextWidget::Activate() {
|
void TextWidget::Activate() {
|
||||||
last_activate_time_millisecs_ =
|
last_activate_time_millisecs_ =
|
||||||
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
|
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
|
||||||
@ -594,11 +526,82 @@ void TextWidget::Activate() {
|
|||||||
call->ScheduleWeak();
|
call->ScheduleWeak();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editable_ && ShouldUseStringEditDialog()) {
|
// Bring up an editor if applicable.
|
||||||
InvokeStringEditDialog();
|
if (editable_ && ShouldUseStringEditor_()) {
|
||||||
|
InvokeStringEditor_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto TextWidget::ShouldUseStringEditor_() const -> bool {
|
||||||
|
if (g_core->HeadlessMode()) {
|
||||||
|
BA_LOG_ONCE(
|
||||||
|
LogLevel::kError,
|
||||||
|
"ShouldUseStringEditDialog_ called in headless; should not happen.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obscure cases such as the text-widget *on* our built-in on-screen
|
||||||
|
// editor (obviously it should itself not pop up an editor).
|
||||||
|
if (force_internal_editing_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user wants to use our widget-based keyboard, always say yes
|
||||||
|
// here.
|
||||||
|
if (g_ui_v1->always_use_internal_on_screen_keyboard()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can take direct key events, no string-editor needed.
|
||||||
|
return !g_base->ui->UIHasDirectKeyboardInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextWidget::InvokeStringEditor_() {
|
||||||
|
assert(g_base->InLogicThread());
|
||||||
|
|
||||||
|
// If there's already a valid edit attached to us, do nothing.
|
||||||
|
if (string_edit_adapter_.Exists()
|
||||||
|
&& !g_base->python->CanPyStringEditAdapterBeReplaced(
|
||||||
|
string_edit_adapter_.Get())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a Python StringEditAdapter for this widget, passing ourself as
|
||||||
|
// the sole arg.
|
||||||
|
auto args = PythonRef::Stolen(Py_BuildValue("(O)", BorrowPyRef()));
|
||||||
|
auto result = g_ui_v1->python->objs()
|
||||||
|
.Get(UIV1Python::ObjID::kTextWidgetStringEditAdapterClass)
|
||||||
|
.Call(args);
|
||||||
|
if (!result.Exists()) {
|
||||||
|
Log(LogLevel::kError, "Error invoking string edit dialog.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this new one is already marked replacable, it means it wasn't able
|
||||||
|
// to register as the active one, so we can ignore it.
|
||||||
|
if (g_base->python->CanPyStringEditAdapterBeReplaced(result.Get())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok looks like we're good; store the adapter and hand it over
|
||||||
|
// to whoever will be driving it.
|
||||||
|
string_edit_adapter_ = result;
|
||||||
|
|
||||||
|
// Use the platform string-editor if we have one unless the user
|
||||||
|
// explicitly wants us to use our own.
|
||||||
|
if (g_base->platform->HaveStringEditor()
|
||||||
|
&& !g_ui_v1->always_use_internal_on_screen_keyboard()) {
|
||||||
|
g_base->platform->InvokeStringEditor(string_edit_adapter_.Get());
|
||||||
|
} else {
|
||||||
|
g_ui_v1->python->InvokeStringEditor(string_edit_adapter_.Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextWidget::AdapterFinished() {
|
||||||
|
BA_PRECONDITION(g_base->InLogicThread());
|
||||||
|
string_edit_adapter_.Release();
|
||||||
|
}
|
||||||
|
|
||||||
auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
||||||
if (g_core->HeadlessMode()) {
|
if (g_core->HeadlessMode()) {
|
||||||
return false;
|
return false;
|
||||||
@ -619,17 +622,17 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we're doing inline editing, handle clipboard paste.
|
// If we're doing inline editing, handle clipboard paste.
|
||||||
if (editable() && !ShouldUseStringEditDialog()
|
if (editable() && !ShouldUseStringEditor_()
|
||||||
&& m.type == base::WidgetMessage::Type::kPaste) {
|
&& m.type == base::WidgetMessage::Type::kPaste) {
|
||||||
if (g_core->platform->ClipboardIsSupported()) {
|
if (g_core->platform->ClipboardIsSupported()) {
|
||||||
if (g_core->platform->ClipboardHasText()) {
|
if (g_core->platform->ClipboardHasText()) {
|
||||||
// Just enter it char by char as if we had typed it...
|
// Just enter it char by char as if we had typed it...
|
||||||
AddCharsToText(g_core->platform->ClipboardGetText());
|
AddCharsToText_(g_core->platform->ClipboardGetText());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we're doing inline editing, handle some key events.
|
// If we're doing inline editing, handle some key events.
|
||||||
if (m.has_keysym && !ShouldUseStringEditDialog()) {
|
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);
|
||||||
|
|
||||||
@ -795,12 +798,12 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
|||||||
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 we're using an edit dialog, any attempted text input just kicks us
|
||||||
// over to that.
|
// over to that.
|
||||||
if (editable() && ShouldUseStringEditDialog()) {
|
if (editable() && ShouldUseStringEditor_()) {
|
||||||
InvokeStringEditDialog();
|
InvokeStringEditor_();
|
||||||
} else {
|
} else {
|
||||||
// Otherwise apply the text directly.
|
// Otherwise apply the text directly.
|
||||||
if (editable() && m.sval != nullptr) {
|
if (editable() && m.sval != nullptr) {
|
||||||
AddCharsToText(*m.sval);
|
AddCharsToText_(*m.sval);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -810,8 +813,8 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
|||||||
if (!IsSelectable()) {
|
if (!IsSelectable()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
float x{ScaleAdjustedX(m.fval1)};
|
float x{ScaleAdjustedX_(m.fval1)};
|
||||||
float y{ScaleAdjustedY(m.fval2)};
|
float y{ScaleAdjustedY_(m.fval2)};
|
||||||
bool claimed = (m.fval3 > 0.0f);
|
bool claimed = (m.fval3 > 0.0f);
|
||||||
if (claimed) {
|
if (claimed) {
|
||||||
mouse_over_ = clear_mouse_over_ = false;
|
mouse_over_ = clear_mouse_over_ = false;
|
||||||
@ -829,8 +832,8 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
|||||||
if (!IsSelectable()) {
|
if (!IsSelectable()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
float x{ScaleAdjustedX(m.fval1)};
|
float x{ScaleAdjustedX_(m.fval1)};
|
||||||
float y{ScaleAdjustedY(m.fval2)};
|
float y{ScaleAdjustedY_(m.fval2)};
|
||||||
|
|
||||||
auto click_count = static_cast<int>(m.fval3);
|
auto click_count = static_cast<int>(m.fval3);
|
||||||
|
|
||||||
@ -874,8 +877,8 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case base::WidgetMessage::Type::kMouseUp: {
|
case base::WidgetMessage::Type::kMouseUp: {
|
||||||
float x{ScaleAdjustedX(m.fval1)};
|
float x{ScaleAdjustedX_(m.fval1)};
|
||||||
float y{ScaleAdjustedY(m.fval2)};
|
float y{ScaleAdjustedY_(m.fval2)};
|
||||||
bool claimed = (m.fval3 > 0.0f);
|
bool claimed = (m.fval3 > 0.0f);
|
||||||
|
|
||||||
if (clear_pressed_ && !claimed && editable()
|
if (clear_pressed_ && !claimed && editable()
|
||||||
@ -903,12 +906,12 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
|||||||
&& (y < (height_ + top_overlap)) && !claimed) {
|
&& (y < (height_ + top_overlap)) && !claimed) {
|
||||||
Activate();
|
Activate();
|
||||||
pressed_activate_ = false;
|
pressed_activate_ = false;
|
||||||
} else if (editable_ && ShouldUseStringEditDialog()
|
} else if (editable_ && ShouldUseStringEditor_()
|
||||||
&& (x >= (-left_overlap)) && (x < (width_ + right_overlap))
|
&& (x >= (-left_overlap)) && (x < (width_ + right_overlap))
|
||||||
&& (y >= (-bottom_overlap)) && (y < (height_ + top_overlap))
|
&& (y >= (-bottom_overlap)) && (y < (height_ + top_overlap))
|
||||||
&& !claimed) {
|
&& !claimed) {
|
||||||
// With dialog-editing, a click/tap brings up our editor.
|
// With dialog-editing, a click/tap brings up our editor.
|
||||||
InvokeStringEditDialog();
|
InvokeStringEditor_();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pressed buttons always claim mouse-ups presented to them.
|
// Pressed buttons always claim mouse-ups presented to them.
|
||||||
@ -922,19 +925,19 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto TextWidget::ScaleAdjustedX(float x) -> float {
|
auto TextWidget::ScaleAdjustedX_(float x) -> float {
|
||||||
// Account for our center_scale_ value.
|
// Account for our center_scale_ value.
|
||||||
float offsx = x - width_ * 0.5f;
|
float offsx = x - width_ * 0.5f;
|
||||||
return width_ * 0.5f + offsx / center_scale_;
|
return width_ * 0.5f + offsx / center_scale_;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto TextWidget::ScaleAdjustedY(float y) -> float {
|
auto TextWidget::ScaleAdjustedY_(float y) -> float {
|
||||||
// Account for our center_scale_ value.
|
// Account for our center_scale_ value.
|
||||||
float offsy = y - height_ * 0.5f;
|
float offsy = y - height_ * 0.5f;
|
||||||
return height_ * 0.5f + offsy / center_scale_;
|
return height_ * 0.5f + offsy / center_scale_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextWidget::AddCharsToText(const std::string& addchars) {
|
void TextWidget::AddCharsToText_(const std::string& addchars) {
|
||||||
assert(editable());
|
assert(editable());
|
||||||
std::vector<uint32_t> unichars = Utils::UnicodeFromUTF8(text_raw_, "jcjwf8f");
|
std::vector<uint32_t> unichars = Utils::UnicodeFromUTF8(text_raw_, "jcjwf8f");
|
||||||
int len = static_cast<int>(unichars.size());
|
int len = static_cast<int>(unichars.size());
|
||||||
@ -954,7 +957,7 @@ void TextWidget::AddCharsToText(const std::string& addchars) {
|
|||||||
text_translation_dirty_ = true;
|
text_translation_dirty_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextWidget::UpdateTranslation() {
|
void TextWidget::UpdateTranslation_() {
|
||||||
// Apply subs/resources to get our actual text if need be.
|
// Apply subs/resources to get our actual text if need be.
|
||||||
if (text_translation_dirty_) {
|
if (text_translation_dirty_) {
|
||||||
// We don't run translations on user-editable text.
|
// We don't run translations on user-editable text.
|
||||||
@ -970,7 +973,7 @@ void TextWidget::UpdateTranslation() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto TextWidget::GetTextWidth() -> float {
|
auto TextWidget::GetTextWidth() -> float {
|
||||||
UpdateTranslation();
|
UpdateTranslation_();
|
||||||
|
|
||||||
// Should we cache this?
|
// Should we cache this?
|
||||||
return g_base->text_graphics->GetStringWidth(text_translated_, big_);
|
return g_base->text_graphics->GetStringWidth(text_translated_, big_);
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "ballistica/shared/python/python_ref.h"
|
||||||
#include "ballistica/ui_v1/widget/widget.h"
|
#include "ballistica/ui_v1/widget/widget.h"
|
||||||
|
|
||||||
namespace ballistica::ui_v1 {
|
namespace ballistica::ui_v1 {
|
||||||
@ -72,6 +73,7 @@ class TextWidget : public Widget {
|
|||||||
void set_res_scale(float res_scale);
|
void set_res_scale(float res_scale);
|
||||||
auto GetTextWidth() -> float;
|
auto GetTextWidth() -> float;
|
||||||
void OnLanguageChange() override;
|
void OnLanguageChange() override;
|
||||||
|
void AdapterFinished();
|
||||||
|
|
||||||
static TextWidget* GetAndroidStringEditWidget();
|
static TextWidget* GetAndroidStringEditWidget();
|
||||||
|
|
||||||
@ -86,14 +88,12 @@ class TextWidget : public Widget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto ScaleAdjustedX(float x) -> float;
|
auto ScaleAdjustedX_(float x) -> float;
|
||||||
auto ScaleAdjustedY(float y) -> float;
|
auto ScaleAdjustedY_(float y) -> float;
|
||||||
void AddCharsToText(const std::string& addchars);
|
void AddCharsToText_(const std::string& addchars);
|
||||||
auto ShouldUseStringEditDialog() const -> bool;
|
auto ShouldUseStringEditor_() const -> bool;
|
||||||
void InvokeStringEditDialog();
|
void InvokeStringEditor_();
|
||||||
void UpdateTranslation();
|
void UpdateTranslation_();
|
||||||
// static bool always_use_internal_keyboard_;
|
|
||||||
static Object::WeakRef<TextWidget> android_string_edit_widget_;
|
|
||||||
float res_scale_{1.0f};
|
float res_scale_{1.0f};
|
||||||
bool enabled_{true};
|
bool enabled_{true};
|
||||||
millisecs_t birth_time_millisecs_{};
|
millisecs_t birth_time_millisecs_{};
|
||||||
@ -150,9 +150,10 @@ class TextWidget : public Widget {
|
|||||||
millisecs_t last_activate_time_millisecs_{};
|
millisecs_t last_activate_time_millisecs_{};
|
||||||
millisecs_t last_carat_change_time_millisecs_{};
|
millisecs_t last_carat_change_time_millisecs_{};
|
||||||
|
|
||||||
// we keep these at the bottom so they're torn down first..
|
// We keep these at the bottom so they're torn down first.
|
||||||
Object::Ref<base::PythonContextCall> on_return_press_call_;
|
Object::Ref<base::PythonContextCall> on_return_press_call_;
|
||||||
Object::Ref<base::PythonContextCall> on_activate_call_;
|
Object::Ref<base::PythonContextCall> on_activate_call_;
|
||||||
|
PythonRef string_edit_adapter_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ballistica::ui_v1
|
} // namespace ballistica::ui_v1
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from babase import (
|
|||||||
_env,
|
_env,
|
||||||
_error,
|
_error,
|
||||||
_general,
|
_general,
|
||||||
|
_ui,
|
||||||
)
|
)
|
||||||
|
|
||||||
# The C++ layer looks for this variable:
|
# The C++ layer looks for this variable:
|
||||||
@ -23,7 +24,6 @@ values = [
|
|||||||
_hooks.set_config_fullscreen_on, # kSetConfigFullscreenOnCall
|
_hooks.set_config_fullscreen_on, # kSetConfigFullscreenOnCall
|
||||||
_hooks.set_config_fullscreen_off, # kSetConfigFullscreenOffCall
|
_hooks.set_config_fullscreen_off, # kSetConfigFullscreenOffCall
|
||||||
_hooks.not_signed_in_screen_message, # kNotSignedInScreenMessageCall
|
_hooks.not_signed_in_screen_message, # kNotSignedInScreenMessageCall
|
||||||
_hooks.connecting_to_party_message, # kConnectingToPartyMessageCall
|
|
||||||
_hooks.rejecting_invite_already_in_party_message, # kRejectingInviteAlreadyInPartyMessageCall
|
_hooks.rejecting_invite_already_in_party_message, # kRejectingInviteAlreadyInPartyMessageCall
|
||||||
_hooks.connection_failed_message, # kConnectionFailedMessageCall
|
_hooks.connection_failed_message, # kConnectionFailedMessageCall
|
||||||
_hooks.temporarily_unavailable_message, # kTemporarilyUnavailableMessageCall
|
_hooks.temporarily_unavailable_message, # kTemporarilyUnavailableMessageCall
|
||||||
@ -52,6 +52,7 @@ values = [
|
|||||||
_hooks.remove_in_game_ads_message, # kRemoveInGameAdsMessageCall
|
_hooks.remove_in_game_ads_message, # kRemoveInGameAdsMessageCall
|
||||||
_hooks.do_quit, # kQuitCall
|
_hooks.do_quit, # kQuitCall
|
||||||
_hooks.show_post_purchase_message, # kShowPostPurchaseMessageCall
|
_hooks.show_post_purchase_message, # kShowPostPurchaseMessageCall
|
||||||
|
_hooks.string_edit_adapter_can_be_replaced, # kStringEditAdapterCanBeReplacedCall
|
||||||
_language.Lstr, # kLStrClass
|
_language.Lstr, # kLStrClass
|
||||||
_general.Call, # kCallClass
|
_general.Call, # kCallClass
|
||||||
_apputils.garbage_collect_session_end, # kGarbageCollectSessionEndCall
|
_apputils.garbage_collect_session_end, # kGarbageCollectSessionEndCall
|
||||||
@ -80,4 +81,5 @@ values = [
|
|||||||
_hooks.open_url_with_webbrowser_module, # kOpenURLWithWebBrowserModuleCall
|
_hooks.open_url_with_webbrowser_module, # kOpenURLWithWebBrowserModuleCall
|
||||||
_env.on_native_module_import, # kEnvOnNativeModuleImportCall
|
_env.on_native_module_import, # kEnvOnNativeModuleImportCall
|
||||||
_env.on_main_thread_start_app, # kOnMainThreadStartAppCall
|
_env.on_main_thread_start_app, # kOnMainThreadStartAppCall
|
||||||
|
_ui.DevConsoleStringEditAdapter, # kDevConsoleStringEditAdapterClass
|
||||||
]
|
]
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import bauiv1.onscreenkeyboard
|
import bauiv1.onscreenkeyboard
|
||||||
from bauiv1 import _hooks
|
from bauiv1 import _hooks
|
||||||
|
from bauiv1._uitypes import TextWidgetStringEditAdapter
|
||||||
|
|
||||||
# The C++ layer looks for this variable:
|
# The C++ layer looks for this variable:
|
||||||
values = [
|
values = [
|
||||||
@ -21,4 +22,5 @@ values = [
|
|||||||
_hooks.quit_window, # kQuitWindowCall
|
_hooks.quit_window, # kQuitWindowCall
|
||||||
_hooks.device_menu_press, # kDeviceMenuPressCall
|
_hooks.device_menu_press, # kDeviceMenuPressCall
|
||||||
_hooks.show_url_window, # kShowURLWindowCall
|
_hooks.show_url_window, # kShowURLWindowCall
|
||||||
|
TextWidgetStringEditAdapter, # kTextWidgetStringEditAdapterClass
|
||||||
]
|
]
|
||||||
|
|||||||
@ -39,6 +39,12 @@ class _EmptyObj:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# A dead weak-ref should be immutable, right? So we can create exactly
|
||||||
|
# one and return it for all cases that need an empty weak-ref.
|
||||||
|
_g_empty_weak_ref = weakref.ref(_EmptyObj())
|
||||||
|
assert _g_empty_weak_ref() is None
|
||||||
|
|
||||||
|
|
||||||
# TODO: kill this and just use efro.call.tpartial
|
# TODO: kill this and just use efro.call.tpartial
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
Call = Call
|
Call = Call
|
||||||
@ -148,8 +154,11 @@ def empty_weakref(objtype: type[T]) -> weakref.ref[T]:
|
|||||||
# At runtime, all weakrefs are the same; our type arg is just
|
# At runtime, all weakrefs are the same; our type arg is just
|
||||||
# for the static type checker.
|
# for the static type checker.
|
||||||
del objtype # Unused.
|
del objtype # Unused.
|
||||||
|
|
||||||
# Just create an object and let it die. Is there a cleaner way to do this?
|
# Just create an object and let it die. Is there a cleaner way to do this?
|
||||||
return weakref.ref(_EmptyObj()) # type: ignore
|
# return weakref.ref(_EmptyObj()) # type: ignore
|
||||||
|
|
||||||
|
return _g_empty_weak_ref # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def data_size_str(bytecount: int) -> str:
|
def data_size_str(bytecount: int) -> str:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user