pythonnative 0.40.0__cp313-cp313-win_amd64.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.
Files changed (418) hide show
  1. pythonnative/__init__.py +411 -0
  2. pythonnative/_ios_log.py +92 -0
  3. pythonnative/_yoga.cp313-win_amd64.pyd +0 -0
  4. pythonnative/alerts.py +236 -0
  5. pythonnative/animated.py +1687 -0
  6. pythonnative/animation_graph.py +78 -0
  7. pythonnative/appearance.py +124 -0
  8. pythonnative/bootstrap.py +100 -0
  9. pythonnative/bridge/__init__.py +302 -0
  10. pythonnative/bridge/android.py +78 -0
  11. pythonnative/bridge/codec.py +170 -0
  12. pythonnative/bridge/commits.py +114 -0
  13. pythonnative/bridge/fake.py +243 -0
  14. pythonnative/bridge/ios.py +138 -0
  15. pythonnative/bridge/web.py +489 -0
  16. pythonnative/cli/__init__.py +7 -0
  17. pythonnative/cli/pn.py +1214 -0
  18. pythonnative/component.py +271 -0
  19. pythonnative/components/__init__.py +111 -0
  20. pythonnative/components/_base.py +166 -0
  21. pythonnative/components/controls.py +521 -0
  22. pythonnative/components/layout.py +518 -0
  23. pythonnative/components/lists.py +785 -0
  24. pythonnative/components/media.py +204 -0
  25. pythonnative/components/overlays.py +107 -0
  26. pythonnative/components/pressable.py +252 -0
  27. pythonnative/components/structural.py +167 -0
  28. pythonnative/components/text.py +291 -0
  29. pythonnative/devclient.py +824 -0
  30. pythonnative/devserver/__init__.py +34 -0
  31. pythonnative/devserver/server.py +718 -0
  32. pythonnative/devserver/static/animation_graph.js +82 -0
  33. pythonnative/devserver/static/bridge.js +147 -0
  34. pythonnative/devserver/static/colors.js +70 -0
  35. pythonnative/devserver/static/host.js +552 -0
  36. pythonnative/devserver/static/index.html +60 -0
  37. pythonnative/devserver/static/layout.js +85 -0
  38. pythonnative/devserver/static/preview.css +415 -0
  39. pythonnative/devserver/static/renderer.js +1994 -0
  40. pythonnative/devserver/static/shell.js +345 -0
  41. pythonnative/devserver/static/yoga/LICENSE +21 -0
  42. pythonnative/devserver/static/yoga/binaries/yoga-wasm-base64-esm.js +75 -0
  43. pythonnative/devserver/static/yoga/src/generated/YGEnums.d.ts +189 -0
  44. pythonnative/devserver/static/yoga/src/generated/YGEnums.js +211 -0
  45. pythonnative/devserver/static/yoga/src/generated/YGEnums.js.map +1 -0
  46. pythonnative/devserver/static/yoga/src/index.d.ts +12 -0
  47. pythonnative/devserver/static/yoga/src/index.js +16 -0
  48. pythonnative/devserver/static/yoga/src/index.js.map +1 -0
  49. pythonnative/devserver/static/yoga/src/load.d.ts +11 -0
  50. pythonnative/devserver/static/yoga/src/load.js +17 -0
  51. pythonnative/devserver/static/yoga/src/load.js.map +1 -0
  52. pythonnative/devserver/static/yoga/src/wrapAssembly.d.ts +155 -0
  53. pythonnative/devserver/static/yoga/src/wrapAssembly.js +125 -0
  54. pythonnative/devserver/static/yoga/src/wrapAssembly.js.map +1 -0
  55. pythonnative/devserver/watcher.py +267 -0
  56. pythonnative/devserver/ws.py +421 -0
  57. pythonnative/diagnostics.py +241 -0
  58. pythonnative/element.py +159 -0
  59. pythonnative/equality.py +19 -0
  60. pythonnative/events.py +231 -0
  61. pythonnative/gestures.py +1333 -0
  62. pythonnative/hooks.py +1621 -0
  63. pythonnative/hosts/__init__.py +63 -0
  64. pythonnative/hosts/base.py +596 -0
  65. pythonnative/hosts/native.py +302 -0
  66. pythonnative/hot_reload.py +585 -0
  67. pythonnative/layout.py +328 -0
  68. pythonnative/mutations.py +142 -0
  69. pythonnative/native/android/build.gradle +52 -0
  70. pythonnative/native/android/gradle.properties +3 -0
  71. pythonnative/native/android/settings.gradle +12 -0
  72. pythonnative/native/android/src/main/AndroidManifest.xml +11 -0
  73. pythonnative/native/android/src/main/cpp/CMakeLists.txt +9 -0
  74. pythonnative/native/android/src/main/cpp/yoga_jni.cpp +52 -0
  75. pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeModules.kt +5 -0
  76. pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeProps.kt +3705 -0
  77. pythonnative/native/android/src/main/java/com/pythonnative/generated/PNContracts.kt +129 -0
  78. pythonnative/native/android/src/main/java/com/pythonnative/runtime/PNBridge.kt +261 -0
  79. pythonnative/native/android/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  80. pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationGraph.kt +108 -0
  81. pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  82. pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +257 -0
  83. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/CommitState.kt +106 -0
  84. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  85. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +40 -0
  86. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  87. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  88. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  89. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +117 -0
  90. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  91. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +37 -0
  92. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +32 -0
  93. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  94. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  95. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  96. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  97. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  98. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +162 -0
  99. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  100. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  101. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +86 -0
  102. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  103. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  104. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScreenStackManager.kt +110 -0
  105. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  106. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  107. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  108. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  109. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +250 -0
  110. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  111. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  112. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +159 -0
  113. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  114. pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  115. pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  116. pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  117. pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/NativeLayout.kt +145 -0
  118. pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/YogaNode.kt +29 -0
  119. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  120. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  121. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +61 -0
  122. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  123. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +158 -0
  124. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  125. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +99 -0
  126. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  127. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  128. pythonnative/native/android/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  129. pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +188 -0
  130. pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  131. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNAccessibilityDelegate.kt +48 -0
  132. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNBorderDrawable.kt +76 -0
  133. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  134. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNFrameLayout.kt +49 -0
  135. pythonnative/native/android/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  136. pythonnative/native/android/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  137. pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  138. pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  139. pythonnative/native/android/src/test/java/com/pythonnative/runtime/PromiseTest.kt +89 -0
  140. pythonnative/native/ios/Package.swift +23 -0
  141. pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimationGraph.swift +199 -0
  142. pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimator.swift +312 -0
  143. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNBridge.swift +255 -0
  144. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNCommit.swift +104 -0
  145. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNRegistry.swift +142 -0
  146. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNTransaction.swift +166 -0
  147. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  148. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNButtonManagers.swift +207 -0
  149. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  150. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  151. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  152. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  153. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNImageManager.swift +222 -0
  154. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  155. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  156. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScreenStackManager.swift +107 -0
  157. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  158. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  159. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextInputManager.swift +297 -0
  160. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  161. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  162. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  163. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  164. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  165. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +155 -0
  166. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  167. pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeModules.swift +1 -0
  168. pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeProps.swift +3775 -0
  169. pythonnative/native/ios/Sources/PythonNativeKit/Generated/PNContracts.swift +46 -0
  170. pythonnative/native/ios/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  171. pythonnative/native/ios/Sources/PythonNativeKit/Layout/PNLayout.swift +173 -0
  172. pythonnative/native/ios/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  173. pythonnative/native/ios/Sources/PythonNativeKit/Modules/HostModule.swift +80 -0
  174. pythonnative/native/ios/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  175. pythonnative/native/ios/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  176. pythonnative/native/ios/Sources/PythonNativeKit/Modules/PNNativeModule.swift +168 -0
  177. pythonnative/native/ios/Sources/PythonNativeKit/Modules/PermissionModules.swift +266 -0
  178. pythonnative/native/ios/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  179. pythonnative/native/ios/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  180. pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  181. pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNViewController.swift +237 -0
  182. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  183. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  184. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  185. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  186. pythonnative/native/ios/Tests/PythonNativeKitTests/PNAnimationGraphTests.swift +26 -0
  187. pythonnative/native/ios/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  188. pythonnative/native/ios/Tests/PythonNativeKitTests/PNCommitTests.swift +86 -0
  189. pythonnative/native/ios/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  190. pythonnative/native/ios/Tests/PythonNativeKitTests/PNManagerTests.swift +126 -0
  191. pythonnative/native/ios/Tests/PythonNativeKitTests/PNModuleTests.swift +111 -0
  192. pythonnative/native/ios/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  193. pythonnative/native/yoga/LICENSE +21 -0
  194. pythonnative/native/yoga/Package.swift +9 -0
  195. pythonnative/native/yoga/VENDORED.md +2 -0
  196. pythonnative/native/yoga/include/PNStyle.h +10 -0
  197. pythonnative/native/yoga/include/module.modulemap +5 -0
  198. pythonnative/native/yoga/include/yoga/YGConfig.h +158 -0
  199. pythonnative/native/yoga/include/yoga/YGEnums.h +142 -0
  200. pythonnative/native/yoga/include/yoga/YGMacros.h +99 -0
  201. pythonnative/native/yoga/include/yoga/YGNode.h +304 -0
  202. pythonnative/native/yoga/include/yoga/YGNodeLayout.h +35 -0
  203. pythonnative/native/yoga/include/yoga/YGNodeStyle.h +130 -0
  204. pythonnative/native/yoga/include/yoga/YGPixelGrid.h +29 -0
  205. pythonnative/native/yoga/include/yoga/YGValue.h +84 -0
  206. pythonnative/native/yoga/include/yoga/Yoga.h +21 -0
  207. pythonnative/native/yoga/python.cpp +16 -0
  208. pythonnative/native/yoga/style.cpp +272 -0
  209. pythonnative/native/yoga/yoga/CMakeLists.txt +41 -0
  210. pythonnative/native/yoga/yoga/YGConfig.cpp +92 -0
  211. pythonnative/native/yoga/yoga/YGConfig.h +158 -0
  212. pythonnative/native/yoga/yoga/YGEnums.cpp +262 -0
  213. pythonnative/native/yoga/yoga/YGEnums.h +142 -0
  214. pythonnative/native/yoga/yoga/YGMacros.h +99 -0
  215. pythonnative/native/yoga/yoga/YGNode.cpp +366 -0
  216. pythonnative/native/yoga/yoga/YGNode.h +304 -0
  217. pythonnative/native/yoga/yoga/YGNodeLayout.cpp +92 -0
  218. pythonnative/native/yoga/yoga/YGNodeLayout.h +35 -0
  219. pythonnative/native/yoga/yoga/YGNodeStyle.cpp +405 -0
  220. pythonnative/native/yoga/yoga/YGNodeStyle.h +130 -0
  221. pythonnative/native/yoga/yoga/YGPixelGrid.cpp +22 -0
  222. pythonnative/native/yoga/yoga/YGPixelGrid.h +29 -0
  223. pythonnative/native/yoga/yoga/YGValue.cpp +20 -0
  224. pythonnative/native/yoga/yoga/YGValue.h +84 -0
  225. pythonnative/native/yoga/yoga/Yoga.h +21 -0
  226. pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.cpp +563 -0
  227. pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.h +41 -0
  228. pythonnative/native/yoga/yoga/algorithm/Align.h +72 -0
  229. pythonnative/native/yoga/yoga/algorithm/Baseline.cpp +78 -0
  230. pythonnative/native/yoga/yoga/algorithm/Baseline.h +21 -0
  231. pythonnative/native/yoga/yoga/algorithm/BoundAxis.h +79 -0
  232. pythonnative/native/yoga/yoga/algorithm/Cache.cpp +121 -0
  233. pythonnative/native/yoga/yoga/algorithm/Cache.h +30 -0
  234. pythonnative/native/yoga/yoga/algorithm/CalculateLayout.cpp +2428 -0
  235. pythonnative/native/yoga/yoga/algorithm/CalculateLayout.h +38 -0
  236. pythonnative/native/yoga/yoga/algorithm/FlexDirection.h +120 -0
  237. pythonnative/native/yoga/yoga/algorithm/FlexLine.cpp +124 -0
  238. pythonnative/native/yoga/yoga/algorithm/FlexLine.h +75 -0
  239. pythonnative/native/yoga/yoga/algorithm/PixelGrid.cpp +132 -0
  240. pythonnative/native/yoga/yoga/algorithm/PixelGrid.h +29 -0
  241. pythonnative/native/yoga/yoga/algorithm/SizingMode.h +73 -0
  242. pythonnative/native/yoga/yoga/algorithm/TrailingPosition.h +44 -0
  243. pythonnative/native/yoga/yoga/config/Config.cpp +135 -0
  244. pythonnative/native/yoga/yoga/config/Config.h +92 -0
  245. pythonnative/native/yoga/yoga/debug/AssertFatal.cpp +53 -0
  246. pythonnative/native/yoga/yoga/debug/AssertFatal.h +29 -0
  247. pythonnative/native/yoga/yoga/debug/Log.cpp +107 -0
  248. pythonnative/native/yoga/yoga/debug/Log.h +34 -0
  249. pythonnative/native/yoga/yoga/enums/Align.h +47 -0
  250. pythonnative/native/yoga/yoga/enums/BoxSizing.h +40 -0
  251. pythonnative/native/yoga/yoga/enums/Dimension.h +40 -0
  252. pythonnative/native/yoga/yoga/enums/Direction.h +41 -0
  253. pythonnative/native/yoga/yoga/enums/Display.h +41 -0
  254. pythonnative/native/yoga/yoga/enums/Edge.h +47 -0
  255. pythonnative/native/yoga/yoga/enums/Errata.h +41 -0
  256. pythonnative/native/yoga/yoga/enums/ExperimentalFeature.h +39 -0
  257. pythonnative/native/yoga/yoga/enums/FlexDirection.h +42 -0
  258. pythonnative/native/yoga/yoga/enums/Gutter.h +41 -0
  259. pythonnative/native/yoga/yoga/enums/Justify.h +44 -0
  260. pythonnative/native/yoga/yoga/enums/LogLevel.h +44 -0
  261. pythonnative/native/yoga/yoga/enums/MeasureMode.h +41 -0
  262. pythonnative/native/yoga/yoga/enums/NodeType.h +40 -0
  263. pythonnative/native/yoga/yoga/enums/Overflow.h +41 -0
  264. pythonnative/native/yoga/yoga/enums/PhysicalEdge.h +21 -0
  265. pythonnative/native/yoga/yoga/enums/PositionType.h +41 -0
  266. pythonnative/native/yoga/yoga/enums/Unit.h +42 -0
  267. pythonnative/native/yoga/yoga/enums/Wrap.h +41 -0
  268. pythonnative/native/yoga/yoga/enums/YogaEnums.h +85 -0
  269. pythonnative/native/yoga/yoga/event/event.cpp +87 -0
  270. pythonnative/native/yoga/yoga/event/event.h +130 -0
  271. pythonnative/native/yoga/yoga/module.modulemap +21 -0
  272. pythonnative/native/yoga/yoga/node/CachedMeasurement.h +53 -0
  273. pythonnative/native/yoga/yoga/node/LayoutResults.cpp +48 -0
  274. pythonnative/native/yoga/yoga/node/LayoutResults.h +122 -0
  275. pythonnative/native/yoga/yoga/node/LayoutableChildren.h +148 -0
  276. pythonnative/native/yoga/yoga/node/Node.cpp +443 -0
  277. pythonnative/native/yoga/yoga/node/Node.h +337 -0
  278. pythonnative/native/yoga/yoga/numeric/Comparison.h +81 -0
  279. pythonnative/native/yoga/yoga/numeric/FloatOptional.h +93 -0
  280. pythonnative/native/yoga/yoga/style/SmallValueBuffer.h +133 -0
  281. pythonnative/native/yoga/yoga/style/Style.h +757 -0
  282. pythonnative/native/yoga/yoga/style/StyleLength.h +107 -0
  283. pythonnative/native/yoga/yoga/style/StyleValueHandle.h +98 -0
  284. pythonnative/native/yoga/yoga/style/StyleValuePool.h +127 -0
  285. pythonnative/native_modules/__init__.py +124 -0
  286. pythonnative/native_modules/app_state.py +99 -0
  287. pythonnative/native_modules/battery.py +75 -0
  288. pythonnative/native_modules/biometrics.py +42 -0
  289. pythonnative/native_modules/camera.py +65 -0
  290. pythonnative/native_modules/clipboard.py +46 -0
  291. pythonnative/native_modules/fallback.py +324 -0
  292. pythonnative/native_modules/file_system.py +145 -0
  293. pythonnative/native_modules/haptics.py +75 -0
  294. pythonnative/native_modules/linking.py +123 -0
  295. pythonnative/native_modules/location.py +75 -0
  296. pythonnative/native_modules/net_info.py +112 -0
  297. pythonnative/native_modules/notifications.py +129 -0
  298. pythonnative/native_modules/permissions.py +108 -0
  299. pythonnative/native_modules/registry.py +526 -0
  300. pythonnative/native_modules/secure_store.py +52 -0
  301. pythonnative/native_modules/share.py +51 -0
  302. pythonnative/native_views/__init__.py +375 -0
  303. pythonnative/native_views/base.py +316 -0
  304. pythonnative/native_views/bridge_backend.py +277 -0
  305. pythonnative/navigation/__init__.py +103 -0
  306. pythonnative/navigation/container.py +154 -0
  307. pythonnative/navigation/handle.py +540 -0
  308. pythonnative/navigation/hooks.py +133 -0
  309. pythonnative/navigation/host.py +58 -0
  310. pythonnative/navigation/linking.py +200 -0
  311. pythonnative/navigation/navigators.py +659 -0
  312. pythonnative/navigation/screen.py +149 -0
  313. pythonnative/navigation/state.py +269 -0
  314. pythonnative/net.py +248 -0
  315. pythonnative/platform.py +166 -0
  316. pythonnative/platform_metrics.py +252 -0
  317. pythonnative/preview.py +287 -0
  318. pythonnative/profiling.py +101 -0
  319. pythonnative/project/__init__.py +68 -0
  320. pythonnative/project/android.py +557 -0
  321. pythonnative/project/builder.py +817 -0
  322. pythonnative/project/config.py +773 -0
  323. pythonnative/project/deps.py +719 -0
  324. pythonnative/project/devices.py +304 -0
  325. pythonnative/project/doctor.py +283 -0
  326. pythonnative/project/fingerprint.py +171 -0
  327. pythonnative/project/icons.py +247 -0
  328. pythonnative/project/ios.py +325 -0
  329. pythonnative/project/lockfile.py +100 -0
  330. pythonnative/project/permissions.py +361 -0
  331. pythonnative/project/plugins.py +519 -0
  332. pythonnative/project/runtime_assets.py +183 -0
  333. pythonnative/py.typed +0 -0
  334. pythonnative/query.py +153 -0
  335. pythonnative/reconciler/__init__.py +29 -0
  336. pythonnative/reconciler/boundaries.py +375 -0
  337. pythonnative/reconciler/children.py +88 -0
  338. pythonnative/reconciler/core.py +1165 -0
  339. pythonnative/reconciler/layout_pass.py +393 -0
  340. pythonnative/reconciler/vnode.py +266 -0
  341. pythonnative/refresh.py +55 -0
  342. pythonnative/runtime.py +306 -0
  343. pythonnative/scheduler.py +159 -0
  344. pythonnative/sdk/__init__.py +153 -0
  345. pythonnative/sdk/_components.py +427 -0
  346. pythonnative/sdk/builtins.py +91 -0
  347. pythonnative/sdk/codegen.py +149 -0
  348. pythonnative/sdk/module_codegen.py +184 -0
  349. pythonnative/sdk/schema.py +224 -0
  350. pythonnative/sdk/templates/contracts.kt +46 -0
  351. pythonnative/sdk/templates/contracts.swift +46 -0
  352. pythonnative/storage.py +184 -0
  353. pythonnative/style.py +841 -0
  354. pythonnative/suspense.py +183 -0
  355. pythonnative/templates/android_template/app/build.gradle +75 -0
  356. pythonnative/templates/android_template/app/proguard-rules.pro +21 -0
  357. pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +24 -0
  358. pythonnative/templates/android_template/app/src/debug/AndroidManifest.xml +7 -0
  359. pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +32 -0
  360. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +130 -0
  361. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -0
  362. pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
  363. pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +30 -0
  364. pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +10 -0
  365. pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
  366. pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
  367. pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  368. pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  369. pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  370. pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  371. pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  372. pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  373. pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  374. pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  375. pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  376. pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  377. pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +22 -0
  378. pythonnative/templates/android_template/app/src/main/res/values/colors.xml +5 -0
  379. pythonnative/templates/android_template/app/src/main/res/values/strings.xml +5 -0
  380. pythonnative/templates/android_template/app/src/main/res/values/themes.xml +9 -0
  381. pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +7 -0
  382. pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +13 -0
  383. pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +19 -0
  384. pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +17 -0
  385. pythonnative/templates/android_template/build.gradle +10 -0
  386. pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
  387. pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +6 -0
  388. pythonnative/templates/android_template/gradle.properties +23 -0
  389. pythonnative/templates/android_template/gradlew +185 -0
  390. pythonnative/templates/android_template/gradlew.bat +89 -0
  391. pythonnative/templates/android_template/settings.gradle +17 -0
  392. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +51 -0
  393. pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  394. pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
  395. pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +6 -0
  396. pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +25 -0
  397. pythonnative/templates/ios_template/ios_template/BridgingHeader.h +14 -0
  398. pythonnative/templates/ios_template/ios_template/Info.plist +28 -0
  399. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +390 -0
  400. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +61 -0
  401. pythonnative/templates/ios_template/ios_template/ViewController.swift +23 -0
  402. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +745 -0
  403. pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  404. pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +36 -0
  405. pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +41 -0
  406. pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +32 -0
  407. pythonnative/testing/__init__.py +53 -0
  408. pythonnative/testing/backend.py +276 -0
  409. pythonnative/testing/harness.py +369 -0
  410. pythonnative/utils.py +145 -0
  411. pythonnative-0.40.0.dist-info/DELVEWHEEL +2 -0
  412. pythonnative-0.40.0.dist-info/METADATA +150 -0
  413. pythonnative-0.40.0.dist-info/RECORD +418 -0
  414. pythonnative-0.40.0.dist-info/WHEEL +5 -0
  415. pythonnative-0.40.0.dist-info/entry_points.txt +2 -0
  416. pythonnative-0.40.0.dist-info/licenses/LICENSE +21 -0
  417. pythonnative-0.40.0.dist-info/top_level.txt +1 -0
  418. pythonnative.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
@@ -0,0 +1,1687 @@
1
+ """Animated values, derived animated nodes, and native-driven animation.
2
+
3
+ Modeled on React Native's ``Animated`` API with an ``async``-aware
4
+ completion contract. The core primitives are:
5
+
6
+ - [`AnimatedValue`][pythonnative.animated.AnimatedValue]: a numeric
7
+ cell attached to native view properties; animations drive it over
8
+ time.
9
+ - **Derived nodes**: every animated node supports
10
+ [`interpolate`][pythonnative.animated.AnimatedNode.interpolate]
11
+ (range mapping with numeric, color, and angle outputs) and Python
12
+ arithmetic (``opacity * 0.5``, ``x + y``, ``-value``), producing
13
+ read-only [`AnimatedNode`][pythonnative.animated.AnimatedNode]
14
+ instances that update whenever their inputs change.
15
+ - ``Animated.timing`` / ``Animated.spring`` / ``Animated.decay``:
16
+ animation factories. The objects they return implement
17
+ ``__await__``, so you can write ``await Animated.timing(v, to=1.0)``
18
+ to suspend until the animation finishes.
19
+ - ``Animated.sequence`` / ``Animated.parallel`` / ``Animated.stagger``
20
+ / ``Animated.delay`` / ``Animated.loop``: composition; also
21
+ awaitable.
22
+ - ``Animated.event``: build an event-prop callback that copies event
23
+ fields into animated values (``on_scroll=pn.Animated.event(y=v)``).
24
+ - ``Animated.diff_clamp``: accumulate an input's *deltas* into a
25
+ clamped range (the collapsing-header primitive).
26
+ - ``Animated.View`` / ``Animated.Text`` / ``Animated.Image``:
27
+ components whose ``style`` may contain animated nodes, including
28
+ inside ``transform`` entries.
29
+
30
+ Driver architecture (the **native driver**):
31
+
32
+ When an animation starts, PythonNative compiles its spec (curve,
33
+ duration, target value) and offers it to the platform handler of every
34
+ native view the value is attached to
35
+ ([`ViewHandler.start_animation`][pythonnative.native_views.base.ViewHandler.start_animation]).
36
+
37
+ - **Accepted** (iOS Core Animation, Android ``ViewPropertyAnimator`` /
38
+ ``DynamicAnimation``): the platform animates the property entirely
39
+ natively; no Python code runs per frame. Python receives exactly one
40
+ callback when the animation settles, updates the
41
+ [`AnimatedValue`][pythonnative.animated.AnimatedValue], and resolves
42
+ any awaiting tasks.
43
+ - **Declined** (unattached values, callable easings,
44
+ values feeding Python-side listeners or derived nodes): a single
45
+ background thread ticks the animation at ~60 Hz from Python, pushing
46
+ each frame through ``set_animated_property``. Semantics are
47
+ identical; only the frame source differs.
48
+
49
+ Values driven by *events* (scroll offsets via ``Animated.event``,
50
+ gesture translations) flow through Python: the native listener fires,
51
+ the bound values update, and every attachment (including derived
52
+ nodes) is pushed in the same call.
53
+
54
+ Example:
55
+ ```python
56
+ import pythonnative as pn
57
+
58
+
59
+ @pn.component
60
+ def FadeIn():
61
+ opacity = pn.use_animated_value(0.0)
62
+
63
+ async def fade_in():
64
+ await pn.Animated.timing(opacity, to=1.0, duration=400)
65
+ await pn.Animated.timing(opacity, to=0.5, duration=200)
66
+
67
+ pn.use_effect(fade_in, [])
68
+
69
+ return pn.Animated.View(
70
+ pn.Text("Hello!"),
71
+ style={"opacity": opacity, "padding": 20},
72
+ )
73
+ ```
74
+ """
75
+
76
+ from __future__ import annotations
77
+
78
+ import asyncio
79
+ import bisect
80
+ import itertools
81
+ import math
82
+ import threading
83
+ import time
84
+ import weakref
85
+ from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
86
+
87
+ from .element import Element
88
+ from .hooks import Ref, use_effect, use_ref
89
+ from .runtime import resolve_future
90
+ from .style import StyleProp, resolve_style
91
+
92
+ # Maximum frame rate at which the Python fallback ticker drives
93
+ # animations (native-driven animations run at the display's refresh
94
+ # rate, managed by the platform).
95
+ _TARGET_FPS = 60.0
96
+ _FRAME_DT = 1.0 / _TARGET_FPS
97
+
98
+ # Upper bound on how much wall-clock time the fallback loop will try to
99
+ # catch up on in a single iteration after thread starvation. At 60 fps
100
+ # this is ~333 ms of simulated motion; further drift is dropped to keep
101
+ # the loop responsive.
102
+ _MAX_CATCHUP_FRAMES = 20
103
+
104
+ _EASINGS: Dict[str, Callable[[float], float]] = {
105
+ "linear": lambda t: t,
106
+ "ease_in": lambda t: t * t,
107
+ "ease_out": lambda t: 1.0 - (1.0 - t) * (1.0 - t),
108
+ "ease_in_out": lambda t: 3.0 * t * t - 2.0 * t * t * t,
109
+ "ease_in_quad": lambda t: t * t,
110
+ "ease_out_quad": lambda t: 1.0 - (1.0 - t) * (1.0 - t),
111
+ "bounce": lambda t: (
112
+ # Robert Penner's bounce out, common easing.
113
+ 7.5625 * t * t
114
+ if t < 1 / 2.75
115
+ else (
116
+ 7.5625 * (t - 1.5 / 2.75) * (t - 1.5 / 2.75) + 0.75
117
+ if t < 2 / 2.75
118
+ else (
119
+ 7.5625 * (t - 2.25 / 2.75) * (t - 2.25 / 2.75) + 0.9375
120
+ if t < 2.5 / 2.75
121
+ else 7.5625 * (t - 2.625 / 2.75) * (t - 2.625 / 2.75) + 0.984375
122
+ )
123
+ )
124
+ ),
125
+ }
126
+
127
+
128
+ def _resolve_easing(name: Any) -> Callable[[float], float]:
129
+ if callable(name):
130
+ return name
131
+ return _EASINGS.get(str(name), _EASINGS["ease_in_out"])
132
+
133
+
134
+ def _backend() -> Any:
135
+ """Return the active native-view registry (the animation backend)."""
136
+ from .native_views import get_registry
137
+
138
+ return get_registry()
139
+
140
+
141
+ # Process-unique ids for native animations, so completion callbacks can
142
+ # be routed without holding references on the native side.
143
+ _anim_id_counter = itertools.count(1)
144
+
145
+
146
+ # ======================================================================
147
+ # AnimatedNode: the shared graph-node base
148
+ # ======================================================================
149
+
150
+
151
+ class AnimatedNode:
152
+ """Base class for every animated node (settable leaves and derived nodes).
153
+
154
+ An animated node holds a current output value and a set of
155
+ ``(tag, prop)`` **attachments** binding it to native view
156
+ properties. Whenever the node's output changes (a leaf was set or
157
+ animated, or an input of a derived node changed), the new value is
158
+ pushed to every attachment through the registry's
159
+ ``set_animated_property`` and to every Python-side listener, then
160
+ propagated to derived nodes built from this one.
161
+
162
+ Derived nodes are constructed with
163
+ [`interpolate`][pythonnative.animated.AnimatedNode.interpolate],
164
+ with Python arithmetic operators (``+``, ``-``, ``*``, ``/``,
165
+ ``%``, unary ``-``), or with ``Animated.diff_clamp``. They are
166
+ read-only: only [`AnimatedValue`][pythonnative.AnimatedValue]
167
+ leaves can be set or animated directly.
168
+ """
169
+
170
+ __slots__ = ("_subscribers", "_attachments", "_lock", "_children", "__weakref__")
171
+
172
+ def __init__(self) -> None:
173
+ self._subscribers: List[Tuple[str, Callable[[Any], None]]] = []
174
+ self._attachments: List[Tuple[int, str]] = []
175
+ self._lock = threading.Lock()
176
+ # Derived nodes built from this one. Weak so a discarded
177
+ # interpolation doesn't keep receiving pushes forever.
178
+ self._children: "weakref.WeakSet[AnimatedNode]" = weakref.WeakSet()
179
+
180
+ # -- value -----------------------------------------------------------
181
+
182
+ @property
183
+ def value(self) -> Any:
184
+ """Return the node's current output value."""
185
+ raise NotImplementedError
186
+
187
+ def __float__(self) -> float:
188
+ try:
189
+ return float(self.value)
190
+ except (TypeError, ValueError):
191
+ return 0.0
192
+
193
+ # -- graph -----------------------------------------------------------
194
+
195
+ def _adopt_child(self, child: "AnimatedNode") -> None:
196
+ with self._lock:
197
+ self._children.add(child)
198
+
199
+ def _has_dependents(self) -> bool:
200
+ """Whether any derived node consumes this node's output."""
201
+ with self._lock:
202
+ return len(self._children) > 0
203
+
204
+ def _refresh(self) -> None:
205
+ """Hook for stateful derived nodes to update from their inputs."""
206
+
207
+ def _propagate(self) -> None:
208
+ """Push the current output to attachments/listeners and descend."""
209
+ self._refresh()
210
+ current = self.value
211
+ with self._lock:
212
+ subs = list(self._subscribers)
213
+ attachments = list(self._attachments)
214
+ children = list(self._children)
215
+ if attachments:
216
+ try:
217
+ backend = _backend()
218
+ for tag, prop in attachments:
219
+ backend.set_animated_property(tag, prop, current)
220
+ except Exception:
221
+ pass
222
+ for _prop, cb in subs:
223
+ try:
224
+ cb(current)
225
+ except Exception:
226
+ pass
227
+ for child in children:
228
+ child._propagate()
229
+
230
+ # -- bindings ----------------------------------------------------------
231
+
232
+ def attach(self, tag: int, prop: str) -> Callable[[], None]:
233
+ """Bind this node to ``prop`` of the native view under ``tag``.
234
+
235
+ The current value is pushed immediately so the view reflects it
236
+ even if no animation is running. Returns a detach callable.
237
+ """
238
+ binding = (tag, prop)
239
+ with self._lock:
240
+ self._attachments.append(binding)
241
+ try:
242
+ _backend().set_animated_property(tag, prop, self.value)
243
+ except Exception:
244
+ pass
245
+
246
+ from .animation_graph import install
247
+
248
+ install(self)
249
+
250
+ def _detach() -> None:
251
+ with self._lock:
252
+ try:
253
+ self._attachments.remove(binding)
254
+ except ValueError:
255
+ pass
256
+ install(self, detached_tag=tag)
257
+
258
+ return _detach
259
+
260
+ def attachments(self) -> List[Tuple[int, str]]:
261
+ """Snapshot of the current ``(tag, prop)`` bindings."""
262
+ with self._lock:
263
+ return list(self._attachments)
264
+
265
+ # -- listeners ---------------------------------------------------------
266
+
267
+ def add_listener(self, prop: str, callback: Callable[[Any], None]) -> Callable[[], None]:
268
+ """Register ``callback`` for Python-driven changes to this node.
269
+
270
+ Returns an unsubscribe callable. ``prop`` is metadata only; it
271
+ lets the subscriber differentiate this binding from others on
272
+ the same node.
273
+ """
274
+ with self._lock:
275
+ self._subscribers.append((prop, callback))
276
+
277
+ def _unsubscribe() -> None:
278
+ with self._lock:
279
+ try:
280
+ self._subscribers.remove((prop, callback))
281
+ except ValueError:
282
+ pass
283
+
284
+ return _unsubscribe
285
+
286
+ def has_listeners(self) -> bool:
287
+ """Whether any Python-side listeners are registered."""
288
+ with self._lock:
289
+ return bool(self._subscribers)
290
+
291
+ # -- derivation --------------------------------------------------------
292
+
293
+ def interpolate(
294
+ self,
295
+ input_range: Sequence[float],
296
+ output_range: Sequence[Any],
297
+ extrapolate: str = "extend",
298
+ extrapolate_left: Optional[str] = None,
299
+ extrapolate_right: Optional[str] = None,
300
+ ) -> "AnimatedInterpolation":
301
+ """Map this node's value through an input/output range.
302
+
303
+ Mirrors React Native's ``interpolate``. ``output_range`` may
304
+ contain numbers, colors (``"#RRGGBB"`` / ``"#AARRGGBB"``), or
305
+ angle strings (``"45deg"`` / ``"0.5rad"``, emitted as numeric
306
+ degrees for the ``rotate`` transform).
307
+
308
+ Args:
309
+ input_range: Monotonically non-decreasing breakpoints for
310
+ this node's value. At least two entries.
311
+ output_range: Output breakpoints, same length as
312
+ ``input_range``.
313
+ extrapolate: Behavior outside the input range:
314
+ ``"extend"`` (continue the edge segment's slope,
315
+ default), ``"clamp"`` (pin to the edge output), or
316
+ ``"identity"`` (return the input unchanged).
317
+ extrapolate_left: Override ``extrapolate`` below the range.
318
+ extrapolate_right: Override ``extrapolate`` above the range.
319
+
320
+ Returns:
321
+ A derived, read-only animated node.
322
+
323
+ Example:
324
+ ```python
325
+ header_height = scroll_y.interpolate(
326
+ input_range=[0, 120],
327
+ output_range=[160, 56],
328
+ extrapolate="clamp",
329
+ )
330
+ ```
331
+ """
332
+ return AnimatedInterpolation(
333
+ self,
334
+ input_range,
335
+ output_range,
336
+ extrapolate=extrapolate,
337
+ extrapolate_left=extrapolate_left,
338
+ extrapolate_right=extrapolate_right,
339
+ )
340
+
341
+ # -- arithmetic --------------------------------------------------------
342
+
343
+ def __add__(self, other: Any) -> "_AnimatedOperation":
344
+ return _AnimatedOperation("add", lambda a, b: a + b, [self, other])
345
+
346
+ def __radd__(self, other: Any) -> "_AnimatedOperation":
347
+ return _AnimatedOperation("add", lambda a, b: a + b, [other, self])
348
+
349
+ def __sub__(self, other: Any) -> "_AnimatedOperation":
350
+ return _AnimatedOperation("subtract", lambda a, b: a - b, [self, other])
351
+
352
+ def __rsub__(self, other: Any) -> "_AnimatedOperation":
353
+ return _AnimatedOperation("subtract", lambda a, b: a - b, [other, self])
354
+
355
+ def __mul__(self, other: Any) -> "_AnimatedOperation":
356
+ return _AnimatedOperation("multiply", lambda a, b: a * b, [self, other])
357
+
358
+ def __rmul__(self, other: Any) -> "_AnimatedOperation":
359
+ return _AnimatedOperation("multiply", lambda a, b: a * b, [other, self])
360
+
361
+ def __truediv__(self, other: Any) -> "_AnimatedOperation":
362
+ return _AnimatedOperation("divide", lambda a, b: a / b if b else 0.0, [self, other])
363
+
364
+ def __rtruediv__(self, other: Any) -> "_AnimatedOperation":
365
+ return _AnimatedOperation("divide", lambda a, b: a / b if b else 0.0, [other, self])
366
+
367
+ def __mod__(self, other: Any) -> "_AnimatedOperation":
368
+ return _AnimatedOperation("modulo", lambda a, b: math.fmod(a, b) if b else 0.0, [self, other])
369
+
370
+ def __neg__(self) -> "_AnimatedOperation":
371
+ return _AnimatedOperation("negate", lambda a: -a, [self])
372
+
373
+
374
+ # ======================================================================
375
+ # AnimatedValue: the settable leaf
376
+ # ======================================================================
377
+
378
+
379
+ class AnimatedValue(AnimatedNode):
380
+ """A numeric cell that can be attached to native view properties.
381
+
382
+ Animated components (``Animated.View`` et al.) **attach** the value
383
+ to ``(tag, prop)`` bindings after mount. Setting the value pushes
384
+ the new number to every attached native view through the registry's
385
+ ``set_animated_property`` (and through every derived node built
386
+ from this value), and when an animation can be driven natively, the
387
+ platform animates those same bindings directly.
388
+
389
+ Python-side listeners registered via
390
+ [`add_listener`][pythonnative.animated.AnimatedNode.add_listener]
391
+ observe every Python-driven change. Natively-driven animations
392
+ intentionally skip per-frame Python callbacks (that's the point);
393
+ listeners see the final settled value.
394
+ """
395
+
396
+ __slots__ = ("_value", "_native_group")
397
+
398
+ def __init__(self, initial: float = 0.0) -> None:
399
+ super().__init__()
400
+ self._value = float(initial)
401
+ # The in-flight native animation group driving this value, if any.
402
+ self._native_group: Optional["_NativeAnimationGroup"] = None
403
+
404
+ @property
405
+ def value(self) -> float:
406
+ """Return the current numeric value (without subscribing)."""
407
+ return self._value
408
+
409
+ def set_value(self, new_value: float) -> None:
410
+ """Set the value immediately, pushing to native views and listeners."""
411
+ self._apply(float(new_value), push_native=True)
412
+ from .animation_graph import install
413
+
414
+ graph = install(self)
415
+ if graph and graph["bindings"]:
416
+ backend = _backend()
417
+ backend.set_animated_property(graph["bindings"][0][0], f"_pn_graph:{id(self)}", float(new_value))
418
+
419
+ def _apply(self, new_value: float, push_native: bool) -> None:
420
+ with self._lock:
421
+ self._value = new_value
422
+ subs = list(self._subscribers)
423
+ attachments = list(self._attachments)
424
+ children = list(self._children)
425
+ if push_native and attachments:
426
+ try:
427
+ backend = _backend()
428
+ for tag, prop in attachments:
429
+ backend.set_animated_property(tag, prop, new_value)
430
+ except Exception:
431
+ pass
432
+ for prop, cb in subs:
433
+ try:
434
+ cb(new_value)
435
+ except Exception:
436
+ pass
437
+ for child in children:
438
+ child._propagate()
439
+
440
+ # -- native handoff ------------------------------------------------
441
+
442
+ def _adopt_native_group(self, group: Optional["_NativeAnimationGroup"]) -> None:
443
+ previous = self._native_group
444
+ self._native_group = group
445
+ if previous is not None and previous is not group:
446
+ previous.cancel()
447
+
448
+ def stop_animation(self) -> None:
449
+ """Cancel any in-flight animation on this value (native or Python)."""
450
+ self._adopt_native_group(None)
451
+ _manager.cancel_for_value(self)
452
+
453
+ def __repr__(self) -> str:
454
+ return f"AnimatedValue({self._value:g})"
455
+
456
+
457
+ # ======================================================================
458
+ # Derived nodes
459
+ # ======================================================================
460
+
461
+
462
+ def _parse_color_output(value: str) -> Optional[Tuple[int, int, int, int]]:
463
+ """Parse ``"#RRGGBB"`` / ``"#AARRGGBB"`` into an ``(a, r, g, b)`` tuple."""
464
+ c = value.strip().lstrip("#")
465
+ if len(c) == 6:
466
+ c = "FF" + c
467
+ if len(c) != 8:
468
+ return None
469
+ try:
470
+ raw = int(c, 16)
471
+ except ValueError:
472
+ return None
473
+ return ((raw >> 24) & 0xFF, (raw >> 16) & 0xFF, (raw >> 8) & 0xFF, raw & 0xFF)
474
+
475
+
476
+ def _parse_angle_output(value: str) -> Optional[float]:
477
+ """Parse ``"45deg"`` / ``"0.5rad"`` into numeric degrees."""
478
+ text = value.strip()
479
+ try:
480
+ if text.endswith("deg"):
481
+ return float(text[:-3])
482
+ if text.endswith("rad"):
483
+ return math.degrees(float(text[:-3]))
484
+ except ValueError:
485
+ return None
486
+ return None
487
+
488
+
489
+ class AnimatedInterpolation(AnimatedNode):
490
+ """Read-only node mapping a parent node through an input/output range.
491
+
492
+ Built via
493
+ [`AnimatedNode.interpolate`][pythonnative.animated.AnimatedNode.interpolate];
494
+ see that method for the semantics of the arguments.
495
+ """
496
+
497
+ __slots__ = (
498
+ "_parent",
499
+ "_inputs",
500
+ "_outputs",
501
+ "_kind",
502
+ "_left",
503
+ "_right",
504
+ )
505
+
506
+ def __init__(
507
+ self,
508
+ parent: AnimatedNode,
509
+ input_range: Sequence[float],
510
+ output_range: Sequence[Any],
511
+ extrapolate: str = "extend",
512
+ extrapolate_left: Optional[str] = None,
513
+ extrapolate_right: Optional[str] = None,
514
+ ) -> None:
515
+ super().__init__()
516
+ inputs = [float(v) for v in input_range]
517
+ outputs = list(output_range)
518
+ if len(inputs) < 2:
519
+ raise ValueError("interpolate() needs at least two input_range entries")
520
+ if len(inputs) != len(outputs):
521
+ raise ValueError("interpolate() input_range and output_range must have the same length")
522
+ for a, b in zip(inputs, inputs[1:]):
523
+ if b < a:
524
+ raise ValueError("interpolate() input_range must be monotonically non-decreasing")
525
+
526
+ kind = "number"
527
+ first = outputs[0]
528
+ if isinstance(first, str):
529
+ if _parse_color_output(first) is not None:
530
+ kind = "color"
531
+ outputs = [_parse_color_output(str(v)) for v in outputs]
532
+ if any(v is None for v in outputs):
533
+ raise ValueError("interpolate() color output_range entries must all be colors")
534
+ else:
535
+ angles = [_parse_angle_output(str(v)) for v in outputs]
536
+ if any(v is None for v in angles):
537
+ raise ValueError(f"interpolate() cannot parse output value {first!r}")
538
+ outputs = angles
539
+ else:
540
+ outputs = [float(v) for v in outputs]
541
+
542
+ self._parent = parent
543
+ self._inputs = inputs
544
+ self._outputs: List[Any] = outputs
545
+ self._kind = kind
546
+ self._left = extrapolate_left or extrapolate
547
+ self._right = extrapolate_right or extrapolate
548
+ parent._adopt_child(self)
549
+
550
+ @property
551
+ def value(self) -> Any:
552
+ """Return the interpolated output for the parent's current value."""
553
+ return self._compute(float(self._parent))
554
+
555
+ def _compute(self, x: float) -> Any:
556
+ inputs = self._inputs
557
+ n = len(inputs)
558
+ if x < inputs[0]:
559
+ if self._left == "identity":
560
+ return x
561
+ if self._left == "clamp":
562
+ x = inputs[0]
563
+ i = 0
564
+ elif x > inputs[-1]:
565
+ if self._right == "identity":
566
+ return x
567
+ if self._right == "clamp":
568
+ x = inputs[-1]
569
+ i = n - 2
570
+ else:
571
+ i = max(0, min(n - 2, bisect.bisect_right(inputs, x) - 1))
572
+
573
+ x0, x1 = inputs[i], inputs[i + 1]
574
+ span = x1 - x0
575
+ t = 0.0 if span <= 0 else (x - x0) / span
576
+
577
+ if self._kind == "color":
578
+ c0 = self._outputs[i]
579
+ c1 = self._outputs[i + 1]
580
+ t_cl = max(0.0, min(1.0, t))
581
+ channels = [int(round(c0[j] + (c1[j] - c0[j]) * t_cl)) for j in range(4)]
582
+ a, r, g, b = (max(0, min(255, ch)) for ch in channels)
583
+ return f"#{a:02X}{r:02X}{g:02X}{b:02X}"
584
+
585
+ y0 = self._outputs[i]
586
+ y1 = self._outputs[i + 1]
587
+ return y0 + (y1 - y0) * t
588
+
589
+ def __repr__(self) -> str:
590
+ return f"AnimatedInterpolation({self._inputs} -> {self._outputs})"
591
+
592
+
593
+ class _AnimatedOperation(AnimatedNode):
594
+ """Read-only node computed from other nodes (and constants) by ``fn``."""
595
+
596
+ __slots__ = ("_op", "_fn", "_parents")
597
+
598
+ def __init__(self, op: str, fn: Callable[..., float], parents: List[Any]) -> None:
599
+ super().__init__()
600
+ self._op = op
601
+ self._fn = fn
602
+ self._parents = list(parents)
603
+ for parent in self._parents:
604
+ if isinstance(parent, AnimatedNode):
605
+ parent._adopt_child(self)
606
+
607
+ @property
608
+ def value(self) -> float:
609
+ args = [float(p) if isinstance(p, AnimatedNode) else float(p) for p in self._parents]
610
+ try:
611
+ return float(self._fn(*args))
612
+ except Exception:
613
+ return 0.0
614
+
615
+ def __repr__(self) -> str:
616
+ return f"AnimatedOperation({self._op})"
617
+
618
+
619
+ class _AnimatedDiffClamp(AnimatedNode):
620
+ """Accumulate a parent's *deltas* into a clamped range.
621
+
622
+ Mirrors React Native's ``Animated.diffClamp``: the output moves by
623
+ the same amount as the input but is pinned to ``[min, max]``, so
624
+ scrolling far down then slightly up immediately re-reveals a
625
+ collapsing header regardless of absolute offset.
626
+ """
627
+
628
+ __slots__ = ("_parent", "_min", "_max", "_last_input", "_current")
629
+
630
+ def __init__(self, parent: AnimatedNode, min_value: float, max_value: float) -> None:
631
+ super().__init__()
632
+ if max_value < min_value:
633
+ raise ValueError("diff_clamp() requires min_value <= max_value")
634
+ self._parent = parent
635
+ self._min = float(min_value)
636
+ self._max = float(max_value)
637
+ self._last_input = float(parent)
638
+ self._current = max(self._min, min(self._max, self._last_input))
639
+ parent._adopt_child(self)
640
+
641
+ @property
642
+ def value(self) -> float:
643
+ return self._current
644
+
645
+ def _refresh(self) -> None:
646
+ latest = float(self._parent)
647
+ delta = latest - self._last_input
648
+ self._last_input = latest
649
+ self._current = max(self._min, min(self._max, self._current + delta))
650
+
651
+
652
+ # ======================================================================
653
+ # Animated.event
654
+ # ======================================================================
655
+
656
+
657
+ class AnimatedEvent:
658
+ """Callable event handler copying event fields into animated values.
659
+
660
+ Built via ``pn.Animated.event(...)``. Each keyword argument names a
661
+ field on the incoming event payload (a dict key for scroll payloads
662
+ such as ``{"x": ..., "y": ...}``, or an attribute for
663
+ [`GestureEvent`][pythonnative.gestures.GestureEvent] instances) and
664
+ maps it onto an [`AnimatedValue`][pythonnative.AnimatedValue].
665
+
666
+ Because the result is an ordinary callable, it can be passed to any
667
+ event prop:
668
+
669
+ ```python
670
+ scroll_y = pn.use_animated_value(0.0)
671
+ pn.ScrollView(..., on_scroll=pn.Animated.event(y=scroll_y))
672
+
673
+ tx = pn.use_animated_value(0.0)
674
+ gestures.Pan(on_change=pn.Animated.event(translation_x=tx))
675
+ ```
676
+ """
677
+
678
+ __slots__ = ("_bindings", "_listener")
679
+
680
+ def __init__(self, listener: Optional[Callable[..., None]] = None, **bindings: AnimatedValue) -> None:
681
+ for name, node in bindings.items():
682
+ if not isinstance(node, AnimatedValue):
683
+ raise TypeError(
684
+ f"Animated.event() field {name!r} must map to an AnimatedValue "
685
+ f"(got {type(node).__name__}); derived nodes are read-only."
686
+ )
687
+ self._bindings = dict(bindings)
688
+ self._listener = listener
689
+
690
+ def __call__(self, payload: Any = None, *args: Any) -> None:
691
+ """Write bound payload fields into their values, then run the listener.
692
+
693
+ Args:
694
+ payload: The event payload; a dict is read by key, any
695
+ other object by attribute. Missing or non-numeric
696
+ fields are skipped.
697
+ *args: Extra positional arguments forwarded to the
698
+ listener.
699
+ """
700
+ for name, node in self._bindings.items():
701
+ raw: Any = None
702
+ if isinstance(payload, dict):
703
+ raw = payload.get(name)
704
+ elif payload is not None:
705
+ raw = getattr(payload, name, None)
706
+ if raw is None:
707
+ continue
708
+ try:
709
+ if getattr(_backend(), "install_animation_graph", None) is not None:
710
+ # Native already evaluated the graph at the input timestamp.
711
+ # Python observes the sample without echoing an older frame.
712
+ node._value = float(raw)
713
+ else:
714
+ node.set_value(float(raw))
715
+ except (TypeError, ValueError):
716
+ continue
717
+ if self._listener is not None:
718
+ try:
719
+ self._listener(payload, *args)
720
+ except Exception:
721
+ pass
722
+
723
+
724
+ # ======================================================================
725
+ # Python fallback driver
726
+ # ======================================================================
727
+
728
+
729
+ class _AnimationManager:
730
+ """Single-threaded fallback driver for Python-ticked animations.
731
+
732
+ Holds a list of ``_RunningAnimation`` instances and ticks them at
733
+ ~60 Hz. The thread starts on first use and idles when nothing is
734
+ active. Native-driven animations never touch this loop.
735
+ """
736
+
737
+ def __init__(self) -> None:
738
+ self._lock = threading.Lock()
739
+ self._animations: List[_RunningAnimation] = []
740
+ self._thread: Optional[threading.Thread] = None
741
+ self._stopped = False
742
+
743
+ def add(self, anim: "_RunningAnimation") -> None:
744
+ with self._lock:
745
+ self._animations.append(anim)
746
+ self._ensure_thread_locked()
747
+
748
+ def remove(self, anim: "_RunningAnimation") -> None:
749
+ with self._lock:
750
+ try:
751
+ self._animations.remove(anim)
752
+ except ValueError:
753
+ pass
754
+
755
+ def cancel_for_value(self, value: AnimatedValue) -> None:
756
+ """Cancel every queued/running Python-driven animation on ``value``."""
757
+ with self._lock:
758
+ stale = [a for a in self._animations if a.value is value]
759
+ for anim in stale:
760
+ self._animations.remove(anim)
761
+ for anim in stale:
762
+ anim._finish()
763
+
764
+ def _ensure_thread_locked(self) -> None:
765
+ if self._thread is not None and self._thread.is_alive():
766
+ return
767
+ self._thread = threading.Thread(target=self._loop, daemon=True, name="pn-animated")
768
+ self._thread.start()
769
+
770
+ def _loop(self) -> None:
771
+ last = time.monotonic()
772
+ # Clamping the per-tick dt is important for numerical stability:
773
+ # an underdamped spring with a 0.3 s step explodes immediately,
774
+ # and the animation thread can be starved for several frames
775
+ # during render bursts. We integrate physics on a clamped dt
776
+ # (max 2 target frames) and sub-step when wall-clock has
777
+ # advanced more than that, so the perceived motion still tracks
778
+ # real time at most a couple of frames behind. After an extreme
779
+ # starvation (e.g. the app was backgrounded for seconds) we cap
780
+ # the catch-up at ``_MAX_CATCHUP_FRAMES`` worth of physics; any
781
+ # further wall-clock drift is dropped on the floor, which keeps
782
+ # the loop responsive instead of spinning forward through
783
+ # hundreds of substeps.
784
+ max_step = _FRAME_DT * 2.0
785
+ max_catchup = _FRAME_DT * _MAX_CATCHUP_FRAMES
786
+ while not self._stopped:
787
+ now = time.monotonic()
788
+ dt = now - last
789
+ last = now
790
+ with self._lock:
791
+ active = list(self._animations)
792
+ if not active:
793
+ time.sleep(0.05)
794
+ last = time.monotonic()
795
+ continue
796
+ remaining = min(dt, max_catchup)
797
+ while remaining > 0.0:
798
+ step = remaining if remaining <= max_step else max_step
799
+ remaining -= step
800
+ for anim in active:
801
+ if getattr(anim, "_completed", False):
802
+ continue
803
+ try:
804
+ finished = anim.advance(step)
805
+ except Exception:
806
+ finished = True
807
+ if finished:
808
+ self.remove(anim)
809
+ time.sleep(_FRAME_DT)
810
+
811
+
812
+ _manager = _AnimationManager()
813
+
814
+
815
+ # ======================================================================
816
+ # Python-driven animation primitives (the fallback path)
817
+ # ======================================================================
818
+
819
+
820
+ class _RunningAnimation:
821
+ """Base class for Python-ticked animations; ``advance()`` returns True when done."""
822
+
823
+ def __init__(self, value: AnimatedValue) -> None:
824
+ self.value = value
825
+ self._completion_futures: List[asyncio.Future[None]] = []
826
+ self._completed = False
827
+
828
+ def add_completion_future(self, future: asyncio.Future[None]) -> None:
829
+ """Register ``future`` to be resolved when the animation ends."""
830
+ self._completion_futures.append(future)
831
+ if self._completed:
832
+ resolve_future(future, None)
833
+
834
+ def advance(self, dt: float) -> bool:
835
+ raise NotImplementedError
836
+
837
+ def _finish(self) -> None:
838
+ if self._completed:
839
+ return
840
+ self._completed = True
841
+ for fut in self._completion_futures:
842
+ resolve_future(fut, None)
843
+
844
+
845
+ class _TimingAnimation(_RunningAnimation):
846
+ def __init__(
847
+ self,
848
+ value: AnimatedValue,
849
+ to: float,
850
+ duration: float,
851
+ easing: Callable[[float], float],
852
+ ) -> None:
853
+ super().__init__(value)
854
+ self._from = value.value
855
+ self._to = float(to)
856
+ self._duration = max(0.001, float(duration) / 1000.0)
857
+ self._easing = easing
858
+ self._elapsed = 0.0
859
+
860
+ def advance(self, dt: float) -> bool:
861
+ self._elapsed += dt
862
+ progress = min(1.0, self._elapsed / self._duration)
863
+ eased = self._easing(progress)
864
+ new_val = self._from + (self._to - self._from) * eased
865
+ self.value.set_value(new_val)
866
+ if progress >= 1.0:
867
+ self._finish()
868
+ return True
869
+ return False
870
+
871
+
872
+ class _SpringAnimation(_RunningAnimation):
873
+ """Damped harmonic spring driver."""
874
+
875
+ def __init__(
876
+ self,
877
+ value: AnimatedValue,
878
+ to: float,
879
+ stiffness: float,
880
+ damping: float,
881
+ mass: float,
882
+ initial_velocity: float = 0.0,
883
+ ) -> None:
884
+ super().__init__(value)
885
+ self._to = float(to)
886
+ self._velocity = float(initial_velocity)
887
+ self._stiffness = float(stiffness)
888
+ self._damping = float(damping)
889
+ self._mass = float(mass)
890
+ self._rest_threshold = 0.001
891
+
892
+ def advance(self, dt: float) -> bool:
893
+ x = self.value.value
894
+ a = (-self._stiffness * (x - self._to) - self._damping * self._velocity) / self._mass
895
+ self._velocity += a * dt
896
+ new_x = x + self._velocity * dt
897
+ self.value.set_value(new_x)
898
+ if abs(new_x - self._to) < self._rest_threshold and abs(self._velocity) < self._rest_threshold:
899
+ self.value.set_value(self._to)
900
+ self._finish()
901
+ return True
902
+ return False
903
+
904
+
905
+ class _DecayAnimation(_RunningAnimation):
906
+ def __init__(self, value: AnimatedValue, velocity: float, deceleration: float) -> None:
907
+ super().__init__(value)
908
+ self._velocity = float(velocity)
909
+ self._deceleration = float(deceleration)
910
+ self._rest_threshold = 0.001
911
+
912
+ def advance(self, dt: float) -> bool:
913
+ self._velocity *= math.exp(-self._deceleration * dt * 1000.0)
914
+ new_x = self.value.value + self._velocity * dt
915
+ self.value.set_value(new_x)
916
+ if abs(self._velocity) < self._rest_threshold:
917
+ self._finish()
918
+ return True
919
+ return False
920
+
921
+
922
+ class _DelayAnimation(_RunningAnimation):
923
+ def __init__(self, duration_ms: float) -> None:
924
+ super().__init__(AnimatedValue(0.0))
925
+ self._elapsed = 0.0
926
+ self._duration = max(0.001, duration_ms / 1000.0)
927
+
928
+ def advance(self, dt: float) -> bool:
929
+ self._elapsed += dt
930
+ if self._elapsed >= self._duration:
931
+ self._finish()
932
+ return True
933
+ return False
934
+
935
+
936
+ # ======================================================================
937
+ # Native-driven animation group
938
+ # ======================================================================
939
+
940
+
941
+ class _NativeAnimationGroup:
942
+ """One logical animation fanned out to N natively-animated views.
943
+
944
+ Each attached ``(tag, prop)`` binding gets its own ``anim_id``; the
945
+ group completes when the platform reports completion for all of
946
+ them. Cancellation asks each platform handler for the current
947
+ presentation value so the ``AnimatedValue`` lands wherever the view
948
+ visually was.
949
+ """
950
+
951
+ def __init__(self, value: AnimatedValue, final_value: float) -> None:
952
+ self.value = value
953
+ self.final_value = final_value
954
+ self._targets: Dict[int, Tuple[int, str]] = {} # anim_id -> (tag, prop)
955
+ self._pending: set = set()
956
+ self._completion_futures: List[asyncio.Future[None]] = []
957
+ self._completed = False
958
+ self._lock = threading.Lock()
959
+
960
+ def add_target(self, anim_id: int, tag: int, prop: str) -> None:
961
+ with self._lock:
962
+ self._targets[anim_id] = (tag, prop)
963
+ self._pending.add(anim_id)
964
+ _native_groups[anim_id] = self
965
+
966
+ def add_completion_future(self, future: asyncio.Future[None]) -> None:
967
+ with self._lock:
968
+ done = self._completed
969
+ if not done:
970
+ self._completion_futures.append(future)
971
+ if done:
972
+ resolve_future(future, None)
973
+
974
+ def target_completed(self, anim_id: int, finished: bool) -> None:
975
+ with self._lock:
976
+ self._pending.discard(anim_id)
977
+ remaining = len(self._pending)
978
+ _native_groups.pop(anim_id, None)
979
+ if remaining == 0:
980
+ self._settle(self.final_value if finished else None)
981
+
982
+ def cancel(self) -> None:
983
+ """Cancel all in-flight native animations, syncing to presentation values."""
984
+ with self._lock:
985
+ targets = dict(self._targets)
986
+ self._pending.clear()
987
+ presentation: Optional[float] = None
988
+ try:
989
+ backend = _backend()
990
+ for anim_id, (tag, _prop) in targets.items():
991
+ _native_groups.pop(anim_id, None)
992
+ current = backend.cancel_animation(tag, anim_id)
993
+ if current is not None:
994
+ try:
995
+ presentation = float(current)
996
+ except (TypeError, ValueError):
997
+ pass
998
+ except Exception:
999
+ pass
1000
+ self._settle(presentation)
1001
+
1002
+ def _settle(self, end_value: Optional[float]) -> None:
1003
+ with self._lock:
1004
+ if self._completed:
1005
+ return
1006
+ self._completed = True
1007
+ futures = list(self._completion_futures)
1008
+ self._completion_futures.clear()
1009
+ if self.value._native_group is self:
1010
+ self.value._native_group = None
1011
+ if end_value is not None:
1012
+ # The native side already shows this value; update the
1013
+ # Python cell (and listeners) without re-pushing.
1014
+ self.value._apply(end_value, push_native=False)
1015
+ for fut in futures:
1016
+ resolve_future(fut, None)
1017
+
1018
+
1019
+ # anim_id -> group, for routing completion callbacks from platform handlers.
1020
+ _native_groups: Dict[int, _NativeAnimationGroup] = {}
1021
+
1022
+
1023
+ def native_animation_completed(anim_id: int, finished: bool = True) -> None:
1024
+ """Report a natively-driven animation as settled.
1025
+
1026
+ Called by platform handlers from their completion callbacks (iOS
1027
+ ``UIView`` completion blocks, Android ``withEndAction`` /
1028
+ ``DynamicAnimation.OnAnimationEndListener``). Safe to call from any
1029
+ thread; unknown ids are ignored (e.g. an animation cancelled
1030
+ moments before its completion fired).
1031
+
1032
+ Args:
1033
+ anim_id: The id passed to ``ViewHandler.start_animation``.
1034
+ finished: ``False`` when the platform reports the animation was
1035
+ interrupted rather than running to completion.
1036
+ """
1037
+ group = _native_groups.get(anim_id)
1038
+ if group is not None:
1039
+ group.target_completed(anim_id, finished)
1040
+
1041
+
1042
+ def _projected_final_value(spec: Dict[str, Any]) -> float:
1043
+ """Compute where an animation will settle, from its spec."""
1044
+ kind = spec.get("kind")
1045
+ if kind == "decay":
1046
+ # v(t) = v0 · e^(−k·1000·t) ⇒ ∫v dt = v0 / (k·1000)
1047
+ v0 = float(spec.get("velocity", 0.0))
1048
+ k = max(1e-6, float(spec.get("deceleration", 0.997)))
1049
+ return float(spec.get("from", 0.0)) + v0 / (k * 1000.0)
1050
+ return float(spec.get("to", spec.get("from", 0.0)))
1051
+
1052
+
1053
+ def _start_native(value: AnimatedValue, spec: Dict[str, Any]) -> Optional[_NativeAnimationGroup]:
1054
+ """Offer ``spec`` to the platform for every binding of ``value``.
1055
+
1056
+ Returns the live group when **all** bindings accepted the native
1057
+ animation; otherwise rolls back any accepted targets and returns
1058
+ ``None`` so the caller falls back to the Python ticker.
1059
+ """
1060
+ try:
1061
+ backend = _backend()
1062
+ except Exception:
1063
+ return None
1064
+ from .animation_graph import install
1065
+
1066
+ graph = install(value)
1067
+ if graph and graph["bindings"]:
1068
+ targets = [(graph["bindings"][0][0], f"_pn_graph:{id(value)}")]
1069
+ else:
1070
+ targets = value.attachments()
1071
+ if value._has_dependents():
1072
+ return None
1073
+ if not targets or value.has_listeners():
1074
+ return None
1075
+
1076
+ group = _NativeAnimationGroup(value, _projected_final_value(spec))
1077
+ accepted: List[Tuple[int, int]] = [] # (anim_id, tag)
1078
+ for tag, prop in targets:
1079
+ anim_id = next(_anim_id_counter)
1080
+ try:
1081
+ ok = backend.start_animation(tag, anim_id, prop, spec)
1082
+ except Exception:
1083
+ ok = False
1084
+ if not ok:
1085
+ for prev_id, prev_tag in accepted:
1086
+ _native_groups.pop(prev_id, None)
1087
+ try:
1088
+ backend.cancel_animation(prev_tag, prev_id)
1089
+ except Exception:
1090
+ pass
1091
+ return None
1092
+ group.add_target(anim_id, tag, prop)
1093
+ accepted.append((anim_id, tag))
1094
+ return group
1095
+
1096
+
1097
+ # ======================================================================
1098
+ # Public animation handles
1099
+ # ======================================================================
1100
+
1101
+
1102
+ class _AwaitableAnimation:
1103
+ """Base for awaitable animation handles.
1104
+
1105
+ Subclasses implement :meth:`start` and :meth:`stop`. Awaiting the
1106
+ handle (``await handle``) starts the animation if necessary and
1107
+ suspends until it completes. Cancelling the awaiting task calls
1108
+ :meth:`stop`.
1109
+
1110
+ Calling :meth:`start` returns ``self`` so handles can be chained
1111
+ or stashed: ``handle = pn.Animated.timing(...).start()``.
1112
+ """
1113
+
1114
+ def start(self) -> "_AwaitableAnimation":
1115
+ raise NotImplementedError
1116
+
1117
+ def stop(self) -> None:
1118
+ raise NotImplementedError
1119
+
1120
+ def run(self) -> "_AwaitableAnimation":
1121
+ """Return ``self`` for explicit ``await handle.run()`` style.
1122
+
1123
+ Equivalent to ``await handle`` directly; provided because some
1124
+ readers prefer the slightly more explicit form, particularly
1125
+ when storing the awaitable before resolving it.
1126
+ """
1127
+ return self
1128
+
1129
+ async def _drive(self) -> None:
1130
+ raise NotImplementedError
1131
+
1132
+ def __await__(self) -> Any:
1133
+ try:
1134
+ asyncio.get_running_loop()
1135
+ except RuntimeError as exc:
1136
+ raise RuntimeError(
1137
+ "Animations can only be awaited from inside an asyncio task; "
1138
+ "use handle.start() to fire-and-forget instead."
1139
+ ) from exc
1140
+
1141
+ async def _runner() -> None:
1142
+ try:
1143
+ await self._drive()
1144
+ except asyncio.CancelledError:
1145
+ self.stop()
1146
+ raise
1147
+
1148
+ return _runner().__await__()
1149
+
1150
+
1151
+ class _AnimationHandle(_AwaitableAnimation):
1152
+ """Public handle returned by ``Animated.timing`` / ``.spring`` / ``.decay``.
1153
+
1154
+ Each ``.start()`` call snapshots the value's current state, prefers
1155
+ the native driver, and falls back to a fresh Python-ticked
1156
+ animation otherwise (matches React Native: the ``Animated.timing``
1157
+ return value is reusable).
1158
+ """
1159
+
1160
+ def __init__(
1161
+ self,
1162
+ value: Optional[AnimatedValue],
1163
+ spec_factory: Callable[[], Dict[str, Any]],
1164
+ fallback_factory: Callable[[], _RunningAnimation],
1165
+ native_eligible: bool = True,
1166
+ ) -> None:
1167
+ self._value = value
1168
+ self._spec_factory = spec_factory
1169
+ self._fallback_factory = fallback_factory
1170
+ self._native_eligible = native_eligible
1171
+ self._python_anim: Optional[_RunningAnimation] = None
1172
+ self._native_group: Optional[_NativeAnimationGroup] = None
1173
+
1174
+ def start(self) -> "_AnimationHandle":
1175
+ """Begin the animation. Returns ``self`` for chaining."""
1176
+ self.stop()
1177
+ if self._value is not None and self._native_eligible:
1178
+ spec = self._spec_factory()
1179
+ group = _start_native(self._value, spec)
1180
+ if group is not None:
1181
+ self._native_group = group
1182
+ self._value._adopt_native_group(group)
1183
+ return self
1184
+ anim = self._fallback_factory()
1185
+ self._python_anim = anim
1186
+ _manager.add(anim)
1187
+ return self
1188
+
1189
+ def stop(self) -> None:
1190
+ """Cancel the running instance (no-op if not running)."""
1191
+ if self._native_group is not None:
1192
+ group = self._native_group
1193
+ self._native_group = None
1194
+ if self._value is not None and self._value._native_group is group:
1195
+ self._value._native_group = None
1196
+ group.cancel()
1197
+ if self._python_anim is not None:
1198
+ anim = self._python_anim
1199
+ self._python_anim = None
1200
+ anim._finish()
1201
+ _manager.remove(anim)
1202
+
1203
+ def _is_running(self) -> bool:
1204
+ if self._native_group is not None and not self._native_group._completed:
1205
+ return True
1206
+ if self._python_anim is not None and not self._python_anim._completed:
1207
+ return True
1208
+ return False
1209
+
1210
+ async def _drive(self) -> None:
1211
+ # (Re)start unless an instance is currently mid-flight, so a
1212
+ # reused handle (``Animated.loop``, awaiting the same handle
1213
+ # twice) runs a fresh animation instead of resolving instantly
1214
+ # against the finished previous instance.
1215
+ if not self._is_running():
1216
+ self.start()
1217
+ loop = asyncio.get_running_loop()
1218
+ future: asyncio.Future[None] = loop.create_future()
1219
+ if self._native_group is not None:
1220
+ self._native_group.add_completion_future(future)
1221
+ elif self._python_anim is not None:
1222
+ self._python_anim.add_completion_future(future)
1223
+ else:
1224
+ return
1225
+ await future
1226
+
1227
+
1228
+ class _CompositeAnimation(_AwaitableAnimation):
1229
+ """Run a list of animations in sequence, in parallel, or staggered."""
1230
+
1231
+ def __init__(self, items: List[Any], mode: str, stagger_ms: float = 0.0) -> None:
1232
+ self._items = list(items)
1233
+ self._mode = mode
1234
+ self._stagger_ms = float(stagger_ms)
1235
+
1236
+ def start(self) -> "_CompositeAnimation":
1237
+ """Schedule the composite on the framework runtime, fire-and-forget."""
1238
+ from .runtime import run_async
1239
+
1240
+ run_async(self._drive())
1241
+ return self
1242
+
1243
+ def stop(self) -> None:
1244
+ for item in self._items:
1245
+ try:
1246
+ item.stop()
1247
+ except Exception:
1248
+ pass
1249
+
1250
+ async def _drive(self) -> None:
1251
+ if self._mode == "parallel":
1252
+ await asyncio.gather(*(self._await_item(item) for item in self._items))
1253
+ return
1254
+ if self._mode == "stagger":
1255
+ delay_s = max(0.0, self._stagger_ms) / 1000.0
1256
+
1257
+ async def _delayed(index: int, item: Any) -> None:
1258
+ if index > 0 and delay_s > 0.0:
1259
+ await asyncio.sleep(delay_s * index)
1260
+ await self._await_item(item)
1261
+
1262
+ await asyncio.gather(*(_delayed(i, item) for i, item in enumerate(self._items)))
1263
+ return
1264
+ for item in self._items:
1265
+ await self._await_item(item)
1266
+
1267
+ @staticmethod
1268
+ async def _await_item(item: Any) -> None:
1269
+ if item is None:
1270
+ return
1271
+ # ``_AwaitableAnimation`` and plain awaitables/coroutines are
1272
+ # both supported: lets users mix in ``asyncio.sleep``.
1273
+ await item
1274
+
1275
+
1276
+ class _LoopAnimation(_AwaitableAnimation):
1277
+ """Repeat an animation, resetting its values before each iteration."""
1278
+
1279
+ def __init__(self, animation: Any, iterations: int = -1, reset: bool = True) -> None:
1280
+ self._animation = animation
1281
+ self._iterations = int(iterations)
1282
+ self._reset = bool(reset)
1283
+ self._stopped = False
1284
+
1285
+ def start(self) -> "_LoopAnimation":
1286
+ from .runtime import run_async
1287
+
1288
+ self._stopped = False
1289
+ run_async(self._drive())
1290
+ return self
1291
+
1292
+ def stop(self) -> None:
1293
+ self._stopped = True
1294
+ try:
1295
+ self._animation.stop()
1296
+ except Exception:
1297
+ pass
1298
+
1299
+ def _collect_values(self, item: Any, out: List[AnimatedValue]) -> None:
1300
+ if isinstance(item, _AnimationHandle):
1301
+ if item._value is not None and item._value not in out:
1302
+ out.append(item._value)
1303
+ elif isinstance(item, _CompositeAnimation):
1304
+ for sub in item._items:
1305
+ self._collect_values(sub, out)
1306
+ elif isinstance(item, _LoopAnimation):
1307
+ self._collect_values(item._animation, out)
1308
+
1309
+ async def _drive(self) -> None:
1310
+ self._stopped = False
1311
+ values: List[AnimatedValue] = []
1312
+ self._collect_values(self._animation, values)
1313
+ origins = [(v, v.value) for v in values]
1314
+ count = 0
1315
+ while not self._stopped and (self._iterations < 0 or count < self._iterations):
1316
+ if self._reset and count > 0:
1317
+ for value, origin in origins:
1318
+ value.set_value(origin)
1319
+ await self._animation
1320
+ count += 1
1321
+
1322
+
1323
+ # ======================================================================
1324
+ # Animated component wrappers
1325
+ # ======================================================================
1326
+
1327
+ # Transform-entry keys that may carry animated nodes; the key doubles
1328
+ # as the ``set_animated_property`` prop name.
1329
+ _ANIMATED_TRANSFORM_KEYS = frozenset(
1330
+ {"translate_x", "translate_y", "scale", "scale_x", "scale_y", "rotate"},
1331
+ )
1332
+
1333
+
1334
+ def _resolve_style_with_values(style: StyleProp) -> Tuple[Dict[str, Any], Dict[str, AnimatedNode]]:
1335
+ """Split ``style`` into a plain dict and animated bindings.
1336
+
1337
+ Animated nodes in the style (top-level values *and* values inside
1338
+ ``transform`` entries) are replaced with their current numeric
1339
+ value in ``plain_style`` and recorded in ``animated_bindings`` so
1340
+ the wrapping component can attach them after mount.
1341
+ """
1342
+ flat = resolve_style(style)
1343
+ bindings: Dict[str, AnimatedNode] = {}
1344
+ plain: Dict[str, Any] = {}
1345
+ for k, v in flat.items():
1346
+ if isinstance(v, AnimatedNode):
1347
+ bindings[k] = v
1348
+ plain[k] = v.value
1349
+ elif k == "transform" and v is not None:
1350
+ entries = v if isinstance(v, list) else [v]
1351
+ plain_entries: List[Any] = []
1352
+ for entry in entries:
1353
+ if not isinstance(entry, dict):
1354
+ plain_entries.append(entry)
1355
+ continue
1356
+ clean_entry: Dict[str, Any] = {}
1357
+ for prop, val in entry.items():
1358
+ if isinstance(val, AnimatedNode) and prop in _ANIMATED_TRANSFORM_KEYS:
1359
+ bindings[prop] = val
1360
+ clean_entry[prop] = val.value
1361
+ else:
1362
+ clean_entry[prop] = val
1363
+ plain_entries.append(clean_entry)
1364
+ plain[k] = plain_entries
1365
+ else:
1366
+ plain[k] = v
1367
+ return plain, bindings
1368
+
1369
+
1370
+ def _make_animated_factory(
1371
+ element_type: str,
1372
+ accept_children: bool,
1373
+ ) -> Callable[..., Element]:
1374
+ """Build an animated wrapper for ``element_type``."""
1375
+ from .component import Component
1376
+
1377
+ def _animated(*children: Any, **kwargs: Any) -> Element:
1378
+ from .components import Image as _Image
1379
+ from .components import Text as _Text
1380
+ from .components import View as _View
1381
+
1382
+ kids: List[Any] = list(children)
1383
+ style = kwargs.pop("style", None)
1384
+ plain_style, bindings = _resolve_style_with_values(style)
1385
+
1386
+ ref = use_ref(None)
1387
+ attached: Ref[List[Callable[[], None]]] = use_ref([])
1388
+
1389
+ def _attach_bindings() -> None:
1390
+ tag = ref._pn_tag
1391
+ if tag is None:
1392
+ return
1393
+ # Derived nodes can be rebuilt on every render. Install their new
1394
+ # bindings before releasing the old ones so a running native graph
1395
+ # never temporarily loses every owner.
1396
+ detachers = [value.attach(tag, _animated_prop_name(prop)) for prop, value in bindings.items()]
1397
+ previous = attached.current
1398
+ attached.current = detachers
1399
+ for detach in previous:
1400
+ detach()
1401
+
1402
+ def _unmount_bindings() -> Callable[[], None]:
1403
+ def _cleanup() -> None:
1404
+ for detach in attached.current:
1405
+ detach()
1406
+ attached.current = []
1407
+
1408
+ return _cleanup
1409
+
1410
+ # Re-attach whenever the binding set changes identity.
1411
+ use_effect(_attach_bindings, [tuple(sorted((k, id(v)) for k, v in bindings.items()))])
1412
+ use_effect(_unmount_bindings, [])
1413
+
1414
+ if element_type == "Text":
1415
+ text = children[0] if children else kwargs.pop("text", "")
1416
+ return _Text(text, style=plain_style, ref=ref, **kwargs)
1417
+ if element_type == "Image":
1418
+ source = kids[0] if kids else kwargs.pop("source", "")
1419
+ return _Image(source, style=plain_style, ref=ref, **kwargs)
1420
+ if not accept_children:
1421
+ kids = []
1422
+ return _View(*kids, style=plain_style, ref=ref, **kwargs)
1423
+
1424
+ return Component(_animated, display_name=f"Animated.{element_type}")
1425
+
1426
+
1427
+ def _animated_prop_name(prop: str) -> str:
1428
+ """Map a style key to the name expected by ``set_animated_property``."""
1429
+ return prop
1430
+
1431
+
1432
+ # ======================================================================
1433
+ # Public API
1434
+ # ======================================================================
1435
+
1436
+
1437
+ class _AnimatedNamespace:
1438
+ """Public ``Animated`` namespace.
1439
+
1440
+ Exposes the ``Value`` type, animation factories, composers,
1441
+ derived-node helpers (``event``, ``diff_clamp``), and component
1442
+ wrappers (``View``, ``Text``, ``Image``).
1443
+ """
1444
+
1445
+ Value = AnimatedValue
1446
+
1447
+ @staticmethod
1448
+ def timing(
1449
+ value: AnimatedValue,
1450
+ *,
1451
+ to: float,
1452
+ duration: float = 300.0,
1453
+ easing: Any = "ease_in_out",
1454
+ ) -> _AnimationHandle:
1455
+ """Interpolate ``value`` to ``to`` over ``duration`` ms with ``easing``."""
1456
+
1457
+ def _spec() -> Dict[str, Any]:
1458
+ return {
1459
+ "kind": "timing",
1460
+ "from": value.value,
1461
+ "to": float(to),
1462
+ "duration_ms": float(duration),
1463
+ "easing": str(easing),
1464
+ }
1465
+
1466
+ def _fallback() -> _RunningAnimation:
1467
+ return _TimingAnimation(value, to, duration, _resolve_easing(easing))
1468
+
1469
+ # Callable easings can't cross the bridge; tick them in Python.
1470
+ return _AnimationHandle(value, _spec, _fallback, native_eligible=not callable(easing))
1471
+
1472
+ @staticmethod
1473
+ def spring(
1474
+ value: AnimatedValue,
1475
+ *,
1476
+ to: float,
1477
+ stiffness: float = 100.0,
1478
+ damping: float = 10.0,
1479
+ mass: float = 1.0,
1480
+ initial_velocity: float = 0.0,
1481
+ ) -> _AnimationHandle:
1482
+ """Run a damped harmonic spring toward ``to``."""
1483
+
1484
+ def _spec() -> Dict[str, Any]:
1485
+ return {
1486
+ "kind": "spring",
1487
+ "from": value.value,
1488
+ "to": float(to),
1489
+ "stiffness": float(stiffness),
1490
+ "damping": float(damping),
1491
+ "mass": float(mass),
1492
+ "initial_velocity": float(initial_velocity),
1493
+ }
1494
+
1495
+ def _fallback() -> _RunningAnimation:
1496
+ return _SpringAnimation(value, to, stiffness, damping, mass, initial_velocity)
1497
+
1498
+ return _AnimationHandle(value, _spec, _fallback)
1499
+
1500
+ @staticmethod
1501
+ def decay(
1502
+ value: AnimatedValue,
1503
+ *,
1504
+ velocity: float,
1505
+ deceleration: float = 0.997,
1506
+ ) -> _AnimationHandle:
1507
+ """Decelerate ``value`` from ``velocity`` (units/ms) until it rests."""
1508
+
1509
+ def _spec() -> Dict[str, Any]:
1510
+ return {
1511
+ "kind": "decay",
1512
+ "from": value.value,
1513
+ "velocity": float(velocity),
1514
+ "deceleration": float(deceleration),
1515
+ }
1516
+
1517
+ def _fallback() -> _RunningAnimation:
1518
+ return _DecayAnimation(value, velocity, deceleration)
1519
+
1520
+ return _AnimationHandle(value, _spec, _fallback)
1521
+
1522
+ @staticmethod
1523
+ def parallel(animations: List[Any]) -> _CompositeAnimation:
1524
+ """Run all ``animations`` concurrently; complete when all finish."""
1525
+ return _CompositeAnimation(animations, "parallel")
1526
+
1527
+ @staticmethod
1528
+ def sequence(animations: List[Any]) -> _CompositeAnimation:
1529
+ """Run ``animations`` one after another."""
1530
+ return _CompositeAnimation(animations, "sequence")
1531
+
1532
+ @staticmethod
1533
+ def stagger(delay: float, animations: List[Any]) -> _CompositeAnimation:
1534
+ """Run ``animations`` in parallel, each starting ``delay`` ms after the previous.
1535
+
1536
+ Args:
1537
+ delay: Milliseconds between successive starts.
1538
+ animations: Animation handles (or awaitables) to run.
1539
+
1540
+ Example:
1541
+ ```python
1542
+ pn.Animated.stagger(80, [
1543
+ pn.Animated.timing(v, to=1.0) for v in card_opacities
1544
+ ]).start()
1545
+ ```
1546
+ """
1547
+ return _CompositeAnimation(animations, "stagger", stagger_ms=delay)
1548
+
1549
+ @staticmethod
1550
+ def loop(animation: Any, *, iterations: int = -1, reset: bool = True) -> _LoopAnimation:
1551
+ """Repeat ``animation``, optionally forever.
1552
+
1553
+ Values driven by the animation are captured when the loop
1554
+ starts and restored before each iteration (matching React
1555
+ Native's ``resetBeforeIteration``), so ``timing`` loops replay
1556
+ the same motion instead of animating in place.
1557
+
1558
+ Args:
1559
+ animation: A handle from ``timing`` / ``spring`` / ``decay``
1560
+ or a ``sequence`` / ``parallel`` / ``stagger`` composite.
1561
+ iterations: Number of repetitions; ``-1`` (default) loops
1562
+ until [`stop`][pythonnative.animated._LoopAnimation.stop]
1563
+ is called or the awaiting task is cancelled.
1564
+ reset: When ``False``, values continue from wherever the
1565
+ previous iteration ended.
1566
+
1567
+ Example:
1568
+ ```python
1569
+ pulse = pn.Animated.loop(
1570
+ pn.Animated.sequence([
1571
+ pn.Animated.timing(scale, to=1.15, duration=350),
1572
+ pn.Animated.timing(scale, to=1.0, duration=350),
1573
+ ]),
1574
+ ).start()
1575
+ # later: pulse.stop()
1576
+ ```
1577
+ """
1578
+ return _LoopAnimation(animation, iterations=iterations, reset=reset)
1579
+
1580
+ @staticmethod
1581
+ def delay(duration: float) -> _AnimationHandle:
1582
+ """Wait ``duration`` ms before continuing in a sequence."""
1583
+
1584
+ def _spec() -> Dict[str, Any]:
1585
+ return {"kind": "delay", "duration_ms": float(duration)}
1586
+
1587
+ def _fallback() -> _RunningAnimation:
1588
+ return _DelayAnimation(duration)
1589
+
1590
+ return _AnimationHandle(None, _spec, _fallback)
1591
+
1592
+ @staticmethod
1593
+ def event(listener: Optional[Callable[..., None]] = None, **bindings: AnimatedValue) -> AnimatedEvent:
1594
+ """Build a callback that copies event fields into animated values.
1595
+
1596
+ Pass the result to any event prop. Each keyword maps a payload
1597
+ field (dict key or dataclass attribute) onto an
1598
+ [`AnimatedValue`][pythonnative.AnimatedValue]:
1599
+
1600
+ ```python
1601
+ scroll_y = pn.use_animated_value(0.0)
1602
+ pn.ScrollView(..., on_scroll=pn.Animated.event(y=scroll_y))
1603
+
1604
+ tx = pn.use_animated_value(0.0)
1605
+ gestures.Pan(on_change=pn.Animated.event(translation_x=tx))
1606
+ ```
1607
+
1608
+ Args:
1609
+ listener: Optional plain callback invoked with the raw
1610
+ event after the values update.
1611
+ **bindings: ``field_name=animated_value`` pairs.
1612
+
1613
+ Returns:
1614
+ A callable [`AnimatedEvent`][pythonnative.animated.AnimatedEvent].
1615
+ """
1616
+ return AnimatedEvent(listener, **bindings)
1617
+
1618
+ @staticmethod
1619
+ def diff_clamp(node: AnimatedNode, min_value: float, max_value: float) -> AnimatedNode:
1620
+ """Accumulate ``node``'s deltas into ``[min_value, max_value]``.
1621
+
1622
+ The classic collapsing-header primitive: unlike ``interpolate``
1623
+ with ``"clamp"`` (which pins the *absolute* input), the output
1624
+ tracks input *movement*, so a small scroll upward immediately
1625
+ re-reveals the header no matter how far down the list is.
1626
+
1627
+ ```python
1628
+ header_shift = pn.Animated.diff_clamp(scroll_y, 0, 56)
1629
+ ```
1630
+ """
1631
+ return _AnimatedDiffClamp(node, min_value, max_value)
1632
+
1633
+ View = staticmethod(_make_animated_factory("View", accept_children=True))
1634
+ Text = staticmethod(_make_animated_factory("Text", accept_children=False))
1635
+ Image = staticmethod(_make_animated_factory("Image", accept_children=False))
1636
+
1637
+
1638
+ Animated = _AnimatedNamespace()
1639
+
1640
+
1641
+ def use_animated_value(initial: float = 0.0) -> AnimatedValue:
1642
+ """Return an [`AnimatedValue`][pythonnative.AnimatedValue] that is stable across renders.
1643
+
1644
+ Convenience wrapper for the common pattern
1645
+ ``pn.use_memo(lambda: AnimatedValue(initial), [])``. The same
1646
+ instance is returned on every render of the same component, so
1647
+ you can drive it from event handlers without recreating it.
1648
+
1649
+ Args:
1650
+ initial: The starting numeric value.
1651
+
1652
+ Returns:
1653
+ A mount-stable [`AnimatedValue`][pythonnative.AnimatedValue].
1654
+
1655
+ Example:
1656
+ ```python
1657
+ import pythonnative as pn
1658
+
1659
+
1660
+ @pn.component
1661
+ def FadeIn():
1662
+ opacity = pn.use_animated_value(0.0)
1663
+
1664
+ async def fade_in():
1665
+ await pn.Animated.timing(opacity, to=1.0, duration=300)
1666
+
1667
+ pn.use_effect(fade_in, [])
1668
+ return pn.Animated.View(
1669
+ pn.Text("Hello"),
1670
+ style=pn.style(opacity=opacity),
1671
+ )
1672
+ ```
1673
+ """
1674
+ from .hooks import use_memo
1675
+
1676
+ return use_memo(lambda: AnimatedValue(initial), [])
1677
+
1678
+
1679
+ __all__ = [
1680
+ "AnimatedNode",
1681
+ "AnimatedValue",
1682
+ "AnimatedInterpolation",
1683
+ "AnimatedEvent",
1684
+ "Animated",
1685
+ "use_animated_value",
1686
+ "native_animation_completed",
1687
+ ]