Skip to content

Commit b46cbd9

Browse files
Add MkDocs documentation with GitHub Pages deployment (#176)
* Add MkDocs documentation with GitHub Pages deployment - Set up MkDocs with Material theme (mkdocs.yml, docs dependency group) - Add GitHub Actions workflow for building and deploying to GitHub Pages - Write documentation covering getting started, guide, renderers, reference, and examples - Add site/ to .gitignore * Update docs for text interpolation, pygfx text elements, and tree view features Document features merged from master: text expressions with {{ }} in element content, pygfx <text> element support (text/markdown attributes, child text nodes), keyed list reconciliation behavior, and PySide tree view drag-and-drop with preserved selection/expansion state. Add the new landmarks, keyed tree, and tree DnD examples to the example pages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Update docs for PySide text elements and --show-code/CGX_DEBUG Document the new PySide text element support (#191): update the renderer-support note in the template syntax guide, add a Text Content section to the PySide renderer page, and switch the counter examples to text interpolation to match examples/pyside/counter.cgx. Document the --show-code CLI flag and the CGX_DEBUG environment variable (#175) on the CLI page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Update actions in docs workflow to match the other pipelines Bump checkout to v7, setup-python to v6, and pin setup-uv to the same sha (v8.3.0) as ci.yml and benchmark.yml. Bump upload-pages-artifact and deploy-pages to v5. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent e501252 commit b46cbd9

23 files changed

Lines changed: 1869 additions & 0 deletions

.github/workflows/docs.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Docs
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: false
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v7
22+
- name: Install uv
23+
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
24+
- name: Set up Python
25+
uses: actions/setup-python@v6
26+
with:
27+
python-version-file: "pyproject.toml"
28+
- name: Install docs dependencies
29+
run: uv sync --only-group docs
30+
- name: Build docs
31+
run: uv run --no-sync mkdocs build
32+
- name: Upload artifact
33+
uses: actions/upload-pages-artifact@v5
34+
with:
35+
path: site
36+
37+
deploy:
38+
environment:
39+
name: github-pages
40+
url: ${{ steps.deployment.outputs.page_url }}
41+
runs-on: ubuntu-latest
42+
needs: build
43+
steps:
44+
- name: Deploy to GitHub Pages
45+
id: deployment
46+
uses: actions/deploy-pages@v5

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ build
77
*.spec
88
uv.lock
99
.venv
10+
site
1011
.benchmarks

docs/examples/pygfx-examples.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Pygfx Examples
2+
3+
These examples demonstrate 3D scene rendering with Pygfx. Run them with:
4+
5+
```sh
6+
uv run collagraph --renderer pygfx examples/pygfx/<file>.cgx
7+
```
8+
9+
## Interactive Sphere
10+
11+
A sphere that changes color on hover:
12+
13+
```html title="pygfx-component.cgx"
14+
<group>
15+
<ambient-light />
16+
<directional-light />
17+
<mesh
18+
:material="material()"
19+
:geometry="sphere"
20+
@pointer_enter="lambda ev: hover(True)"
21+
@pointer_leave="lambda ev: hover(False)"
22+
/>
23+
</group>
24+
25+
<script>
26+
import pygfx as gfx
27+
import collagraph as cg
28+
29+
sphere = gfx.sphere_geometry(radius=3)
30+
red = gfx.MeshPhongMaterial(color=(1, 0.2, 0.2), pick_write=True)
31+
blue = gfx.MeshPhongMaterial(color=(0.2, 0.4, 1.0), pick_write=True)
32+
33+
class Ball(cg.Component):
34+
def material(self):
35+
hovered = self.state.get("hovered", False)
36+
return red if hovered else blue
37+
38+
def hover(self, hover):
39+
self.state["hovered"] = hover
40+
</script>
41+
```
42+
43+
## Point Cloud
44+
45+
A dynamic point cloud with selection and hover state. Uses `v-for` to render multiple 3D objects and accepts props via CLI `--state`:
46+
47+
```sh
48+
uv run collagraph --renderer pygfx --state '{"count": 100}' examples/pygfx/point_cloud.cgx
49+
```
50+
51+
```html title="point_cloud.cgx"
52+
<ambient-light />
53+
<point-light />
54+
<group>
55+
<Point
56+
v-for="idx, position in enumerate(positions)"
57+
:position="position"
58+
:material="'selected' if idx == selected else 'hovered' if idx == hovered else 'default'"
59+
:index="idx"
60+
@selected="set_selected"
61+
@hovered="set_hovered"
62+
/>
63+
</group>
64+
65+
<script>
66+
import random
67+
import pygfx as gfx
68+
import collagraph
69+
from observ import watch
70+
from point import Point
71+
72+
class PointCloud(collagraph.Component):
73+
def init(self):
74+
self.state["positions"] = []
75+
self.state["hovered"] = -1
76+
self.state["selected"] = -1
77+
78+
self.watchers = {}
79+
self.watchers["count"] = watch(
80+
lambda: self.props["count"],
81+
self.update_positions,
82+
immediate=True,
83+
)
84+
85+
def update_positions(self):
86+
new_count = self.props["count"]
87+
old_count = len(self.state["positions"])
88+
if new_count > old_count:
89+
self.state["positions"].extend(
90+
[(random.randint(-20, 20), random.randint(-20, 20), random.randint(-20, 20))
91+
for _ in range(new_count - old_count)]
92+
)
93+
elif new_count < old_count:
94+
del self.state["positions"][0:old_count - new_count]
95+
96+
def set_hovered(self, index):
97+
self.state["hovered"] = index
98+
99+
def set_selected(self, index):
100+
if self.state["selected"] == index:
101+
self.state["selected"] = -1
102+
else:
103+
self.state["selected"] = index
104+
</script>
105+
```
106+
107+
## Landmarks with Text Labels
108+
109+
Random 3D landmarks with labels rendered as text content inside `<text>` elements, using `{{ }}` interpolation. Click a landmark sphere to increment and rename its label:
110+
111+
```sh
112+
uv run collagraph --renderer pygfx --state '{"count": 25}' examples/pygfx/landmarks.cgx
113+
```
114+
115+
```html title="landmarks.cgx (excerpt)"
116+
<group v-for="idx, landmark in enumerate(landmarks)">
117+
<group :local.position="landmark['position']">
118+
<mesh
119+
:geometry="sphere_geometry"
120+
:material="selected_sphere_material if idx == selected else sphere_material"
121+
@click="lambda ev: click_landmark(idx)"
122+
/>
123+
<text
124+
anchor="bottom-center"
125+
:font_size="14"
126+
:local.position="(0, 0.75, 0)"
127+
:material="selected_label_material if idx == selected else label_material"
128+
screen_space
129+
>
130+
{{landmark['name']}}
131+
</text>
132+
</group>
133+
</group>
134+
```
135+
136+
## Combined PySide + Pygfx
137+
138+
You can embed a Pygfx canvas inside a PySide6 application. See [`examples/pygfx/combined-example.py`](https://github.com/fork-tongue/collagraph/tree/master/examples/pygfx/combined-example.py) for a full example of rendering 3D content within a Qt widget layout.
139+
140+
## More Examples
141+
142+
See the [`examples/pygfx/`](https://github.com/fork-tongue/collagraph/tree/master/examples/pygfx) directory for:
143+
144+
- `point.cgx` -- Individual point component with hover/select
145+
- `button.cgx` / `numberpad.cgx` -- Reusable 3D UI components
146+
- `component-example.py` -- Pygfx with timer-based interaction
147+
- `render_widget.cgx` -- Wrapping PygfxRenderer inside a PySide widget

docs/examples/pyside-examples.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# PySide6 Examples
2+
3+
These examples demonstrate common patterns with the PySide6 renderer. Run them with:
4+
5+
```sh
6+
uv run collagraph examples/pyside/<file>.cgx
7+
```
8+
9+
## Counter
10+
11+
A minimal reactive component:
12+
13+
```html title="counter.cgx"
14+
<widget>
15+
<label :text="f'Count: {count}'" />
16+
<button text="bump" @clicked="bump" />
17+
</widget>
18+
19+
<script>
20+
import collagraph as cg
21+
22+
class Counter(cg.Component):
23+
def init(self):
24+
self.state["count"] = 0
25+
26+
def bump(self):
27+
self.state["count"] += 1
28+
</script>
29+
```
30+
31+
## Todo App
32+
33+
Demonstrates list management, text input, events, and child components:
34+
35+
```html title="todo_example.cgx"
36+
<window title="My First TODO app">
37+
<widget name="main-content">
38+
<label text="What do you want to do?" />
39+
<lineedit :text="text" @text_edited="handle_change" />
40+
<button text="Add" @clicked="handle_submit" />
41+
<label text="To do:" />
42+
<TodoList
43+
:items="items"
44+
@clicked="handle_complete"
45+
/>
46+
</widget>
47+
</window>
48+
49+
<script>
50+
import collagraph as cg
51+
from todo_list import TodoList
52+
53+
class TodoApp(cg.Component):
54+
def init(self):
55+
self.state["items"] = ["Groceries", "Laundry"]
56+
self.state["text"] = ""
57+
58+
def handle_change(self, event):
59+
self.state["text"] = event
60+
61+
def handle_submit(self):
62+
if todo := self.state["text"]:
63+
if todo in self.state["items"]:
64+
return
65+
self.state["items"].append(todo)
66+
self.state["text"] = ""
67+
68+
def handle_complete(self, event):
69+
self.state["items"].remove(event)
70+
</script>
71+
```
72+
73+
## Layouts
74+
75+
Collagraph supports all standard Qt layouts:
76+
77+
```html title="layout_example.cgx (excerpt)"
78+
<!-- Horizontal box layout -->
79+
<groupbox title="Horizontal" :layout="{'type': 'Box', 'direction': 'LeftToRight'}">
80+
<button v-for="i in range(4)" :key="i" :text="f'Button {i}'" />
81+
</groupbox>
82+
83+
<!-- Grid layout -->
84+
<groupbox title="Grid" :layout="{'type': 'Grid'}">
85+
<label text="Name:" grid_index="(0, 0)" />
86+
<lineedit grid_index="(0, 1)" />
87+
</groupbox>
88+
89+
<!-- Form layout -->
90+
<groupbox title="Form" :layout="{'type': 'Form'}">
91+
<lineedit form_label="Name:" form_index="0" />
92+
<combobox form_label="Type:" form_index="1" />
93+
</groupbox>
94+
```
95+
96+
## Window with Menus
97+
98+
```html
99+
<window>
100+
<menubar>
101+
<menu title="File">
102+
<action text="Open" @triggered="open_file" />
103+
<action separator />
104+
<action text="Quit" @triggered="quit" />
105+
</menu>
106+
</menubar>
107+
<widget>
108+
<!-- content -->
109+
</widget>
110+
</window>
111+
```
112+
113+
## More Examples
114+
115+
See the [`examples/pyside/`](https://github.com/fork-tongue/collagraph/tree/master/examples/pyside) directory for:
116+
117+
- `slider_example.cgx` -- Slider widgets
118+
- `combobox_example.cgx` -- Dropdown menus
119+
- `tabs_example.cgx` -- Tab widgets
120+
- `tree_widget_example.cgx` -- Tree views
121+
- `keyed_tree_demo.cgx` -- Keyed reorders that preserve tree selection/expansion
122+
- `tree_dnd_example.cgx` -- Drag and drop in a tree widget, driven by reactive state
123+
- `dialog_example.cgx` -- Dialogs
124+
- `template_refs.cgx` -- Using template refs
125+
- `big_list.cgx` -- Performance with large lists

docs/getting-started/cli.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# CLI
2+
3+
Collagraph includes a CLI to run components directly without writing a Python entry point.
4+
5+
## Usage
6+
7+
```sh
8+
collagraph [OPTIONS] <component.cgx>
9+
```
10+
11+
Or with uv:
12+
13+
```sh
14+
uv run collagraph [OPTIONS] <component.cgx>
15+
```
16+
17+
## Options
18+
19+
| Option | Default | Description |
20+
|--------|---------|-------------|
21+
| `--renderer {pyside,pygfx,dict}` | `pyside` | Renderer to use |
22+
| `--state <json>` | - | Initial state as JSON string or path to JSON file |
23+
| `--hot-reload`, `-H` | off | Enable hot reload (auto-update on file changes) |
24+
| `--show-code` | off | Pretty print the compiled Python code for the component and exit |
25+
26+
## Examples
27+
28+
```sh
29+
# Run a PySide component
30+
uv run collagraph examples/pyside/counter.cgx
31+
32+
# Run a Pygfx component
33+
uv run collagraph --renderer pygfx examples/pygfx/pygfx-component.cgx
34+
35+
# Run with initial state
36+
uv run collagraph --state '{"name": "World"}' hello.cgx
37+
38+
# Run with hot reload
39+
uv run collagraph -H examples/pyside/counter.cgx
40+
41+
# Inspect the Python code that is compiled for a component
42+
uv run collagraph --show-code examples/pyside/counter.cgx
43+
```
44+
45+
## Debugging Compiled Components
46+
47+
Templates are compiled to Python render methods. Besides `--show-code`, you can set the `CGX_DEBUG` environment variable to have the generated source written to a temporary file that is used as the compile filename. Debuggers (pdb, PyCharm, VS Code) can then step through the generated render methods with correct source display:
48+
49+
```sh
50+
CGX_DEBUG=1 uv run collagraph examples/pyside/counter.cgx
51+
```

0 commit comments

Comments
 (0)