kigo-gui-framework 3.0__tar.gz → 3.2__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-3.2/PKG-INFO +35 -0
- kigo_gui_framework-3.2/README.md +13 -0
- kigo_gui_framework-3.2/README.txt +2 -0
- kigo_gui_framework-3.2/kigo/android/__init__.py +10 -0
- kigo_gui_framework-3.2/kigo/android/info.py +11 -0
- kigo_gui_framework-3.2/kigo/android/lifecycle.py +20 -0
- kigo_gui_framework-3.2/kigo/android/ossupport.py +181 -0
- kigo_gui_framework-3.2/kigo/cli/__init__.py +1 -0
- kigo_gui_framework-3.2/kigo/cli/doctor.py +90 -0
- kigo_gui_framework-3.2/kigo/cli/main.py +46 -0
- kigo_gui_framework-3.2/kigo/debug/__init__.py +3 -0
- kigo_gui_framework-3.2/kigo/debug/freeze.py +54 -0
- kigo_gui_framework-3.2/kigo/ecs/__init__.py +6 -0
- kigo_gui_framework-3.2/kigo/ecs/component.py +5 -0
- kigo_gui_framework-3.2/kigo/ecs/entity.py +1 -0
- kigo_gui_framework-3.2/kigo/ecs/render.py +38 -0
- kigo_gui_framework-3.2/kigo/ecs/system.py +10 -0
- kigo_gui_framework-3.2/kigo/ecs/world.py +59 -0
- kigo_gui_framework-3.2/kigo/inspector/__init__.py +3 -0
- kigo_gui_framework-3.2/kigo/inspector/inspector.py +50 -0
- kigo_gui_framework-3.2/kigo/inspector/overlay.py +36 -0
- kigo_gui_framework-3.2/kigo/inspector/panel.py +41 -0
- kigo_gui_framework-3.2/kigo/logging/__init__.py +3 -0
- kigo_gui_framework-3.2/kigo/logging/jsonlog.py +49 -0
- kigo_gui_framework-3.2/kigo/net/__init__.py +9 -0
- kigo_gui_framework-3.2/kigo/net/qnetwork.py +165 -0
- kigo_gui_framework-3.2/kigo/qt/__init__.py +3 -0
- kigo_gui_framework-3.2/kigo/qt/backend.py +13 -0
- kigo_gui_framework-3.2/kigo/studio/__init__.py +9 -0
- kigo_gui_framework-3.2/kigo/studio/core.py +40 -0
- kigo_gui_framework-3.2/kigo/studio/overlay.py +49 -0
- kigo_gui_framework-3.2/kigo/wasm/__init__.py +5 -0
- kigo_gui_framework-3.2/kigo/wasm/executor.py +45 -0
- kigo_gui_framework-3.2/kigo/wasm/module.py +9 -0
- kigo_gui_framework-3.2/kigo_gui_framework.egg-info/PKG-INFO +35 -0
- kigo_gui_framework-3.2/kigo_gui_framework.egg-info/SOURCES.txt +61 -0
- kigo_gui_framework-3.2/kigo_gui_framework.egg-info/entry_points.txt +2 -0
- kigo_gui_framework-3.2/kigo_gui_framework.egg-info/requires.txt +6 -0
- kigo_gui_framework-3.2/kigo_gui_framework.egg-info/top_level.txt +1 -0
- kigo_gui_framework-3.2/pyproject.toml +71 -0
- kigo_gui_framework-3.0/PKG-INFO +0 -35
- kigo_gui_framework-3.0/README.txt +0 -10
- kigo_gui_framework-3.0/kigo_gui_framework.egg-info/PKG-INFO +0 -35
- kigo_gui_framework-3.0/kigo_gui_framework.egg-info/SOURCES.txt +0 -28
- kigo_gui_framework-3.0/kigo_gui_framework.egg-info/requires.txt +0 -12
- kigo_gui_framework-3.0/kigo_gui_framework.egg-info/top_level.txt +0 -1
- kigo_gui_framework-3.0/pyproject.toml +0 -51
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/MANIFEST.in +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/_init_.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/accelerate.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/app.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/fx_gl2d.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/fx_quick.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/gpu.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/hud.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/hwaccel.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/media.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/physics.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/physics_policy.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/platform.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/runtime.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/shader_demo.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/shim.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/skins.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/style.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/tree.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo/widgets.py +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/kigo_gui_framework.egg-info/dependency_links.txt +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/setup.cfg +0 -0
- {kigo_gui_framework-3.0 → kigo_gui_framework-3.2}/setup.py +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kigo-gui-framework
|
|
3
|
+
Version: 3.2
|
|
4
|
+
Summary: Kigo has Time Freeze Debugging and a Visual Inspector now.
|
|
5
|
+
Author: Anand Kumar
|
|
6
|
+
License: Zlib
|
|
7
|
+
Keywords: gui,qt,pyqt,webengine,opengl,wasm,android,chromeos,framework
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: License :: OSI Approved :: zlib/libpng License
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: PyQt6
|
|
17
|
+
Requires-Dist: pybullet
|
|
18
|
+
Requires-Dist: pybullet-data
|
|
19
|
+
Requires-Dist: PyQt6-WebEngine
|
|
20
|
+
Requires-Dist: PyOpenGL
|
|
21
|
+
Requires-Dist: wasmtime
|
|
22
|
+
|
|
23
|
+
Kigo now has support for networking and
|
|
24
|
+
SunOS
|
|
25
|
+
Solaris
|
|
26
|
+
FreeBSD
|
|
27
|
+
Android
|
|
28
|
+
Linux
|
|
29
|
+
Mac
|
|
30
|
+
Windous
|
|
31
|
+
ChromeOS
|
|
32
|
+
Kigo also has two CLI commands:
|
|
33
|
+
kigo run
|
|
34
|
+
kigo doctor.
|
|
35
|
+
CLASS 5 IS TOUGH. VERY TOUGH. but you will get updates
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from kigo.qt.backend import QtCore
|
|
2
|
+
|
|
3
|
+
# Alias Signal correctly for PyQt6 and PySide6
|
|
4
|
+
Signal = getattr(QtCore, "Signal", QtCore.pyqtSignal)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AndroidLifecycle(QtCore.QObject):
|
|
8
|
+
paused = Signal()
|
|
9
|
+
resumed = Signal()
|
|
10
|
+
|
|
11
|
+
def __init__(self, app):
|
|
12
|
+
super().__init__()
|
|
13
|
+
self.app = app
|
|
14
|
+
app.applicationStateChanged.connect(self._on_state)
|
|
15
|
+
|
|
16
|
+
def _on_state(self, state):
|
|
17
|
+
if state == QtCore.Qt.ApplicationState.ApplicationSuspended:
|
|
18
|
+
self.paused.emit()
|
|
19
|
+
elif state == QtCore.Qt.ApplicationState.ApplicationActive:
|
|
20
|
+
self.resumed.emit()
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Zlib
|
|
2
|
+
# kigo/platform_info.py
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
import platform as _platform
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# -------------------------------------------------
|
|
10
|
+
# Mobile platforms
|
|
11
|
+
# -------------------------------------------------
|
|
12
|
+
|
|
13
|
+
def is_android() -> bool:
|
|
14
|
+
"""True if running on Android (ARC++ or native)."""
|
|
15
|
+
return hasattr(sys, "getandroidapilevel")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def android_api_level():
|
|
19
|
+
"""Return Android API level or None."""
|
|
20
|
+
if hasattr(sys, "getandroidapilevel"):
|
|
21
|
+
return sys.getandroidapilevel()
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_ios() -> bool:
|
|
26
|
+
"""True if running on iOS (future-ready)."""
|
|
27
|
+
return sys.platform == "ios"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# -------------------------------------------------
|
|
31
|
+
# Desktop OS detection
|
|
32
|
+
# -------------------------------------------------
|
|
33
|
+
|
|
34
|
+
def is_windows() -> bool:
|
|
35
|
+
return sys.platform.startswith("win")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def is_linux() -> bool:
|
|
39
|
+
return sys.platform.startswith("linux")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_macos() -> bool:
|
|
43
|
+
return sys.platform == "darwin"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_freebsd() -> bool:
|
|
47
|
+
return sys.platform.startswith("freebsd")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_openbsd() -> bool:
|
|
51
|
+
return sys.platform.startswith("openbsd")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def is_sunos() -> bool:
|
|
55
|
+
# Solaris reports as "sunos"
|
|
56
|
+
return sys.platform == "sunos"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# -------------------------------------------------
|
|
60
|
+
# ChromeOS detection (Crostini / ARC++)
|
|
61
|
+
# -------------------------------------------------
|
|
62
|
+
|
|
63
|
+
def is_chromeos() -> bool:
|
|
64
|
+
"""
|
|
65
|
+
Detect ChromeOS.
|
|
66
|
+
Covers:
|
|
67
|
+
- Linux (Crostini)
|
|
68
|
+
- Android Runtime (ARC++)
|
|
69
|
+
"""
|
|
70
|
+
# Crostini exposes this env var
|
|
71
|
+
if "SOMMELIER_VERSION" in os.environ:
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
# Fallback: os-release check
|
|
75
|
+
try:
|
|
76
|
+
with open("/etc/os-release", "r", encoding="utf-8") as f:
|
|
77
|
+
data = f.read().lower()
|
|
78
|
+
if "chromeos" in data or "chromiumos" in data:
|
|
79
|
+
return True
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# -------------------------------------------------
|
|
87
|
+
# Platform groups
|
|
88
|
+
# -------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def is_mobile() -> bool:
|
|
91
|
+
return is_android() or is_ios()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def is_desktop() -> bool:
|
|
95
|
+
return not is_mobile()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def is_bsd() -> bool:
|
|
99
|
+
return is_freebsd() or is_openbsd()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def is_unix_desktop() -> bool:
|
|
103
|
+
return (
|
|
104
|
+
is_linux()
|
|
105
|
+
or is_macos()
|
|
106
|
+
or is_bsd()
|
|
107
|
+
or is_sunos()
|
|
108
|
+
or is_chromeos()
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# -------------------------------------------------
|
|
113
|
+
# Window system detection (X11 / Wayland)
|
|
114
|
+
# -------------------------------------------------
|
|
115
|
+
|
|
116
|
+
def window_system() -> str:
|
|
117
|
+
"""
|
|
118
|
+
Returns:
|
|
119
|
+
'wayland', 'x11', or 'unknown'
|
|
120
|
+
"""
|
|
121
|
+
if os.environ.get("WAYLAND_DISPLAY"):
|
|
122
|
+
return "wayland"
|
|
123
|
+
if os.environ.get("DISPLAY"):
|
|
124
|
+
return "x11"
|
|
125
|
+
return "unknown"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# -------------------------------------------------
|
|
129
|
+
# Qt backend decision
|
|
130
|
+
# -------------------------------------------------
|
|
131
|
+
|
|
132
|
+
def qt_backend() -> str:
|
|
133
|
+
"""
|
|
134
|
+
Android → PySide6
|
|
135
|
+
Everything else → PyQt6
|
|
136
|
+
"""
|
|
137
|
+
if is_android():
|
|
138
|
+
return "pyside"
|
|
139
|
+
return "pyqt"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# -------------------------------------------------
|
|
143
|
+
# Human-readable platform name
|
|
144
|
+
# -------------------------------------------------
|
|
145
|
+
|
|
146
|
+
def platform_name() -> str:
|
|
147
|
+
if is_android():
|
|
148
|
+
return "android"
|
|
149
|
+
if is_chromeos():
|
|
150
|
+
return "chromeos"
|
|
151
|
+
if is_ios():
|
|
152
|
+
return "ios"
|
|
153
|
+
if is_windows():
|
|
154
|
+
return "windows"
|
|
155
|
+
if is_macos():
|
|
156
|
+
return "macos"
|
|
157
|
+
if is_linux():
|
|
158
|
+
return "linux"
|
|
159
|
+
if is_freebsd():
|
|
160
|
+
return "freebsd"
|
|
161
|
+
if is_openbsd():
|
|
162
|
+
return "openbsd"
|
|
163
|
+
if is_sunos():
|
|
164
|
+
return "sunos/solaris"
|
|
165
|
+
return _platform.system().lower()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# -------------------------------------------------
|
|
169
|
+
# Debug / dev helper
|
|
170
|
+
# -------------------------------------------------
|
|
171
|
+
|
|
172
|
+
def summary() -> dict:
|
|
173
|
+
return {
|
|
174
|
+
"platform": platform_name(),
|
|
175
|
+
"chromeos": is_chromeos(),
|
|
176
|
+
"mobile": is_mobile(),
|
|
177
|
+
"desktop": is_desktop(),
|
|
178
|
+
"window_system": window_system(),
|
|
179
|
+
"qt_backend": qt_backend(),
|
|
180
|
+
"android_api": android_api_level(),
|
|
181
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__=[]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Zlib
|
|
2
|
+
# kigo/cli/doctor.py
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
import importlib.util
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def ok(msg): print(f"✅ {msg}")
|
|
10
|
+
def warn(msg): print(f"⚠️ {msg}")
|
|
11
|
+
def err(msg): print(f"❌ {msg}")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run_doctor():
|
|
15
|
+
print("Kigo Doctor running…\n")
|
|
16
|
+
|
|
17
|
+
checks = [
|
|
18
|
+
check_python,
|
|
19
|
+
check_app_file,
|
|
20
|
+
check_kigo_import,
|
|
21
|
+
check_qt_backend,
|
|
22
|
+
check_platform_file,
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
failed = False
|
|
26
|
+
for check in checks:
|
|
27
|
+
try:
|
|
28
|
+
result = check()
|
|
29
|
+
if result is False:
|
|
30
|
+
failed = True
|
|
31
|
+
except Exception as e:
|
|
32
|
+
err(f"{check.__name__} crashed: {e}")
|
|
33
|
+
failed = True
|
|
34
|
+
|
|
35
|
+
print()
|
|
36
|
+
if failed:
|
|
37
|
+
err("Problems detected. Fix the issues above.")
|
|
38
|
+
sys.exit(1)
|
|
39
|
+
else:
|
|
40
|
+
ok("No critical issues found. You’re good to go 🚀")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# -------------------------
|
|
44
|
+
# Individual checks
|
|
45
|
+
# -------------------------
|
|
46
|
+
|
|
47
|
+
def check_python():
|
|
48
|
+
if sys.version_info < (3, 9):
|
|
49
|
+
err("Python 3.9+ is required")
|
|
50
|
+
return False
|
|
51
|
+
ok(f"Python {sys.version.split()[0]}")
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def check_app_file():
|
|
56
|
+
if not os.path.exists("app.py"):
|
|
57
|
+
err("app.py not found")
|
|
58
|
+
return False
|
|
59
|
+
ok("app.py found")
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def check_kigo_import():
|
|
64
|
+
try:
|
|
65
|
+
import kigo
|
|
66
|
+
ok("Kigo importable")
|
|
67
|
+
return True
|
|
68
|
+
except Exception as e:
|
|
69
|
+
err(f"Kigo import failed: {e}")
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def check_qt_backend():
|
|
74
|
+
try:
|
|
75
|
+
from kigo.qt.backend import QtCore
|
|
76
|
+
ok("Qt backend OK")
|
|
77
|
+
return True
|
|
78
|
+
except Exception as e:
|
|
79
|
+
err(f"Qt backend failed: {e}")
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def check_platform_file():
|
|
84
|
+
try:
|
|
85
|
+
from kigo.platform_info import platform_name
|
|
86
|
+
ok(f"Platform detected: {platform_name()}")
|
|
87
|
+
return True
|
|
88
|
+
except Exception as e:
|
|
89
|
+
err(f"Platform detection failed: {e}")
|
|
90
|
+
return False
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Zlib
|
|
2
|
+
# kigo/cli/main.py
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from kigo.cli.doctor import run_doctor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
parser = argparse.ArgumentParser(
|
|
12
|
+
prog="kigo",
|
|
13
|
+
description="Kigo CLI"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
sub = parser.add_subparsers(dest="command")
|
|
17
|
+
|
|
18
|
+
# kigo run
|
|
19
|
+
sub.add_parser("run", help="Run the Kigo app")
|
|
20
|
+
|
|
21
|
+
# kigo doctor
|
|
22
|
+
sub.add_parser("doctor", help="Diagnose common Kigo issues")
|
|
23
|
+
|
|
24
|
+
args = parser.parse_args()
|
|
25
|
+
|
|
26
|
+
if args.command == "run":
|
|
27
|
+
run_app()
|
|
28
|
+
elif args.command == "doctor":
|
|
29
|
+
run_doctor()
|
|
30
|
+
else:
|
|
31
|
+
parser.print_help()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def run_app():
|
|
35
|
+
"""
|
|
36
|
+
Runs app.py in the current directory.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
__import__("app")
|
|
40
|
+
except ModuleNotFoundError:
|
|
41
|
+
print("❌ No app.py found in current directory. Please rename your file to app.py and try again.")
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
print("❌ Failed to run app:")
|
|
45
|
+
print(e)
|
|
46
|
+
sys.exit(1)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Zlib
|
|
2
|
+
# kigo/debug/freeze.py
|
|
3
|
+
|
|
4
|
+
from kigo.qt.backend import QtCore, QtWidgets
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def freeze(reason: str = "Execution paused", *, data: dict | None = None):
|
|
8
|
+
"""
|
|
9
|
+
Freeze execution while keeping the Qt event loop alive.
|
|
10
|
+
Used for visual + state debugging.
|
|
11
|
+
|
|
12
|
+
This does NOT crash or block Qt internally.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Dialog shown during freeze
|
|
16
|
+
dialog = QtWidgets.QDialog()
|
|
17
|
+
dialog.setWindowTitle("Kigo Time Freeze")
|
|
18
|
+
dialog.setModal(False)
|
|
19
|
+
dialog.setWindowFlags(
|
|
20
|
+
QtCore.Qt.WindowType.WindowStaysOnTopHint
|
|
21
|
+
| QtCore.Qt.WindowType.Dialog
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
layout = QtWidgets.QVBoxLayout(dialog)
|
|
25
|
+
|
|
26
|
+
label = QtWidgets.QLabel(f"🧊 {reason}")
|
|
27
|
+
label.setStyleSheet("font-weight: bold; font-size: 14px;")
|
|
28
|
+
layout.addWidget(label)
|
|
29
|
+
|
|
30
|
+
if data:
|
|
31
|
+
text = QtWidgets.QPlainTextEdit()
|
|
32
|
+
text.setReadOnly(True)
|
|
33
|
+
pretty = "\n".join(f"{k}: {v}" for k, v in data.items())
|
|
34
|
+
text.setPlainText(pretty)
|
|
35
|
+
layout.addWidget(text)
|
|
36
|
+
|
|
37
|
+
btn = QtWidgets.QPushButton("Resume")
|
|
38
|
+
layout.addWidget(btn)
|
|
39
|
+
|
|
40
|
+
# Local event loop = time freeze
|
|
41
|
+
loop = QtCore.QEventLoop()
|
|
42
|
+
|
|
43
|
+
btn.clicked.connect(loop.quit)
|
|
44
|
+
dialog.finished.connect(loop.quit)
|
|
45
|
+
|
|
46
|
+
dialog.show()
|
|
47
|
+
|
|
48
|
+
# Process paint events before freezing
|
|
49
|
+
QtWidgets.QApplication.processEvents()
|
|
50
|
+
|
|
51
|
+
# TIME FREEZE STARTS HERE
|
|
52
|
+
loop.exec()
|
|
53
|
+
|
|
54
|
+
dialog.close()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
EntityId=int
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# kigo/ecs/render.py
|
|
2
|
+
from kigo.ecs.system import System
|
|
3
|
+
from kigo.ecs.world import World
|
|
4
|
+
from kigo.qt.backend import QtGui
|
|
5
|
+
from .render import Transform, Renderable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RenderSystem(System):
|
|
9
|
+
"""
|
|
10
|
+
Pulls render data from ECS and issues draw calls.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, painter: QtGui.QPainter):
|
|
14
|
+
self.painter = painter
|
|
15
|
+
|
|
16
|
+
def update(self, world: World, dt: float):
|
|
17
|
+
entities = world.get_entities_with(Transform, Renderable)
|
|
18
|
+
|
|
19
|
+
for e in entities:
|
|
20
|
+
transform = world.get_component(e, Transform)
|
|
21
|
+
renderable = world.get_component(e, Renderable)
|
|
22
|
+
|
|
23
|
+
self._draw(transform, renderable)
|
|
24
|
+
|
|
25
|
+
def _draw(self, t: Transform, r: Renderable):
|
|
26
|
+
self.painter.save()
|
|
27
|
+
self.painter.translate(t.x, t.y)
|
|
28
|
+
self.painter.rotate(t.rotation)
|
|
29
|
+
self.painter.scale(t.scale, t.scale)
|
|
30
|
+
|
|
31
|
+
if r.kind == "rect":
|
|
32
|
+
self.painter.drawRect(0, 0, 50, 50)
|
|
33
|
+
|
|
34
|
+
elif r.kind == "image" and r.resource:
|
|
35
|
+
pix = QtGui.QPixmap(r.resource)
|
|
36
|
+
self.painter.drawPixmap(0, 0, pix)
|
|
37
|
+
|
|
38
|
+
self.painter.restore()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from typing import Dict, Type, Set
|
|
3
|
+
|
|
4
|
+
from .entity import EntityId
|
|
5
|
+
from .component import Component
|
|
6
|
+
from .system import System
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class World:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self._next_entity: EntityId = 1
|
|
12
|
+
self._components: Dict[Type[Component], Dict[EntityId, Component]] = defaultdict(dict)
|
|
13
|
+
self._systems: list[System] = []
|
|
14
|
+
|
|
15
|
+
# ----------------------------
|
|
16
|
+
# Entity
|
|
17
|
+
# ----------------------------
|
|
18
|
+
def create_entity(self) -> EntityId:
|
|
19
|
+
eid = self._next_entity
|
|
20
|
+
self._next_entity += 1
|
|
21
|
+
return eid
|
|
22
|
+
|
|
23
|
+
def remove_entity(self, entity: EntityId):
|
|
24
|
+
for comp_map in self._components.values():
|
|
25
|
+
comp_map.pop(entity, None)
|
|
26
|
+
|
|
27
|
+
# ----------------------------
|
|
28
|
+
# Components
|
|
29
|
+
# ----------------------------
|
|
30
|
+
def add_component(self, entity: EntityId, component: Component):
|
|
31
|
+
self._components[type(component)][entity] = component
|
|
32
|
+
|
|
33
|
+
def remove_component(self, entity: EntityId, component_type: Type[Component]):
|
|
34
|
+
self._components[component_type].pop(entity, None)
|
|
35
|
+
|
|
36
|
+
def get_component(self, entity: EntityId, component_type: Type[Component]):
|
|
37
|
+
return self._components[component_type].get(entity)
|
|
38
|
+
|
|
39
|
+
def get_entities_with(self, *component_types: Type[Component]) -> Set[EntityId]:
|
|
40
|
+
if not component_types:
|
|
41
|
+
return set()
|
|
42
|
+
|
|
43
|
+
entity_sets = [
|
|
44
|
+
set(self._components[ct].keys())
|
|
45
|
+
for ct in component_types
|
|
46
|
+
if ct in self._components
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
return set.intersection(*entity_sets) if entity_sets else set()
|
|
50
|
+
|
|
51
|
+
# ----------------------------
|
|
52
|
+
# Systems
|
|
53
|
+
# ----------------------------
|
|
54
|
+
def add_system(self, system: System):
|
|
55
|
+
self._systems.append(system)
|
|
56
|
+
|
|
57
|
+
def update(self, dt: float):
|
|
58
|
+
for system in self._systems:
|
|
59
|
+
system.update(self, dt)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Zlib
|
|
2
|
+
# kigo/inspector/inspector.py
|
|
3
|
+
|
|
4
|
+
from kigo.qt.backend import QtCore, QtWidgets
|
|
5
|
+
from kigo.inspector.overlay import Overlay
|
|
6
|
+
from kigo.inspector.panel import InspectorPanel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Inspector(QtCore.QObject):
|
|
10
|
+
def __init__(self, app: QtWidgets.QApplication):
|
|
11
|
+
super().__init__()
|
|
12
|
+
self.app = app
|
|
13
|
+
self.overlay = Overlay()
|
|
14
|
+
self.panel = InspectorPanel()
|
|
15
|
+
|
|
16
|
+
self.app.installEventFilter(self)
|
|
17
|
+
|
|
18
|
+
def attach(self):
|
|
19
|
+
for w in self.app.topLevelWidgets():
|
|
20
|
+
if isinstance(w, QtWidgets.QMainWindow):
|
|
21
|
+
w.addDockWidget(
|
|
22
|
+
QtCore.Qt.DockWidgetArea.RightDockWidgetArea,
|
|
23
|
+
self.panel,
|
|
24
|
+
)
|
|
25
|
+
self.overlay.setParent(w)
|
|
26
|
+
self.overlay.resize(w.size())
|
|
27
|
+
|
|
28
|
+
def eventFilter(self, obj, event):
|
|
29
|
+
if isinstance(event, QtCore.QEvent):
|
|
30
|
+
if event.type() == QtCore.QEvent.Type.MouseMove:
|
|
31
|
+
widget = self.app.widgetAt(event.globalPosition().toPoint())
|
|
32
|
+
if widget:
|
|
33
|
+
rect = widget.rect()
|
|
34
|
+
top_left = widget.mapToGlobal(rect.topLeft())
|
|
35
|
+
self.overlay.highlight(
|
|
36
|
+
QtCore.QRect(top_left, rect.size())
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
elif event.type() == QtCore.QEvent.Type.MouseButtonPress:
|
|
40
|
+
widget = self.app.widgetAt(event.globalPosition().toPoint())
|
|
41
|
+
self.panel.inspect(widget)
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def enable_inspector(app: QtWidgets.QApplication):
|
|
48
|
+
inspector = Inspector(app)
|
|
49
|
+
inspector.attach()
|
|
50
|
+
return inspector
|