pythonnative 0.36.0__py3-none-any.whl → 0.37.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonnative/__init__.py +6 -4
- pythonnative/_ios_log.py +1 -1
- pythonnative/alerts.py +34 -88
- pythonnative/bootstrap.py +90 -0
- pythonnative/bridge/__init__.py +306 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +164 -0
- pythonnative/bridge/fake.py +236 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/components/layout.py +65 -3
- pythonnative/gestures.py +13 -10
- pythonnative/hooks.py +41 -26
- pythonnative/hosts/__init__.py +13 -36
- pythonnative/hosts/native.py +348 -0
- pythonnative/images.py +6 -5
- pythonnative/native_modules/__init__.py +30 -29
- pythonnative/native_modules/app_state.py +10 -6
- pythonnative/native_modules/battery.py +20 -84
- pythonnative/native_modules/biometrics.py +11 -124
- pythonnative/native_modules/camera.py +15 -291
- pythonnative/native_modules/clipboard.py +14 -109
- pythonnative/native_modules/desktop.py +322 -0
- pythonnative/native_modules/file_system.py +15 -21
- pythonnative/native_modules/haptics.py +19 -129
- pythonnative/native_modules/linking.py +24 -137
- pythonnative/native_modules/location.py +28 -180
- pythonnative/native_modules/net_info.py +25 -135
- pythonnative/native_modules/notifications.py +36 -257
- pythonnative/native_modules/permissions.py +20 -184
- pythonnative/native_modules/registry.py +513 -0
- pythonnative/native_modules/secure_store.py +18 -155
- pythonnative/native_modules/share.py +8 -113
- pythonnative/native_views/__init__.py +67 -86
- pythonnative/native_views/bridge_backend.py +264 -0
- pythonnative/platform.py +19 -15
- pythonnative/platform_metrics.py +3 -3
- pythonnative/project/android.py +0 -3
- pythonnative/project/builder.py +38 -16
- pythonnative/project/config.py +12 -2
- pythonnative/project/plugins.py +366 -0
- pythonnative/py.typed +0 -0
- pythonnative/runtime.py +35 -203
- pythonnative/sdk/__init__.py +48 -33
- pythonnative/sdk/_components.py +57 -76
- pythonnative/storage.py +18 -243
- pythonnative/templates/android_template/app/build.gradle +6 -9
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
- pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
- pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
- pythonnative/templates/android_template/settings.gradle +1 -0
- pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
- pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
- pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
- pythonnative/utils.py +4 -82
- pythonnative/virtual_rows.py +3 -3
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
- pythonnative-0.37.0.dist-info/RECORD +263 -0
- pythonnative/hosts/android.py +0 -269
- pythonnative/hosts/ios.py +0 -404
- pythonnative/native_views/android.py +0 -3702
- pythonnative/native_views/ios.py +0 -5253
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
- pythonnative-0.36.0.dist-info/RECORD +0 -155
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
package com.pythonnative.runtime.gestures
|
|
2
|
+
|
|
3
|
+
import org.json.JSONArray
|
|
4
|
+
import org.json.JSONObject
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Turn a raw pointer-event stream into arbitrated gesture payloads.
|
|
8
|
+
*
|
|
9
|
+
* One arbiter serves one view. Positions are in the view's coordinate
|
|
10
|
+
* space (dp); times are seconds on any monotonic clock. Beyond running
|
|
11
|
+
* each gesture's state machine, the arbiter enforces the relationships
|
|
12
|
+
* serialized by Python:
|
|
13
|
+
*
|
|
14
|
+
* - Two gestures not in each other's `simultaneous` sets race; when
|
|
15
|
+
* one activates, the other is force-failed for the interaction.
|
|
16
|
+
* - A gesture with a `wait_for` set may only activate after all of
|
|
17
|
+
* those gestures have failed; its output is buffered meanwhile and
|
|
18
|
+
* either flushed or discarded.
|
|
19
|
+
*
|
|
20
|
+
* This class is plain Kotlin (no Android types) so it is unit-testable
|
|
21
|
+
* with scripted pointer sequences.
|
|
22
|
+
*/
|
|
23
|
+
class GestureArbiter(specs: List<GestureConfig>, private val emitOut: EmitFn) {
|
|
24
|
+
private enum class State { POSSIBLE, WAITING, ACTIVE, DONE, FAILED }
|
|
25
|
+
|
|
26
|
+
private val pointers = LinkedHashMap<Int, Point>()
|
|
27
|
+
private val recognizers = ArrayList<Recognizer>()
|
|
28
|
+
private val indices = ArrayList<Int>()
|
|
29
|
+
private val sim = HashMap<Int, Set<Int>?>()
|
|
30
|
+
private val waitFor = HashMap<Int, Set<Int>>()
|
|
31
|
+
private val states = HashMap<Int, State>()
|
|
32
|
+
private val buffers = HashMap<Int, ArrayList<Map<String, Any?>>>()
|
|
33
|
+
private var lastT = 0.0
|
|
34
|
+
|
|
35
|
+
init {
|
|
36
|
+
specs.forEachIndexed { i, spec ->
|
|
37
|
+
val recognizer = Recognizer.create(i, spec) { index, payload -> mediate(index, payload) } ?: return@forEachIndexed
|
|
38
|
+
recognizers.add(recognizer)
|
|
39
|
+
indices.add(i)
|
|
40
|
+
sim[i] = spec.simultaneous
|
|
41
|
+
waitFor[i] = spec.waitFor
|
|
42
|
+
states[i] = State.POSSIBLE
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// -- pointer input ---------------------------------------------------
|
|
47
|
+
|
|
48
|
+
/** Record a pointer press and advance every recognizer. */
|
|
49
|
+
fun pointerDown(pointerId: Int, x: Double, y: Double, t: Double) {
|
|
50
|
+
lastT = t
|
|
51
|
+
if (pointers.isEmpty() && states.values.none { it == State.WAITING }) {
|
|
52
|
+
for (i in indices) states[i] = State.POSSIBLE
|
|
53
|
+
buffers.clear()
|
|
54
|
+
}
|
|
55
|
+
pointers[pointerId] = Point(x, y)
|
|
56
|
+
for (r in recognizers) r.down(pointers, t)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Record pointer travel and advance every recognizer. */
|
|
60
|
+
fun pointerMove(pointerId: Int, x: Double, y: Double, t: Double) {
|
|
61
|
+
lastT = t
|
|
62
|
+
if (!pointers.containsKey(pointerId)) return
|
|
63
|
+
pointers[pointerId] = Point(x, y)
|
|
64
|
+
for (r in recognizers) r.move(pointers, t)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Record a pointer release and advance every recognizer. */
|
|
68
|
+
fun pointerUp(pointerId: Int, x: Double, y: Double, t: Double) {
|
|
69
|
+
lastT = t
|
|
70
|
+
pointers.remove(pointerId)
|
|
71
|
+
for (r in recognizers) r.up(pointers, t, x, y)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Abort every in-flight gesture (for example, touch stolen by a scroll parent). */
|
|
75
|
+
fun cancel(t: Double) {
|
|
76
|
+
lastT = t
|
|
77
|
+
pointers.clear()
|
|
78
|
+
buffers.clear()
|
|
79
|
+
for (i in indices) if (states[i] == State.WAITING) states[i] = State.FAILED
|
|
80
|
+
for (r in recognizers) r.cancel(t)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Advance time-based recognizers (long press, multi-tap windows). */
|
|
84
|
+
fun poll(t: Double) {
|
|
85
|
+
lastT = t
|
|
86
|
+
for (r in recognizers) r.poll(t)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Earliest time [poll] should be called, or `null`. */
|
|
90
|
+
fun nextDeadline(): Double? = recognizers.mapNotNull { it.deadline() }.minOrNull()
|
|
91
|
+
|
|
92
|
+
/** Whether a pan gesture is currently activated. */
|
|
93
|
+
fun hasActivePan(): Boolean = recognizers.any { it is PanRecognizer && it.active }
|
|
94
|
+
|
|
95
|
+
// -- arbitration -------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
private fun recognizerFor(index: Int): Recognizer? = recognizers.firstOrNull { it.index == index }
|
|
98
|
+
|
|
99
|
+
private fun isSimultaneous(a: Int, b: Int): Boolean {
|
|
100
|
+
val simA = sim[a] ?: return true
|
|
101
|
+
val simB = sim[b] ?: return true
|
|
102
|
+
return b in simA && a in simB
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private fun mediate(index: Int, payload: Map<String, Any?>) {
|
|
106
|
+
val state = payload["state"]
|
|
107
|
+
val current = states[index] ?: State.POSSIBLE
|
|
108
|
+
if (state == GestureState.FAILED) {
|
|
109
|
+
if (current == State.POSSIBLE || current == State.WAITING) setFailed(index)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
when (current) {
|
|
113
|
+
State.FAILED -> return
|
|
114
|
+
State.ACTIVE -> {
|
|
115
|
+
emitOut(index, payload)
|
|
116
|
+
if (state == GestureState.ENDED) {
|
|
117
|
+
states[index] = State.DONE
|
|
118
|
+
onResolved(index, succeeded = true)
|
|
119
|
+
} else if (state == GestureState.CANCELLED) {
|
|
120
|
+
states[index] = State.FAILED
|
|
121
|
+
onResolved(index, succeeded = false)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
State.WAITING -> buffers.getOrPut(index) { ArrayList() }.add(payload)
|
|
125
|
+
State.DONE -> {}
|
|
126
|
+
State.POSSIBLE -> requestActivation(index, payload)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private fun requestActivation(index: Int, payload: Map<String, Any?>) {
|
|
131
|
+
for (j in indices) {
|
|
132
|
+
if (j == index) continue
|
|
133
|
+
val s = states[j]
|
|
134
|
+
if ((s == State.ACTIVE || s == State.DONE) && !isSimultaneous(index, j)) {
|
|
135
|
+
setFailed(index)
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
val targets = (waitFor[index] ?: emptySet()).filter { states.containsKey(it) }
|
|
140
|
+
if (targets.any { states[it] == State.ACTIVE || states[it] == State.DONE }) {
|
|
141
|
+
setFailed(index)
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
if (targets.any { states[it] == State.POSSIBLE || states[it] == State.WAITING }) {
|
|
145
|
+
states[index] = State.WAITING
|
|
146
|
+
buffers.getOrPut(index) { ArrayList() }.add(payload)
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
activate(index, listOf(payload))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private fun activate(index: Int, payloads: List<Map<String, Any?>>) {
|
|
153
|
+
val last = payloads.lastOrNull()?.get("state")
|
|
154
|
+
val discreteDone = last == GestureState.ENDED || last == GestureState.CANCELLED
|
|
155
|
+
states[index] = if (discreteDone) State.DONE else State.ACTIVE
|
|
156
|
+
for (j in indices) {
|
|
157
|
+
if (j == index) continue
|
|
158
|
+
val s = states[j]
|
|
159
|
+
if ((s == State.POSSIBLE || s == State.WAITING) && !isSimultaneous(index, j)) {
|
|
160
|
+
recognizerFor(j)?.forceFail(lastT)
|
|
161
|
+
setFailed(j)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (p in payloads) emitOut(index, p)
|
|
165
|
+
if (states[index] == State.DONE) onResolved(index, succeeded = true)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private fun setFailed(index: Int) {
|
|
169
|
+
if (states[index] == State.FAILED) return
|
|
170
|
+
states[index] = State.FAILED
|
|
171
|
+
buffers.remove(index)
|
|
172
|
+
onResolved(index, succeeded = false)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private fun onResolved(index: Int, succeeded: Boolean) {
|
|
176
|
+
for (waiter in indices) {
|
|
177
|
+
if (states[waiter] != State.WAITING) continue
|
|
178
|
+
val wf = waitFor[waiter] ?: emptySet()
|
|
179
|
+
if (index !in wf) continue
|
|
180
|
+
if (succeeded) {
|
|
181
|
+
recognizerFor(waiter)?.forceFail(lastT)
|
|
182
|
+
setFailed(waiter)
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
185
|
+
val targets = wf.filter { states.containsKey(it) }
|
|
186
|
+
if (targets.any { states[it] == State.POSSIBLE || states[it] == State.WAITING }) continue
|
|
187
|
+
if (targets.any { states[it] == State.ACTIVE || states[it] == State.DONE }) {
|
|
188
|
+
recognizerFor(waiter)?.forceFail(lastT)
|
|
189
|
+
setFailed(waiter)
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
val payloads = buffers.remove(waiter) ?: ArrayList()
|
|
193
|
+
states[waiter] = State.POSSIBLE
|
|
194
|
+
activate(waiter, payloads)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
companion object {
|
|
199
|
+
/** Decode the serialized `gestures` prop (a JSON array of spec objects). */
|
|
200
|
+
fun decodeSpecs(specs: JSONArray?): List<GestureConfig> {
|
|
201
|
+
if (specs == null) return emptyList()
|
|
202
|
+
val out = ArrayList<GestureConfig>(specs.length())
|
|
203
|
+
for (i in 0 until specs.length()) {
|
|
204
|
+
val spec = specs.optJSONObject(i)
|
|
205
|
+
out.add(if (spec == null) GestureConfig(kind = "") else decodeSpec(spec))
|
|
206
|
+
}
|
|
207
|
+
return out
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Decode one spec object. */
|
|
211
|
+
fun decodeSpec(spec: JSONObject): GestureConfig {
|
|
212
|
+
return GestureConfig(
|
|
213
|
+
kind = spec.optString("kind", ""),
|
|
214
|
+
nTaps = spec.optInt("n_taps", 1),
|
|
215
|
+
maxDistance = spec.optDouble("max_distance", 12.0),
|
|
216
|
+
minDurationMs = spec.optDouble("min_duration_ms", 500.0),
|
|
217
|
+
minDistance = spec.optDouble("min_distance", 10.0),
|
|
218
|
+
minPointers = spec.optInt("min_pointers", 1),
|
|
219
|
+
direction = spec.optString("direction", "any"),
|
|
220
|
+
minVelocity = spec.optDouble("min_velocity", 300.0),
|
|
221
|
+
nPointers = spec.optInt("n_pointers", 1),
|
|
222
|
+
simultaneous = spec.optJSONArray("simultaneous")?.let { intSet(it) },
|
|
223
|
+
waitFor = spec.optJSONArray("wait_for")?.let { intSet(it) } ?: emptySet(),
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private fun intSet(array: JSONArray): Set<Int> {
|
|
228
|
+
val out = HashSet<Int>()
|
|
229
|
+
for (i in 0 until array.length()) out.add(array.optInt(i))
|
|
230
|
+
return out
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
package com.pythonnative.runtime.gestures
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.os.SystemClock
|
|
5
|
+
import android.view.MotionEvent
|
|
6
|
+
import android.view.View
|
|
7
|
+
import com.pythonnative.runtime.PNBridge
|
|
8
|
+
import com.pythonnative.runtime.bridge.JsonUtil
|
|
9
|
+
import com.pythonnative.runtime.bridge.MainThread
|
|
10
|
+
import com.pythonnative.runtime.bridge.PNLog
|
|
11
|
+
import com.pythonnative.runtime.bridge.ViewRecord
|
|
12
|
+
import com.pythonnative.runtime.components.PNEvents
|
|
13
|
+
import com.pythonnative.runtime.components.ViewStyler
|
|
14
|
+
import org.json.JSONArray
|
|
15
|
+
import kotlin.math.max
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Binds `MotionEvent` streams on PythonNative views to a
|
|
19
|
+
* [GestureArbiter] and forwards its output as `gesture:<index>`
|
|
20
|
+
* events. Time-based deadlines (long press, multi-tap windows) are
|
|
21
|
+
* polled with the main `Handler`, scheduled at the arbiter's next
|
|
22
|
+
* deadline. When a pan activates, the parent is asked not to intercept
|
|
23
|
+
* so an enclosing scroll view can't steal the drag.
|
|
24
|
+
*/
|
|
25
|
+
object GestureCoordinator {
|
|
26
|
+
private const val KEY_ARBITER = "arbiter"
|
|
27
|
+
private const val KEY_BOUND = "gestures_bound"
|
|
28
|
+
private const val KEY_POLL_DEADLINE = "poll_deadline"
|
|
29
|
+
private const val KEY_POLL_TOKEN = "poll_token"
|
|
30
|
+
|
|
31
|
+
/** Install (or replace) the arbiter for `record` from its `gestures` prop value. */
|
|
32
|
+
fun bind(record: ViewRecord, specs: Any?) {
|
|
33
|
+
val state = record.state
|
|
34
|
+
val array = specs as? JSONArray
|
|
35
|
+
if (array == null || array.length() == 0) {
|
|
36
|
+
state[KEY_ARBITER] = null
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
val tag = record.tag
|
|
40
|
+
val arbiter = GestureArbiter(GestureArbiter.decodeSpecs(array)) { index, payload ->
|
|
41
|
+
PNEvents.fireTag(tag, "gesture:$index", JsonUtil.args(payload))
|
|
42
|
+
}
|
|
43
|
+
state[KEY_ARBITER] = arbiter
|
|
44
|
+
if (state[KEY_BOUND] == true) return
|
|
45
|
+
state[KEY_BOUND] = true
|
|
46
|
+
bindTouchStream(record)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Drop the arbiter and any pending poll for `record`. */
|
|
50
|
+
fun unbind(record: ViewRecord) {
|
|
51
|
+
record.state[KEY_ARBITER] = null
|
|
52
|
+
record.state[KEY_POLL_DEADLINE] = null
|
|
53
|
+
record.state[KEY_POLL_TOKEN] = ((record.state[KEY_POLL_TOKEN] as? Int) ?: 0) + 1
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Whether `record` currently has gestures wired. */
|
|
57
|
+
fun hasArbiter(record: ViewRecord): Boolean = record.state[KEY_ARBITER] is GestureArbiter
|
|
58
|
+
|
|
59
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
60
|
+
private fun bindTouchStream(record: ViewRecord) {
|
|
61
|
+
record.view.setOnTouchListener { v, event ->
|
|
62
|
+
if (ViewStyler.pointerEventsBlocked(v)) return@setOnTouchListener false
|
|
63
|
+
feed(record, event)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Translate one `MotionEvent` into arbiter pointer calls. Returns
|
|
69
|
+
* `true` while any gesture spec is wired (to keep receiving the
|
|
70
|
+
* stream), `false` when there is nothing to feed.
|
|
71
|
+
*/
|
|
72
|
+
fun feed(record: ViewRecord, event: MotionEvent): Boolean {
|
|
73
|
+
val arbiter = record.state[KEY_ARBITER] as? GestureArbiter ?: return false
|
|
74
|
+
try {
|
|
75
|
+
val t = now()
|
|
76
|
+
val density = PNBridge.density()
|
|
77
|
+
when (event.actionMasked) {
|
|
78
|
+
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
|
|
79
|
+
val idx = event.actionIndex
|
|
80
|
+
arbiter.pointerDown(event.getPointerId(idx), (event.getX(idx) / density).toDouble(), (event.getY(idx) / density).toDouble(), t)
|
|
81
|
+
}
|
|
82
|
+
MotionEvent.ACTION_MOVE -> {
|
|
83
|
+
for (i in 0 until event.pointerCount) {
|
|
84
|
+
arbiter.pointerMove(event.getPointerId(i), (event.getX(i) / density).toDouble(), (event.getY(i) / density).toDouble(), t)
|
|
85
|
+
}
|
|
86
|
+
if (arbiter.hasActivePan()) record.view.parent?.requestDisallowInterceptTouchEvent(true)
|
|
87
|
+
}
|
|
88
|
+
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
|
|
89
|
+
val idx = event.actionIndex
|
|
90
|
+
arbiter.pointerUp(event.getPointerId(idx), (event.getX(idx) / density).toDouble(), (event.getY(idx) / density).toDouble(), t)
|
|
91
|
+
}
|
|
92
|
+
MotionEvent.ACTION_CANCEL -> arbiter.cancel(t)
|
|
93
|
+
}
|
|
94
|
+
schedulePoll(record)
|
|
95
|
+
} catch (e: Exception) {
|
|
96
|
+
PNLog.rateLimited("gesture-feed", "gesture feed failed", e)
|
|
97
|
+
}
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Seconds on the monotonic uptime clock. */
|
|
102
|
+
private fun now(): Double = SystemClock.uptimeMillis() / 1000.0
|
|
103
|
+
|
|
104
|
+
private fun schedulePoll(record: ViewRecord) {
|
|
105
|
+
val state = record.state
|
|
106
|
+
val arbiter = state[KEY_ARBITER] as? GestureArbiter ?: return
|
|
107
|
+
val deadline = arbiter.nextDeadline() ?: return
|
|
108
|
+
val pending = state[KEY_POLL_DEADLINE] as? Double
|
|
109
|
+
if (pending != null && pending <= deadline + 0.001) return
|
|
110
|
+
val delayMs = max(1L, ((deadline - now()) * 1000.0).toLong())
|
|
111
|
+
val token = ((state[KEY_POLL_TOKEN] as? Int) ?: 0) + 1
|
|
112
|
+
state[KEY_POLL_TOKEN] = token
|
|
113
|
+
state[KEY_POLL_DEADLINE] = deadline
|
|
114
|
+
MainThread.postDelayed({
|
|
115
|
+
if (state[KEY_POLL_TOKEN] == token) state[KEY_POLL_DEADLINE] = null
|
|
116
|
+
val live = state[KEY_ARBITER] as? GestureArbiter ?: return@postDelayed
|
|
117
|
+
live.poll(now())
|
|
118
|
+
schedulePoll(record)
|
|
119
|
+
}, delayMs)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Convenience for managers that own their own touch listener (Pressable). */
|
|
123
|
+
fun feed(view: View, event: MotionEvent): Boolean {
|
|
124
|
+
val record = PNBridge.registry.recordFor(view) ?: return false
|
|
125
|
+
return feed(record, event)
|
|
126
|
+
}
|
|
127
|
+
}
|