pyxpo 0.1.0a1__tar.gz
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.
- pyxpo-0.1.0a1/LICENSE +21 -0
- pyxpo-0.1.0a1/MANIFEST.in +12 -0
- pyxpo-0.1.0a1/PKG-INFO +136 -0
- pyxpo-0.1.0a1/README.md +107 -0
- pyxpo-0.1.0a1/pyproject.toml +54 -0
- pyxpo-0.1.0a1/setup.cfg +4 -0
- pyxpo-0.1.0a1/src/pyxpo/__init__.py +106 -0
- pyxpo-0.1.0a1/src/pyxpo/__main__.py +6 -0
- pyxpo-0.1.0a1/src/pyxpo/_internal/__init__.py +0 -0
- pyxpo-0.1.0a1/src/pyxpo/_internal/modname.py +15 -0
- pyxpo-0.1.0a1/src/pyxpo/_internal/svgpath.py +117 -0
- pyxpo-0.1.0a1/src/pyxpo/_keys.py +53 -0
- pyxpo-0.1.0a1/src/pyxpo/_native/common/pyxpo_bridge.c +115 -0
- pyxpo-0.1.0a1/src/pyxpo/_native/common/pyxpo_bridge.h +26 -0
- pyxpo-0.1.0a1/src/pyxpo/_native/common/pyxpo_layout.c +918 -0
- pyxpo-0.1.0a1/src/pyxpo/_native/common/pyxpo_layout.h +47 -0
- pyxpo-0.1.0a1/src/pyxpo/_native/common/pyxpo_utf.c +80 -0
- pyxpo-0.1.0a1/src/pyxpo/_native/common/pyxpo_utf.h +18 -0
- pyxpo-0.1.0a1/src/pyxpo/animated/__init__.py +38 -0
- pyxpo-0.1.0a1/src/pyxpo/animated/api.py +237 -0
- pyxpo-0.1.0a1/src/pyxpo/animated/easing.py +135 -0
- pyxpo-0.1.0a1/src/pyxpo/animated/engine.py +351 -0
- pyxpo-0.1.0a1/src/pyxpo/animated/nodes.py +167 -0
- pyxpo-0.1.0a1/src/pyxpo/animated/runtime.py +183 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/__init__.py +1 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/_bridge.py +57 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/_env.py +77 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/_events.py +22 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/alert.py +46 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/appearance.py +27 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/appstate.py +36 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/back_handler.py +51 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/clipboard.py +15 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/constants.py +71 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/dimensions.py +33 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/i18n_manager.py +18 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/keyboard.py +43 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/linking.py +35 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/pan_responder.py +72 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/pixel_ratio.py +24 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/platform.py +36 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/splash_screen.py +17 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/toast_android.py +22 -0
- pyxpo-0.1.0a1/src/pyxpo/apis/vibration.py +21 -0
- pyxpo-0.1.0a1/src/pyxpo/app.py +384 -0
- pyxpo-0.1.0a1/src/pyxpo/app_config/__init__.py +33 -0
- pyxpo-0.1.0a1/src/pyxpo/app_config/colors.py +30 -0
- pyxpo-0.1.0a1/src/pyxpo/app_config/images.py +120 -0
- pyxpo-0.1.0a1/src/pyxpo/app_config/resolve.py +142 -0
- pyxpo-0.1.0a1/src/pyxpo/app_config/schema.py +238 -0
- pyxpo-0.1.0a1/src/pyxpo/build/__init__.py +0 -0
- pyxpo-0.1.0a1/src/pyxpo/build/android.py +332 -0
- pyxpo-0.1.0a1/src/pyxpo/build/android_config.py +196 -0
- pyxpo-0.1.0a1/src/pyxpo/build/app_json.py +25 -0
- pyxpo-0.1.0a1/src/pyxpo/build/autolink.py +336 -0
- pyxpo-0.1.0a1/src/pyxpo/build/elfcheck.py +40 -0
- pyxpo-0.1.0a1/src/pyxpo/build/icons.py +95 -0
- pyxpo-0.1.0a1/src/pyxpo/build/ios.py +299 -0
- pyxpo-0.1.0a1/src/pyxpo/build/ios_config.py +100 -0
- pyxpo-0.1.0a1/src/pyxpo/build/ios_wrap.py +97 -0
- pyxpo-0.1.0a1/src/pyxpo/build/prebuild.py +117 -0
- pyxpo-0.1.0a1/src/pyxpo/build/runtime.py +298 -0
- pyxpo-0.1.0a1/src/pyxpo/build/runtime_manifest.json +25 -0
- pyxpo-0.1.0a1/src/pyxpo/build/splash.py +116 -0
- pyxpo-0.1.0a1/src/pyxpo/channel.py +40 -0
- pyxpo-0.1.0a1/src/pyxpo/channel_native.py +21 -0
- pyxpo-0.1.0a1/src/pyxpo/cli.py +399 -0
- pyxpo-0.1.0a1/src/pyxpo/component.py +182 -0
- pyxpo-0.1.0a1/src/pyxpo/components/__init__.py +1 -0
- pyxpo-0.1.0a1/src/pyxpo/components/_common.py +16 -0
- pyxpo-0.1.0a1/src/pyxpo/components/_handles.py +53 -0
- pyxpo-0.1.0a1/src/pyxpo/components/_native.py +29 -0
- pyxpo-0.1.0a1/src/pyxpo/components/activity_indicator.py +18 -0
- pyxpo-0.1.0a1/src/pyxpo/components/button.py +23 -0
- pyxpo-0.1.0a1/src/pyxpo/components/image.py +36 -0
- pyxpo-0.1.0a1/src/pyxpo/components/keyboard_avoiding_view.py +25 -0
- pyxpo-0.1.0a1/src/pyxpo/components/lists.py +245 -0
- pyxpo-0.1.0a1/src/pyxpo/components/modal.py +70 -0
- pyxpo-0.1.0a1/src/pyxpo/components/picker.py +53 -0
- pyxpo-0.1.0a1/src/pyxpo/components/pressable.py +79 -0
- pyxpo-0.1.0a1/src/pyxpo/components/refresh_control.py +12 -0
- pyxpo-0.1.0a1/src/pyxpo/components/safe_area_view.py +28 -0
- pyxpo-0.1.0a1/src/pyxpo/components/scroll_view.py +28 -0
- pyxpo-0.1.0a1/src/pyxpo/components/slider.py +23 -0
- pyxpo-0.1.0a1/src/pyxpo/components/status_bar.py +70 -0
- pyxpo-0.1.0a1/src/pyxpo/components/switch.py +19 -0
- pyxpo-0.1.0a1/src/pyxpo/components/text.py +81 -0
- pyxpo-0.1.0a1/src/pyxpo/components/text_input.py +8 -0
- pyxpo-0.1.0a1/src/pyxpo/components/touchables.py +87 -0
- pyxpo-0.1.0a1/src/pyxpo/components/view.py +5 -0
- pyxpo-0.1.0a1/src/pyxpo/config.py +76 -0
- pyxpo-0.1.0a1/src/pyxpo/config_plugins.py +230 -0
- pyxpo-0.1.0a1/src/pyxpo/dev/__init__.py +1 -0
- pyxpo-0.1.0a1/src/pyxpo/dev/client.py +76 -0
- pyxpo-0.1.0a1/src/pyxpo/dev/reloader.py +257 -0
- pyxpo-0.1.0a1/src/pyxpo/dev/server.py +245 -0
- pyxpo-0.1.0a1/src/pyxpo/element.py +75 -0
- pyxpo-0.1.0a1/src/pyxpo/fonts.py +53 -0
- pyxpo-0.1.0a1/src/pyxpo/gesture_handler.py +350 -0
- pyxpo-0.1.0a1/src/pyxpo/hooks.py +424 -0
- pyxpo-0.1.0a1/src/pyxpo/libs.py +42 -0
- pyxpo-0.1.0a1/src/pyxpo/module_api.py +44 -0
- pyxpo-0.1.0a1/src/pyxpo/module_testing.py +51 -0
- pyxpo-0.1.0a1/src/pyxpo/modules.py +53 -0
- pyxpo-0.1.0a1/src/pyxpo/native_config.py +32 -0
- pyxpo-0.1.0a1/src/pyxpo/ops.py +130 -0
- pyxpo-0.1.0a1/src/pyxpo/platforms.py +11 -0
- pyxpo-0.1.0a1/src/pyxpo/reconciler.py +469 -0
- pyxpo-0.1.0a1/src/pyxpo/renderers/__init__.py +0 -0
- pyxpo-0.1.0a1/src/pyxpo/renderers/console.py +64 -0
- pyxpo-0.1.0a1/src/pyxpo/renderers/mock.py +211 -0
- pyxpo-0.1.0a1/src/pyxpo/router/__init__.py +18 -0
- pyxpo-0.1.0a1/src/pyxpo/router/discovery.py +208 -0
- pyxpo-0.1.0a1/src/pyxpo/router/entry.py +23 -0
- pyxpo-0.1.0a1/src/pyxpo/router/hooks.py +143 -0
- pyxpo-0.1.0a1/src/pyxpo/router/layouts/__init__.py +0 -0
- pyxpo-0.1.0a1/src/pyxpo/router/layouts/drawer.py +107 -0
- pyxpo-0.1.0a1/src/pyxpo/router/layouts/header.py +53 -0
- pyxpo-0.1.0a1/src/pyxpo/router/layouts/slot.py +9 -0
- pyxpo-0.1.0a1/src/pyxpo/router/layouts/stack.py +62 -0
- pyxpo-0.1.0a1/src/pyxpo/router/layouts/tabs.py +84 -0
- pyxpo-0.1.0a1/src/pyxpo/router/link.py +36 -0
- pyxpo-0.1.0a1/src/pyxpo/router/matching.py +122 -0
- pyxpo-0.1.0a1/src/pyxpo/router/root.py +85 -0
- pyxpo-0.1.0a1/src/pyxpo/router/router.py +57 -0
- pyxpo-0.1.0a1/src/pyxpo/router/runtime.py +59 -0
- pyxpo-0.1.0a1/src/pyxpo/router/screen.py +114 -0
- pyxpo-0.1.0a1/src/pyxpo/router/store.py +469 -0
- pyxpo-0.1.0a1/src/pyxpo/router/theme.py +32 -0
- pyxpo-0.1.0a1/src/pyxpo/safe_area.py +28 -0
- pyxpo-0.1.0a1/src/pyxpo/style.py +387 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/cpp/CMakeLists.txt +13 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/cpp/pyxpo_jni.c +151 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/cpp/pyxpo_layout_jni.c +111 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/module-api/build.gradle.kts +16 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/module-api/src/main/AndroidManifest.xml +1 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/module-api/src/main/java/dev/pyxpo/moduleapi/PxModule.kt +32 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/Animated.kt +374 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/AppConfig.kt +63 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/BoxDrawable.kt +133 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/Images.kt +99 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/MainActivity.kt +142 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/NativeLayout.kt +63 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/OpApplier.kt +1244 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/PxControls.kt +135 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/PxModuleHost.kt +94 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/PxModuleRegistry.kt +6 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/PxPerf.kt +32 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/PxViews.kt +197 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/gen/runtime/java/dev/pyxpo/runtime/PyxpoRuntime.kt +92 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/user/app/build.gradle.kts +79 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/user/app/src/main/AndroidManifest.xml +47 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/user/build.gradle.kts +4 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/user/gradle/wrapper/gradle-wrapper.jar +0 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/user/gradle/wrapper/gradle-wrapper.properties +7 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/user/gradle.properties +3 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/user/gradlew +252 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/user/gradlew.bat +94 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/android/user/settings.gradle.kts +11 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/app/app/+not-found.py.tmpl +20 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/app/app/__init__.py.tmpl +0 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/app/app/_layout.py.tmpl +13 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/app/app/details/[id].py.tmpl +20 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/app/app/index.py.tmpl +24 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/app/gitignore.tmpl +2 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/app/pyproject.toml.tmpl +9 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/gen/runtime/Animated.swift +414 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/gen/runtime/AppConfig.swift +71 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/gen/runtime/Images.swift +35 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/gen/runtime/OpApplier.swift +974 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/gen/runtime/PxModule.swift +78 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/gen/runtime/PxModuleRegistry.swift +2 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/gen/runtime/PxViews.swift +405 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/gen/runtime/PyxpoRuntime.h +18 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/gen/runtime/PyxpoRuntime.m +140 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/gen/scripts/dylib-Info-template.plist +26 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/gen/scripts/install_python.sh +21 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/user/App/AppDelegate.swift +121 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/user/App/Info.plist +38 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/user/App/Pyxpo-Bridging-Header.h +2 -0
- pyxpo-0.1.0a1/src/pyxpo/templates/ios/user/project.yml +52 -0
- pyxpo-0.1.0a1/src/pyxpo/term.py +39 -0
- pyxpo-0.1.0a1/src/pyxpo/vector_icons.py +89 -0
- pyxpo-0.1.0a1/src/pyxpo.egg-info/PKG-INFO +136 -0
- pyxpo-0.1.0a1/src/pyxpo.egg-info/SOURCES.txt +187 -0
- pyxpo-0.1.0a1/src/pyxpo.egg-info/dependency_links.txt +1 -0
- pyxpo-0.1.0a1/src/pyxpo.egg-info/entry_points.txt +2 -0
- pyxpo-0.1.0a1/src/pyxpo.egg-info/requires.txt +6 -0
- pyxpo-0.1.0a1/src/pyxpo.egg-info/top_level.txt +1 -0
pyxpo-0.1.0a1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vishal Chaubey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
include LICENSE README.md
|
|
2
|
+
recursive-include src/pyxpo/templates *
|
|
3
|
+
recursive-include src/pyxpo/_native *
|
|
4
|
+
include src/pyxpo/build/runtime_manifest.json
|
|
5
|
+
prune dist
|
|
6
|
+
prune native
|
|
7
|
+
prune .cache
|
|
8
|
+
prune tests
|
|
9
|
+
prune wheels
|
|
10
|
+
prune examples
|
|
11
|
+
prune packages
|
|
12
|
+
global-exclude __pycache__ *.pyc .DS_Store
|
pyxpo-0.1.0a1/PKG-INFO
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyxpo
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Python-first mobile app framework with React-style components and native widgets
|
|
5
|
+
Author: Vishal Chaubey
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pyxpo/pyxpo
|
|
8
|
+
Project-URL: Issues, https://github.com/pyxpo/pyxpo/issues
|
|
9
|
+
Keywords: mobile,android,ios,react-native,expo,python,ui,framework
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: tomli>=2; python_version < "3.11"
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# Pyxpo
|
|
31
|
+
|
|
32
|
+
React-Native-style Python framework: declarative components + hooks -> reconciler -> batched JSON ops -> native widgets.
|
|
33
|
+
See DESIGN.md (architecture) and PLAN.md (roadmap). Android and iOS are both supported.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
```
|
|
37
|
+
pip install pyxpo
|
|
38
|
+
pyxpo create demo && cd demo
|
|
39
|
+
pyxpo doctor # check Xcode / Android SDK / Gradle
|
|
40
|
+
pyxpo run android # or: pyxpo run ios
|
|
41
|
+
```
|
|
42
|
+
The first mobile build downloads the prebuilt CPython runtime (about 20-60 MB per target) into `~/.cache/pyxpo`.
|
|
43
|
+
|
|
44
|
+
## Develop pyxpo itself
|
|
45
|
+
```
|
|
46
|
+
tools/dev_install.sh # .venv with core + every module package in packages/, all editable
|
|
47
|
+
.venv/bin/pyxpo create demo && cd demo && ../.venv/bin/pyxpo run desktop # type 1 + Enter to press the button, q to quit
|
|
48
|
+
.venv/bin/pytest
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
A project is an `app/` folder of routes, like Expo Router:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
app/
|
|
55
|
+
_layout.py # Stack / Tabs / Drawer / Slot around the routes below
|
|
56
|
+
index.py # "/"
|
|
57
|
+
details/[id].py # "/details/7" -> useLocalSearchParams()["id"]
|
|
58
|
+
+not-found.py # unmatched URLs
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# app/index.py
|
|
63
|
+
from pyxpo import Button, Text, View, useState
|
|
64
|
+
from pyxpo.router import Link
|
|
65
|
+
|
|
66
|
+
def Index():
|
|
67
|
+
n, setN = useState(0)
|
|
68
|
+
return View(Text("Count: ", n), Button(title="Add", onPress=lambda: setN(n + 1)),
|
|
69
|
+
Link("Details", href="/details/%d" % n))
|
|
70
|
+
|
|
71
|
+
default = Index
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Hooks: use_state, use_effect, use_ref, use_memo, use_callback, use_context, use_async. Lists need `key=`.
|
|
75
|
+
`pyxpo add-lib <pkg>` checks mobile wheel availability (note: full tensorflow/torch cannot run on mobile).
|
|
76
|
+
|
|
77
|
+
## Native modules
|
|
78
|
+
|
|
79
|
+
Features beyond the core are optional native modules, each its own pip package (like Expo's `expo-image-picker`).
|
|
80
|
+
Only the modules an app lists are compiled into it.
|
|
81
|
+
|
|
82
|
+
| Package | Import | `modules` entry |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `pyxpo-file-system` | `from pyxpo_file_system import File, Directory, Paths` | `file-system` |
|
|
85
|
+
| `pyxpo-image-manipulator` | `from pyxpo_image_manipulator import ImageManipulator` | `image-manipulator` |
|
|
86
|
+
| `pyxpo-image-picker` | `from pyxpo_image_picker import ImagePicker` | `image-picker` |
|
|
87
|
+
| `pyxpo-media-library` | `from pyxpo_media_library import MediaLibrary` | `media-library` |
|
|
88
|
+
| `pyxpo-secure-store` | `import pyxpo_secure_store as SecureStore` | `secure-store` |
|
|
89
|
+
| `pyxpo-sharing` | `from pyxpo_sharing import Share` | `sharing` |
|
|
90
|
+
| `pyxpo-storage` | `from pyxpo_storage import storage` | `storage` |
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
pip install pyxpo-image-picker
|
|
94
|
+
```
|
|
95
|
+
```toml
|
|
96
|
+
[tool.pyxpo]
|
|
97
|
+
modules = ["image-picker"]
|
|
98
|
+
```
|
|
99
|
+
Their sources live in `packages/`; see each package's README. Third-party modules are pip packages that register a
|
|
100
|
+
`pyxpo.modules` entry point the same way.
|
|
101
|
+
|
|
102
|
+
## React Native style API
|
|
103
|
+
Every RN spelling is an alias; the snake_case API keeps working. No JSX (not valid Python), so children are call arguments.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from pyxpo import StyleSheet, View, Text, Pressable, FlatList, SectionList, useState, useEffect, component
|
|
107
|
+
|
|
108
|
+
styles = StyleSheet.create( # or StyleSheet.create({"title": {...}})
|
|
109
|
+
title=dict(fontSize=24, fontWeight="bold", marginBottom=12),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
@component
|
|
113
|
+
def App():
|
|
114
|
+
count, setCount = useState(0)
|
|
115
|
+
useEffect(lambda: print("count", count), [count])
|
|
116
|
+
return View(
|
|
117
|
+
Text("Hello", style=styles.title),
|
|
118
|
+
Pressable(Text("+"), onPress=lambda: setCount(count + 1),
|
|
119
|
+
style=lambda state: {"opacity": 0.5 if state.pressed else 1}),
|
|
120
|
+
FlatList(data=[1, 2], renderItem=lambda p: Text(p["item"]), keyExtractor=lambda i, _: i),
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- Style keys and props accept camelCase (`backgroundColor`, `onPress`, `numberOfLines`, `placeholderTextColor`, `testID`). Giving both spellings of one prop raises `TypeError`. User components accept a camelCase key when its snake_case form is a parameter.
|
|
125
|
+
- Hooks: `useState`, `useEffect`, `useLayoutEffect`, `useReducer`, `useRef`, `useMemo`, `useCallback`, `useContext`, `useId`, `useImperativeHandle`, `useSyncExternalStore`, `useTransition`, `useDeferredValue`. Effects are exception-safe: one failing effect or cleanup does not stop the others, errors go to `ErrorBoundary(on_error=...)` or `app.on_error`. Child effects run before parent effects. Effects must be sync functions (an `async def` effect runs as a task and warns).
|
|
126
|
+
- `ErrorBoundary(*children, fallback=lambda error, reset: ...)`.
|
|
127
|
+
- `FlatList` `renderItem` with one parameter gets `{"item", "index"}`; with two it gets `(item, index)`. `SectionList(sections=[{"title", "data"}], renderItem, renderSectionHeader)` (no sticky headers yet).
|
|
128
|
+
- Modules: `Platform.OS`, `Dimensions.addEventListener("change", fn)` (returns an object with `.remove()`), `BackHandler.addEventListener("hardwareBackPress", fn)`, `Keyboard.addListener("keyboardDidShow", fn)`, `Linking.openURL`, `Alert.alert(title, msg, [{"text", "onPress"}])`.
|
|
129
|
+
- RN style values that have no native equivalent (`borderStyle`, `tintColor`, `resizeMode`, ...) log a warning and are ignored. `Pressable` `hit_slop` and `delay_long_press` are accepted but not applied natively yet.
|
|
130
|
+
- Example: `examples/rn_style`.
|
|
131
|
+
|
|
132
|
+
## Routing (`pyxpo.router`, Expo Router)
|
|
133
|
+
- Files under `app/` are routes: `index`, `[id]` (dynamic), `[...rest]` (catch-all), `(group)/` (no URL segment), `_layout.py` (navigator), `+not-found.py`. A route module exports `default` (or has a single `@component`).
|
|
134
|
+
- Layouts: `Stack`, `Tabs`, `Drawer`, `Slot`; configure with `Stack.Screen(name=..., options={...})` or `screenOptions`. Options follow react-navigation (`title`, `headerShown`, `headerRight`, `tabBarIcon`, ...). A layout may export `unstable_settings = {"initialRouteName": ...}`.
|
|
135
|
+
- `router.push/navigate/replace/back/dismiss/dismissAll/setParams`, `<Link href=...>`, `<Redirect href=...>`; hooks `useRouter`, `useLocalSearchParams`, `useGlobalSearchParams`, `usePathname`, `useSegments`, `useNavigation`, `useIsFocused`, `useFocusEffect`. Theme: `ThemeProvider`, `DefaultTheme`, `DarkTheme`, `useTheme`.
|
|
136
|
+
- Android hardware back, the stack edge-swipe and the drawer all go through one navigation store. iOS Swift controls and the native stack are not ported yet.
|
pyxpo-0.1.0a1/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Pyxpo
|
|
2
|
+
|
|
3
|
+
React-Native-style Python framework: declarative components + hooks -> reconciler -> batched JSON ops -> native widgets.
|
|
4
|
+
See DESIGN.md (architecture) and PLAN.md (roadmap). Android and iOS are both supported.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
```
|
|
8
|
+
pip install pyxpo
|
|
9
|
+
pyxpo create demo && cd demo
|
|
10
|
+
pyxpo doctor # check Xcode / Android SDK / Gradle
|
|
11
|
+
pyxpo run android # or: pyxpo run ios
|
|
12
|
+
```
|
|
13
|
+
The first mobile build downloads the prebuilt CPython runtime (about 20-60 MB per target) into `~/.cache/pyxpo`.
|
|
14
|
+
|
|
15
|
+
## Develop pyxpo itself
|
|
16
|
+
```
|
|
17
|
+
tools/dev_install.sh # .venv with core + every module package in packages/, all editable
|
|
18
|
+
.venv/bin/pyxpo create demo && cd demo && ../.venv/bin/pyxpo run desktop # type 1 + Enter to press the button, q to quit
|
|
19
|
+
.venv/bin/pytest
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A project is an `app/` folder of routes, like Expo Router:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
app/
|
|
26
|
+
_layout.py # Stack / Tabs / Drawer / Slot around the routes below
|
|
27
|
+
index.py # "/"
|
|
28
|
+
details/[id].py # "/details/7" -> useLocalSearchParams()["id"]
|
|
29
|
+
+not-found.py # unmatched URLs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
# app/index.py
|
|
34
|
+
from pyxpo import Button, Text, View, useState
|
|
35
|
+
from pyxpo.router import Link
|
|
36
|
+
|
|
37
|
+
def Index():
|
|
38
|
+
n, setN = useState(0)
|
|
39
|
+
return View(Text("Count: ", n), Button(title="Add", onPress=lambda: setN(n + 1)),
|
|
40
|
+
Link("Details", href="/details/%d" % n))
|
|
41
|
+
|
|
42
|
+
default = Index
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Hooks: use_state, use_effect, use_ref, use_memo, use_callback, use_context, use_async. Lists need `key=`.
|
|
46
|
+
`pyxpo add-lib <pkg>` checks mobile wheel availability (note: full tensorflow/torch cannot run on mobile).
|
|
47
|
+
|
|
48
|
+
## Native modules
|
|
49
|
+
|
|
50
|
+
Features beyond the core are optional native modules, each its own pip package (like Expo's `expo-image-picker`).
|
|
51
|
+
Only the modules an app lists are compiled into it.
|
|
52
|
+
|
|
53
|
+
| Package | Import | `modules` entry |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `pyxpo-file-system` | `from pyxpo_file_system import File, Directory, Paths` | `file-system` |
|
|
56
|
+
| `pyxpo-image-manipulator` | `from pyxpo_image_manipulator import ImageManipulator` | `image-manipulator` |
|
|
57
|
+
| `pyxpo-image-picker` | `from pyxpo_image_picker import ImagePicker` | `image-picker` |
|
|
58
|
+
| `pyxpo-media-library` | `from pyxpo_media_library import MediaLibrary` | `media-library` |
|
|
59
|
+
| `pyxpo-secure-store` | `import pyxpo_secure_store as SecureStore` | `secure-store` |
|
|
60
|
+
| `pyxpo-sharing` | `from pyxpo_sharing import Share` | `sharing` |
|
|
61
|
+
| `pyxpo-storage` | `from pyxpo_storage import storage` | `storage` |
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
pip install pyxpo-image-picker
|
|
65
|
+
```
|
|
66
|
+
```toml
|
|
67
|
+
[tool.pyxpo]
|
|
68
|
+
modules = ["image-picker"]
|
|
69
|
+
```
|
|
70
|
+
Their sources live in `packages/`; see each package's README. Third-party modules are pip packages that register a
|
|
71
|
+
`pyxpo.modules` entry point the same way.
|
|
72
|
+
|
|
73
|
+
## React Native style API
|
|
74
|
+
Every RN spelling is an alias; the snake_case API keeps working. No JSX (not valid Python), so children are call arguments.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from pyxpo import StyleSheet, View, Text, Pressable, FlatList, SectionList, useState, useEffect, component
|
|
78
|
+
|
|
79
|
+
styles = StyleSheet.create( # or StyleSheet.create({"title": {...}})
|
|
80
|
+
title=dict(fontSize=24, fontWeight="bold", marginBottom=12),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@component
|
|
84
|
+
def App():
|
|
85
|
+
count, setCount = useState(0)
|
|
86
|
+
useEffect(lambda: print("count", count), [count])
|
|
87
|
+
return View(
|
|
88
|
+
Text("Hello", style=styles.title),
|
|
89
|
+
Pressable(Text("+"), onPress=lambda: setCount(count + 1),
|
|
90
|
+
style=lambda state: {"opacity": 0.5 if state.pressed else 1}),
|
|
91
|
+
FlatList(data=[1, 2], renderItem=lambda p: Text(p["item"]), keyExtractor=lambda i, _: i),
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- Style keys and props accept camelCase (`backgroundColor`, `onPress`, `numberOfLines`, `placeholderTextColor`, `testID`). Giving both spellings of one prop raises `TypeError`. User components accept a camelCase key when its snake_case form is a parameter.
|
|
96
|
+
- Hooks: `useState`, `useEffect`, `useLayoutEffect`, `useReducer`, `useRef`, `useMemo`, `useCallback`, `useContext`, `useId`, `useImperativeHandle`, `useSyncExternalStore`, `useTransition`, `useDeferredValue`. Effects are exception-safe: one failing effect or cleanup does not stop the others, errors go to `ErrorBoundary(on_error=...)` or `app.on_error`. Child effects run before parent effects. Effects must be sync functions (an `async def` effect runs as a task and warns).
|
|
97
|
+
- `ErrorBoundary(*children, fallback=lambda error, reset: ...)`.
|
|
98
|
+
- `FlatList` `renderItem` with one parameter gets `{"item", "index"}`; with two it gets `(item, index)`. `SectionList(sections=[{"title", "data"}], renderItem, renderSectionHeader)` (no sticky headers yet).
|
|
99
|
+
- Modules: `Platform.OS`, `Dimensions.addEventListener("change", fn)` (returns an object with `.remove()`), `BackHandler.addEventListener("hardwareBackPress", fn)`, `Keyboard.addListener("keyboardDidShow", fn)`, `Linking.openURL`, `Alert.alert(title, msg, [{"text", "onPress"}])`.
|
|
100
|
+
- RN style values that have no native equivalent (`borderStyle`, `tintColor`, `resizeMode`, ...) log a warning and are ignored. `Pressable` `hit_slop` and `delay_long_press` are accepted but not applied natively yet.
|
|
101
|
+
- Example: `examples/rn_style`.
|
|
102
|
+
|
|
103
|
+
## Routing (`pyxpo.router`, Expo Router)
|
|
104
|
+
- Files under `app/` are routes: `index`, `[id]` (dynamic), `[...rest]` (catch-all), `(group)/` (no URL segment), `_layout.py` (navigator), `+not-found.py`. A route module exports `default` (or has a single `@component`).
|
|
105
|
+
- Layouts: `Stack`, `Tabs`, `Drawer`, `Slot`; configure with `Stack.Screen(name=..., options={...})` or `screenOptions`. Options follow react-navigation (`title`, `headerShown`, `headerRight`, `tabBarIcon`, ...). A layout may export `unstable_settings = {"initialRouteName": ...}`.
|
|
106
|
+
- `router.push/navigate/replace/back/dismiss/dismissAll/setParams`, `<Link href=...>`, `<Redirect href=...>`; hooks `useRouter`, `useLocalSearchParams`, `useGlobalSearchParams`, `usePathname`, `useSegments`, `useNavigation`, `useIsFocused`, `useFocusEffect`. Theme: `ThemeProvider`, `DefaultTheme`, `DarkTheme`, `useTheme`.
|
|
107
|
+
- Android hardware back, the stack edge-swipe and the drawer all go through one navigation store. iOS Swift controls and the native stack are not ported yet.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyxpo"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Python-first mobile app framework with React-style components and native widgets"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Vishal Chaubey" }]
|
|
13
|
+
keywords = ["mobile", "android", "ios", "react-native", "expo", "python", "ui", "framework"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: MacOS",
|
|
18
|
+
"Operating System :: POSIX :: Linux",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: User Interfaces",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
27
|
+
]
|
|
28
|
+
requires-python = ">=3.9"
|
|
29
|
+
dependencies = ["tomli>=2; python_version < '3.11'"]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = ["pytest"]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/pyxpo/pyxpo"
|
|
36
|
+
Issues = "https://github.com/pyxpo/pyxpo/issues"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
pyxpo = "pyxpo.cli:main"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.dynamic]
|
|
45
|
+
version = { attr = "pyxpo.__version__" }
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
pyxpo = ["templates/**/*", "_native/**/*", "build/runtime_manifest.json"]
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.exclude-package-data]
|
|
51
|
+
"*" = ["__pycache__", "*.pyc"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
pyxpo-0.1.0a1/setup.cfg
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Pyxpo: React-Native-style mobile UI framework for Python.
|
|
2
|
+
|
|
3
|
+
The top-level namespace mirrors `react-native` (components, APIs, hooks); routing lives in `pyxpo.router`
|
|
4
|
+
(the `expo-router` equivalent) and icons in `pyxpo.vector_icons`.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os as _os
|
|
9
|
+
|
|
10
|
+
from .component import enable_registry as _enable_registry
|
|
11
|
+
|
|
12
|
+
if _os.environ.get("PYXPO_DEV_URL"):
|
|
13
|
+
_enable_registry(True) # keep Component identity across hot reloads; must run before app code
|
|
14
|
+
|
|
15
|
+
from .animated import Animated, Easing, use_animated_value
|
|
16
|
+
from .app import App, create_app, run_app
|
|
17
|
+
from .channel import Channel, LocalChannel
|
|
18
|
+
from .component import Component, ErrorBoundary, component, memo
|
|
19
|
+
from .element import Element
|
|
20
|
+
|
|
21
|
+
from .components.activity_indicator import ActivityIndicator
|
|
22
|
+
from .components.button import Button
|
|
23
|
+
from .components.image import Image, ImageBackground
|
|
24
|
+
from .components.keyboard_avoiding_view import KeyboardAvoidingView
|
|
25
|
+
from .components.lists import FlatList, SectionList, VirtualizedList
|
|
26
|
+
from .components.modal import Modal
|
|
27
|
+
from .components.picker import Picker
|
|
28
|
+
from .components.pressable import Pressable
|
|
29
|
+
from .components.refresh_control import RefreshControl
|
|
30
|
+
from .components.safe_area_view import SafeAreaView
|
|
31
|
+
from .components.scroll_view import ScrollView
|
|
32
|
+
from .components.slider import Slider
|
|
33
|
+
from .components.status_bar import StatusBar
|
|
34
|
+
from .components.switch import Switch
|
|
35
|
+
from .components.text import Text
|
|
36
|
+
from .components.text_input import TextInput
|
|
37
|
+
from .components.touchables import (TouchableHighlight, TouchableNativeFeedback, TouchableOpacity,
|
|
38
|
+
TouchableWithoutFeedback)
|
|
39
|
+
from .components.view import View
|
|
40
|
+
|
|
41
|
+
from .apis.alert import Alert
|
|
42
|
+
from .apis.appearance import Appearance
|
|
43
|
+
from .apis.appstate import AppState
|
|
44
|
+
from .apis.back_handler import BackHandler
|
|
45
|
+
from .apis.clipboard import Clipboard
|
|
46
|
+
from .apis.dimensions import Dimensions
|
|
47
|
+
from .apis.i18n_manager import I18nManager
|
|
48
|
+
from .apis.keyboard import Keyboard
|
|
49
|
+
from .apis.linking import Linking
|
|
50
|
+
from .apis.pan_responder import PanResponder
|
|
51
|
+
from .apis.pixel_ratio import PixelRatio
|
|
52
|
+
from .apis.platform import Platform
|
|
53
|
+
from .apis.toast_android import ToastAndroid
|
|
54
|
+
from .apis.splash_screen import SplashScreen
|
|
55
|
+
from .apis.constants import Constants
|
|
56
|
+
from .apis.vibration import Vibration
|
|
57
|
+
|
|
58
|
+
from .style import StyleSheet, processColor
|
|
59
|
+
from .hooks import (HookError, createContext, create_context, use_async, use_callback, use_context, use_debug_value,
|
|
60
|
+
use_deferred_value, use_effect, use_id, use_imperative_handle, use_layout_effect, use_memo,
|
|
61
|
+
use_reducer, use_ref, use_state, use_sync_external_store, use_transition, use_color_scheme,
|
|
62
|
+
use_safe_area_frame, use_safe_area_insets, use_window_dimensions, useAsync, useCallback, useColorScheme, useContext,
|
|
63
|
+
useDebugValue, useDeferredValue, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo,
|
|
64
|
+
useReducer, useRef, useSafeAreaFrame, useSafeAreaInsets, useState, useSyncExternalStore, useTransition,
|
|
65
|
+
useWindowDimensions)
|
|
66
|
+
|
|
67
|
+
useAnimatedValue = use_animated_value
|
|
68
|
+
|
|
69
|
+
__version__ = "0.1.0a1"
|
|
70
|
+
|
|
71
|
+
__all__ = [
|
|
72
|
+
# app
|
|
73
|
+
"App", "create_app", "run_app", "Channel", "LocalChannel", "Component", "component", "memo", "Element",
|
|
74
|
+
# components
|
|
75
|
+
"View", "Text", "TextInput", "Image", "ImageBackground", "ScrollView", "FlatList", "SectionList",
|
|
76
|
+
"VirtualizedList", "Pressable", "TouchableOpacity", "TouchableHighlight", "TouchableWithoutFeedback",
|
|
77
|
+
"TouchableNativeFeedback", "Button", "Switch", "ActivityIndicator", "Modal", "KeyboardAvoidingView",
|
|
78
|
+
"SafeAreaView", "StatusBar", "RefreshControl", "Picker", "Slider", "ErrorBoundary",
|
|
79
|
+
# apis
|
|
80
|
+
"Alert", "Animated", "Easing", "Appearance", "AppState", "BackHandler", "Clipboard", "Dimensions",
|
|
81
|
+
"I18nManager", "Keyboard", "Linking", "PanResponder", "PixelRatio", "Platform", "StyleSheet", "ToastAndroid",
|
|
82
|
+
"SplashScreen", "Constants", "Vibration", "processColor",
|
|
83
|
+
# hooks
|
|
84
|
+
"HookError", "createContext", "create_context", "use_async", "use_callback", "use_context", "use_debug_value",
|
|
85
|
+
"use_deferred_value", "use_effect", "use_id", "use_imperative_handle", "use_layout_effect", "use_memo",
|
|
86
|
+
"use_reducer", "use_ref", "use_state", "use_sync_external_store", "use_transition", "use_color_scheme",
|
|
87
|
+
"use_safe_area_frame", "use_safe_area_insets", "use_window_dimensions", "use_animated_value", "useAnimatedValue", "useAsync",
|
|
88
|
+
"useCallback", "useColorScheme", "useContext", "useDebugValue", "useDeferredValue", "useEffect", "useId",
|
|
89
|
+
"useImperativeHandle", "useLayoutEffect", "useMemo", "useReducer", "useRef", "useSafeAreaFrame", "useSafeAreaInsets", "useState",
|
|
90
|
+
"useSyncExternalStore", "useTransition", "useWindowDimensions",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
_MOVED = {"Share": "sharing", "MediaLibrary": "media-library", "ImagePicker": "image-picker", "ImageManipulator": "image-manipulator",
|
|
95
|
+
"ImageTooLargeError": "image-manipulator"}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def __getattr__(name: str):
|
|
99
|
+
if name in _MOVED:
|
|
100
|
+
mod = _MOVED[name]
|
|
101
|
+
raise AttributeError(
|
|
102
|
+
"pyxpo.%s moved to the optional native module package pyxpo-%s: run `pip install pyxpo-%s`, "
|
|
103
|
+
"add '%s' to [tool.pyxpo] modules in pyproject.toml, and use `from pyxpo_%s import %s`"
|
|
104
|
+
% (name, mod, mod, mod, mod.replace("-", "_"), name)
|
|
105
|
+
)
|
|
106
|
+
raise AttributeError("module 'pyxpo' has no attribute %r" % name)
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Module naming shared by the router's route loader and the hot reloader."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def module_name_for(rel: str) -> Optional[str]:
|
|
8
|
+
"""`app/(tabs)/[id].py` -> `app.(tabs).[id]`; `app/__init__.py` -> `app`. None for non-.py paths.
|
|
9
|
+
|
|
10
|
+
Names with brackets or parentheses are not importable with `import`; the router loads them by file path.
|
|
11
|
+
"""
|
|
12
|
+
if not rel.endswith(".py"):
|
|
13
|
+
return None
|
|
14
|
+
name = rel[:-3].replace("/", ".")
|
|
15
|
+
return name[: -len(".__init__")] if name.endswith(".__init__") else name
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""SVG path data -> absolute move/line/cubic/close commands, which the native `Path` view replays."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any, Dict, List
|
|
7
|
+
|
|
8
|
+
_TOKEN = re.compile(r"[MmLlHhVvCcSsQqAaZz]|[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?")
|
|
9
|
+
_ARGC = {"M": 2, "L": 2, "H": 1, "V": 1, "C": 6, "S": 4, "Q": 4, "A": 7, "Z": 0}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _arc(x1: float, y1: float, rx: float, ry: float, phi: float, fa: int, fs: int, x2: float, y2: float) -> List[List[float]]:
|
|
13
|
+
if rx == 0 or ry == 0 or (x1 == x2 and y1 == y2):
|
|
14
|
+
return [[1, x2, y2]]
|
|
15
|
+
rx, ry = abs(rx), abs(ry)
|
|
16
|
+
cp, sp = math.cos(math.radians(phi)), math.sin(math.radians(phi))
|
|
17
|
+
dx, dy = (x1 - x2) / 2, (y1 - y2) / 2
|
|
18
|
+
x1p, y1p = cp * dx + sp * dy, -sp * dx + cp * dy
|
|
19
|
+
lam = x1p ** 2 / rx ** 2 + y1p ** 2 / ry ** 2
|
|
20
|
+
if lam > 1:
|
|
21
|
+
s = math.sqrt(lam)
|
|
22
|
+
rx, ry = rx * s, ry * s
|
|
23
|
+
num = rx ** 2 * ry ** 2 - rx ** 2 * y1p ** 2 - ry ** 2 * x1p ** 2
|
|
24
|
+
den = rx ** 2 * y1p ** 2 + ry ** 2 * x1p ** 2
|
|
25
|
+
co = math.sqrt(max(0.0, num / den)) * (-1 if fa == fs else 1)
|
|
26
|
+
cxp, cyp = co * rx * y1p / ry, -co * ry * x1p / rx
|
|
27
|
+
cx, cy = cp * cxp - sp * cyp + (x1 + x2) / 2, sp * cxp + cp * cyp + (y1 + y2) / 2
|
|
28
|
+
|
|
29
|
+
def ang(ux: float, uy: float, vx: float, vy: float) -> float:
|
|
30
|
+
a = math.atan2(ux * vy - uy * vx, ux * vx + uy * vy)
|
|
31
|
+
return a
|
|
32
|
+
|
|
33
|
+
t1 = ang(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry)
|
|
34
|
+
dt = ang((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry)
|
|
35
|
+
if not fs and dt > 0:
|
|
36
|
+
dt -= 2 * math.pi
|
|
37
|
+
elif fs and dt < 0:
|
|
38
|
+
dt += 2 * math.pi
|
|
39
|
+
n = max(1, int(math.ceil(abs(dt) / (math.pi / 2) - 1e-9)))
|
|
40
|
+
step = dt / n
|
|
41
|
+
k = 4 / 3 * math.tan(step / 4)
|
|
42
|
+
out: List[List[float]] = []
|
|
43
|
+
t = t1
|
|
44
|
+
for _ in range(n):
|
|
45
|
+
c1, s1, c2, s2 = math.cos(t), math.sin(t), math.cos(t + step), math.sin(t + step)
|
|
46
|
+
p = [(c1 - k * s1), (s1 + k * c1), (c2 + k * s2), (s2 - k * c2), c2, s2]
|
|
47
|
+
pts = []
|
|
48
|
+
for i in range(0, 6, 2):
|
|
49
|
+
ux, uy = p[i] * rx, p[i + 1] * ry
|
|
50
|
+
pts += [cp * ux - sp * uy + cx, sp * ux + cp * uy + cy]
|
|
51
|
+
out.append([2] + pts)
|
|
52
|
+
t += step
|
|
53
|
+
out[-1][-2:] = [x2, y2]
|
|
54
|
+
return out
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def parse_path(d: str) -> List[List[float]]:
|
|
58
|
+
"""SVG path data -> commands: [0,x,y] move, [1,x,y] line, [2,x1,y1,x2,y2,x,y] cubic, [3] close."""
|
|
59
|
+
toks = _TOKEN.findall(d)
|
|
60
|
+
out: List[List[float]] = []
|
|
61
|
+
i = 0
|
|
62
|
+
cmd = ""
|
|
63
|
+
x = y = sx = sy = 0.0
|
|
64
|
+
lcx = lcy = None # last control point (for S)
|
|
65
|
+
prev = ""
|
|
66
|
+
while i < len(toks):
|
|
67
|
+
if toks[i].isalpha():
|
|
68
|
+
cmd = toks[i]
|
|
69
|
+
i += 1
|
|
70
|
+
if cmd in "Zz":
|
|
71
|
+
out.append([3])
|
|
72
|
+
x, y = sx, sy
|
|
73
|
+
prev = "Z"
|
|
74
|
+
continue
|
|
75
|
+
up = cmd.upper()
|
|
76
|
+
n = _ARGC[up]
|
|
77
|
+
a = [float(t) for t in toks[i:i + n]]
|
|
78
|
+
if len(a) < n:
|
|
79
|
+
break
|
|
80
|
+
i += n
|
|
81
|
+
rel = cmd.islower()
|
|
82
|
+
ox, oy = (x, y) if rel else (0.0, 0.0)
|
|
83
|
+
if up == "M":
|
|
84
|
+
x, y = a[0] + ox, a[1] + oy
|
|
85
|
+
sx, sy = x, y
|
|
86
|
+
out.append([0, x, y])
|
|
87
|
+
cmd = "l" if rel else "L"
|
|
88
|
+
prev = "M"
|
|
89
|
+
continue
|
|
90
|
+
if up == "L":
|
|
91
|
+
x, y = a[0] + ox, a[1] + oy
|
|
92
|
+
out.append([1, x, y])
|
|
93
|
+
elif up == "H":
|
|
94
|
+
x = a[0] + ox
|
|
95
|
+
out.append([1, x, y])
|
|
96
|
+
elif up == "V":
|
|
97
|
+
y = a[0] + oy
|
|
98
|
+
out.append([1, x, y])
|
|
99
|
+
elif up == "C":
|
|
100
|
+
c = [a[0] + ox, a[1] + oy, a[2] + ox, a[3] + oy, a[4] + ox, a[5] + oy]
|
|
101
|
+
out.append([2] + c)
|
|
102
|
+
lcx, lcy, x, y = c[2], c[3], c[4], c[5]
|
|
103
|
+
elif up == "S":
|
|
104
|
+
c1x, c1y = (2 * x - lcx, 2 * y - lcy) if prev == "C" and lcx is not None else (x, y)
|
|
105
|
+
c = [c1x, c1y, a[0] + ox, a[1] + oy, a[2] + ox, a[3] + oy]
|
|
106
|
+
out.append([2] + c)
|
|
107
|
+
lcx, lcy, x, y = c[2], c[3], c[4], c[5]
|
|
108
|
+
elif up == "Q":
|
|
109
|
+
qx, qy, ex, ey = a[0] + ox, a[1] + oy, a[2] + ox, a[3] + oy
|
|
110
|
+
out.append([2, x + 2 / 3 * (qx - x), y + 2 / 3 * (qy - y), ex + 2 / 3 * (qx - ex), ey + 2 / 3 * (qy - ey), ex, ey])
|
|
111
|
+
x, y = ex, ey
|
|
112
|
+
elif up == "A":
|
|
113
|
+
ex, ey = a[5] + ox, a[6] + oy
|
|
114
|
+
out.extend(_arc(x, y, a[0], a[1], a[2], int(a[3]), int(a[4]), ex, ey))
|
|
115
|
+
x, y = ex, ey
|
|
116
|
+
prev = up
|
|
117
|
+
return out
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""camelCase -> snake_case name conversion shared by style keys and props (React Native spellings are aliases)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
_SPLIT = re.compile(r"(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])")
|
|
8
|
+
_SNAKE: Dict[str, str] = {}
|
|
9
|
+
# names whose RN spelling does not follow the mechanical rule
|
|
10
|
+
_OVERRIDES: Dict[str, str] = {"testID": "test_id", "nativeID": "native_id"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def to_snake(name: str) -> str:
|
|
14
|
+
"""`fontSize` -> `font_size`; names that are already lower case pass through untouched."""
|
|
15
|
+
if name.islower():
|
|
16
|
+
return name
|
|
17
|
+
hit = _SNAKE.get(name)
|
|
18
|
+
if hit is None:
|
|
19
|
+
hit = _OVERRIDES.get(name) or _SPLIT.sub("_", name).lower()
|
|
20
|
+
if len(_SNAKE) > 4096:
|
|
21
|
+
_SNAKE.clear()
|
|
22
|
+
_SNAKE[name] = hit
|
|
23
|
+
return hit
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# RN prop names that map to a differently named pyxpo prop (everything else follows to_snake)
|
|
27
|
+
PROP_OVERRIDES: Dict[str, str] = {
|
|
28
|
+
"placeholderTextColor": "placeholder_color", "returnKeyType": "return_key", "secureTextEntry": "secure",
|
|
29
|
+
"viewBox": "viewbox", "testID": "test_id", "nativeID": "native_id",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def prop_name(k: str) -> str:
|
|
34
|
+
return k if k.islower() else (PROP_OVERRIDES.get(k) or to_snake(k))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def alias_props(props: Dict[str, Any]) -> Dict[str, Any]:
|
|
38
|
+
"""Converts camelCase prop names to the snake_case ones. Giving both spellings raises like a duplicate kwarg."""
|
|
39
|
+
out: Dict[str, Any] = {}
|
|
40
|
+
for k, v in props.items():
|
|
41
|
+
n = prop_name(k)
|
|
42
|
+
if n in out or (n != k and n in props):
|
|
43
|
+
raise TypeError("got both %r and %r" % (k, n))
|
|
44
|
+
out[n] = v
|
|
45
|
+
return out
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def snake_aliases(cls: Any) -> Any:
|
|
49
|
+
"""Gives every camelCase static method of `cls` a snake_case twin (`addEventListener` -> `add_event_listener`)."""
|
|
50
|
+
for name, val in list(vars(cls).items()):
|
|
51
|
+
if not name.startswith("_") and not name.islower() and isinstance(val, (staticmethod, classmethod)):
|
|
52
|
+
setattr(cls, to_snake(name), val)
|
|
53
|
+
return cls
|