pythonnative 0.35.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/cli/pn.py +24 -6
- 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/mutations.py +21 -0
- 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.35.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.35.0.dist-info/RECORD +0 -155
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
package com.pythonnative.runtime
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import com.pythonnative.runtime.animation.PNAnimator
|
|
7
|
+
import com.pythonnative.runtime.bridge.JsonUtil
|
|
8
|
+
import com.pythonnative.runtime.bridge.MainThread
|
|
9
|
+
import com.pythonnative.runtime.bridge.PNRegistry
|
|
10
|
+
import com.pythonnative.runtime.bridge.TransactionApplier
|
|
11
|
+
import com.pythonnative.runtime.bridge.ViewRegistry
|
|
12
|
+
import com.pythonnative.runtime.modules.BuiltinModules
|
|
13
|
+
import com.pythonnative.runtime.modules.ModuleEnvelope
|
|
14
|
+
import com.pythonnative.runtime.modules.Promise
|
|
15
|
+
import org.json.JSONObject
|
|
16
|
+
import java.util.Locale
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The single Java class Python touches.
|
|
20
|
+
*
|
|
21
|
+
* Every entry point mirrors one C symbol of the iOS bridge and is
|
|
22
|
+
* expected to run on the main thread (Python's asyncio loop lives
|
|
23
|
+
* there). Calls from other threads are logged but still executed so a
|
|
24
|
+
* misbehaving caller fails loudly in logcat instead of silently.
|
|
25
|
+
*/
|
|
26
|
+
object PNBridge {
|
|
27
|
+
/** Logcat tag used by the whole runtime. */
|
|
28
|
+
const val TAG = "PythonNative"
|
|
29
|
+
|
|
30
|
+
/** Protocol version compiled into this library. */
|
|
31
|
+
const val PROTOCOL_VERSION = 1
|
|
32
|
+
|
|
33
|
+
private var host: PythonHost? = null
|
|
34
|
+
private var activity: Activity? = null
|
|
35
|
+
private val hostReadyListeners = ArrayList<Runnable>()
|
|
36
|
+
|
|
37
|
+
/** Tag to view bookkeeping shared by the applier and managers. */
|
|
38
|
+
val registry = ViewRegistry()
|
|
39
|
+
|
|
40
|
+
private val applier by lazy { TransactionApplier(registry) }
|
|
41
|
+
|
|
42
|
+
/** The protocol version Python must match before starting. */
|
|
43
|
+
@JvmStatic
|
|
44
|
+
fun protocolVersion(): Int = PROTOCOL_VERSION
|
|
45
|
+
|
|
46
|
+
/** Install the Python callback target; flushes callbacks queued before it existed. */
|
|
47
|
+
@JvmStatic
|
|
48
|
+
fun setHost(host: PythonHost) {
|
|
49
|
+
this.host = host
|
|
50
|
+
val pending = ArrayList(hostReadyListeners)
|
|
51
|
+
hostReadyListeners.clear()
|
|
52
|
+
for (listener in pending) {
|
|
53
|
+
try {
|
|
54
|
+
listener.run()
|
|
55
|
+
} catch (e: Exception) {
|
|
56
|
+
Log.e(TAG, "host-ready listener failed", e)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Whether a [PythonHost] has been installed. */
|
|
62
|
+
fun hasHost(): Boolean = host != null
|
|
63
|
+
|
|
64
|
+
/** Run `runnable` now if a host is installed, otherwise once [setHost] is called. */
|
|
65
|
+
fun runWhenHostReady(runnable: Runnable) {
|
|
66
|
+
if (host != null) runnable.run() else hostReadyListeners.add(runnable)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Record the current activity; modules and managers derive their `Context` from it. */
|
|
70
|
+
@JvmStatic
|
|
71
|
+
fun setContext(activity: Activity) {
|
|
72
|
+
this.activity = activity
|
|
73
|
+
PNRegistry.ensureBuiltins()
|
|
74
|
+
BuiltinModules.onContextAttached(activity)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Forget `activity` if it is the current one (called from `Activity.onDestroy`). */
|
|
78
|
+
@JvmStatic
|
|
79
|
+
fun clearContext(activity: Activity) {
|
|
80
|
+
if (this.activity === activity) this.activity = null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The current activity, or `null` before [setContext]. */
|
|
84
|
+
fun activity(): Activity? = activity
|
|
85
|
+
|
|
86
|
+
/** The context used to create views; throws before [setContext]. */
|
|
87
|
+
fun context(): Context =
|
|
88
|
+
activity ?: throw IllegalStateException("PNBridge.setContext(activity) has not been called")
|
|
89
|
+
|
|
90
|
+
/** Display density of the current context (1.0 when no context is attached). */
|
|
91
|
+
fun density(): Float {
|
|
92
|
+
val ctx = activity ?: return 1f
|
|
93
|
+
val d = ctx.resources.displayMetrics.density
|
|
94
|
+
return if (d > 0f) d else 1f
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ------------------------------------------------------------------
|
|
98
|
+
// Python -> native
|
|
99
|
+
// ------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
/** Apply one serialized transaction (a JSON array of ops). */
|
|
102
|
+
@JvmStatic
|
|
103
|
+
fun apply(transactionJson: String) {
|
|
104
|
+
assertMain("apply")
|
|
105
|
+
applier.apply(transactionJson)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Measure the view with `tag` under the given constraints (dp; `1e6`
|
|
110
|
+
* means unconstrained) and return `"w,h"` in dp.
|
|
111
|
+
*/
|
|
112
|
+
@JvmStatic
|
|
113
|
+
fun measure(tag: Long, maxWidth: Double, maxHeight: Double): String {
|
|
114
|
+
assertMain("measure")
|
|
115
|
+
val record = registry.get(tag)
|
|
116
|
+
if (record == null) {
|
|
117
|
+
Log.w(TAG, "measure: unknown tag $tag")
|
|
118
|
+
return "0,0"
|
|
119
|
+
}
|
|
120
|
+
val size = try {
|
|
121
|
+
record.manager.measure(record.view, maxWidth, maxHeight)
|
|
122
|
+
} catch (e: Exception) {
|
|
123
|
+
Log.e(TAG, "measure failed for ${record.typeName}#$tag", e)
|
|
124
|
+
floatArrayOf(0f, 0f)
|
|
125
|
+
}
|
|
126
|
+
return String.format(Locale.US, "%.3f,%.3f", size[0], size[1])
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Run an imperative command on one view and return its JSON result. */
|
|
130
|
+
@JvmStatic
|
|
131
|
+
fun command(tag: Long, name: String, argsJson: String): String? {
|
|
132
|
+
assertMain("command")
|
|
133
|
+
val record = registry.get(tag) ?: return null
|
|
134
|
+
return try {
|
|
135
|
+
val args = if (argsJson.isBlank()) JSONObject() else JSONObject(argsJson)
|
|
136
|
+
JsonUtil.encode(record.manager.command(record.view, name, args))
|
|
137
|
+
} catch (e: Exception) {
|
|
138
|
+
Log.e(TAG, "command '$name' failed for ${record.typeName}#$tag", e)
|
|
139
|
+
null
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Handle an animation request (`set`, `start`, or `cancel`) for one view. */
|
|
144
|
+
@JvmStatic
|
|
145
|
+
fun animate(tag: Long, requestJson: String): String? {
|
|
146
|
+
assertMain("animate")
|
|
147
|
+
val record = registry.get(tag) ?: return null
|
|
148
|
+
return try {
|
|
149
|
+
PNAnimator.handle(record, JSONObject(requestJson))
|
|
150
|
+
} catch (e: Exception) {
|
|
151
|
+
Log.e(TAG, "animate failed for ${record.typeName}#$tag: $requestJson", e)
|
|
152
|
+
null
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Call a native module method; `argsJson` is `{"call_id": n, "args": {...}}`. */
|
|
157
|
+
@JvmStatic
|
|
158
|
+
fun call(module: String, method: String, argsJson: String): String? {
|
|
159
|
+
assertMain("call")
|
|
160
|
+
val (callId, args) = try {
|
|
161
|
+
ModuleEnvelope.decodeCall(argsJson)
|
|
162
|
+
} catch (e: Exception) {
|
|
163
|
+
return ModuleEnvelope.error("malformed call envelope: ${e.message}", "bad_request")
|
|
164
|
+
}
|
|
165
|
+
val target = PNRegistry.module(module)
|
|
166
|
+
?: return ModuleEnvelope.error("unknown module '$module'", "unknown_module")
|
|
167
|
+
val promise = Promise(callId, module)
|
|
168
|
+
try {
|
|
169
|
+
target.call(method, args, promise)
|
|
170
|
+
} catch (e: Exception) {
|
|
171
|
+
Log.e(TAG, "module $module.$method threw", e)
|
|
172
|
+
if (!promise.isSettled) promise.reject(e.message ?: e.toString(), "exception")
|
|
173
|
+
}
|
|
174
|
+
return promise.markReturned()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ------------------------------------------------------------------
|
|
178
|
+
// native -> Python
|
|
179
|
+
// ------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Deliver one callback to Python. Returns `null` when no host is
|
|
183
|
+
* installed or the Python handler raised (the error is logged).
|
|
184
|
+
*/
|
|
185
|
+
fun callPython(kind: String, tag: Long, name: String, payloadJson: String): String? {
|
|
186
|
+
val target = host
|
|
187
|
+
if (target == null) {
|
|
188
|
+
Log.w(TAG, "callPython($kind, $tag, $name) dropped: no PythonHost installed")
|
|
189
|
+
return null
|
|
190
|
+
}
|
|
191
|
+
return try {
|
|
192
|
+
target.callback(kind, tag, name, payloadJson)
|
|
193
|
+
} catch (e: Exception) {
|
|
194
|
+
Log.e(TAG, "Python callback $kind/$name (tag $tag) raised", e)
|
|
195
|
+
null
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ------------------------------------------------------------------
|
|
200
|
+
// Threading helpers
|
|
201
|
+
// ------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
/** Whether the caller is on the main thread. */
|
|
204
|
+
fun isMainThread(): Boolean = MainThread.isMain()
|
|
205
|
+
|
|
206
|
+
/** Post `runnable` to the main thread. */
|
|
207
|
+
fun post(runnable: Runnable) = MainThread.post(runnable)
|
|
208
|
+
|
|
209
|
+
private fun assertMain(entry: String) {
|
|
210
|
+
if (!MainThread.isMain()) {
|
|
211
|
+
Log.w(TAG, "PNBridge.$entry called off the main thread (${Thread.currentThread().name})")
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.pythonnative.runtime
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The reverse direction of the bridge: native to Python.
|
|
5
|
+
*
|
|
6
|
+
* The app template installs an implementation that forwards to
|
|
7
|
+
* `pythonnative.bridge.native_callback` through Chaquopy. The runtime
|
|
8
|
+
* library itself never touches Chaquopy, which keeps it unit-testable
|
|
9
|
+
* with plain JUnit.
|
|
10
|
+
*
|
|
11
|
+
* `kind` is one of `event`, `module`, `host`, `animation`, or `pump`;
|
|
12
|
+
* see the bridge protocol documentation for the payload of each.
|
|
13
|
+
*/
|
|
14
|
+
interface PythonHost {
|
|
15
|
+
/**
|
|
16
|
+
* Deliver one callback to Python and return its (JSON-encoded)
|
|
17
|
+
* result, or `null` when the handler produced nothing.
|
|
18
|
+
*/
|
|
19
|
+
fun callback(kind: String, tag: Long, name: String, payloadJson: String): String?
|
|
20
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
package com.pythonnative.runtime.animation
|
|
2
|
+
|
|
3
|
+
import org.json.JSONArray
|
|
4
|
+
import org.json.JSONObject
|
|
5
|
+
import kotlin.math.max
|
|
6
|
+
import kotlin.math.sqrt
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Pure-Kotlin decoding of animation specs (`pn.Animated.timing`,
|
|
10
|
+
* `spring`, `decay`) and their mapping to Android animator parameters.
|
|
11
|
+
* Kept free of Android types so the math is unit-testable.
|
|
12
|
+
*/
|
|
13
|
+
object AnimationSpecs {
|
|
14
|
+
/** `FlingAnimation` scales `friction` by this constant internally (`DragForce`). */
|
|
15
|
+
const val FLING_FRICTION_SCALE = 4.2
|
|
16
|
+
|
|
17
|
+
/** Easing curves identical to the Python ticker's `_EASINGS`. */
|
|
18
|
+
val EASINGS: Map<String, (Float) -> Float> = mapOf(
|
|
19
|
+
"linear" to { t -> t },
|
|
20
|
+
"ease" to { t -> 3f * t * t - 2f * t * t * t },
|
|
21
|
+
"ease_in" to { t -> t * t },
|
|
22
|
+
"ease_out" to { t -> 1f - (1f - t) * (1f - t) },
|
|
23
|
+
"ease_in_out" to { t -> 3f * t * t - 2f * t * t * t },
|
|
24
|
+
"ease_in_quad" to { t -> t * t },
|
|
25
|
+
"ease_out_quad" to { t -> 1f - (1f - t) * (1f - t) },
|
|
26
|
+
"bounce" to { t -> bounceOut(t) },
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
/** Decoded `timing` parameters. */
|
|
30
|
+
data class Timing(val from: Double, val to: Double, val durationMs: Long, val easing: Any?)
|
|
31
|
+
|
|
32
|
+
/** Decoded `spring` parameters mapped to `SpringForce` terms. */
|
|
33
|
+
data class Spring(
|
|
34
|
+
val from: Double,
|
|
35
|
+
val to: Double,
|
|
36
|
+
/** `stiffness / mass`, so the natural frequency matches the Python integrator. */
|
|
37
|
+
val stiffness: Double,
|
|
38
|
+
/** `damping / (2 * sqrt(stiffness * mass))`. */
|
|
39
|
+
val dampingRatio: Double,
|
|
40
|
+
/** Units per second. */
|
|
41
|
+
val initialVelocity: Double,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
/** Decoded `decay` parameters mapped to `FlingAnimation` terms. */
|
|
45
|
+
data class Decay(
|
|
46
|
+
val from: Double,
|
|
47
|
+
/** Units per second (Python specs use units per millisecond). */
|
|
48
|
+
val startVelocity: Double,
|
|
49
|
+
/** `FlingAnimation.setFriction` value reproducing `v(t) = v0 * exp(-k * 1000 * t)`. */
|
|
50
|
+
val friction: Double,
|
|
51
|
+
/** Where the value settles: `from + v0 / (k * 1000)`. */
|
|
52
|
+
val projectedFinal: Double,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
fun kind(spec: JSONObject): String = spec.optString("kind", "")
|
|
56
|
+
|
|
57
|
+
fun timing(spec: JSONObject): Timing = Timing(
|
|
58
|
+
from = spec.optDouble("from", 0.0),
|
|
59
|
+
to = spec.optDouble("to", 0.0),
|
|
60
|
+
durationMs = max(0L, spec.optDouble("duration_ms", 300.0).toLong()),
|
|
61
|
+
easing = spec.opt("easing"),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
fun spring(spec: JSONObject): Spring {
|
|
65
|
+
val stiffness = max(1e-6, spec.optDouble("stiffness", 100.0))
|
|
66
|
+
val damping = max(0.0, spec.optDouble("damping", 10.0))
|
|
67
|
+
val mass = max(1e-6, spec.optDouble("mass", 1.0))
|
|
68
|
+
return Spring(
|
|
69
|
+
from = spec.optDouble("from", 0.0),
|
|
70
|
+
to = spec.optDouble("to", 0.0),
|
|
71
|
+
stiffness = stiffness / mass,
|
|
72
|
+
dampingRatio = dampingRatio(stiffness, damping, mass),
|
|
73
|
+
initialVelocity = spec.optDouble("initial_velocity", 0.0),
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** `damping / (2 * sqrt(stiffness * mass))`. */
|
|
78
|
+
fun dampingRatio(stiffness: Double, damping: Double, mass: Double): Double =
|
|
79
|
+
damping / (2.0 * sqrt(max(1e-12, stiffness * mass)))
|
|
80
|
+
|
|
81
|
+
fun decay(spec: JSONObject): Decay {
|
|
82
|
+
val v0PerMs = spec.optDouble("velocity", 0.0)
|
|
83
|
+
val k = max(1e-6, spec.optDouble("deceleration", 0.997))
|
|
84
|
+
val from = spec.optDouble("from", 0.0)
|
|
85
|
+
return Decay(
|
|
86
|
+
from = from,
|
|
87
|
+
startVelocity = v0PerMs * 1000.0,
|
|
88
|
+
friction = decayFriction(k),
|
|
89
|
+
projectedFinal = from + v0PerMs / (k * 1000.0),
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The Python ticker decays velocity as `exp(-k * dt_ms)`, that is
|
|
95
|
+
* `exp(-k * 1000 * t_s)`. `FlingAnimation` decays as
|
|
96
|
+
* `exp(-4.2 * friction * t_s)`, so `friction = k * 1000 / 4.2`.
|
|
97
|
+
*/
|
|
98
|
+
fun decayFriction(deceleration: Double): Double = deceleration * 1000.0 / FLING_FRICTION_SCALE
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Resolve an easing value. Names map to [EASINGS]; a four-number
|
|
102
|
+
* array is returned as cubic-bezier control points (`DoubleArray`)
|
|
103
|
+
* for the caller to build a `PathInterpolator`. Unknown names fall
|
|
104
|
+
* back to `ease_in_out` like the Python ticker.
|
|
105
|
+
*/
|
|
106
|
+
fun resolveEasing(easing: Any?): Any {
|
|
107
|
+
if (easing is JSONArray && easing.length() == 4) {
|
|
108
|
+
return DoubleArray(4) { easing.optDouble(it, 0.0) }
|
|
109
|
+
}
|
|
110
|
+
if (easing is String) {
|
|
111
|
+
val trimmed = easing.trim()
|
|
112
|
+
if (trimmed.startsWith("cubic-bezier(") || trimmed.startsWith("cubic_bezier(")) {
|
|
113
|
+
val inner = trimmed.substringAfter('(').substringBeforeLast(')')
|
|
114
|
+
val parts = inner.split(',').mapNotNull { it.trim().toDoubleOrNull() }
|
|
115
|
+
if (parts.size == 4) return parts.toDoubleArray()
|
|
116
|
+
}
|
|
117
|
+
return EASINGS[trimmed] ?: EASINGS.getValue("ease_in_out")
|
|
118
|
+
}
|
|
119
|
+
return EASINGS.getValue("ease_in_out")
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private fun bounceOut(t: Float): Float {
|
|
123
|
+
val n1 = 7.5625f
|
|
124
|
+
val d1 = 2.75f
|
|
125
|
+
return when {
|
|
126
|
+
t < 1f / d1 -> n1 * t * t
|
|
127
|
+
t < 2f / d1 -> {
|
|
128
|
+
val u = t - 1.5f / d1
|
|
129
|
+
n1 * u * u + 0.75f
|
|
130
|
+
}
|
|
131
|
+
t < 2.5f / d1 -> {
|
|
132
|
+
val u = t - 2.25f / d1
|
|
133
|
+
n1 * u * u + 0.9375f
|
|
134
|
+
}
|
|
135
|
+
else -> {
|
|
136
|
+
val u = t - 2.625f / d1
|
|
137
|
+
n1 * u * u + 0.984375f
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
package com.pythonnative.runtime.animation
|
|
2
|
+
|
|
3
|
+
import android.animation.Animator
|
|
4
|
+
import android.animation.AnimatorListenerAdapter
|
|
5
|
+
import android.animation.ArgbEvaluator
|
|
6
|
+
import android.animation.TimeInterpolator
|
|
7
|
+
import android.animation.ValueAnimator
|
|
8
|
+
import android.view.View
|
|
9
|
+
import android.view.animation.PathInterpolator
|
|
10
|
+
import android.widget.TextView
|
|
11
|
+
import androidx.dynamicanimation.animation.DynamicAnimation
|
|
12
|
+
import androidx.dynamicanimation.animation.FlingAnimation
|
|
13
|
+
import androidx.dynamicanimation.animation.FloatPropertyCompat
|
|
14
|
+
import androidx.dynamicanimation.animation.SpringAnimation
|
|
15
|
+
import androidx.dynamicanimation.animation.SpringForce
|
|
16
|
+
import com.pythonnative.runtime.PNBridge
|
|
17
|
+
import com.pythonnative.runtime.bridge.JsonUtil
|
|
18
|
+
import com.pythonnative.runtime.bridge.PNLog
|
|
19
|
+
import com.pythonnative.runtime.bridge.ViewRecord
|
|
20
|
+
import com.pythonnative.runtime.components.PNColor
|
|
21
|
+
import com.pythonnative.runtime.components.ViewStyler
|
|
22
|
+
import org.json.JSONObject
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Native driver for `animate` requests on the animatable props
|
|
26
|
+
* (`opacity`, `background_color`, `translate_x`, `translate_y`,
|
|
27
|
+
* `scale`, `scale_x`, `scale_y`, `rotate`, plus `color` for text).
|
|
28
|
+
*
|
|
29
|
+
* `timing` runs on a `ValueAnimator`, `spring` on a `SpringAnimation`,
|
|
30
|
+
* and `decay` on a `FlingAnimation`. Completion is reported with
|
|
31
|
+
* `callback("animation", 0, "", {"id": n, "finished": bool})`.
|
|
32
|
+
*/
|
|
33
|
+
object PNAnimator {
|
|
34
|
+
private class Running(val view: View, val prop: String, val cancel: () -> Unit)
|
|
35
|
+
|
|
36
|
+
private val running = HashMap<Long, Running>()
|
|
37
|
+
|
|
38
|
+
/** Handle one request object; returns the JSON reply string per the spec. */
|
|
39
|
+
fun handle(record: ViewRecord, request: JSONObject): String? {
|
|
40
|
+
val view = record.view
|
|
41
|
+
return when (request.optString("op")) {
|
|
42
|
+
"set" -> {
|
|
43
|
+
record.manager.setAnimatedProperty(view, request.optString("prop"), request.opt("value"))
|
|
44
|
+
null
|
|
45
|
+
}
|
|
46
|
+
"start" -> {
|
|
47
|
+
val id = request.optLong("id")
|
|
48
|
+
val spec = request.optJSONObject("spec") ?: JSONObject()
|
|
49
|
+
val ok = record.manager.startAnimation(view, id, request.optString("prop"), spec)
|
|
50
|
+
JSONObject().put("ok", ok).toString()
|
|
51
|
+
}
|
|
52
|
+
"cancel" -> {
|
|
53
|
+
val value = record.manager.cancelAnimation(view, request.optLong("id"))
|
|
54
|
+
if (value == null) null else JSONObject().put("value", JsonUtil.wrap(value)).toString()
|
|
55
|
+
}
|
|
56
|
+
else -> null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ------------------------------------------------------------------
|
|
61
|
+
// Property access
|
|
62
|
+
// ------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
/** Apply one animation frame to `view`. Unknown props are ignored. */
|
|
65
|
+
fun setProperty(view: View, prop: String, value: Any?) {
|
|
66
|
+
if (JsonUtil.isNull(value)) return
|
|
67
|
+
try {
|
|
68
|
+
when (prop) {
|
|
69
|
+
"opacity" -> view.alpha = JsonUtil.toDouble(value).toFloat()
|
|
70
|
+
"translate_x" -> view.translationX = px(JsonUtil.toDouble(value))
|
|
71
|
+
"translate_y" -> view.translationY = px(JsonUtil.toDouble(value))
|
|
72
|
+
"scale" -> {
|
|
73
|
+
val s = JsonUtil.toDouble(value).toFloat()
|
|
74
|
+
view.scaleX = s
|
|
75
|
+
view.scaleY = s
|
|
76
|
+
}
|
|
77
|
+
"scale_x" -> view.scaleX = JsonUtil.toDouble(value).toFloat()
|
|
78
|
+
"scale_y" -> view.scaleY = JsonUtil.toDouble(value).toFloat()
|
|
79
|
+
"rotate" -> view.rotation = ViewStyler.angleDegrees(value)
|
|
80
|
+
"background_color" -> ViewStyler.setAnimatedBackground(view, value)
|
|
81
|
+
"color" -> (view as? TextView)?.setTextColor(PNColor.parseOr(value, 0xFF000000.toInt()))
|
|
82
|
+
}
|
|
83
|
+
} catch (e: Exception) {
|
|
84
|
+
PNLog.swallowed("PNAnimator.setProperty", e)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Read the current (presentation) value of an animatable prop, or `null`. */
|
|
89
|
+
fun readProperty(view: View, prop: String): Any? = when (prop) {
|
|
90
|
+
"opacity" -> view.alpha.toDouble()
|
|
91
|
+
"translate_x" -> view.translationX / PNBridge.density().toDouble()
|
|
92
|
+
"translate_y" -> view.translationY / PNBridge.density().toDouble()
|
|
93
|
+
"scale", "scale_x" -> view.scaleX.toDouble()
|
|
94
|
+
"scale_y" -> view.scaleY.toDouble()
|
|
95
|
+
"rotate" -> view.rotation.toDouble()
|
|
96
|
+
else -> null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ------------------------------------------------------------------
|
|
100
|
+
// Start / cancel
|
|
101
|
+
// ------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
/** Start a native animation; `false` means Python should tick it instead. */
|
|
104
|
+
fun start(view: View, id: Long, prop: String, spec: JSONObject): Boolean {
|
|
105
|
+
cancelQuietly(id)
|
|
106
|
+
return try {
|
|
107
|
+
when (AnimationSpecs.kind(spec)) {
|
|
108
|
+
"timing" -> startTiming(view, id, prop, spec)
|
|
109
|
+
"spring" -> startSpring(view, id, prop, spec)
|
|
110
|
+
"decay" -> startDecay(view, id, prop, spec)
|
|
111
|
+
else -> false
|
|
112
|
+
}
|
|
113
|
+
} catch (e: Exception) {
|
|
114
|
+
PNLog.rateLimited("anim-start", "native animation failed for '$prop'", e)
|
|
115
|
+
running.remove(id)
|
|
116
|
+
false
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Cancel `id` and return the presentation value of its prop, if readable. */
|
|
121
|
+
fun cancel(id: Long): Any? {
|
|
122
|
+
val entry = running.remove(id) ?: return null
|
|
123
|
+
try {
|
|
124
|
+
entry.cancel()
|
|
125
|
+
} catch (e: Exception) {
|
|
126
|
+
PNLog.swallowed("PNAnimator.cancel", e)
|
|
127
|
+
}
|
|
128
|
+
return readProperty(entry.view, entry.prop)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private fun cancelQuietly(id: Long) {
|
|
132
|
+
running.remove(id)?.let { runCatching { it.cancel() } }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private fun complete(id: Long, finished: Boolean) {
|
|
136
|
+
if (running.remove(id) == null) return
|
|
137
|
+
PNBridge.callPython("animation", 0, "", JSONObject().put("id", id).put("finished", finished).toString())
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private fun startTiming(view: View, id: Long, prop: String, spec: JSONObject): Boolean {
|
|
141
|
+
val timing = AnimationSpecs.timing(spec)
|
|
142
|
+
val animator: ValueAnimator
|
|
143
|
+
if (prop == "background_color") {
|
|
144
|
+
val from = PNColor.parse(spec.opt("from")) ?: return false
|
|
145
|
+
val to = PNColor.parse(spec.opt("to")) ?: return false
|
|
146
|
+
animator = ValueAnimator.ofObject(ArgbEvaluator(), from, to)
|
|
147
|
+
animator.addUpdateListener { ViewStyler.setAnimatedBackground(view, it.animatedValue as Int) }
|
|
148
|
+
} else if (prop == "color") {
|
|
149
|
+
val from = PNColor.parse(spec.opt("from")) ?: return false
|
|
150
|
+
val to = PNColor.parse(spec.opt("to")) ?: return false
|
|
151
|
+
animator = ValueAnimator.ofObject(ArgbEvaluator(), from, to)
|
|
152
|
+
animator.addUpdateListener { (view as? TextView)?.setTextColor(it.animatedValue as Int) }
|
|
153
|
+
} else {
|
|
154
|
+
if (!isFloatProp(prop)) return false
|
|
155
|
+
animator = ValueAnimator.ofFloat(timing.from.toFloat(), timing.to.toFloat())
|
|
156
|
+
animator.addUpdateListener { setProperty(view, prop, it.animatedValue as Float) }
|
|
157
|
+
}
|
|
158
|
+
animator.duration = timing.durationMs
|
|
159
|
+
animator.interpolator = interpolator(timing.easing)
|
|
160
|
+
var cancelled = false
|
|
161
|
+
animator.addListener(object : AnimatorListenerAdapter() {
|
|
162
|
+
override fun onAnimationCancel(animation: Animator) {
|
|
163
|
+
cancelled = true
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
override fun onAnimationEnd(animation: Animator) {
|
|
167
|
+
complete(id, !cancelled)
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
running[id] = Running(view, prop) { animator.cancel() }
|
|
171
|
+
animator.start()
|
|
172
|
+
return true
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private fun startSpring(view: View, id: Long, prop: String, spec: JSONObject): Boolean {
|
|
176
|
+
val params = AnimationSpecs.spring(spec)
|
|
177
|
+
val property = floatProperty(prop) ?: return false
|
|
178
|
+
val scale = unitScale(prop)
|
|
179
|
+
val anim = SpringAnimation(view, property, (params.to * scale).toFloat())
|
|
180
|
+
anim.spring = SpringForce((params.to * scale).toFloat())
|
|
181
|
+
.setStiffness(params.stiffness.toFloat())
|
|
182
|
+
.setDampingRatio(params.dampingRatio.toFloat())
|
|
183
|
+
anim.setStartValue((params.from * scale).toFloat())
|
|
184
|
+
anim.setStartVelocity((params.initialVelocity * scale).toFloat())
|
|
185
|
+
anim.addEndListener { _, canceled, _, _ -> complete(id, !canceled) }
|
|
186
|
+
running[id] = Running(view, prop) { anim.cancel() }
|
|
187
|
+
anim.start()
|
|
188
|
+
return true
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private fun startDecay(view: View, id: Long, prop: String, spec: JSONObject): Boolean {
|
|
192
|
+
val params = AnimationSpecs.decay(spec)
|
|
193
|
+
val property = floatProperty(prop) ?: return false
|
|
194
|
+
val scale = unitScale(prop)
|
|
195
|
+
val anim = FlingAnimation(view, property)
|
|
196
|
+
anim.setStartValue((params.from * scale).toFloat())
|
|
197
|
+
anim.setStartVelocity((params.startVelocity * scale).toFloat())
|
|
198
|
+
anim.friction = params.friction.toFloat()
|
|
199
|
+
anim.addEndListener { _, canceled, _, _ -> complete(id, !canceled) }
|
|
200
|
+
running[id] = Running(view, prop) { anim.cancel() }
|
|
201
|
+
anim.start()
|
|
202
|
+
return true
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ------------------------------------------------------------------
|
|
206
|
+
// Helpers
|
|
207
|
+
// ------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
private fun px(dp: Double): Float = (dp * PNBridge.density()).toFloat()
|
|
210
|
+
|
|
211
|
+
private fun isFloatProp(prop: String): Boolean =
|
|
212
|
+
prop in setOf("opacity", "translate_x", "translate_y", "scale", "scale_x", "scale_y", "rotate")
|
|
213
|
+
|
|
214
|
+
/** Multiplier from spec units to native units (dp to px for translations). */
|
|
215
|
+
private fun unitScale(prop: String): Double =
|
|
216
|
+
if (prop == "translate_x" || prop == "translate_y") PNBridge.density().toDouble() else 1.0
|
|
217
|
+
|
|
218
|
+
/** A `FloatPropertyCompat` for physics animations; `null` for unsupported props. */
|
|
219
|
+
private fun floatProperty(prop: String): FloatPropertyCompat<View>? = when (prop) {
|
|
220
|
+
"opacity" -> DynamicAnimation.ALPHA
|
|
221
|
+
"translate_x" -> DynamicAnimation.TRANSLATION_X
|
|
222
|
+
"translate_y" -> DynamicAnimation.TRANSLATION_Y
|
|
223
|
+
"scale_x" -> DynamicAnimation.SCALE_X
|
|
224
|
+
"scale_y" -> DynamicAnimation.SCALE_Y
|
|
225
|
+
"rotate" -> DynamicAnimation.ROTATION
|
|
226
|
+
"scale" -> object : FloatPropertyCompat<View>("pnScale") {
|
|
227
|
+
override fun getValue(v: View): Float = v.scaleX
|
|
228
|
+
override fun setValue(v: View, value: Float) {
|
|
229
|
+
v.scaleX = value
|
|
230
|
+
v.scaleY = value
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Color props are not floats; physics specs on them fall back to the Python ticker.
|
|
234
|
+
else -> null
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private fun interpolator(easing: Any?): TimeInterpolator {
|
|
238
|
+
return when (val resolved = AnimationSpecs.resolveEasing(easing)) {
|
|
239
|
+
is DoubleArray -> PathInterpolator(
|
|
240
|
+
resolved[0].toFloat(), resolved[1].toFloat(), resolved[2].toFloat(), resolved[3].toFloat(),
|
|
241
|
+
)
|
|
242
|
+
else -> {
|
|
243
|
+
@Suppress("UNCHECKED_CAST")
|
|
244
|
+
val fn = resolved as (Float) -> Float
|
|
245
|
+
TimeInterpolator { t -> fn(t) }
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|