kigo-gui-framework 2.4__tar.gz → 2.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/PKG-INFO +4 -5
  2. kigo_gui_framework-2.6/README.txt +1 -0
  3. kigo_gui_framework-2.6/kigo/app.py +107 -0
  4. kigo_gui_framework-2.6/kigo/gpu.py +206 -0
  5. kigo_gui_framework-2.6/kigo/shader_demo.py +23 -0
  6. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo_gui_framework.egg-info/PKG-INFO +4 -5
  7. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo_gui_framework.egg-info/SOURCES.txt +3 -1
  8. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo_gui_framework.egg-info/requires.txt +1 -0
  9. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/pyproject.toml +4 -4
  10. kigo_gui_framework-2.4/README.txt +0 -3
  11. kigo_gui_framework-2.4/kigo/app.py +0 -409
  12. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/MANIFEST.in +0 -0
  13. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/_init_.py +0 -0
  14. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/accelerate.py +0 -0
  15. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/fx_gl2d.py +0 -0
  16. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/fx_quick.py +0 -0
  17. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/hud.py +0 -0
  18. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/hwaccel.py +0 -0
  19. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/media.py +0 -0
  20. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/physics.py +0 -0
  21. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/physics_policy.py +0 -0
  22. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/runtime.py +0 -0
  23. /kigo_gui_framework-2.4/kigo/qt.py → /kigo_gui_framework-2.6/kigo/shim.py +0 -0
  24. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/skins.py +0 -0
  25. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/style.py +0 -0
  26. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/tree.py +0 -0
  27. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/widgets.py +0 -0
  28. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo_gui_framework.egg-info/dependency_links.txt +0 -0
  29. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo_gui_framework.egg-info/top_level.txt +0 -0
  30. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/setup.cfg +0 -0
  31. {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/setup.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kigo-gui-framework
3
- Version: 2.4
4
- Summary: Added WASM and HUD (HUD executed by F2) check out the website- https://kigo-the-best-gui-framework.netlify.app
3
+ Version: 2.6
4
+ Summary: Added Android support, smoke shader and ECS. Note that Kigo does NOT COME WITH ANY MALWARE, IF YOU SPOT IT PLEASE EMAIL bot28482@gmail.com
5
5
  Author-email: "Anand Kumar, Utkarsh Raghav Roy" <swiss.armyknife@icloud.com>
6
6
  License: zlib
7
7
  Keywords: gui,qt,pyqt6,physics,pybullet,simulation,robotics,animation
@@ -16,12 +16,11 @@ Requires-Dist: PyQt6==6.7.0
16
16
  Requires-Dist: pybullet>=3.2.5
17
17
  Requires-Dist: pybullet_data
18
18
  Requires-Dist: wasmtime>=25.0.0
19
+ Requires-Dist: pyopengl>=3.1.10
19
20
  Provides-Extra: web
20
21
  Requires-Dist: PyQt6-WebEngine==6.7.0; extra == "web"
21
22
  Provides-Extra: full
22
23
  Requires-Dist: markdown; extra == "full"
23
24
  Requires-Dist: PyQt6-WebEngine==6.7.0; extra == "full"
24
25
 
25
- Asset Caching
26
- Kigo introduces asset caching. What it does is keep things in memory so that if your app needs them it can access that thing much faster. Also I passed
27
- in the exam and am going to class five pray for me plz
26
+ Kigo now has Android support (experimental), ECS and a smoke shader.
@@ -0,0 +1 @@
1
+ Kigo now has Android support (experimental), ECS and a smoke shader.
@@ -0,0 +1,107 @@
1
+ # SPDX-License-Identifier: Zlib
2
+ # kigo/app.py
3
+
4
+ from __future__ import annotations
5
+ import sys
6
+ import time
7
+
8
+ from kigo.qt.backend import QtCore, QtWidgets, IS_ANDROID
9
+ from kigo.android import AndroidLifecycle, is_android
10
+
11
+
12
+ class App:
13
+ """
14
+ Base application class for Kigo.
15
+
16
+ - Desktop: PyQt6
17
+ - Android: PySide6
18
+ - Touch-friendly
19
+ - Lifecycle-aware
20
+ """
21
+
22
+ def __init__(self, *, dev: bool = False):
23
+ self.dev = dev
24
+ self._last_time = time.perf_counter()
25
+
26
+ # ----------------------------------
27
+ # Qt Application
28
+ # ----------------------------------
29
+ self.qt_app = QtWidgets.QApplication.instance()
30
+ if self.qt_app is None:
31
+ self.qt_app = QtWidgets.QApplication(sys.argv)
32
+
33
+ # ----------------------------------
34
+ # Android lifecycle
35
+ # ----------------------------------
36
+ self.lifecycle = None
37
+ if is_android():
38
+ self.lifecycle = AndroidLifecycle(self.qt_app)
39
+ self.lifecycle.paused.connect(self.on_pause)
40
+ self.lifecycle.resumed.connect(self.on_resume)
41
+
42
+ # ----------------------------------
43
+ # Frame update timer
44
+ # ----------------------------------
45
+ self._timer = QtCore.QTimer()
46
+ self._timer.timeout.connect(self._tick)
47
+
48
+ # --------------------------------------------------
49
+ # App lifecycle (override in subclasses)
50
+ # --------------------------------------------------
51
+ def on_start(self):
52
+ """
53
+ Called once when the app starts.
54
+ Override this to build UI.
55
+ """
56
+ pass
57
+
58
+ def on_pause(self):
59
+ """
60
+ Android only: app moved to background.
61
+ Override if needed.
62
+ """
63
+ if self.dev:
64
+ print("[Kigo] App paused")
65
+
66
+ def on_resume(self):
67
+ """
68
+ Android only: app returned to foreground.
69
+ Override if needed.
70
+ """
71
+ if self.dev:
72
+ print("[Kigo] App resumed")
73
+
74
+ def update(self, dt: float):
75
+ """
76
+ Called every frame.
77
+ Override for animations, logic, physics.
78
+ """
79
+ pass
80
+
81
+ # --------------------------------------------------
82
+ # Internal loop
83
+ # --------------------------------------------------
84
+ def _tick(self):
85
+ now = time.perf_counter()
86
+ dt = now - self._last_time
87
+ self._last_time = now
88
+ self.update(dt)
89
+
90
+ # --------------------------------------------------
91
+ # Run
92
+ # --------------------------------------------------
93
+ def run(self, fps: int = 60):
94
+ """
95
+ Start the application.
96
+ """
97
+ self.on_start()
98
+
99
+ interval_ms = int(1000 / max(1, fps))
100
+ self._timer.start(interval_ms)
101
+
102
+ if self.dev:
103
+ backend = "PySide6" if IS_ANDROID else "PyQt6"
104
+ platform = "Android" if is_android() else "Desktop"
105
+ print(f"[Kigo] Running on {platform} using {backend}")
106
+
107
+ return self.qt_app.exec()
@@ -0,0 +1,206 @@
1
+ # SPDX-License-Identifier: Zlib
2
+ # kigo/gpu.py
3
+
4
+ from __future__ import annotations
5
+ from typing import Any, Dict, Optional
6
+
7
+ from kigo.qt import QtCore, QtWidgets
8
+
9
+ # QtPy provides these modules when OpenGL is available
10
+ try:
11
+ from qtpy import QtOpenGLWidgets, QtOpenGL, QtGui
12
+ except Exception:
13
+ QtOpenGLWidgets = None
14
+ QtOpenGL = None
15
+ QtGui = None
16
+
17
+
18
+ _VERTEX_GLSL_120 = r"""
19
+ #version 120
20
+ attribute vec2 a_pos;
21
+ attribute vec2 a_uv;
22
+ varying vec2 v_uv;
23
+
24
+ void main() {
25
+ v_uv = a_uv;
26
+ gl_Position = vec4(a_pos.xy, 0.0, 1.0);
27
+ }
28
+ """
29
+
30
+
31
+ def _wrap_fragment_glsl_120(user_fragment: str) -> str:
32
+ """
33
+ Accepts your preset fragments (that use varying v_uv and gl_FragColor),
34
+ ensures they compile with GLSL 120 by NOT rewriting your code.
35
+ Requirement: user fragment must declare:
36
+ varying highp vec2 v_uv; (or varying vec2 v_uv;)
37
+ and must write gl_FragColor.
38
+ """
39
+ s = (user_fragment or "").strip()
40
+ if not s:
41
+ # fallback shader (solid magenta)
42
+ return r"""
43
+ #version 120
44
+ varying vec2 v_uv;
45
+ uniform float time;
46
+ void main() { gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0); }
47
+ """
48
+ if not s.startswith("#version"):
49
+ s = "#version 120\n" + s
50
+ return s
51
+
52
+
53
+ class _GLShaderWidget(QtOpenGLWidgets.QOpenGLWidget): # type: ignore
54
+ """
55
+ Internal OpenGL widget that renders a fullscreen quad using VBO+VAO.
56
+ """
57
+
58
+ def __init__(self, fragment_shader: str, parent=None):
59
+ super().__init__(parent)
60
+
61
+ # Prefer compatibility-style GLSL 120 so your existing presets compile
62
+ fmt = self.format()
63
+ fmt.setVersion(2, 1)
64
+ fmt.setProfile(fmt.OpenGLContextProfile.NoProfile)
65
+ self.setFormat(fmt) # QOpenGLWidget supports setFormat [2](https://doc.qt.io/qtforpython-6.5/PySide6/QtOpenGLWidgets/QOpenGLWidget.html)
66
+
67
+ self._frag_src_user = fragment_shader
68
+ self._uniforms: Dict[str, Any] = {}
69
+
70
+ self._program = None
71
+ self._vao = None
72
+ self._vbo = None
73
+ self._gl = None
74
+
75
+ # attribute locations (cached)
76
+ self._loc_pos = -1
77
+ self._loc_uv = -1
78
+
79
+ def set_uniform(self, name: str, value: Any):
80
+ self._uniforms[name] = value
81
+ self.update()
82
+
83
+ def initializeGL(self):
84
+ # QOpenGLWidget makes the context current here [2](https://doc.qt.io/qtforpython-6.5/PySide6/QtOpenGLWidgets/QOpenGLWidget.html)[3](https://doc.qt.io/qt-6/qopenglwidget.html)
85
+ self._gl = self.context().functions()
86
+
87
+ # Create program
88
+ self._program = QtOpenGL.QOpenGLShaderProgram(self) # [4](https://doc.qt.io/qt-6/qopenglshaderprogram.html)
89
+ ok_v = self._program.addShaderFromSourceCode(
90
+ QtOpenGL.QOpenGLShader.ShaderTypeBit.Vertex, _VERTEX_GLSL_120
91
+ )
92
+ frag_src = _wrap_fragment_glsl_120(self._frag_src_user)
93
+ ok_f = self._program.addShaderFromSourceCode(
94
+ QtOpenGL.QOpenGLShader.ShaderTypeBit.Fragment, frag_src
95
+ )
96
+
97
+ if not ok_v or not ok_f or not self._program.link():
98
+ # If shader fails, draw clear color only; keep app running
99
+ self._program = None
100
+ self._gl.glClearColor(0.2, 0.0, 0.2, 1.0)
101
+ return
102
+
103
+ self._program.bind()
104
+
105
+ # Attribute locations
106
+ self._loc_pos = self._program.attributeLocation("a_pos")
107
+ self._loc_uv = self._program.attributeLocation("a_uv")
108
+
109
+ # Fullscreen quad (triangle strip): 4 vertices
110
+ # a_pos(x,y), a_uv(u,v)
111
+ data = [
112
+ -1.0, -1.0, 0.0, 0.0,
113
+ 1.0, -1.0, 1.0, 0.0,
114
+ -1.0, 1.0, 0.0, 1.0,
115
+ 1.0, 1.0, 1.0, 1.0,
116
+ ]
117
+
118
+ # VAO (remembers VBO + attribute bindings) [5](https://wikis.khronos.org/opengl/Tutorial2:_VAOs,_VBOs,_Vertex_and_Fragment_Shaders_%28C_/_SDL%29)
119
+ self._vao = QtOpenGL.QOpenGLVertexArrayObject(self)
120
+ self._vao.create()
121
+ self._vao.bind()
122
+
123
+ # VBO (stores vertex data on GPU) [5](https://wikis.khronos.org/opengl/Tutorial2:_VAOs,_VBOs,_Vertex_and_Fragment_Shaders_%28C_/_SDL%29)
124
+ self._vbo = QtOpenGL.QOpenGLBuffer(QtOpenGL.QOpenGLBuffer.Type.VertexBuffer)
125
+ self._vbo.create()
126
+ self._vbo.bind()
127
+ self._vbo.setUsagePattern(QtOpenGL.QOpenGLBuffer.UsagePattern.StaticDraw)
128
+ self._vbo.allocate(bytes(bytearray(float(x).hex().encode() for x in []))) # placeholder
129
+
130
+ # Qt buffer allocate wants bytes; easiest is to use QByteArray
131
+ arr = QtCore.QByteArray()
132
+ arr.resize(4 * 4 * 4) # 4 vertices * 4 floats * 4 bytes
133
+ # write float32 into QByteArray
134
+ import struct
135
+ packed = struct.pack("<16f", *data)
136
+ for i, b in enumerate(packed):
137
+ arr[i] = b
138
+ self._vbo.allocate(arr)
139
+
140
+ stride = 4 * 4 # 4 floats per vertex
141
+
142
+ # Position attribute
143
+ self._program.enableAttributeArray(self._loc_pos)
144
+ self._program.setAttributeBuffer(self._loc_pos, QtOpenGL.GL_FLOAT, 0, 2, stride)
145
+
146
+ # UV attribute
147
+ self._program.enableAttributeArray(self._loc_uv)
148
+ self._program.setAttributeBuffer(self._loc_uv, QtOpenGL.GL_FLOAT, 2 * 4, 2, stride)
149
+
150
+ self._vbo.release()
151
+ self._vao.release()
152
+ self._program.release()
153
+
154
+ self._gl.glClearColor(0.0, 0.0, 0.0, 1.0)
155
+
156
+ def resizeGL(self, w: int, h: int):
157
+ if self._gl:
158
+ self._gl.glViewport(0, 0, w, h)
159
+
160
+ def paintGL(self):
161
+ if not self._gl:
162
+ return
163
+
164
+ self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT)
165
+
166
+ if not self._program or not self._vao:
167
+ return
168
+
169
+ self._program.bind()
170
+ self._vao.bind()
171
+
172
+ # Push uniforms
173
+ # (Only sets float/int; extend later if needed)
174
+ for k, v in self._uniforms.items():
175
+ try:
176
+ if isinstance(v, (int,)):
177
+ self._program.setUniformValue(k, int(v))
178
+ else:
179
+ self._program.setUniformValue(k, float(v))
180
+ except Exception:
181
+ pass
182
+
183
+ # Draw triangle strip quad
184
+ self._gl.glDrawArrays(self._gl.GL_TRIANGLE_STRIP, 0, 4)
185
+
186
+ self._vao.release()
187
+ self._program.release()
188
+
189
+
190
+ class ShaderView:
191
+ """
192
+ Kigo-facing widget wrapper.
193
+ User code stays Kigo-style:
194
+ view = ShaderView(fragment_shader=shader)
195
+ view.set_uniform("time", t)
196
+ """
197
+
198
+ def __init__(self, fragment_shader: str):
199
+ if QtOpenGLWidgets is None or QtOpenGL is None:
200
+ raise RuntimeError("OpenGL backend not available")
201
+
202
+ self.qt_widget = _GLShaderWidget(fragment_shader)
203
+
204
+ def set_uniform(self, name: str, value: Any):
205
+ self.qt_widget.set_uniform(name, value)
206
+
@@ -0,0 +1,23 @@
1
+ from kigo.app import App
2
+ from kigo.ui import Window
3
+ from kigo.gpu import ShaderView
4
+
5
+ # choose ONE shader (fire/water presets from your page)
6
+ shader = fire
7
+ # shader = water
8
+
9
+ class Demo(App):
10
+ def on_start(self):
11
+ self.main_window = Window(title="Kigo VBO Shader Demo", size=(600, 400))
12
+ self.view = ShaderView(fragment_shader=shader)
13
+ self.main_window.set_content(self.view)
14
+ self.main_window.show()
15
+
16
+ self.t = 0.0
17
+ self.schedule(self.update)
18
+
19
+ def update(self, dt):
20
+ self.t += dt
21
+ self.view.set_uniform("time", self.t)
22
+
23
+ Demo(dev=True).run()
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kigo-gui-framework
3
- Version: 2.4
4
- Summary: Added WASM and HUD (HUD executed by F2) check out the website- https://kigo-the-best-gui-framework.netlify.app
3
+ Version: 2.6
4
+ Summary: Added Android support, smoke shader and ECS. Note that Kigo does NOT COME WITH ANY MALWARE, IF YOU SPOT IT PLEASE EMAIL bot28482@gmail.com
5
5
  Author-email: "Anand Kumar, Utkarsh Raghav Roy" <swiss.armyknife@icloud.com>
6
6
  License: zlib
7
7
  Keywords: gui,qt,pyqt6,physics,pybullet,simulation,robotics,animation
@@ -16,12 +16,11 @@ Requires-Dist: PyQt6==6.7.0
16
16
  Requires-Dist: pybullet>=3.2.5
17
17
  Requires-Dist: pybullet_data
18
18
  Requires-Dist: wasmtime>=25.0.0
19
+ Requires-Dist: pyopengl>=3.1.10
19
20
  Provides-Extra: web
20
21
  Requires-Dist: PyQt6-WebEngine==6.7.0; extra == "web"
21
22
  Provides-Extra: full
22
23
  Requires-Dist: markdown; extra == "full"
23
24
  Requires-Dist: PyQt6-WebEngine==6.7.0; extra == "full"
24
25
 
25
- Asset Caching
26
- Kigo introduces asset caching. What it does is keep things in memory so that if your app needs them it can access that thing much faster. Also I passed
27
- in the exam and am going to class five pray for me plz
26
+ Kigo now has Android support (experimental), ECS and a smoke shader.
@@ -7,13 +7,15 @@ kigo/accelerate.py
7
7
  kigo/app.py
8
8
  kigo/fx_gl2d.py
9
9
  kigo/fx_quick.py
10
+ kigo/gpu.py
10
11
  kigo/hud.py
11
12
  kigo/hwaccel.py
12
13
  kigo/media.py
13
14
  kigo/physics.py
14
15
  kigo/physics_policy.py
15
- kigo/qt.py
16
16
  kigo/runtime.py
17
+ kigo/shader_demo.py
18
+ kigo/shim.py
17
19
  kigo/skins.py
18
20
  kigo/style.py
19
21
  kigo/tree.py
@@ -2,6 +2,7 @@ PyQt6==6.7.0
2
2
  pybullet>=3.2.5
3
3
  pybullet_data
4
4
  wasmtime>=25.0.0
5
+ pyopengl>=3.1.10
5
6
 
6
7
  [full]
7
8
  markdown
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "kigo-gui-framework"
7
- version = "2.4"
8
- description = "Added WASM and HUD (HUD executed by F2) check out the website- https://kigo-the-best-gui-framework.netlify.app"
7
+ version = "2.6"
8
+ description = "Added Android support, smoke shader and ECS. Note that Kigo does NOT COME WITH ANY MALWARE, IF YOU SPOT IT PLEASE EMAIL bot28482@gmail.com"
9
9
  readme = "README.txt"
10
10
  requires-python = ">=3.8"
11
11
  license = { text = "zlib" }
@@ -37,9 +37,9 @@ dependencies = [
37
37
  "PyQt6==6.7.0",
38
38
  "pybullet>=3.2.5",
39
39
  "pybullet_data",
40
- "wasmtime>=25.0.0"
40
+ "wasmtime>=25.0.0",
41
+ "pyopengl>=3.1.10",
41
42
  ]
42
-
43
43
  [project.optional-dependencies]
44
44
  web = ["PyQt6-WebEngine==6.7.0"]
45
45
  full = ["markdown", "PyQt6-WebEngine==6.7.0"]
@@ -1,3 +0,0 @@
1
- Asset Caching
2
- Kigo introduces asset caching. What it does is keep things in memory so that if your app needs them it can access that thing much faster. Also I passed
3
- in the exam and am going to class five pray for me plz
@@ -1,409 +0,0 @@
1
- # SPDX-License-Identifier: Zlib
2
- # kigo/app.py — Core Kigo App with Studio (Esc), HUD (F2), and Python/WASM modes.
3
-
4
- from __future__ import annotations
5
-
6
- import sys
7
- from dataclasses import dataclass
8
- from typing import Any, Dict, Optional
9
-
10
- from kigo.qt import QtWidgets, QtCore, QtGui
11
-
12
-
13
- # =====================================================
14
- # Runtime state
15
- # =====================================================
16
-
17
- class Runtime:
18
- def __init__(self, mode: str = "python"):
19
- if mode is None:
20
- mode = "python"
21
- mode = str(mode).strip().lower()
22
-
23
- if mode not in ("python", "wasm"):
24
- raise ValueError("mode must be 'python' or 'wasm'")
25
-
26
- self.mode = mode
27
-
28
- # Counters for HUD
29
- self.python_calls = 0
30
- self.wasm_calls = 0
31
- self.wasm_hits = 0
32
- self.wasm_fallbacks = 0
33
-
34
- # Capability flags
35
- self.wasm_available = False
36
- self.wasm_reason = ""
37
-
38
- def is_wasm(self) -> bool:
39
- return self.mode == "wasm"
40
-
41
-
42
- # =====================================================
43
- # Hot-path marker
44
- # =====================================================
45
-
46
- def hot(*, wasm: Optional[str] = None, module: str = "default"):
47
- """
48
- Marks a function as eligible for WASM acceleration.
49
-
50
- Example:
51
- @hot(wasm="mul42", module="math")
52
- def heavy(x: int) -> int:
53
- return x * 42
54
- """
55
- def decorate(fn):
56
- fn.__kigo_hot__ = True
57
- fn.__kigo_wasm_export__ = wasm or fn.__name__
58
- fn.__kigo_wasm_module__ = module
59
- return fn
60
- return decorate
61
-
62
-
63
- # =====================================================
64
- # WASM Executor (Wasmtime)
65
- # =====================================================
66
-
67
- @dataclass
68
- class _WasmHandle:
69
- store: Any
70
- exports: Dict[str, Any]
71
-
72
-
73
- class WasmExecutor:
74
- """
75
- Loads WASM modules from a registry and calls exported functions.
76
-
77
- Supports:
78
- - WAT source (string starting with "(module")
79
- - file path to .wasm
80
- """
81
-
82
- def __init__(self, runtime: Runtime):
83
- self.runtime = runtime
84
- self._mods: Dict[str, _WasmHandle] = {}
85
- self._enabled = False
86
-
87
- try:
88
- from wasmtime import Store, Module, Instance
89
- self._Store = Store
90
- self._Module = Module
91
- self._Instance = Instance
92
- self._enabled = True
93
- self.runtime.wasm_available = True
94
- except Exception as e:
95
- # Honest fallback: no wasmtime, no wasm mode.
96
- self.runtime.wasm_available = False
97
- self.runtime.wasm_reason = f"wasmtime not available: {e}"
98
- self._enabled = False
99
-
100
- def enabled(self) -> bool:
101
- return self._enabled
102
-
103
- def load_registry(self, registry: Dict[str, Any]) -> None:
104
- if not self._enabled:
105
- return
106
-
107
- for name, spec in (registry or {}).items():
108
- try:
109
- self._load_one(name, spec)
110
- except Exception as e:
111
- # Don’t crash app: just mark wasm as partially available
112
- self.runtime.wasm_reason = f"module '{name}' failed: {e}"
113
-
114
- def _load_one(self, name: str, spec: Any) -> None:
115
- store = self._Store()
116
-
117
- # Allow either raw WAT string or a dict {"wat": "..."} / {"file": "..."}
118
- wat_src = None
119
- file_path = None
120
-
121
- if isinstance(spec, str):
122
- s = spec.lstrip()
123
- if s.startswith("(module"):
124
- wat_src = spec
125
- else:
126
- file_path = spec
127
- elif isinstance(spec, dict):
128
- if "wat" in spec:
129
- wat_src = spec["wat"]
130
- elif "file" in spec:
131
- file_path = spec["file"]
132
- else:
133
- raise ValueError("invalid module spec dict; use {'wat': ...} or {'file': ...}")
134
- else:
135
- raise ValueError("invalid module spec; use WAT string, wasm file path, or dict spec")
136
-
137
- if wat_src is not None:
138
- module = self._Module(store.engine, wat_src)
139
- else:
140
- module = self._Module.from_file(store.engine, file_path)
141
-
142
- instance = self._Instance(store, module, [])
143
- exports = instance.exports(store)
144
-
145
- self._mods[str(name)] = _WasmHandle(store=store, exports=exports)
146
-
147
- def has_export(self, module: str, export: str) -> bool:
148
- if not self._enabled:
149
- return False
150
- h = self._mods.get(module)
151
- return bool(h and export in h.exports)
152
-
153
- def call(self, module: str, export: str, *args):
154
- self.runtime.wasm_calls += 1
155
- h = self._mods[module]
156
- fn = h.exports[export]
157
- return fn(h.store, *args)
158
-
159
-
160
- # =====================================================
161
- # Live HUD (F2 toggled, top-right)
162
- # =====================================================
163
-
164
- class LiveHUD(QtWidgets.QWidget):
165
- def __init__(self, runtime: Runtime, parent=None):
166
- super().__init__(parent)
167
- self.runtime = runtime
168
-
169
- self.setFixedSize(240, 130)
170
- self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents, True)
171
-
172
- self.setStyleSheet("""
173
- background: rgba(0, 0, 0, 160);
174
- color: #00ffcc;
175
- font-family: Consolas;
176
- font-size: 11px;
177
- border-radius: 6px;
178
- """)
179
-
180
- self.label = QtWidgets.QLabel(self)
181
- self.label.setGeometry(10, 8, 220, 114)
182
-
183
- self.timer = QtCore.QTimer(self)
184
- self.timer.timeout.connect(self.refresh)
185
- self.timer.start(250)
186
-
187
- self.hide()
188
-
189
- def attach_to(self, window: QtWidgets.QWidget):
190
- self.setParent(window)
191
- self.reposition()
192
- window.installEventFilter(self)
193
-
194
- def eventFilter(self, obj, event):
195
- if event.type() == QtCore.QEvent.Type.Resize:
196
- self.reposition()
197
- return False
198
-
199
- def reposition(self):
200
- if not self.parent():
201
- return
202
- r = self.parent().rect()
203
- self.move(r.width() - self.width() - 10, 10)
204
-
205
- def refresh(self):
206
- total = self.runtime.wasm_hits + self.runtime.wasm_fallbacks
207
- hit_pct = (self.runtime.wasm_hits * 100.0 / total) if total else 0.0
208
-
209
- wasm_state = "OK" if self.runtime.wasm_available else "OFF"
210
- if self.runtime.is_wasm() and not self.runtime.wasm_available:
211
- wasm_state = "FALLBACK"
212
-
213
- self.label.setText(
214
- "KIGO HUD\n"
215
- "────────────\n"
216
- f"Mode: {self.runtime.mode.upper()}\n"
217
- f"WASM: {wasm_state}\n"
218
- f"WASM hits: {self.runtime.wasm_hits} ({hit_pct:.0f}%)\n"
219
- f"WASM fallbacks: {self.runtime.wasm_fallbacks}\n"
220
- f"Python calls: {self.runtime.python_calls}\n"
221
- f"WASM calls: {self.runtime.wasm_calls}"
222
- )
223
-
224
-
225
- # =====================================================
226
- # Studio overlay (Esc toggled) — dev only
227
- # =====================================================
228
-
229
- class StudioOverlay(QtWidgets.QWidget):
230
- def __init__(self, parent=None):
231
- super().__init__(parent)
232
-
233
- self.setWindowFlags(
234
- QtCore.Qt.WindowType.Tool |
235
- QtCore.Qt.WindowType.FramelessWindowHint |
236
- QtCore.Qt.WindowType.WindowStaysOnTopHint
237
- )
238
- self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True)
239
-
240
- self.setStyleSheet("""
241
- QWidget {
242
- background-color: rgba(15, 15, 15, 215);
243
- color: #e0e0e0;
244
- font-family: Consolas, monospace;
245
- }
246
- """)
247
-
248
- layout = QtWidgets.QVBoxLayout(self)
249
- layout.setContentsMargins(24, 24, 24, 24)
250
- layout.setSpacing(12)
251
-
252
- title = QtWidgets.QLabel("Kigo Studio")
253
- title.setStyleSheet("font-size: 20px; font-weight: bold;")
254
-
255
- hint = QtWidgets.QLabel(
256
- "Esc — toggle Studio\n"
257
- "F2 — toggle HUD\n"
258
- "Inspector panels coming soon"
259
- )
260
-
261
- layout.addWidget(title)
262
- layout.addWidget(hint)
263
- layout.addStretch(1)
264
-
265
- def showEvent(self, e):
266
- if self.parent():
267
- self.setGeometry(self.parent().rect())
268
- super().showEvent(e)
269
-
270
-
271
- class StudioController(QtCore.QObject):
272
- def __init__(self, app: QtWidgets.QApplication, overlay: StudioOverlay):
273
- super().__init__()
274
- self.overlay = overlay
275
- app.installEventFilter(self)
276
-
277
- def eventFilter(self, obj, event):
278
- if event.type() == QtCore.QEvent.Type.KeyPress:
279
- if event.key() == QtCore.Qt.Key.Key_Escape:
280
- self.toggle()
281
- return True
282
- return False
283
-
284
- def toggle(self):
285
- if self.overlay.isVisible():
286
- self.overlay.hide()
287
- else:
288
- self.overlay.show()
289
- self.overlay.raise_()
290
-
291
-
292
- # =====================================================
293
- # Core App
294
- # =====================================================
295
-
296
- class App:
297
- """
298
- Core Kigo application.
299
-
300
- - mode: "python" (default) or "wasm"
301
- - F2 toggles HUD
302
- - Esc toggles Studio (only if studio=True)
303
- """
304
-
305
- def __init__(self, *, mode: str = "python", dev: bool = False, studio: bool = False):
306
- self.runtime = Runtime(mode)
307
- self.dev = bool(dev)
308
- self.studio = bool(studio)
309
-
310
- self.qt_app = QtWidgets.QApplication(sys.argv)
311
-
312
- self.main_window: Optional[QtWidgets.QWidget] = None
313
-
314
- # WASM executor (real if wasmtime present)
315
- self.wasm = WasmExecutor(self.runtime) if self.runtime.is_wasm() else None
316
-
317
- # If wasm mode requested but not available -> honest fallback
318
- if self.runtime.is_wasm() and (not self.wasm or not self.wasm.enabled()):
319
- self.runtime.mode = "python"
320
-
321
- # Load wasm module registry if in wasm mode and runtime available
322
- if self.runtime.is_wasm() and self.wasm and self.wasm.enabled():
323
- try:
324
- from kigo.wasm.module import WASM_MODULES
325
- except Exception:
326
- WASM_MODULES = {}
327
- self.runtime.wasm_reason = "WASM registry not found"
328
- self.wasm.load_registry(WASM_MODULES)
329
-
330
- self.hud: Optional[LiveHUD] = None
331
- self._hud_shortcut = None
332
-
333
- self._studio_overlay: Optional[StudioOverlay] = None
334
- self._studio_controller: Optional[StudioController] = None
335
-
336
- # -----------------------------
337
- # Unified execution path
338
- # -----------------------------
339
- def call(self, fn, *args):
340
- # Try WASM only for hot functions
341
- if getattr(fn, "__kigo_hot__", False) and self.runtime.is_wasm() and self.wasm:
342
- mod = getattr(fn, "__kigo_wasm_module__", "default")
343
- exp = getattr(fn, "__kigo_wasm_export__", fn.__name__)
344
-
345
- if self.wasm.has_export(mod, exp):
346
- self.runtime.wasm_hits += 1
347
- return self.wasm.call(mod, exp, *args)
348
-
349
- # wasm mode but export missing => fallback
350
- self.runtime.wasm_fallbacks += 1
351
- self.runtime.python_calls += 1
352
- return fn(*args)
353
-
354
- # Normal python path
355
- self.runtime.python_calls += 1
356
- return fn(*args)
357
-
358
- # -----------------------------
359
- # App lifecycle
360
- # -----------------------------
361
- def run(self):
362
- self.on_start()
363
-
364
- if self.dev:
365
- self._attach_hud()
366
-
367
- if self.dev and self.studio:
368
- self._attach_studio()
369
-
370
- sys.exit(self.qt_app.exec())
371
-
372
- def on_start(self):
373
- """
374
- Override in user app. Must set self.main_window and show it.
375
- """
376
- raise NotImplementedError("on_start() not implemented")
377
-
378
- # -----------------------------
379
- # HUD wiring (F2 toggle)
380
- # -----------------------------
381
- def _attach_hud(self):
382
- if not self.main_window:
383
- raise RuntimeError("main_window not set")
384
-
385
- self.hud = LiveHUD(self.runtime, parent=self.main_window)
386
- self.hud.attach_to(self.main_window)
387
-
388
- self._hud_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("F2"), self.main_window)
389
- self._hud_shortcut.setContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)
390
- self._hud_shortcut.activated.connect(self._toggle_hud)
391
-
392
- def _toggle_hud(self):
393
- if not self.hud:
394
- return
395
- if self.hud.isVisible():
396
- self.hud.hide()
397
- else:
398
- self.hud.show()
399
- self.hud.raise_()
400
-
401
- # -----------------------------
402
- # Studio wiring (Esc toggle)
403
- # -----------------------------
404
- def _attach_studio(self):
405
- if not self.main_window:
406
- raise RuntimeError("main_window not set")
407
-
408
- self._studio_overlay = StudioOverlay(parent=self.main_window)
409
- self._studio_controller = StudioController(self.qt_app, self._studio_overlay)