| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -40,6 +40,7 @@ PythonNative is a cross-platform toolkit for building native Android and iOS app | |||
| 40 | 40 | - **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge. | |
| 41 | 41 | - **Custom-component SDK:** Wrap any platform widget as a first-class element with type-checked props via `pythonnative.sdk` (`Props`, `@native_component`, `element_factory`). Plugins distributed on PyPI auto-register through the `pythonnative.handlers` entry-point group. | |
| 42 | 42 | - **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app. | |
| 43 | + - **Instant desktop preview:** `pn preview` renders your app in a native desktop window via Tkinter with Fast Refresh on every save — iterate on layout, state, and navigation in milliseconds without booting a simulator or device. The reconciler, hooks, layout engine, and navigation are the same code that ships to the phone. | ||
| 43 | 44 | - **Native-backed navigation:** Declarative `Stack`, `Tab`, and `Drawer` navigators inspired by React Navigation. The root stack drives the platform's native navigation controller (`UINavigationController` on iOS, AndroidX Navigation Component on Android), so transitions, back gestures, and the hardware back button match what users expect. | |
| 44 | 45 | - **Fast Refresh hot reload:** `pn run --hot-reload` watches `app/` and patches edits into the running app on save, preserving component state across most changes. | |
| 45 | 46 | - **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,10 @@ the documented behavior never drifts from the code. | |||
| 8 | 8 | ||
| 9 | 9 | - `pn init [name]`: scaffold a new project (creates `app/`, | |
| 10 | 10 | `pythonnative.json`, `requirements.txt`, `.gitignore`). | |
| 11 | + - `pn preview [component]`: render the app in a desktop (Tkinter) window | ||
| 12 | + with Fast Refresh — the fastest way to iterate on UI. Flags: | ||
| 13 | + `--width`, `--height`, `--title`, `--no-hot-reload`. See the | ||
| 14 | + [Desktop preview guide](../guides/desktop-preview.md). | ||
| 11 | 15 | - `pn run android|ios`: build and run on a connected device or | |
| 12 | 16 | simulator. Flags: `--prepare-only`, `--hot-reload`, `--no-logs`. | |
| 13 | 17 | - `pn clean`: remove the local `build/` directory. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -324,9 +324,19 @@ if pn.Platform.is_ios: | |||
| 324 | 324 | margin = 16 | |
| 325 | 325 | ``` | |
| 326 | 326 | ||
| 327 | - `pn.Platform.OS` is `"ios"`, `"android"`, or `"test"` (the latter | ||
| 328 | - when running off-device, e.g., in unit tests). The lower-level | ||
| 329 | - `utils.IS_ANDROID` / `utils.IS_IOS` constants are still available. | ||
| 327 | + `pn.Platform.OS` is `"ios"`, `"android"`, `"desktop"` (the `pn preview` | ||
| 328 | + backend — see the [Desktop preview guide](../guides/desktop-preview.md)), | ||
| 329 | + or `"test"` (off-device, e.g. in unit tests). The lower-level | ||
| 330 | + `utils.IS_ANDROID` / `utils.IS_IOS` / `utils.IS_DESKTOP` constants are | ||
| 331 | + still available. | ||
| 332 | + | ||
| 333 | + `Platform.select` matches on the exact key; a `"native"` key is shared | ||
| 334 | + by iOS **and** Android (but not desktop), and a `"default"` key catches | ||
| 335 | + anything unmatched: | ||
| 336 | + | ||
| 337 | + ```python | ||
| 338 | + pad = pn.Platform.select({"native": 16, "desktop": 12, "default": 8}) | ||
| 339 | + ``` | ||
| 330 | 340 | ||
| 331 | 341 | ## Next steps | |
| 332 | 342 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -66,9 +66,13 @@ is a dict-like object that maps element type strings to handler | |||
| 66 | 66 | populates the registry with Chaquopy-backed handlers. | |
| 67 | 67 | - On iOS, `pythonnative.native_views.ios.register_handlers` does the | |
| 68 | 68 | same with rubicon-objc handlers. | |
| 69 | - - On the desktop (during tests), the registry is replaced with a mock | ||
| 70 | - via [`set_registry`][pythonnative.native_views.set_registry] before | ||
| 71 | - any element is rendered. | ||
| 69 | + - On the desktop (`pn preview`, with `PN_PLATFORM=desktop`), | ||
| 70 | + `pythonnative.native_views.desktop.register_handlers` populates the | ||
| 71 | + registry with Tkinter-backed handlers. See the | ||
| 72 | + [Desktop preview guide](../guides/desktop-preview.md). | ||
| 73 | + - Off-device under `pytest`, the registry is replaced with a mock via | ||
| 74 | + [`set_registry`][pythonnative.native_views.set_registry] before any | ||
| 75 | + element is rendered. | ||
| 72 | 76 | ||
| 73 | 77 | Custom widgets follow the same pattern: register a handler under a | |
| 74 | 78 | unique type string, then construct elements with that type and the | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,12 +20,16 @@ project scaffolded with `pn init`. | |||
| 20 | 20 | pn init my-app | |
| 21 | 21 | cd my-app | |
| 22 | 22 | # Edit app/main.py and paste any of the snippets below. | |
| 23 | + pn preview # fast desktop preview with Fast Refresh | ||
| 23 | 24 | pn run android # or: pn run ios | |
| 24 | 25 | ``` | |
| 25 | 26 | ||
| 26 | 27 | The `app/main.py` that `pn init` writes already returns a small | |
| 27 | 28 | counter; replace it with one of the snippets to try a different | |
| 28 | - example. | ||
| 29 | + example. The quickest way to iterate is | ||
| 30 | + [`pn preview`](guides/desktop-preview.md), which renders the app in a | ||
| 31 | + desktop window and reloads on every save; use `pn run` when you want it | ||
| 32 | + on a real device or simulator. | ||
| 29 | 33 | ||
| 30 | 34 | ## Snippets | |
| 31 | 35 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -66,6 +66,38 @@ Key ideas: | |||
| 66 | 66 | ||
| 67 | 67 | When the root `Stack.Navigator` is rendered inside the host's first screen, `navigate(...)` and `go_back()` drive the **native** navigation controller (UINavigationController on iOS, AndroidX Navigation Component on Android). Each pushed screen runs in its own reconciler host, so state on the previous screen is preserved by the platform stack. | |
| 68 | 68 | ||
| 69 | + ## Preview on your desktop | ||
| 70 | + | ||
| 71 | + The fastest way to iterate is `pn preview`, which renders your app in a | ||
| 72 | + desktop window and **Fast Refreshes on every save** — no simulator, no | ||
| 73 | + device build: | ||
| 74 | + | ||
| 75 | + ```bash | ||
| 76 | + pn preview | ||
| 77 | + ``` | ||
| 78 | + | ||
| 79 | + This opens a phone-sized window, mounts your project's `App`, and | ||
| 80 | + watches `app/` for changes. Edit a component, hit save, and the window | ||
| 81 | + updates in place while keeping component state (counters, form input, | ||
| 82 | + scroll position). Navigation, hooks, async, and the flex layout engine | ||
| 83 | + all run exactly as they do on device, because the desktop backend reuses | ||
| 84 | + the same reconciler and layout engine — only the leaf widgets differ | ||
| 85 | + (Tkinter instead of UIKit / Android views). | ||
| 86 | + | ||
| 87 | + ```bash | ||
| 88 | + pn preview # preview the project's entry point (app/main.py → App) | ||
| 89 | + pn preview app.main.Detail # preview a specific component | ||
| 90 | + pn preview --width 768 --height 1024 # tablet-sized window | ||
| 91 | + pn preview --no-hot-reload # disable file watching | ||
| 92 | + ``` | ||
| 93 | + | ||
| 94 | + The preview needs Tkinter, which ships with most Python installs. If | ||
| 95 | + it's missing, install it (`brew install python-tk` on macOS, | ||
| 96 | + `sudo apt-get install python3-tk` on Debian/Ubuntu). The desktop backend | ||
| 97 | + is a **development** surface for layout and logic — some visual chrome is | ||
| 98 | + approximated, and there's no desktop packaging. Ship to devices with | ||
| 99 | + `pn run`. See the [Desktop preview guide](guides/desktop-preview.md). | ||
| 100 | + | ||
| 69 | 101 | ## Run on a platform | |
| 70 | 102 | ||
| 71 | 103 | ```bash | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,173 @@ | |||
| 1 | + # Desktop preview | ||
| 2 | + | ||
| 3 | + `pn preview` renders your app in a native desktop window with **instant | ||
| 4 | + Fast Refresh** on every save. It's the fastest way to build UI in | ||
| 5 | + PythonNative: edit a component, hit save, and see the result in a second | ||
| 6 | + — no simulator boot, no device deploy, no rebuild. | ||
| 7 | + | ||
| 8 | + ```bash | ||
| 9 | + pn preview | ||
| 10 | + ``` | ||
| 11 | + | ||
| 12 | + The preview reuses the **same reconciler, hooks, navigation, async | ||
| 13 | + runtime, and pure-Python flex layout engine** that run on device. Only | ||
| 14 | + the leaf widgets differ: the desktop backend draws with | ||
| 15 | + [Tkinter](https://docs.python.org/3/library/tkinter.html) instead of | ||
| 16 | + UIKit / Android views. So behavior and layout match the device closely, | ||
| 17 | + and your iteration loop collapses from minutes to seconds. | ||
| 18 | + | ||
| 19 | + ## Quick start | ||
| 20 | + | ||
| 21 | + From a project directory (one created by `pn init`, with an `app/main.py` | ||
| 22 | + that defines `App`): | ||
| 23 | + | ||
| 24 | + ```bash | ||
| 25 | + pn preview | ||
| 26 | + ``` | ||
| 27 | + | ||
| 28 | + `pn preview` runs your real app code, so install your project's | ||
| 29 | + dependencies in the same environment first (for example | ||
| 30 | + `pip install -r requirements.txt`). If an import fails, the preview shows | ||
| 31 | + the traceback in the window instead of crashing — install the missing | ||
| 32 | + package or fix the code and save to recover. | ||
| 33 | + | ||
| 34 | + This opens a phone-sized window, mounts your `App`, and starts watching | ||
| 35 | + `app/` for changes. Edit any component and save — the window updates in | ||
| 36 | + place while preserving component state (counters, text input, scroll | ||
| 37 | + position, navigation stack). | ||
| 38 | + | ||
| 39 | + ```bash | ||
| 40 | + pn preview # the project entry point (app/main.py → App) | ||
| 41 | + pn preview app.screens.home # a module whose App attribute to mount | ||
| 42 | + pn preview app.main.DetailScreen # a specific dotted component | ||
| 43 | + pn preview --width 768 --height 1024 # tablet-sized window | ||
| 44 | + pn preview --title "My App" | ||
| 45 | + pn preview --no-hot-reload # mount once, don't watch files | ||
| 46 | + ``` | ||
| 47 | + | ||
| 48 | + ## Requirements | ||
| 49 | + | ||
| 50 | + The preview uses Tkinter, Python's standard GUI toolkit, which ships | ||
| 51 | + with most Python installations. If `pn preview` reports that Tkinter is | ||
| 52 | + missing: | ||
| 53 | + | ||
| 54 | + - **macOS** (Homebrew Python): `brew install python-tk` | ||
| 55 | + - **Debian / Ubuntu**: `sudo apt-get install python3-tk` | ||
| 56 | + - **Windows**: re-run the Python installer and enable the | ||
| 57 | + "tcl/tk and IDLE" optional feature. | ||
| 58 | + | ||
| 59 | + No other dependencies are required — the desktop backend is pure Python. | ||
| 60 | + | ||
| 61 | + ## How it works | ||
| 62 | + | ||
| 63 | + `pn preview` sets `PN_PLATFORM=desktop` and starts | ||
| 64 | + `pythonnative.preview.run_preview`, which: | ||
| 65 | + | ||
| 66 | + 1. Opens a single Tk window with one *stage* frame. | ||
| 67 | + 2. Selects the Tkinter | ||
| 68 | + [native-view registry][pythonnative.native_views.get_registry], so | ||
| 69 | + every `pn.Text`, `pn.Button`, … maps to a Tk widget. | ||
| 70 | + 3. Mounts your `App` through a normal | ||
| 71 | + [`Reconciler`][pythonnative.reconciler.Reconciler] and pushes the | ||
| 72 | + window size in as the layout viewport. | ||
| 73 | + 4. Runs the Tk event loop on the main thread, polling ~60×/second to | ||
| 74 | + apply renders requested from the async runtime thread and to drain | ||
| 75 | + file-change reloads. | ||
| 76 | + 5. Watches `app/` with a | ||
| 77 | + [`FileWatcher`][pythonnative.hot_reload.FileWatcher] and Fast | ||
| 78 | + Refreshes on every save. | ||
| 79 | + | ||
| 80 | + Layout is owned by the engine, not the widgets: the | ||
| 81 | + [flex layout engine](../concepts/layout.md) computes an absolute frame | ||
| 82 | + for every element, and the backend positions each Tk widget with that | ||
| 83 | + frame. This is the same contract the iOS and Android backends follow, so | ||
| 84 | + a column that lays out correctly on a phone lays out the same way in the | ||
| 85 | + preview. | ||
| 86 | + | ||
| 87 | + ### Navigation | ||
| 88 | + | ||
| 89 | + Root navigators (`create_stack_navigator`, tabs, drawer) drive a real | ||
| 90 | + in-process stack of screen hosts in the preview, the same way they drive | ||
| 91 | + `UINavigationController` / AndroidX Navigation on device. `navigate(...)` | ||
| 92 | + pushes a new screen host (preserving the previous screen's state); | ||
| 93 | + `go_back()` pops it. Each screen runs in its own reconciler host. | ||
| 94 | + | ||
| 95 | + ## Fast Refresh | ||
| 96 | + | ||
| 97 | + Saving a `.py` file under `app/` triggers a reload. The preview prefers | ||
| 98 | + **Fast Refresh**: the changed modules are reloaded and the live VNode | ||
| 99 | + tree's function references are swapped in place, so the next render | ||
| 100 | + reuses existing hook state. Edits to a component body keep your | ||
| 101 | + counters, form values, and scroll positions. When a clean swap isn't | ||
| 102 | + possible (structural edits, a raised exception), the preview falls back | ||
| 103 | + to a full remount so you're never stuck with a stale tree. | ||
| 104 | + | ||
| 105 | + If your component raises at import time (a syntax error you're mid-fix | ||
| 106 | + on), the preview shows the traceback as an overlay and recovers | ||
| 107 | + automatically on your next successful save — no restart needed. | ||
| 108 | + | ||
| 109 | + See the [Hot reload guide](hot-reload.md) for the underlying mechanics; | ||
| 110 | + the desktop preview shares the same Fast Refresh engine. | ||
| 111 | + | ||
| 112 | + ## Branching on the platform | ||
| 113 | + | ||
| 114 | + When the preview is running, [`Platform.OS`][pythonnative.Platform] is | ||
| 115 | + `"desktop"`: | ||
| 116 | + | ||
| 117 | + ```python | ||
| 118 | + import pythonnative as pn | ||
| 119 | + | ||
| 120 | + pad = pn.Platform.select({"desktop": 12, "ios": 16, "android": 16, "default": 12}) | ||
| 121 | + ``` | ||
| 122 | + | ||
| 123 | + Note that `Platform.select`'s `"native"` key matches iOS and Android | ||
| 124 | + only — desktop is a development surface, so use an explicit `"desktop"` | ||
| 125 | + key (or `"default"`) for it. You can also check | ||
| 126 | + [`Platform.is_desktop`][pythonnative.Platform] or the | ||
| 127 | + `pythonnative.utils.IS_DESKTOP` flag directly. | ||
| 128 | + | ||
| 129 | + ## What's faithful, and what's approximated | ||
| 130 | + | ||
| 131 | + The preview is a **development tool**, optimized for fidelity of layout | ||
| 132 | + and logic rather than pixel-perfect chrome. | ||
| 133 | + | ||
| 134 | + Faithful: | ||
| 135 | + | ||
| 136 | + - Flex layout, sizing, padding, spacing, absolute positioning. | ||
| 137 | + - Component lifecycle, hooks, effects, context, error boundaries. | ||
| 138 | + - Navigation (stack push/pop, tabs, drawer) and per-screen state. | ||
| 139 | + - The async runtime, `use_query` / `use_mutation`, timers, and | ||
| 140 | + state-driven updates. | ||
| 141 | + - Text wrapping and intrinsic sizing (measured with the same font the | ||
| 142 | + widget renders). | ||
| 143 | + | ||
| 144 | + Approximated or omitted (Tkinter can't express these cheaply): | ||
| 145 | + | ||
| 146 | + - Rounded corners, shadows, gradients, and per-widget opacity. | ||
| 147 | + - Overflow **clipping** — a `ScrollView`'s content renders but isn't | ||
| 148 | + clipped to the viewport, and there's no interactive scrolling. | ||
| 149 | + - Animations show their **end state** rather than smooth interpolation | ||
| 150 | + (translations are applied; scale/rotate/opacity are skipped). | ||
| 151 | + - `Image` loads local PNG/GIF files; network URLs and JPEG fall back to | ||
| 152 | + a labeled placeholder. `WebView` shows a placeholder. | ||
| 153 | + | ||
| 154 | + When the chrome matters, verify on device with `pn run`. | ||
| 155 | + | ||
| 156 | + ## When to use device builds instead | ||
| 157 | + | ||
| 158 | + `pn preview` is for fast UI/logic iteration. Reach for `pn run` when you | ||
| 159 | + need: | ||
| 160 | + | ||
| 161 | + - Pixel-perfect native chrome and platform behaviors. | ||
| 162 | + - Real device APIs (camera, location, notifications, biometrics, …). | ||
| 163 | + - To test packaging, permissions, or store builds. | ||
| 164 | + | ||
| 165 | + There is no desktop packaging target — ship to devices with | ||
| 166 | + `pn run android` / `pn run ios`. | ||
| 167 | + | ||
| 168 | + ## Next steps | ||
| 169 | + | ||
| 170 | + - Reference: [`pn` CLI](../api/cli.md). | ||
| 171 | + - Mechanics shared with device hot reload: [Hot reload](hot-reload.md). | ||
| 172 | + - How layout is computed: [Layout engine](../concepts/layout.md). | ||
| 173 | + - Platform branching: [Platform & accessibility](platform-accessibility.md). | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -53,6 +53,10 @@ produce identical frames on both platforms. | |||
| 53 | 53 | - **Fast Refresh hot reload.** `pn run --hot-reload` watches `app/` | |
| 54 | 54 | and patches the running app in place, preserving component state | |
| 55 | 55 | across most edits. | |
| 56 | + - **Instant desktop preview.** `pn preview` renders your app in a | ||
| 57 | + desktop window with Fast Refresh, so you can iterate on UI, state, | ||
| 58 | + and navigation in milliseconds — no simulator boot required. See the | ||
| 59 | + [Desktop preview guide](guides/desktop-preview.md). | ||
| 56 | 60 | - **An extension SDK.** [`pythonnative.sdk`](api/sdk.md) lets you | |
| 57 | 61 | wrap any platform widget as a first-class element with | |
| 58 | 62 | type-checked props, and PyPI plugins auto-register through the | |
@@ -63,6 +67,8 @@ produce identical frames on both platforms. | |||
| 63 | 67 | ## Quick links | |
| 64 | 68 | ||
| 65 | 69 | - New here? Start with [Getting started](getting-started.md). | |
| 70 | + - Want to see it run right now? Try the | ||
| 71 | + [Desktop preview](guides/desktop-preview.md). | ||
| 66 | 72 | - Want the bigger picture? Read [Mental model](concepts/mental-model.md). | |
| 67 | 73 | - Looking up an API? [Package overview](api/pythonnative.md). | |
| 68 | 74 | - Wrapping a custom widget? Read | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -46,12 +46,22 @@ a small Python factory that returns | |||
| 46 | 46 | ||
| 47 | 47 | ## Does PythonNative work on the desktop? | |
| 48 | 48 | ||
| 49 | - The core (components, hooks, reconciler) is platform-agnostic and runs | ||
| 50 | - on the desktop with a [mock registry](../guides/testing.md#a-minimal-mock-registry). | ||
| 51 | - That's how the test suite works. There is no built-in *desktop* widget | ||
| 52 | - backend; if you need one, plug a Tk or Qt-backed | ||
| 53 | - [`NativeViewRegistry`][pythonnative.native_views.NativeViewRegistry] | ||
| 54 | - in. | ||
| 49 | + Yes — for **previewing**. `pn preview` renders your app in a native | ||
| 50 | + desktop window using a built-in Tkinter backend, with instant Fast | ||
| 51 | + Refresh on every save. It's the fastest inner-loop: see your real UI | ||
| 52 | + and iterate in seconds without booting a simulator or deploying to a | ||
| 53 | + device. The same flex layout engine and reconciler drive it, so what | ||
| 54 | + you see closely matches the device. See the | ||
| 55 | + [Desktop preview guide](../guides/desktop-preview.md). | ||
| 56 | + | ||
| 57 | + The desktop backend is a **development tool**, not a production target: | ||
| 58 | + chrome like rounded corners, shadows, and overflow clipping are | ||
| 59 | + approximated, and there's no app packaging for desktop. Ship to devices | ||
| 60 | + with `pn run android` / `pn run ios`. | ||
| 61 | + | ||
| 62 | + The core (components, hooks, reconciler) is also platform-agnostic and | ||
| 63 | + runs headless with a [mock registry](../guides/testing.md#a-minimal-mock-registry) — | ||
| 64 | + that's how the unit-test suite works. | ||
| 55 | 65 | ||
| 56 | 66 | ## How do I package and distribute my app? | |
| 57 | 67 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,30 @@ | |||
| 1 | + # Hello World | ||
| 2 | + | ||
| 3 | + The smallest PythonNative app: a counter with navigation to a detail | ||
| 4 | + screen. | ||
| 5 | + | ||
| 6 | + ## Preview it on your desktop (fastest) | ||
| 7 | + | ||
| 8 | + From this directory, install the example's dependencies (the preview | ||
| 9 | + imports your real app code), then launch it: | ||
| 10 | + | ||
| 11 | + ```bash | ||
| 12 | + pip install -r requirements.txt | ||
| 13 | + pn preview | ||
| 14 | + ``` | ||
| 15 | + | ||
| 16 | + A desktop window opens running `app/main.py`'s `App`. Edit any component | ||
| 17 | + under `app/`, save, and the window Fast Refreshes in place — no | ||
| 18 | + simulator or device needed. See the | ||
| 19 | + [Desktop preview guide](../../docs/guides/desktop-preview.md). | ||
| 20 | + | ||
| 21 | + ## Run on a device or simulator | ||
| 22 | + | ||
| 23 | + ```bash | ||
| 24 | + pn run ios | ||
| 25 | + # or | ||
| 26 | + pn run android | ||
| 27 | + ``` | ||
| 28 | + | ||
| 29 | + Add `--hot-reload` to push edits to the running app without a full | ||
| 30 | + rebuild. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments