Skip to content

Commit 4c47f6e

Browse files
Merge branch 'master' of github.com:fork-tongue/collagraph into finegrained-reactivity
2 parents 5375148 + 28ee8e6 commit 4c47f6e

18 files changed

Lines changed: 695 additions & 54 deletions

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,10 @@ uv run collagraph examples/pyside/counter.cgx
8181

8282
For more examples, please take a look at the [examples folder](examples).
8383

84-
Currently there are three renderers:
84+
Currently there are two renderers:
8585

8686
* [PysideRenderer](collagraph/renderers/pyside_renderer.py): for rendering PySide6 applications
8787
* [PygfxRenderer](collagraph/renderers/pygfx_renderer.py): for rendering 3D graphic scenes with [Pygfx](https://github.com/pygfx/pygfx)
88-
* [DomRenderer](collagraph/renderers/dom_renderer.py): for rendering to browser DOM through [PyScript](https://pyscript.net) (or rather [Pyodide](https://pyodide.org/en/stable/))
8988

9089
It is possible to create a custom Renderer using the [Renderer](collagraph/renderers/__init__.py) interface, to render to other UI frameworks, for instance wxPython.
9190

collagraph/__main__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ def available_renderers():
1616
"PySideRenderer": "pyside",
1717
"PygfxRenderer": "pygfx",
1818
"DictRenderer": "dict",
19-
# TODO: add support for DomRenderer
20-
# "DomRenderer": "dom",
2119
}.items():
2220
try:
2321
importlib.import_module("collagraph", renderer_type)

collagraph/renderers/__init__.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,6 @@ def remove_event_listener(self, el: Any, event_type: str, value: Callable):
7070

7171
from .dict_renderer import DictRenderer
7272

73-
try:
74-
import js
75-
except ImportError: # pragma: no cover
76-
pass
77-
else:
78-
from .dom_renderer import DomRenderer
79-
8073
try:
8174
import pygfx
8275
except ImportError: # pragma: no cover

collagraph/renderers/dom_renderer.py

Lines changed: 0 additions & 37 deletions
This file was deleted.

collagraph/renderers/pyside/objects/standarditem.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def insert(self, el, anchor=None):
2828
def remove(self, el):
2929
if hasattr(el, "model_index"):
3030
# Only support removal of rows for now
31-
row, column = getattr(el, "model_index")
31+
row, _column = getattr(el, "model_index")
3232
if model := el.model():
3333
index = model.indexFromItem(el)
3434
row = index.row()

collagraph/renderers/pyside_renderer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
from collections import defaultdict
3-
from functools import lru_cache
3+
from functools import lru_cache, partial
44
from typing import Any, Callable
55
from warnings import warn
66

@@ -404,6 +404,11 @@ def add_event_listener(self, el: Any, event_type: str, value: Callable):
404404
# a QObject?) results in a SystemError. Lambdas though _can_ function
405405
# as a slot, so when creating a slot of the value fails, retry with
406406
# a simple lambda.
407+
if isinstance(value, partial):
408+
# In the case that value is a partial object, Pyside 6.9.2 spits
409+
# out a warning 'PytestUnraisableExceptionWarning'. Wrapping the
410+
# partial in a lambda seems to do the trick
411+
raise SystemError
407412
slot = QtCore.Slot()(value)
408413
except SystemError:
409414
# TODO: with some inspection we might be able to figure out the

collagraph/sfc/importer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def find_spec(
3939
# In our case we can just return the existing spec.
4040
return target.__spec__
4141

42-
package, _, module_name = name.rpartition(".")
42+
_package, _, module_name = name.rpartition(".")
4343
sfc_file_name = f"{module_name}.{compiler.SUFFIX}"
4444
directories = sys.path if path is None else path
4545
for directory in directories:

examples/pygfx/combined-example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
renderer.register_element("WgpuCanvas", WgpuCanvas)
1717
gui = cg.Collagraph(renderer=renderer)
1818

19-
state = reactive({"count": 500})
19+
state = reactive({"count": 1000})
2020

2121
# Define Qt structure and map state to the structure
2222
# Pass in the app as a container. Can actually be any truthy object

examples/pygfx/point_cloud.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import random
2+
3+
import pygfx as gfx
4+
5+
from collagraph import h
6+
7+
sphere_geom = gfx.sphere_geometry(radius=0.5)
8+
materials = {
9+
"default": gfx.MeshPhongMaterial(color=[1, 1, 1], pick_write=True),
10+
"selected": gfx.MeshPhongMaterial(color=[1, 0, 0], pick_write=True),
11+
"hovered": gfx.MeshPhongMaterial(color=[1, 0.6, 0], pick_write=True),
12+
"other": gfx.MeshPhongMaterial(color=[1, 0, 0.5], pick_write=True),
13+
}
14+
15+
16+
def rand_point():
17+
return (
18+
random.randint(-20, 20),
19+
random.randint(-20, 20),
20+
random.randint(-20, 20),
21+
)
22+
23+
24+
def point(index, selected, hovered, set_selected, set_hovered):
25+
material = (
26+
"selected"
27+
if index == selected
28+
else "hovered"
29+
if index == hovered
30+
else "default"
31+
)
32+
return h(
33+
"Mesh",
34+
{
35+
"geometry": sphere_geom,
36+
"material": materials[material],
37+
"local.position": positions[index],
38+
"key": index,
39+
"on_click": lambda event: set_selected(index),
40+
"on_pointer_move": lambda event: set_hovered(index),
41+
},
42+
)
43+
44+
45+
positions = []
46+
47+
48+
def PointCloud(props):
49+
global positions
50+
51+
def set_hovered(index):
52+
props["hovered"] = index
53+
54+
def set_selected(index):
55+
if props.get("selected", -1) == index:
56+
props["selected"] = -1
57+
else:
58+
props["selected"] = index
59+
60+
selected = props.get("selected", -1)
61+
hovered = props.get("hovered", -1)
62+
number_of_points = props.get("count", 50)
63+
64+
if len(positions) > number_of_points:
65+
positions = positions[:number_of_points]
66+
elif len(positions) < number_of_points:
67+
positions.extend(
68+
[rand_point() for _ in range(number_of_points - len(positions))]
69+
)
70+
71+
return h(
72+
"Group",
73+
{},
74+
*[
75+
point(i, selected, hovered, set_selected, set_hovered)
76+
for i in range(number_of_points)
77+
],
78+
)

examples/pygfx/pygfx-example.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import pygfx as gfx
2+
from point_cloud import PointCloud, materials, sphere_geom
3+
from wgpu.gui.auto import WgpuCanvas, run
4+
5+
import collagraph as cg
6+
from collagraph import h
7+
8+
if __name__ == "__main__":
9+
canvas = WgpuCanvas(size=(600, 400))
10+
renderer = gfx.renderers.WgpuRenderer(canvas)
11+
12+
camera = gfx.PerspectiveCamera(60, 16 / 9)
13+
camera.local.z = 15
14+
camera.show_pos((0, 0, 0))
15+
16+
controls = gfx.OrbitController(camera)
17+
controls.register_events(renderer)
18+
19+
gui = cg.Collagraph(renderer=cg.PygfxRenderer())
20+
21+
element = h(
22+
"Group",
23+
{
24+
"name": "Landmarks",
25+
},
26+
h("AmbientLight"),
27+
h("PointLight", {"local.position": (0, 70, 70), "cast_shadow": True}),
28+
h(
29+
PointCloud,
30+
# When increasing this number, it will take longer
31+
# and longer for pygfx to create the render pipeline
32+
# (compiling shaders and such), so be careful...
33+
{"count": 500},
34+
),
35+
h(
36+
"Mesh",
37+
{
38+
"name": "Hip",
39+
"local.position": (2, 2, 2),
40+
"geometry": sphere_geom,
41+
"material": materials["other"],
42+
},
43+
),
44+
)
45+
46+
container = gfx.Scene()
47+
48+
def animate():
49+
renderer.render(container, camera)
50+
51+
gui.render(element, container, callback=lambda: canvas.request_draw(animate))
52+
canvas.request_draw(animate)
53+
run()

0 commit comments

Comments
 (0)