Skip to content

Commit 3bd87de

Browse files
committed
feat!: add function components, hooks, layout, styling, hot reload, native APIs, and new UI components
1 parent a529834 commit 3bd87de

32 files changed

+3430
-120
lines changed

CONTRIBUTING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,14 @@ Recommended scopes (choose the smallest, most accurate unit; prefer module/direc
100100
- `cli` – CLI tool and `pn` command (`src/pythonnative/cli/`)
101101
- `components` – declarative element-creating functions (`components.py`)
102102
- `element` – Element descriptor class (`element.py`)
103+
- `hooks` – function components and hooks (`hooks.py`)
104+
- `hot_reload` – file watcher and module reloader (`hot_reload.py`)
105+
- `native_modules` – native API modules for device capabilities (`native_modules/`)
103106
- `native_views` – platform-specific native view creation and updates (`native_views.py`)
104107
- `package``src/pythonnative/__init__.py` exports and package boundary
105108
- `page` – Page component, lifecycle, and reactive state (`page.py`)
106109
- `reconciler` – virtual view tree diffing and reconciliation (`reconciler.py`)
110+
- `style` – StyleSheet and theming (`style.py`)
107111
- `utils` – shared utilities (`utils.py`)
108112

109113
- Other scopes:

docs/api/component-properties.md

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
All style and behaviour properties are passed as keyword arguments to element functions.
44

5+
## Common layout properties
6+
7+
All components accept these layout properties:
8+
9+
- `width` — fixed width in dp (Android) / pt (iOS)
10+
- `height` — fixed height
11+
- `flex` — flex grow factor within Column/Row
12+
- `margin` — outer spacing (int, float, or dict with `horizontal`, `vertical`, `left`, `top`, `right`, `bottom`)
13+
- `min_width`, `max_width` — width constraints
14+
- `min_height`, `max_height` — height constraints
15+
- `align_self` — override parent alignment (`"fill"`, `"center"`, etc.)
16+
- `key` — stable identity for reconciliation
17+
518
## Text
619

720
```python
@@ -42,6 +55,22 @@ pn.Row(*children, spacing=0, padding=None, alignment=None, background_color=None
4255
- `alignment` — cross-axis: `"fill"`, `"center"`, `"leading"`, `"trailing"`, `"start"`, `"end"`, `"top"`, `"bottom"`
4356
- `background_color` — container background
4457

58+
## View
59+
60+
```python
61+
pn.View(*children, background_color=None, padding=None)
62+
```
63+
64+
Generic container (UIView / FrameLayout). Supports all layout properties.
65+
66+
## SafeAreaView
67+
68+
```python
69+
pn.SafeAreaView(*children, background_color=None, padding=None)
70+
```
71+
72+
Container that respects safe area insets (notch, status bar).
73+
4574
## ScrollView
4675

4776
```python
@@ -63,6 +92,9 @@ pn.TextInput(value="", placeholder="", on_change=None, secure=False,
6392
pn.Image(source="", width=None, height=None, scale_type=None, background_color=None)
6493
```
6594

95+
- `source` — image URL (`http://...` / `https://...`) or local resource name
96+
- `scale_type``"cover"`, `"contain"`, `"stretch"`, `"center"`
97+
6698
## Switch
6799

68100
```python
@@ -71,6 +103,14 @@ pn.Switch(value=False, on_change=None)
71103

72104
- `on_change` — callback `(bool) -> None`
73105

106+
## Slider
107+
108+
```python
109+
pn.Slider(value=0.0, min_value=0.0, max_value=1.0, on_change=None)
110+
```
111+
112+
- `on_change` — callback `(float) -> None`
113+
74114
## ProgressBar
75115

76116
```python
@@ -94,7 +134,36 @@ pn.WebView(url="")
94134
## Spacer
95135

96136
```python
97-
pn.Spacer(size=None)
137+
pn.Spacer(size=None, flex=None)
98138
```
99139

100140
- `size` — fixed dimension in dp / pt
141+
- `flex` — flex grow factor
142+
143+
## Pressable
144+
145+
```python
146+
pn.Pressable(child, on_press=None, on_long_press=None)
147+
```
148+
149+
Wraps any child element with tap/long-press handling.
150+
151+
## Modal
152+
153+
```python
154+
pn.Modal(*children, visible=False, on_dismiss=None, title=None, background_color=None)
155+
```
156+
157+
Overlay dialog shown when `visible=True`.
158+
159+
## FlatList
160+
161+
```python
162+
pn.FlatList(data=None, render_item=None, key_extractor=None,
163+
separator_height=0, background_color=None)
164+
```
165+
166+
- `data` — list of items
167+
- `render_item``(item, index) -> Element` function
168+
- `key_extractor``(item, index) -> str` for stable keys
169+
- `separator_height` — spacing between items

docs/api/pythonnative.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,56 @@
99
### Element functions
1010

1111
- `pythonnative.Text`, `Button`, `Column`, `Row`, `ScrollView`, `TextInput`, `Image`, `Switch`, `ProgressBar`, `ActivityIndicator`, `WebView`, `Spacer`
12+
- `pythonnative.View`, `SafeAreaView`, `Modal`, `Slider`, `Pressable`, `FlatList`
1213

1314
Each returns an `Element` descriptor. See the Component Property Reference for full signatures.
1415

1516
### Element
1617

1718
`pythonnative.Element` — the descriptor type returned by element functions. You generally don't create these directly.
1819

20+
### Hooks
21+
22+
Function component primitives:
23+
24+
- `pythonnative.component` — decorator to create a function component
25+
- `pythonnative.use_state(initial)` — local component state
26+
- `pythonnative.use_effect(effect, deps)` — side effects
27+
- `pythonnative.use_memo(factory, deps)` — memoised values
28+
- `pythonnative.use_callback(fn, deps)` — stable function references
29+
- `pythonnative.use_ref(initial)` — mutable ref object
30+
- `pythonnative.use_context(context)` — read from context
31+
- `pythonnative.create_context(default)` — create a new context
32+
- `pythonnative.Provider(context, value, child)` — provide a context value
33+
34+
### Styling
35+
36+
- `pythonnative.StyleSheet` — utility for creating and composing style dicts
37+
- `pythonnative.ThemeContext` — built-in theme context (defaults to light theme)
38+
39+
## Native API modules
40+
41+
- `pythonnative.native_modules.Camera` — photo capture and gallery picking
42+
- `pythonnative.native_modules.Location` — GPS / location services
43+
- `pythonnative.native_modules.FileSystem` — app-scoped file I/O
44+
- `pythonnative.native_modules.Notifications` — local push notifications
45+
1946
## Internal helpers
2047

2148
- `pythonnative.utils.IS_ANDROID` — platform flag with robust detection for Chaquopy/Android.
2249
- `pythonnative.utils.get_android_context()` — returns the current Android `Activity`/`Context` when running on Android.
2350
- `pythonnative.utils.set_android_context(ctx)` — set by `Page` on Android; you generally don't call this directly.
2451
- `pythonnative.utils.get_android_fragment_container()` — returns the current Fragment container `ViewGroup` used for page rendering.
25-
- `pythonnative.utils.set_android_fragment_container(viewGroup)` — set by the host `PageFragment`; you generally don't call this directly.
2652

2753
## Reconciler
2854

29-
`pythonnative.reconciler.Reconciler` — diffs element trees and applies minimal native mutations. Used internally by `Page`.
55+
`pythonnative.reconciler.Reconciler` — diffs element trees and applies minimal native mutations. Supports key-based child reconciliation, function components, and context providers. Used internally by `Page`.
56+
57+
## Hot reload
58+
59+
`pythonnative.hot_reload.FileWatcher` — watches a directory for file changes and triggers a callback. Used by `pn run --hot-reload`.
60+
61+
`pythonnative.hot_reload.ModuleReloader` — reloads changed Python modules on the device and triggers page re-rendering.
3062

3163
## Native view registry
3264

docs/concepts/architecture.md

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,58 @@ PythonNative combines **direct native bindings** with a **declarative reconciler
55
## High-level model
66

77
1. **Declarative element tree:** Your `Page.render()` method returns a tree of `Element` descriptors (similar to React elements / virtual DOM nodes).
8-
2. **Reconciler:** On first render, the reconciler walks the tree and creates real native views via the platform backend. On subsequent renders (triggered by `set_state`), it diffs the new tree against the old one and applies the minimal set of native mutations.
9-
3. **Direct bindings:** Under the hood, native views are created and updated through direct platform calls:
8+
2. **Function components and hooks:** Reusable components with independent state via `@pn.component`, `use_state`, `use_effect`, etc. — inspired by React hooks but designed for Python.
9+
3. **Reconciler:** On first render, the reconciler walks the tree and creates real native views via the platform backend. On subsequent renders (triggered by `set_state` or hook state changes), it diffs the new tree against the old one and applies the minimal set of native mutations.
10+
4. **Key-based reconciliation:** Children can be assigned stable `key` values to preserve identity across re-renders — critical for lists and dynamic content.
11+
5. **Direct bindings:** Under the hood, native views are created and updated through direct platform calls:
1012
- **iOS:** rubicon-objc exposes Objective-C/Swift classes (`UILabel`, `UIButton`, `UIStackView`, etc.).
1113
- **Android:** Chaquopy exposes Java classes (`android.widget.TextView`, `android.widget.Button`, etc.) via the JNI bridge.
12-
4. **Thin native bootstrap:** The host app remains native (Android `Activity` or iOS `UIViewController`). It passes a live instance/pointer into Python, and Python drives the UI through the reconciler.
14+
6. **Thin native bootstrap:** The host app remains native (Android `Activity` or iOS `UIViewController`). It passes a live instance/pointer into Python, and Python drives the UI through the reconciler.
1315

1416
## How it works
1517

1618
```
1719
Page.render() → Element tree → Reconciler → Native views
1820
1921
Page.set_state() → re-render → diff → patch native views
22+
Hook set_state() → re-render → diff → patch native views
2023
```
2124

22-
The reconciler uses **positional diffing** (comparing children by index). When a child at a given position has the same element type, its props are updated in-place on the native view. When the type changes, the old native view is destroyed and a new one is created.
25+
The reconciler uses **key-based diffing** (matching children by key first, then by position). When a child with the same key/type is found, its props are updated in-place on the native view. When the type changes, the old native view is destroyed and a new one is created.
26+
27+
## Component model
28+
29+
PythonNative supports two kinds of components:
30+
31+
### Page classes (screens)
32+
33+
Each screen is a `Page` subclass that bridges native lifecycle events to Python. Pages have `render()`, `set_state()`, navigation (`push`/`pop`), and lifecycle hooks (`on_create`, `on_resume`, etc.).
34+
35+
### Function components (reusable UI)
36+
37+
Decorated with `@pn.component`, these are Python functions that return `Element` trees and can use hooks for state, effects, memoisation, and context. Each call site creates an independent instance with its own hook state.
38+
39+
```python
40+
@pn.component
41+
def counter(initial: int = 0) -> pn.Element:
42+
count, set_count = pn.use_state(initial)
43+
return pn.Text(f"Count: {count}")
44+
```
45+
46+
## Styling
47+
48+
- **Inline styles:** Pass props directly to components (`font_size=24`, `color="#333"`).
49+
- **StyleSheet:** Create reusable named style dictionaries with `pn.StyleSheet.create(...)`.
50+
- **Theming:** Use `pn.ThemeContext` with `pn.Provider` and `pn.use_context` to propagate theme values through the tree.
51+
52+
## Layout
53+
54+
All components support layout properties: `width`, `height`, `flex`, `margin`, `min_width`, `max_width`, `min_height`, `max_height`, `align_self`. Containers (`Column`, `Row`) support `spacing`, `padding`, and `alignment`.
2355

2456
## Comparison
2557

2658
- **Versus React Native:** RN uses JSX + a JavaScript bridge + Yoga layout. PythonNative uses Python + direct native calls + platform layout managers. No JS bridge, no serialisation overhead.
27-
- **Versus NativeScript:** Similar philosophy (direct, synchronous native access), but PythonNative adds a declarative reconciler layer that NativeScript does not have by default.
28-
- **Versus the old imperative API:** The previous PythonNative API required manual `add_view()` calls and explicit setter methods. The new declarative model handles view lifecycle automatically.
59+
- **Versus NativeScript:** Similar philosophy (direct, synchronous native access), but PythonNative adds a declarative reconciler layer and React-like hooks that NativeScript does not have by default.
2960

3061
## iOS flow (Rubicon-ObjC)
3162

@@ -39,6 +70,19 @@ The reconciler uses **positional diffing** (comparing children by index). When a
3970
- `PageFragment` calls `on_create()` on the Python `Page`, which renders and attaches views to the fragment container.
4071
- State changes trigger re-render; the reconciler patches Android views in-place.
4172

73+
## Hot reload
74+
75+
During development, `pn run --hot-reload` watches `app/` for file changes and pushes updated Python files to the running app, enabling near-instant UI updates without full rebuilds.
76+
77+
## Native API modules
78+
79+
PythonNative provides cross-platform modules for common device APIs:
80+
81+
- `pythonnative.native_modules.Camera` — photo capture and gallery
82+
- `pythonnative.native_modules.Location` — GPS / location services
83+
- `pythonnative.native_modules.FileSystem` — app-scoped file I/O
84+
- `pythonnative.native_modules.Notifications` — local push notifications
85+
4286
## Navigation model overview
4387

4488
- See the Navigation guide for full details.

0 commit comments

Comments
 (0)