FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat!: overhaul native bridge, animations, gestures, and lists by owenthcarey · Pull Request #11 · pythonnative/pythonnative · GitHub

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .gradle  (1) .java  (1) .md  (18) .py  (53) .sh  (1) .yaml  (19) .yml  (2) All 7 file types selected
Only manifest files
Deleted files Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
45 changes: 36 additions & 9 deletions .github/workflows/e2e.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,20 @@ jobs:
# ~35th flow. Sharding the run into a few balanced groups — each on
# its own freshly booted emulator — keeps every session well under
# the limit, and parallelizes the Android pass as a bonus. Groups are
# sized so none exceeds ~25 flows; ``components`` is the largest
# single category and runs alone. ``fail-fast: false`` so one shard's
# failure still lets the others report.
# sized so none exceeds ~15 flows; ``components`` (28 flows after the
# gesture/animated additions) crossed the ceiling at ~flow 26, so it
# runs as two halves. ``fail-fast: false`` so one shard's failure
# still lets the others report.
strategy:
fail-fast: false
matrix:
include:
- name: components
suites: components
- name: hooks-nav
suites: hooks navigation
- name: components-a
suites: components-a
- name: components-b
suites: components-b
- name: hooks-nav-gestures
suites: hooks navigation gestures
- name: layout-styling-anim-misc
suites: layout styling animations misc

Expand Down Expand Up @@ -85,6 +88,18 @@ jobs:
arch: x86_64
script: bash -lc "./scripts/run-e2e.sh android ${{ matrix.suites }}"

# Maestro writes per-flow debug output (command log, view-hierarchy
# dumps, failure screenshots) under ~/.maestro/tests. Surface it when
# a shard fails so CI-only failures are diagnosable without guesswork.
- name: Upload Maestro debug output
if: failure()
uses: actions/upload-artifact@v4
with:
name: maestro-debug-android-${{ matrix.name }}
path: ~/.maestro/tests/
if-no-files-found: ignore
retention-days: 7

e2e-ios:
needs: coverage
runs-on: macos-latest
Expand All @@ -104,8 +119,8 @@ jobs:
include:
- name: components
suites: components
- name: hooks-nav
suites: hooks navigation
- name: hooks-nav-gestures
suites: hooks navigation gestures
- name: layout-styling-anim-misc
suites: layout styling animations misc

Expand Down Expand Up @@ -133,3 +148,15 @@ jobs:
run: ./scripts/run-e2e.sh ios ${{ matrix.suites }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# Maestro writes per-flow debug output (command log, view-hierarchy
# dumps, failure screenshots) under ~/.maestro/tests. Surface it when
# a shard fails so CI-only failures are diagnosable without guesswork.
- name: Upload Maestro debug output
if: failure()
uses: actions/upload-artifact@v4
with:
name: maestro-debug-ios-${{ matrix.name }}
path: ~/.maestro/tests/
if-no-files-found: ignore
retention-days: 7
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,12 @@ Recommended scopes (choose the smallest, most accurate unit; prefer module/direc
- `cli` – CLI tool and `pn` command (`src/pythonnative/cli/`)
- `components` – declarative element-creating functions (`components.py`)
- `element` – Element descriptor class (`element.py`)
- `events` – tag-based event routing between native views and Python callbacks (`events.py`)
- `gestures` – gesture descriptors and the pure-Python recognition arbiter (`gestures.py`)
- `hooks` – function components and hooks (`hooks.py`)
- `hot_reload` – file watcher and module reloader (`hot_reload.py`)
- `layout` – pure-Python flexbox engine (`layout.py`)
- `mutations` – batched mutation ops between reconciler and native backends (`mutations.py`)
- `native_modules` – native API modules for device capabilities (`native_modules/`)
- `native_views` – platform-specific native view creation and updates (`native_views/`)
- `navigation` – navigation containers and stack/tab/drawer navigators (`navigation.py`)
Expand Down
5 changes: 4 additions & 1 deletion README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ PythonNative is a cross-platform toolkit for building native Android and iOS app
- **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect`, and navigation with `use_navigation`, all through one consistent pattern.
- **Typed `style` prop:** Pass all visual and layout properties through a single `style` dict, fully described by the `pn.Style` `TypedDict` and the ergonomic `pn.style(...)` helper for IDE autocomplete and static checking. Compose reusable styles with `StyleSheet`.
- **Cross-platform flexbox engine:** A pure-Python, Yoga-style layout engine computes frames once and applies them to native views, so `flex`, `padding`, `aspect_ratio`, and `position: "absolute"` produce the same geometry on Android and iOS.
- **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation. State updates re-render **locally** — only the component whose state changed (and its subtree) re-runs, and unchanged leaves reuse cached intrinsic measurements — so deep UIs stay responsive instead of re-rendering the whole app from the root on every tap.
- **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation. Each commit lands as **one batched transaction** of mutation ops, and event callbacks are routed through a tag-based registry so re-renders that only change closures cost zero native calls. State updates re-render **locally** — only the component whose state changed (and its subtree) re-runs, and unchanged leaves reuse cached intrinsic measurements — so deep UIs stay responsive instead of re-rendering the whole app from the root on every tap.
- **Native-driven animations:** The `Animated` API (timing / spring / decay, awaitable or fire-and-forget) hands animations to Core Animation and `ViewPropertyAnimator` whenever possible, so no Python code runs per frame; a pure-Python ticker covers the rest.
- **Native gesture system:** Attach `Tap`, `LongPress`, `Pan`, `Swipe`, `Pinch`, and `Rotation` recognizers to any view via the `gestures=` prop — backed by `UIGestureRecognizer` on iOS and a unit-testable pure-Python arbiter on Android and desktop.
- **Virtualized lists:** `FlatList` / `SectionList` window their rows in Python over the platform scroll view — uniform, exact, or measured variable heights, grids, headers/footers, infinite scroll, and an imperative scroll controller, identical on every platform.
- **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge.
- **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.
- **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
Expand Down
22 changes: 22 additions & 0 deletions docs/api/gestures.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Gestures

Native-backed gesture recognition, attached to any view-like element
via the `gestures=` prop. Descriptors are frozen dataclasses; their
numeric configuration crosses the bridge while callbacks are routed
through the tag-based event channel. See the
[Gestures guide](../guides/gestures.md) for usage patterns.

::: pythonnative.gestures
options:
show_root_heading: false
show_root_toc_entry: false
members_order: source
filters: ["!^_"]
show_if_no_docstring: true

## See also

- The [Gestures guide](../guides/gestures.md) walks through taps,
drags, and gesture-driven animations.
- [Animated](animated.md) pairs with `Pan` velocity for springs and
decays.
23 changes: 22 additions & 1 deletion docs/api/native_views.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Native views

The bridge between PythonNative's element tree and concrete native
widgets. Every element type maps to a
widgets. Each commit's diff is expressed as a flat list of mutation
ops referencing integer tags, applied through a single
[`apply_mutations`][pythonnative.native_views.NativeViewRegistry.apply_mutations]
call. Every element type maps to a
[`ViewHandler`][pythonnative.native_views.base.ViewHandler]
implementation in the
[`NativeViewRegistry`][pythonnative.native_views.NativeViewRegistry];
Expand All @@ -15,6 +18,24 @@ the platform-specific handlers are registered lazily so importing
members_order: source
filters: ["!^_"]

## Mutation ops

::: pythonnative.mutations
options:
show_root_heading: false
show_root_toc_entry: false
members_order: source
filters: ["!^_"]

## Event routing

::: pythonnative.events
options:
show_root_heading: false
show_root_toc_entry: false
members_order: source
filters: ["!^_"]

## Base classes

::: pythonnative.native_views.base
Expand Down
30 changes: 23 additions & 7 deletions docs/concepts/architecture.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ platform APIs synchronously from Python.
3. **Reconciler.** On first render, the
[`Reconciler`][pythonnative.reconciler.Reconciler] walks the tree
and creates real native views via the platform backend. On
subsequent renders it diffs against the previous tree and applies
the minimal set of native mutations. State-driven renders are
subsequent renders it diffs against the previous tree and emits the
minimal list of mutation ops (create / update / insert / remove /
destroy / set-frame), applied as **one batched transaction** per
commit through
[`apply_mutations`][pythonnative.native_views.NativeViewRegistry.apply_mutations].
State-driven renders are
**local**: a setter marks only its own component subtree dirty, and
[`flush_dirty`][pythonnative.reconciler.Reconciler.flush_dirty]
re-runs just those components instead of the whole app from the
Expand Down Expand Up @@ -95,11 +99,14 @@ and a new one is created.
so [`use_context`][pythonnative.use_context] still resolves. Hooks
record state reads, queue effects, and register memos. No native
mutations happen yet.
3. **Commit phase**: the reconciler applies the diff for each
re-rendered subtree to native views, creating, updating, and
removing views as needed, and bubbles any swapped subtree-root view
up to its nearest native container.
4. **Layout phase**: a layout pass recomputes frames. Leaves whose
3. **Commit phase**: the reconciler turns the diff for each
re-rendered subtree into mutation ops referencing view tags, and
flushes them all in a single `apply_mutations` transaction. Event
callbacks are routed to the Python-side
[`EventRegistry`][pythonnative.events.EventRegistry] instead of
crossing the bridge, so callback-identity churn costs nothing.
4. **Layout phase**: a layout pass recomputes frames and emits
`SetFrameOp`s only for frames that changed. Leaves whose
`Element` is unchanged reuse a cached intrinsic measurement, so
untouched subtrees skip native `measure_intrinsic` calls.
5. **Effect phase**: pending effects are flushed in depth-first order
Expand Down Expand Up @@ -256,9 +263,18 @@ Each handler class maps an element type name (e.g., `"Text"`,
`"Button"`) to platform-native widget creation, property updates, and
child management. The
[`NativeViewRegistry`][pythonnative.native_views.NativeViewRegistry]
owns the tag-to-view table, applies each commit's mutation list, and
lazily imports only the relevant platform module at runtime, so the
package can be imported on any platform for testing.

Native events flow back through a single channel: handlers wire their
platform listeners once at view creation and call
[`dispatch_event`][pythonnative.events.dispatch_event] with the view's
tag; the [`EventRegistry`][pythonnative.events.EventRegistry] resolves
the current Python callback. Gestures
([`pythonnative.gestures`](../api/gestures.md)) and natively-driven
animations (the `Animated` API) ride the same tag infrastructure.

## Comparisons

!!! note "Versus React Native"
Expand Down
6 changes: 3 additions & 3 deletions docs/concepts/components.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ pn.Column(
**Lists:**

- [`FlatList(data, render_item, key_extractor, item_height, ...)`][pythonnative.FlatList]:
scrollable data list. Pass `item_height=` to enable native
virtualization (`UITableView` / `RecyclerView`); rows are mounted
lazily as they scroll into view.
virtualized scrollable data list. Rows are mounted lazily as they
scroll into view; pass `item_height=` (or `get_item_height=`) for
exact extents, or let rows be measured on screen.
- [`SectionList(sections, render_item, render_section_header, item_height, ...)`][pythonnative.SectionList]:
virtualized list with section headers.

Expand Down
Loading
Loading

Back | FazBrowse Home | New Git URL