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.
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/PKG-INFO +4 -5
- kigo_gui_framework-2.6/README.txt +1 -0
- kigo_gui_framework-2.6/kigo/app.py +107 -0
- kigo_gui_framework-2.6/kigo/gpu.py +206 -0
- kigo_gui_framework-2.6/kigo/shader_demo.py +23 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo_gui_framework.egg-info/PKG-INFO +4 -5
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo_gui_framework.egg-info/SOURCES.txt +3 -1
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo_gui_framework.egg-info/requires.txt +1 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/pyproject.toml +4 -4
- kigo_gui_framework-2.4/README.txt +0 -3
- kigo_gui_framework-2.4/kigo/app.py +0 -409
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/MANIFEST.in +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/_init_.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/accelerate.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/fx_gl2d.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/fx_quick.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/hud.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/hwaccel.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/media.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/physics.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/physics_policy.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/runtime.py +0 -0
- /kigo_gui_framework-2.4/kigo/qt.py → /kigo_gui_framework-2.6/kigo/shim.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/skins.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/style.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/tree.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo/widgets.py +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo_gui_framework.egg-info/dependency_links.txt +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo_gui_framework.egg-info/top_level.txt +0 -0
- {kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/setup.cfg +0 -0
- {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
|
-
Summary: Added
|
|
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
|
-
|
|
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
|
-
Summary: Added
|
|
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
|
-
|
|
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
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kigo-gui-framework"
|
|
7
|
-
version = "2.
|
|
8
|
-
description = "Added
|
|
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,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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kigo_gui_framework-2.4 → kigo_gui_framework-2.6}/kigo_gui_framework.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|