mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-19 21:37:57 +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/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
|
||||
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "44dec65bbb43c2424334cce255b55836",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "55489d3d62fd081b83c4df871e40ad27",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "21530b0be2f54d1c457a8c2ca5bfb480",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "a3d058738fc7891bc1d0139654b5fc26",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "8d4b813a4955b6574b4e0e6b413ad7cf",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "7c618d9dac85afc6a7be8c7927693e81",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "2b2ddfe86feb7e701d472264c5d7ea83",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "63f59ed473e1f954786284d6988c1a2b",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "b703788a1e3aef102349db7968b2dd99",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "dd4ea149455ddf77db357bbcf92622ac",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "d0651d0ed865c44f45a0e86a93fdf46b",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "65f43cfa50bf3fb198ccdacb5ac7dfe4",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "a2b3388c4deec4e980a0268b0757ac3a",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "a60d7430b9ba3cf71bc9ffe5944026fc",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "d08625da75aba13159ea4e649b87eff9",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "c265fede38b25b8257ae1e6acb1d8036",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "74f47c43f480f60732b43a0f9b80f76d",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "c9b4d66d5ce318e5cecb7412771fbfba",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "fe6ad3d4cdaadd56326f5e616588b3fb",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "772769bf8f5c49031782f68eaa49c0d3",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "a075beb846859a3bee6b4fc1c4d9369b",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "208a67fb7e7b942988e8520f9570138e",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "a075beb846859a3bee6b4fc1c4d9369b",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "208a67fb7e7b942988e8520f9570138e",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "2a98d808b017ddac714d2f266d443394",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "24c7f0248a8f59e5349db9c040e6bd4f",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "2a98d808b017ddac714d2f266d443394",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "24c7f0248a8f59e5349db9c040e6bd4f",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "e8f8be3a0ba00a2ecb8956c2459107ec",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "741e277f99d48437a5a1b9dacef107ee",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "e8f8be3a0ba00a2ecb8956c2459107ec",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "741e277f99d48437a5a1b9dacef107ee",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "3db2e9a04f23052f3a14390a0f7ba00e",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "15cf0e78e70d952c14c4b5e9ad6ef749",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "a0b27fbfca2dd7404a20997fbfa10a7f",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "15cf0e78e70d952c14c4b5e9ad6ef749",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "25d06d95141284fff10db4a55ed481eb",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "708878d75d73b8510b354b2f353da621",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "b8b36fd481e253e83b3cf90734a7d627",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "17cb96c46a7e1763fdfbc6f48199f547",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ae3b27deef1240beb1b32a17a46b7d90",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "0e2c5cec39ac27d42cb5cd635da996bd",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "6b278874c0c0526494bd94aad4a817ae",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "55d3224895c30042caca26e6d77e406b",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "e8a40affea4d63bc4ca150adfeb973db",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "fb00781c9574d0ca777eb5ced699dc18",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "d53b0c174af88e8a483896fdfd411579",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "bb6fd9371937ebe3f25d253d3046269e",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "f71e0d97424b7bf4b1ad57876ca4f01f",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "6c2ba3735f33f1fd3804bd89a71c75bc",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "cff1c33bb6725fb06020a256ac8e6b16",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "d9d6dbe7c5396a3adb8468fbecbf2b8c",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "2dcddcc4a939a290fd8974d575fa3761",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "cd7f47495207a86d2f72ea0749b69db8",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "e223f88277dafee40cd0e631ee4fa02d",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "1b8a5eac67370bed2a29f81525fbc6eb",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "91debaa22c5ce91586cfdad646a72afe",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "84c8c568e4daeef372806cdc96f4ed48",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "ebf0e8e22d53790b85ceb9f5c507db35",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "ba136eb04e177e264d459443bcde879f",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "fdc1b63de28ebd369b78b2bbeb3c6290",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "126e2eec53a5f7a00d95039ad506b9b4",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "7db3922abdbf3045ef814f235d514cdb",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "b5a1ff440599ccf09bd543d64a1d7aba",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "1e786451b0abe1451f17b908c2d8abb3",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "80db3e75458db18efe1657f8ce686996",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "1e786451b0abe1451f17b908c2d8abb3",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "80db3e75458db18efe1657f8ce686996",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "a29bc1b96b2422dce9154ef8a404a8e6",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "c36dc72d78f9df240ae9f640dee470c2",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "a29bc1b96b2422dce9154ef8a404a8e6",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "c36dc72d78f9df240ae9f640dee470c2",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "8f3b213feb6207ab470a7318de8e5cc9",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "1b24bf754c66d424be3f3809b2523c61",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "8f3b213feb6207ab470a7318de8e5cc9",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "1b24bf754c66d424be3f3809b2523c61",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "93ff05ad705918cf0c47e5946021a2e5",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "61ee5217ce8cfc2d471ffc5d1d8d4ad8",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "d57603910e6c8d153b05a5515ec99ab2",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "61ee5217ce8cfc2d471ffc5d1d8d4ad8",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "2312b4a091f482d64c22f71ca971efe8",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "0d5cb925a29f8185d615d6b954674c6b",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "eb53d93675b81c631d43f2283b59a9ae",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "f15e465066504b1bfadc6fef97bfa6d4",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "bad42242aa1c98b5e6c7318da8b0fa01",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "55366eb80cbd1b22c2636d29b780b6f6",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "c6870a6302ae49bac39f53b390797d8f",
|
||||
"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/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/classic/mgen/pyembed/binding_classic.inc": "3ceb412513963f0818ab39c58bf292e3",
|
||||
"src/ballistica/core/mgen/pyembed/binding_core.inc": "9d0a3c9636138e35284923e0c8311c69",
|
||||
@ -4118,5 +4118,5 @@
|
||||
"src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5",
|
||||
"src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "d80f970053099b3044204bfe29ddefce",
|
||||
"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>stgdict</w>
|
||||
<w>stickman</w>
|
||||
<w>stname</w>
|
||||
<w>storable</w>
|
||||
<w>storagename</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
|
||||
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
|
||||
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,
|
||||
etc.)
|
||||
- The in-app Python console text is now sized up on phone and tablet devices,
|
||||
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
|
||||
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`.
|
||||
@ -24,10 +32,10 @@
|
||||
significantly faster & more efficient.
|
||||
- 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.).
|
||||
- Cleaned up the `babase.quit()` mechanism a bit. The default for the 'soft' arg
|
||||
is now true, so a raw `babase.quit()` should now be a good citizen on mobile
|
||||
- Cleaned up the `babase.quit()` mechanism. The default for the 'soft' arg is
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
1
ballisticakit-cmake/.idea/dictionaries/ericf.xml
generated
1
ballisticakit-cmake/.idea/dictionaries/ericf.xml
generated
@ -1680,6 +1680,7 @@
|
||||
<w>stepnum</w>
|
||||
<w>stepsize</w>
|
||||
<w>stgdict</w>
|
||||
<w>stname</w>
|
||||
<w>storagenames</w>
|
||||
<w>storecmd</w>
|
||||
<w>stot</w>
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
"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__/_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__/modutils.cpython-311.opt-1.pyc",
|
||||
"ba_data/python/babase/_accountv2.py",
|
||||
@ -60,6 +61,7 @@
|
||||
"ba_data/python/babase/_plugin.py",
|
||||
"ba_data/python/babase/_stringedit.py",
|
||||
"ba_data/python/babase/_text.py",
|
||||
"ba_data/python/babase/_ui.py",
|
||||
"ba_data/python/babase/_workspace.py",
|
||||
"ba_data/python/babase/modutils.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/_stringedit.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/modutils.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__/_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__/_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__/modutils.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._net import get_ip_address_type, DEFAULT_REQUEST_TIMEOUT_SECONDS
|
||||
from babase._plugin import PluginSpec, Plugin, PluginSubsystem
|
||||
from babase._stringedit import StringEditAdapter, StringEditSubsystem
|
||||
from babase._text import timestring
|
||||
|
||||
_babase.app = app = App()
|
||||
@ -294,6 +295,8 @@ __all__ = [
|
||||
'SimpleSound',
|
||||
'SpecialChar',
|
||||
'storagename',
|
||||
'StringEditAdapter',
|
||||
'StringEditSubsystem',
|
||||
'TeamNotFoundError',
|
||||
'timestring',
|
||||
'UIScale',
|
||||
|
||||
@ -20,7 +20,7 @@ from typing import TYPE_CHECKING
|
||||
import _babase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
from babase._stringedit import StringEditAdapter
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
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:
|
||||
from babase._language import Lstr
|
||||
|
||||
@ -372,3 +364,11 @@ def show_client_too_old_error() -> None:
|
||||
),
|
||||
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()')
|
||||
|
||||
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:
|
||||
"""(internal)"""
|
||||
|
||||
@ -325,6 +336,9 @@ class Plugin:
|
||||
def on_app_shutdown(self) -> None:
|
||||
"""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:
|
||||
"""Called to ask if we have settings UI we can show."""
|
||||
return False
|
||||
|
||||
@ -8,8 +8,13 @@ own ui toolkits.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import logging
|
||||
import weakref
|
||||
from typing import TYPE_CHECKING, final
|
||||
|
||||
from efro.util import empty_weakref
|
||||
|
||||
import _babase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -20,29 +25,103 @@ class StringEditSubsystem:
|
||||
"""Full string-edit state for the app."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
# print('HELLO FROM STRING EDIT')
|
||||
self.active_adapter = empty_weakref(StringEditAdapter)
|
||||
|
||||
|
||||
class StringEdit:
|
||||
class StringEditAdapter:
|
||||
"""Represents a string editing operation on some object.
|
||||
|
||||
Editable objects such as text widgets or in-app-consoles can
|
||||
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:
|
||||
pass
|
||||
def __init__(
|
||||
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
|
||||
def apply(self, new_text: str) -> None:
|
||||
"""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
|
||||
this StringEdit is no longer the globally active one).
|
||||
this StringEditAdapter is no longer the globally active one).
|
||||
"""
|
||||
if not _babase.in_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)
|
||||
|
||||
@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
|
||||
# using.
|
||||
TARGET_BALLISTICA_BUILD = 21329
|
||||
TARGET_BALLISTICA_BUILD = 21337
|
||||
TARGET_BALLISTICA_VERSION = '1.7.28'
|
||||
|
||||
|
||||
|
||||
@ -241,3 +241,35 @@ def ui_upkeep() -> None:
|
||||
else:
|
||||
remainingchecks.append(check)
|
||||
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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from babase import StringEditAdapter
|
||||
|
||||
import bauiv1 as bui
|
||||
|
||||
|
||||
class OnScreenKeyboardWindow(Window):
|
||||
"""Simple built-in on-screen keyboard."""
|
||||
|
||||
def __init__(self, textwidget: bui.Widget, label: str, max_chars: int):
|
||||
self._target_text = textwidget
|
||||
def __init__(self, adapter: StringEditAdapter):
|
||||
self._adapter = adapter
|
||||
self._width = 700
|
||||
self._height = 400
|
||||
assert babase.app.classic is not None
|
||||
uiscale = babase.app.ui_v1.uiscale
|
||||
top_extra = 20 if uiscale is babase.UIScale.SMALL else 0
|
||||
|
||||
super().__init__(
|
||||
root_widget=_bauiv1.containerwidget(
|
||||
parent=_bauiv1.get_special_widget('overlay_stack'),
|
||||
size=(self._width, self._height + top_extra),
|
||||
transition='in_scale',
|
||||
scale_origin_stack_offset=(
|
||||
self._target_text.get_screen_space_center()
|
||||
),
|
||||
scale_origin_stack_offset=adapter.screen_space_center,
|
||||
scale=(
|
||||
2.0
|
||||
if uiscale is babase.UIScale.SMALL
|
||||
@ -69,7 +70,7 @@ class OnScreenKeyboardWindow(Window):
|
||||
position=(self._width * 0.5, self._height - 41),
|
||||
size=(0, 0),
|
||||
scale=0.95,
|
||||
text=label,
|
||||
text=adapter.description,
|
||||
maxwidth=self._width - 140,
|
||||
color=babase.app.ui_v1.title_color,
|
||||
h_align='center',
|
||||
@ -79,8 +80,8 @@ class OnScreenKeyboardWindow(Window):
|
||||
self._text_field = _bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(70, self._height - 116),
|
||||
max_chars=max_chars,
|
||||
text=cast(str, _bauiv1.textwidget(query=self._target_text)),
|
||||
max_chars=adapter.max_length,
|
||||
text=adapter.initial_text,
|
||||
on_return_press_call=self._done,
|
||||
autoselect=True,
|
||||
size=(self._width - 140, 55),
|
||||
@ -436,13 +437,12 @@ class OnScreenKeyboardWindow(Window):
|
||||
self._refresh()
|
||||
|
||||
def _cancel(self) -> None:
|
||||
self._adapter.cancel()
|
||||
_bauiv1.getsound('swish').play()
|
||||
_bauiv1.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||
|
||||
def _done(self) -> None:
|
||||
_bauiv1.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||
if self._target_text:
|
||||
_bauiv1.textwidget(
|
||||
edit=self._target_text,
|
||||
text=cast(str, _bauiv1.textwidget(query=self._text_field)),
|
||||
)
|
||||
self._adapter.apply(
|
||||
cast(str, _bauiv1.textwidget(query=self._text_field))
|
||||
)
|
||||
|
||||
@ -152,18 +152,8 @@ auto BaseFeatureSet::IsBaseCompletelyImported() -> bool {
|
||||
|
||||
void BaseFeatureSet::OnAssetsAvailable() {
|
||||
assert(InLogicThread());
|
||||
assert(console_ == nullptr);
|
||||
|
||||
// Spin up the in-app console.
|
||||
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();
|
||||
}
|
||||
}
|
||||
ui->OnAssetsAvailable();
|
||||
}
|
||||
|
||||
void BaseFeatureSet::StartApp() {
|
||||
@ -576,21 +566,8 @@ void BaseFeatureSet::DoV1CloudLog(const std::string& msg) {
|
||||
plus()->DirectSendV1CloudLogs(logprefix, logsuffix, false, nullptr);
|
||||
}
|
||||
|
||||
void BaseFeatureSet::PushConsolePrintCall(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 = 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;
|
||||
void BaseFeatureSet::PushDevConsolePrintCall(const std::string& msg) {
|
||||
ui->PushDevConsolePrintCall(msg);
|
||||
}
|
||||
|
||||
PyObject* BaseFeatureSet::GetPyExceptionType(PyExcType exctype) {
|
||||
|
||||
@ -701,7 +701,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
||||
-> PyObject* override;
|
||||
auto FeatureSetFromData(PyObject* obj) -> FeatureSetNativeComponent* 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 PrintPythonStackTrace() -> bool override;
|
||||
auto GetPyLString(PyObject* obj) -> std::string override;
|
||||
@ -754,7 +754,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
||||
Utils* const utils;
|
||||
|
||||
// Variable subsystems.
|
||||
auto* console() const { return console_; }
|
||||
auto* app_mode() const { return app_mode_; }
|
||||
auto* stress_test() const { return stress_test_; }
|
||||
void set_app_mode(AppMode* mode);
|
||||
@ -779,13 +778,11 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
||||
void PrintContextUnavailable_();
|
||||
|
||||
AppMode* app_mode_;
|
||||
DevConsole* console_{};
|
||||
PlusSoftInterface* plus_soft_{};
|
||||
ClassicSoftInterface* classic_soft_{};
|
||||
UIV1SoftInterface* ui_v1_soft_{};
|
||||
StressTest* stress_test_;
|
||||
|
||||
std::string console_startup_messages_;
|
||||
std::mutex shutdown_suppress_lock_;
|
||||
bool shutdown_suppress_disallowed_{};
|
||||
int shutdown_suppress_count_{};
|
||||
|
||||
@ -813,8 +813,8 @@ void Input::PushTextInputEvent(const std::string& text) {
|
||||
if (IsInputLocked()) {
|
||||
return;
|
||||
}
|
||||
if (g_base && g_base->console() != nullptr
|
||||
&& g_base->console()->HandleTextEditing(text)) {
|
||||
if (g_base && g_base->ui->dev_console() != nullptr
|
||||
&& g_base->ui->dev_console()->HandleTextEditing(text)) {
|
||||
return;
|
||||
}
|
||||
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.
|
||||
if (g_base && g_base->console() != nullptr
|
||||
&& g_base->console()->HandleKeyPress(keysym)) {
|
||||
return;
|
||||
if (auto* console = g_base->ui->dev_console()) {
|
||||
if (console->HandleKeyPress(keysym)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
if (g_base->console() != nullptr) {
|
||||
g_base->console()->HandleKeyRelease(keysym);
|
||||
if (g_base->ui->dev_console() != nullptr) {
|
||||
g_base->ui->dev_console()->HandleKeyRelease(keysym);
|
||||
}
|
||||
|
||||
if (keyboard_input_) {
|
||||
|
||||
@ -164,7 +164,7 @@ void Logic::OnInitialAppModeSet() {
|
||||
// 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
|
||||
// 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();
|
||||
}
|
||||
if (g_base->stdio_console) {
|
||||
|
||||
@ -329,4 +329,50 @@ auto BasePlatform::CanBackQuit() -> bool { return false; }
|
||||
void BasePlatform::DoBackQuit() {}
|
||||
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
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#define BALLISTICA_BASE_PLATFORM_BASE_PLATFORM_H_
|
||||
|
||||
#include "ballistica/base/base.h"
|
||||
#include "ballistica/shared/python/python_ref.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
@ -104,13 +105,36 @@ class BasePlatform {
|
||||
bool active);
|
||||
#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.
|
||||
void OpenURL(const std::string& url);
|
||||
|
||||
/// Get the most up-to-date cursor position.
|
||||
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:
|
||||
/// 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.
|
||||
virtual void DoOpenURL(const std::string& url);
|
||||
|
||||
@ -121,11 +145,12 @@ class BasePlatform {
|
||||
virtual ~BasePlatform();
|
||||
|
||||
private:
|
||||
/// Called after our singleton has been instantiated.
|
||||
/// Any construction functionality requiring virtual functions resolving to
|
||||
/// their final class versions can go here.
|
||||
/// Called after our singleton has been instantiated. Any construction
|
||||
/// functionality requiring virtual functions resolving to their final
|
||||
/// class versions can go here.
|
||||
virtual void PostInit();
|
||||
|
||||
PythonRef string_edit_adapter_{};
|
||||
bool ran_base_post_init_{};
|
||||
std::string public_device_uuid_;
|
||||
};
|
||||
|
||||
@ -580,4 +580,25 @@ auto BasePython::DoOnce() -> bool {
|
||||
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
|
||||
|
||||
@ -41,7 +41,6 @@ class BasePython {
|
||||
kSetConfigFullscreenOnCall,
|
||||
kSetConfigFullscreenOffCall,
|
||||
kNotSignedInScreenMessageCall,
|
||||
kConnectingToPartyMessageCall,
|
||||
kRejectingInviteAlreadyInPartyMessageCall,
|
||||
kConnectionFailedMessageCall,
|
||||
kTemporarilyUnavailableMessageCall,
|
||||
@ -104,6 +103,8 @@ class BasePython {
|
||||
kEnvOnNativeModuleImportCall,
|
||||
kOnMainThreadStartAppCall,
|
||||
kAppPushApplyAppConfigCall,
|
||||
kStringEditAdapterCanBeReplacedCall,
|
||||
kDevConsoleStringEditAdapterClass,
|
||||
kLast // Sentinel; must be at end.
|
||||
};
|
||||
|
||||
@ -149,6 +150,8 @@ class BasePython {
|
||||
static auto IsPyEnum_InputType(PyObject* obj) -> bool;
|
||||
static auto GetPyEnum_InputType(PyObject* obj) -> InputType;
|
||||
|
||||
auto CanPyStringEditAdapterBeReplaced(PyObject* o) -> bool;
|
||||
|
||||
auto IsPyLString(PyObject* o) -> bool;
|
||||
auto GetPyLString(PyObject* o) -> 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/support/python_context_call_runnable.h"
|
||||
#include "ballistica/base/support/stress_test.h"
|
||||
#include "ballistica/base/ui/dev_console.h"
|
||||
#include "ballistica/base/ui/ui.h"
|
||||
#include "ballistica/core/platform/core_platform.h"
|
||||
#include "ballistica/shared/foundation/event_loop.h"
|
||||
@ -1516,8 +1517,7 @@ static PyMethodDef PyShutdownSuppressEndDef = {
|
||||
"(internal)\n",
|
||||
};
|
||||
|
||||
// ------------------------ shutdown_suppress_count
|
||||
// ------------------------------
|
||||
// ----------------------- shutdown_suppress_count -----------------------------
|
||||
|
||||
static auto PyShutdownSuppressCount(PyObject* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
@ -1537,6 +1537,79 @@ static PyMethodDef PyShutdownSuppressCountDef = {
|
||||
"(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> {
|
||||
@ -1589,6 +1662,9 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
||||
PyShutdownSuppressBeginDef,
|
||||
PyShutdownSuppressEndDef,
|
||||
PyShutdownSuppressCountDef,
|
||||
PyGetDevConsoleInputTextDef,
|
||||
PySetDevConsoleInputTextDef,
|
||||
PyDevConsoleInputAdapterFinishDef,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
#include "ballistica/base/graphics/text/text_graphics.h"
|
||||
#include "ballistica/base/input/input.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/ui/ui.h"
|
||||
#include "ballistica/core/core.h"
|
||||
@ -27,11 +29,117 @@ const int kDevConsoleStringBreakUpSize = 1950;
|
||||
const int kDevConsoleActivateKey1 = SDLK_BACKQUOTE;
|
||||
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_ {
|
||||
public:
|
||||
Line_(std::string s_in, millisecs_t c)
|
||||
: creation_time(c), s(std::move(s_in)) {}
|
||||
millisecs_t creation_time;
|
||||
Line_(std::string s_in, double c) : creation_time(c), s(std::move(s_in)) {}
|
||||
double creation_time;
|
||||
std::string s;
|
||||
auto GetText() -> TextGroup& {
|
||||
if (!s_mesh_.Exists()) {
|
||||
@ -59,10 +167,123 @@ DevConsole::DevConsole() {
|
||||
title_text_group_.set_text(title);
|
||||
built_text_group_.set_text("Built: " __DATE__ " " __TIME__);
|
||||
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;
|
||||
|
||||
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 {
|
||||
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()) {
|
||||
// (reset input so characters don't continue walking and stuff)
|
||||
g_base->input->ResetHoldStates();
|
||||
if (auto console = g_base->console()) {
|
||||
if (auto console = g_base->ui->dev_console()) {
|
||||
console->ToggleState();
|
||||
}
|
||||
}
|
||||
@ -83,7 +304,7 @@ auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
|
||||
break;
|
||||
}
|
||||
|
||||
if (state_ == State::kInactive) {
|
||||
if (state_ == State_::kInactive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -129,23 +350,7 @@ auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
|
||||
}
|
||||
case SDLK_KP_ENTER:
|
||||
case SDLK_RETURN: {
|
||||
if (!input_enabled_) {
|
||||
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;
|
||||
Exec();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@ -168,9 +373,30 @@ auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
|
||||
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) {
|
||||
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'.
|
||||
ScopedSetContext ssc(g_base->app_mode()->GetForegroundContext());
|
||||
PythonCommand cmd(command, "<console>");
|
||||
@ -180,7 +406,7 @@ void DevConsole::SubmitCommand_(const std::string& command) {
|
||||
if (cmd.CanEval()) {
|
||||
auto obj = cmd.Eval(true, nullptr, nullptr);
|
||||
if (obj.Exists() && obj.Get() != Py_None) {
|
||||
g_base->console()->Print(obj.Repr() + "\n");
|
||||
Print(obj.Repr() + "\n");
|
||||
}
|
||||
} else {
|
||||
// Not eval-able; just exec it.
|
||||
@ -196,36 +422,36 @@ void DevConsole::EnableInput() {
|
||||
|
||||
void DevConsole::Dismiss() {
|
||||
assert(g_base->InLogicThread());
|
||||
if (state_ == State::kInactive) {
|
||||
if (state_ == State_::kInactive) {
|
||||
return;
|
||||
}
|
||||
|
||||
state_prev_ = state_;
|
||||
state_ = State::kInactive;
|
||||
transition_start_ = g_core->GetAppTimeMillisecs();
|
||||
state_ = State_::kInactive;
|
||||
transition_start_ = g_base->logic->display_time();
|
||||
}
|
||||
|
||||
void DevConsole::ToggleState() {
|
||||
assert(g_base->InLogicThread());
|
||||
state_prev_ = state_;
|
||||
switch (state_) {
|
||||
case State::kInactive:
|
||||
state_ = State::kMini;
|
||||
case State_::kInactive:
|
||||
state_ = State_::kMini;
|
||||
break;
|
||||
case State::kMini:
|
||||
state_ = State::kFull;
|
||||
case State_::kMini:
|
||||
state_ = State_::kFull;
|
||||
break;
|
||||
case State::kFull:
|
||||
state_ = State::kInactive;
|
||||
case State_::kFull:
|
||||
state_ = State_::kInactive;
|
||||
break;
|
||||
}
|
||||
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 {
|
||||
assert(g_base->InLogicThread());
|
||||
if (state_ == State::kInactive) {
|
||||
if (state_ == State_::kInactive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -246,7 +472,7 @@ auto DevConsole::HandleKeyRelease(const SDL_Keysym* keysym) -> bool {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@ -259,7 +485,7 @@ void DevConsole::Print(const std::string& s_in) {
|
||||
|
||||
// Spit out all completed lines and keep the last one as lastline.
|
||||
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) {
|
||||
lines_.pop_front();
|
||||
}
|
||||
@ -268,191 +494,209 @@ void DevConsole::Print(const std::string& s_in) {
|
||||
last_line_mesh_dirty_ = true;
|
||||
}
|
||||
|
||||
void DevConsole::Draw(RenderPass* pass) {
|
||||
millisecs_t transition_ticks = 100;
|
||||
float bs = PythonConsoleBaseScale();
|
||||
if ((transition_start_ != 0)
|
||||
&& (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();
|
||||
}
|
||||
auto DevConsole::Bottom_() const -> float {
|
||||
float bs = PythonConsoleBaseScale_();
|
||||
float vw = g_base->graphics->screen_virtual_width();
|
||||
float vh = g_base->graphics->screen_virtual_height();
|
||||
|
||||
// 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);
|
||||
float ratio =
|
||||
(g_base->logic->display_time() - transition_start_) / kTransitionSeconds;
|
||||
float bottom;
|
||||
float mini_size = 90.0f * bs;
|
||||
if (state_ == State_::kMini) {
|
||||
bottom = vh - mini_size;
|
||||
} 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();
|
||||
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.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));
|
||||
}
|
||||
c.Submit();
|
||||
}
|
||||
|
||||
// Draw output lines.
|
||||
{
|
||||
float draw_scale = 0.6f;
|
||||
float v_inc = 18.0f;
|
||||
SimpleComponent c(pass);
|
||||
c.SetTransparent(true);
|
||||
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);
|
||||
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>();
|
||||
}
|
||||
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;
|
||||
{
|
||||
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));
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
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.
|
||||
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;
|
||||
if (v > pass->virtual_height() + 14) {
|
||||
break;
|
||||
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));
|
||||
}
|
||||
}
|
||||
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()) {
|
||||
case UIScale::kLarge:
|
||||
return 1.5f;
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
|
||||
#include "ballistica/base/graphics/renderer/renderer.h"
|
||||
#include "ballistica/shared/foundation/object.h"
|
||||
#include "ballistica/shared/python/python_ref.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
@ -16,7 +17,7 @@ class DevConsole {
|
||||
public:
|
||||
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 HandleKeyPress(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.
|
||||
void EnableInput();
|
||||
|
||||
private:
|
||||
class Line_;
|
||||
auto input_string() const {
|
||||
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);
|
||||
enum class State { kInactive, kMini, kFull };
|
||||
void InvokeStringEditor_();
|
||||
ImageMesh bg_mesh_;
|
||||
ImageMesh stripe_mesh_;
|
||||
ImageMesh shadow_mesh_;
|
||||
@ -50,9 +65,9 @@ class DevConsole {
|
||||
TextGroup input_text_group_;
|
||||
millisecs_t last_input_text_change_time_{};
|
||||
bool input_text_dirty_{true};
|
||||
millisecs_t transition_start_{};
|
||||
State state_{State::kInactive};
|
||||
State state_prev_{State::kInactive};
|
||||
double transition_start_{};
|
||||
State_ state_{State_::kInactive};
|
||||
State_ state_prev_{State_::kInactive};
|
||||
bool input_enabled_{};
|
||||
std::string input_string_;
|
||||
std::list<std::string> input_history_;
|
||||
@ -61,6 +76,9 @@ class DevConsole {
|
||||
std::string last_line_;
|
||||
Object::Ref<TextGroup> last_line_mesh_group_;
|
||||
bool last_line_mesh_dirty_{true};
|
||||
bool python_console_pressed_{};
|
||||
PythonRef string_edit_adapter_;
|
||||
std::list<Button_> buttons_;
|
||||
};
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
@ -125,14 +125,23 @@ auto UI::HandleMouseDown(int button, float x, float y, bool double_click)
|
||||
-> bool {
|
||||
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 vy = g_base->graphics->screen_virtual_height();
|
||||
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()) {
|
||||
handled = g_base->ui_v1()->HandleLegacyRootUIMouseDown(x, y);
|
||||
}
|
||||
@ -151,10 +160,13 @@ void UI::HandleMouseUp(int button, float x, float y) {
|
||||
SendWidgetMessage(
|
||||
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 (auto* console = g_base->console()) {
|
||||
console->ToggleState();
|
||||
if (dev_console_) {
|
||||
dev_console_->ToggleState();
|
||||
}
|
||||
}
|
||||
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) {
|
||||
SendWidgetMessage(
|
||||
WidgetMessage(WidgetMessage::Type::kMouseMove, nullptr, x, y));
|
||||
@ -353,8 +385,8 @@ void UI::Draw(FrameDef* frame_def) {
|
||||
|
||||
void UI::DrawDev(FrameDef* frame_def) {
|
||||
// Draw dev console.
|
||||
if (g_base->console()) {
|
||||
g_base->console()->Draw(frame_def->overlay_pass());
|
||||
if (dev_console_) {
|
||||
dev_console_->Draw(frame_def->overlay_pass());
|
||||
}
|
||||
|
||||
// Draw dev console button.
|
||||
@ -440,10 +472,10 @@ void UI::ShowURL(const std::string& url) {
|
||||
}
|
||||
|
||||
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 (g_base->console() != nullptr && g_base->console()->IsActive()) {
|
||||
g_base->console()->Dismiss();
|
||||
if (dev_console_ != nullptr && dev_console_->IsActive()) {
|
||||
dev_console_->Dismiss();
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -32,6 +32,8 @@ class UI {
|
||||
void OnScreenSizeChange();
|
||||
void StepDisplayTime();
|
||||
|
||||
void OnAssetsAvailable();
|
||||
|
||||
void LanguageChanged();
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
void PushBackButtonCall(InputDevice* input_device);
|
||||
|
||||
@ -98,11 +107,18 @@ class UI {
|
||||
/// device (nullptr to specify none). Can be called from any thread.
|
||||
void PushMainMenuPressCall(InputDevice* device);
|
||||
|
||||
auto* dev_console() const { return dev_console_; }
|
||||
|
||||
void PushDevConsolePrintCall(const std::string& msg);
|
||||
|
||||
private:
|
||||
void MainMenuPress_(InputDevice* device);
|
||||
auto DevConsoleButtonSize_() const -> float;
|
||||
auto InDevConsoleButton_(float x, float y) const -> bool;
|
||||
void DrawDevConsoleButton_(FrameDef* frame_def);
|
||||
|
||||
DevConsole* dev_console_{};
|
||||
std::string dev_console_startup_messages_;
|
||||
Object::WeakRef<InputDevice> ui_input_device_;
|
||||
millisecs_t last_input_device_use_time_{};
|
||||
millisecs_t last_widget_input_reject_err_sound_time_{};
|
||||
|
||||
@ -667,11 +667,6 @@ auto CorePlatform::HaveLeaderboard(const std::string& game,
|
||||
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,
|
||||
const std::string& game,
|
||||
const std::string& game_version) {
|
||||
|
||||
@ -403,10 +403,6 @@ class CorePlatform {
|
||||
|
||||
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.
|
||||
virtual auto DemangleCXXSymbol(const std::string& s) -> std::string;
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ class BaseSoftInterface {
|
||||
virtual auto FeatureSetFromData(PyObject* obj)
|
||||
-> FeatureSetNativeComponent* = 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 PrintPythonStackTrace() -> bool = 0;
|
||||
virtual auto GetPyLString(PyObject* obj) -> std::string = 0;
|
||||
|
||||
@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't modify them here.
|
||||
const int kEngineBuildNumber = 21329;
|
||||
const int kEngineBuildNumber = 21337;
|
||||
const char* kEngineVersion = "1.7.28";
|
||||
const int kEngineApiVersion = 8;
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ void Logging::DisplayLog(const std::string& name, LogLevel level,
|
||||
const std::string& msg) {
|
||||
// Print to the in-app console (with a newline added).
|
||||
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).
|
||||
|
||||
@ -162,6 +162,15 @@ auto PythonRef::ValueAsString() const -> std::string {
|
||||
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 {
|
||||
if (!obj_) {
|
||||
throw Exception("PythonRef is unset.", PyExcType::kValue);
|
||||
|
||||
@ -171,6 +171,7 @@ class PythonRef {
|
||||
-> std::optional<std::list<std::string>>;
|
||||
|
||||
auto ValueAsInt() const -> int64_t;
|
||||
auto ValueAsOptionalInt() const -> std::optional<int64_t>;
|
||||
|
||||
/// Returns whether the underlying PyObject is callable.
|
||||
/// 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* extra_touch_border_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",
|
||||
"parent",
|
||||
@ -1905,9 +1908,12 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
"big",
|
||||
"extra_touch_border_scale",
|
||||
"res_scale",
|
||||
"query_max_chars",
|
||||
"query_description",
|
||||
"adapter_finished",
|
||||
nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, keywds, "|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO",
|
||||
args, keywds, "|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO",
|
||||
const_cast<char**>(kwlist), &edit_obj, &parent_obj, &size_obj,
|
||||
&pos_obj, &text_obj, &v_align_obj, &h_align_obj, &editable_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,
|
||||
&shadow_obj, &autoselect_obj, &rotate_obj, &enabled_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;
|
||||
|
||||
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());
|
||||
}
|
||||
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) {
|
||||
widget = dynamic_cast<TextWidget*>(UIV1Python::GetPyWidget(edit_obj));
|
||||
if (!widget.Exists()) {
|
||||
@ -2095,6 +2120,13 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
if (res_scale_obj != Py_None) {
|
||||
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 (edit_obj == Py_None) {
|
||||
@ -2144,7 +2176,10 @@ static PyMethodDef PyTextWidgetDef = {
|
||||
" always_show_carat: bool | None = None,\n"
|
||||
" big: bool | 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"
|
||||
"\n"
|
||||
"Create or edit a text widget.\n"
|
||||
@ -2757,7 +2792,7 @@ static auto PyConsolePrint(PyObject* self, PyObject* args) -> PyObject* {
|
||||
throw Exception();
|
||||
}
|
||||
const char* c = PyUnicode_AsUTF8(str_obj);
|
||||
g_base->PushConsolePrintCall(c);
|
||||
g_base->PushDevConsolePrintCall(c);
|
||||
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());
|
||||
BA_PRECONDITION(w);
|
||||
|
||||
|
||||
@ -17,7 +17,8 @@ class UIV1Python {
|
||||
void AddPythonClasses(PyObject* module);
|
||||
void ImportPythonObjs();
|
||||
|
||||
void LaunchStringEdit(TextWidget* w);
|
||||
void LaunchStringEditOld(TextWidget* w);
|
||||
void InvokeStringEditor(PyObject* string_edit_adapter_instance);
|
||||
void HandleDeviceMenuPress(base::InputDevice* device);
|
||||
void ShowURL(const std::string& url);
|
||||
|
||||
@ -37,6 +38,7 @@ class UIV1Python {
|
||||
kQuitWindowCall,
|
||||
kDeviceMenuPressCall,
|
||||
kShowURLWindowCall,
|
||||
kTextWidgetStringEditAdapterClass,
|
||||
kLast // Sentinel; must be at end.
|
||||
};
|
||||
|
||||
|
||||
@ -10,12 +10,17 @@
|
||||
#include "ballistica/base/input/device/keyboard_input.h"
|
||||
#include "ballistica/base/input/input.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/ui/ui.h"
|
||||
#include "ballistica/core/core.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/python/python.h"
|
||||
#include "ballistica/shared/python/python_sys.h"
|
||||
#include "ballistica/ui_v1/python/ui_v1_python.h"
|
||||
#include "ballistica/ui_v1/widget/container_widget.h"
|
||||
|
||||
@ -23,25 +28,16 @@ namespace ballistica::ui_v1 {
|
||||
|
||||
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() {
|
||||
// We always show our clear button except for in android when we don't
|
||||
// have a touchscreen (android-tv type situations).
|
||||
//
|
||||
// FIXME - should generalize this to any controller-only situation.
|
||||
if (g_buildconfig.ostype_android()) {
|
||||
if (g_base->input->touch_input() == nullptr) {
|
||||
do_clear_button_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
birth_time_millisecs_ =
|
||||
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.
|
||||
{
|
||||
// We should really be scaling our bounds and things,
|
||||
// but for now lets just do a hacky overall scale.
|
||||
// We should really be scaling our bounds and things, but for now lets
|
||||
// just do a hacky overall scale.
|
||||
base::EmptyComponent c(pass);
|
||||
c.SetTransparent(true);
|
||||
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.
|
||||
UpdateTranslation();
|
||||
UpdateTranslation_();
|
||||
|
||||
if (!text_group_.Exists()) {
|
||||
text_group_ = Object::New<base::TextGroup>();
|
||||
@ -386,7 +382,7 @@ void TextWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
|
||||
// Draw the carat.
|
||||
if (IsHierarchySelected() || always_show_carat_) {
|
||||
bool show_cursor = true;
|
||||
if (ShouldUseStringEditDialog()) {
|
||||
if (ShouldUseStringEditor_()) {
|
||||
show_cursor = false;
|
||||
}
|
||||
if (show_cursor
|
||||
@ -520,70 +516,6 @@ auto TextWidget::GetHeight() -> float {
|
||||
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() {
|
||||
last_activate_time_millisecs_ =
|
||||
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
|
||||
@ -594,11 +526,82 @@ void TextWidget::Activate() {
|
||||
call->ScheduleWeak();
|
||||
}
|
||||
|
||||
if (editable_ && ShouldUseStringEditDialog()) {
|
||||
InvokeStringEditDialog();
|
||||
// Bring up an editor if applicable.
|
||||
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 {
|
||||
if (g_core->HeadlessMode()) {
|
||||
return false;
|
||||
@ -619,17 +622,17 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
||||
}
|
||||
|
||||
// If we're doing inline editing, handle clipboard paste.
|
||||
if (editable() && !ShouldUseStringEditDialog()
|
||||
if (editable() && !ShouldUseStringEditor_()
|
||||
&& m.type == base::WidgetMessage::Type::kPaste) {
|
||||
if (g_core->platform->ClipboardIsSupported()) {
|
||||
if (g_core->platform->ClipboardHasText()) {
|
||||
// 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 (m.has_keysym && !ShouldUseStringEditDialog()) {
|
||||
if (m.has_keysym && !ShouldUseStringEditor_()) {
|
||||
last_carat_change_time_millisecs_ =
|
||||
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: {
|
||||
// If we're using an edit dialog, any attempted text input just kicks us
|
||||
// over to that.
|
||||
if (editable() && ShouldUseStringEditDialog()) {
|
||||
InvokeStringEditDialog();
|
||||
if (editable() && ShouldUseStringEditor_()) {
|
||||
InvokeStringEditor_();
|
||||
} else {
|
||||
// Otherwise apply the text directly.
|
||||
if (editable() && m.sval != nullptr) {
|
||||
AddCharsToText(*m.sval);
|
||||
AddCharsToText_(*m.sval);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -810,8 +813,8 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
||||
if (!IsSelectable()) {
|
||||
return false;
|
||||
}
|
||||
float x{ScaleAdjustedX(m.fval1)};
|
||||
float y{ScaleAdjustedY(m.fval2)};
|
||||
float x{ScaleAdjustedX_(m.fval1)};
|
||||
float y{ScaleAdjustedY_(m.fval2)};
|
||||
bool claimed = (m.fval3 > 0.0f);
|
||||
if (claimed) {
|
||||
mouse_over_ = clear_mouse_over_ = false;
|
||||
@ -829,8 +832,8 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
||||
if (!IsSelectable()) {
|
||||
return false;
|
||||
}
|
||||
float x{ScaleAdjustedX(m.fval1)};
|
||||
float y{ScaleAdjustedY(m.fval2)};
|
||||
float x{ScaleAdjustedX_(m.fval1)};
|
||||
float y{ScaleAdjustedY_(m.fval2)};
|
||||
|
||||
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: {
|
||||
float x{ScaleAdjustedX(m.fval1)};
|
||||
float y{ScaleAdjustedY(m.fval2)};
|
||||
float x{ScaleAdjustedX_(m.fval1)};
|
||||
float y{ScaleAdjustedY_(m.fval2)};
|
||||
bool claimed = (m.fval3 > 0.0f);
|
||||
|
||||
if (clear_pressed_ && !claimed && editable()
|
||||
@ -903,12 +906,12 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
||||
&& (y < (height_ + top_overlap)) && !claimed) {
|
||||
Activate();
|
||||
pressed_activate_ = false;
|
||||
} else if (editable_ && ShouldUseStringEditDialog()
|
||||
} else if (editable_ && ShouldUseStringEditor_()
|
||||
&& (x >= (-left_overlap)) && (x < (width_ + right_overlap))
|
||||
&& (y >= (-bottom_overlap)) && (y < (height_ + top_overlap))
|
||||
&& !claimed) {
|
||||
// With dialog-editing, a click/tap brings up our editor.
|
||||
InvokeStringEditDialog();
|
||||
InvokeStringEditor_();
|
||||
}
|
||||
|
||||
// Pressed buttons always claim mouse-ups presented to them.
|
||||
@ -922,19 +925,19 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto TextWidget::ScaleAdjustedX(float x) -> float {
|
||||
auto TextWidget::ScaleAdjustedX_(float x) -> float {
|
||||
// Account for our center_scale_ value.
|
||||
float offsx = x - width_ * 0.5f;
|
||||
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.
|
||||
float offsy = y - height_ * 0.5f;
|
||||
return height_ * 0.5f + offsy / center_scale_;
|
||||
}
|
||||
|
||||
void TextWidget::AddCharsToText(const std::string& addchars) {
|
||||
void TextWidget::AddCharsToText_(const std::string& addchars) {
|
||||
assert(editable());
|
||||
std::vector<uint32_t> unichars = Utils::UnicodeFromUTF8(text_raw_, "jcjwf8f");
|
||||
int len = static_cast<int>(unichars.size());
|
||||
@ -954,7 +957,7 @@ void TextWidget::AddCharsToText(const std::string& addchars) {
|
||||
text_translation_dirty_ = true;
|
||||
}
|
||||
|
||||
void TextWidget::UpdateTranslation() {
|
||||
void TextWidget::UpdateTranslation_() {
|
||||
// Apply subs/resources to get our actual text if need be.
|
||||
if (text_translation_dirty_) {
|
||||
// We don't run translations on user-editable text.
|
||||
@ -970,7 +973,7 @@ void TextWidget::UpdateTranslation() {
|
||||
}
|
||||
|
||||
auto TextWidget::GetTextWidth() -> float {
|
||||
UpdateTranslation();
|
||||
UpdateTranslation_();
|
||||
|
||||
// Should we cache this?
|
||||
return g_base->text_graphics->GetStringWidth(text_translated_, big_);
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/shared/python/python_ref.h"
|
||||
#include "ballistica/ui_v1/widget/widget.h"
|
||||
|
||||
namespace ballistica::ui_v1 {
|
||||
@ -72,6 +73,7 @@ class TextWidget : public Widget {
|
||||
void set_res_scale(float res_scale);
|
||||
auto GetTextWidth() -> float;
|
||||
void OnLanguageChange() override;
|
||||
void AdapterFinished();
|
||||
|
||||
static TextWidget* GetAndroidStringEditWidget();
|
||||
|
||||
@ -86,14 +88,12 @@ class TextWidget : public Widget {
|
||||
}
|
||||
|
||||
private:
|
||||
auto ScaleAdjustedX(float x) -> float;
|
||||
auto ScaleAdjustedY(float y) -> float;
|
||||
void AddCharsToText(const std::string& addchars);
|
||||
auto ShouldUseStringEditDialog() const -> bool;
|
||||
void InvokeStringEditDialog();
|
||||
void UpdateTranslation();
|
||||
// static bool always_use_internal_keyboard_;
|
||||
static Object::WeakRef<TextWidget> android_string_edit_widget_;
|
||||
auto ScaleAdjustedX_(float x) -> float;
|
||||
auto ScaleAdjustedY_(float y) -> float;
|
||||
void AddCharsToText_(const std::string& addchars);
|
||||
auto ShouldUseStringEditor_() const -> bool;
|
||||
void InvokeStringEditor_();
|
||||
void UpdateTranslation_();
|
||||
float res_scale_{1.0f};
|
||||
bool enabled_{true};
|
||||
millisecs_t birth_time_millisecs_{};
|
||||
@ -150,9 +150,10 @@ class TextWidget : public Widget {
|
||||
millisecs_t last_activate_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_activate_call_;
|
||||
PythonRef string_edit_adapter_;
|
||||
};
|
||||
|
||||
} // namespace ballistica::ui_v1
|
||||
|
||||
@ -15,6 +15,7 @@ from babase import (
|
||||
_env,
|
||||
_error,
|
||||
_general,
|
||||
_ui,
|
||||
)
|
||||
|
||||
# The C++ layer looks for this variable:
|
||||
@ -23,7 +24,6 @@ values = [
|
||||
_hooks.set_config_fullscreen_on, # kSetConfigFullscreenOnCall
|
||||
_hooks.set_config_fullscreen_off, # kSetConfigFullscreenOffCall
|
||||
_hooks.not_signed_in_screen_message, # kNotSignedInScreenMessageCall
|
||||
_hooks.connecting_to_party_message, # kConnectingToPartyMessageCall
|
||||
_hooks.rejecting_invite_already_in_party_message, # kRejectingInviteAlreadyInPartyMessageCall
|
||||
_hooks.connection_failed_message, # kConnectionFailedMessageCall
|
||||
_hooks.temporarily_unavailable_message, # kTemporarilyUnavailableMessageCall
|
||||
@ -52,6 +52,7 @@ values = [
|
||||
_hooks.remove_in_game_ads_message, # kRemoveInGameAdsMessageCall
|
||||
_hooks.do_quit, # kQuitCall
|
||||
_hooks.show_post_purchase_message, # kShowPostPurchaseMessageCall
|
||||
_hooks.string_edit_adapter_can_be_replaced, # kStringEditAdapterCanBeReplacedCall
|
||||
_language.Lstr, # kLStrClass
|
||||
_general.Call, # kCallClass
|
||||
_apputils.garbage_collect_session_end, # kGarbageCollectSessionEndCall
|
||||
@ -80,4 +81,5 @@ values = [
|
||||
_hooks.open_url_with_webbrowser_module, # kOpenURLWithWebBrowserModuleCall
|
||||
_env.on_native_module_import, # kEnvOnNativeModuleImportCall
|
||||
_env.on_main_thread_start_app, # kOnMainThreadStartAppCall
|
||||
_ui.DevConsoleStringEditAdapter, # kDevConsoleStringEditAdapterClass
|
||||
]
|
||||
|
||||
@ -6,6 +6,7 @@ from __future__ import annotations
|
||||
|
||||
import bauiv1.onscreenkeyboard
|
||||
from bauiv1 import _hooks
|
||||
from bauiv1._uitypes import TextWidgetStringEditAdapter
|
||||
|
||||
# The C++ layer looks for this variable:
|
||||
values = [
|
||||
@ -21,4 +22,5 @@ values = [
|
||||
_hooks.quit_window, # kQuitWindowCall
|
||||
_hooks.device_menu_press, # kDeviceMenuPressCall
|
||||
_hooks.show_url_window, # kShowURLWindowCall
|
||||
TextWidgetStringEditAdapter, # kTextWidgetStringEditAdapterClass
|
||||
]
|
||||
|
||||
@ -39,6 +39,12 @@ class _EmptyObj:
|
||||
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
|
||||
if TYPE_CHECKING:
|
||||
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
|
||||
# for the static type checker.
|
||||
del objtype # Unused.
|
||||
|
||||
# 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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user