imgui_debugger 0.1.0__py3-none-any.whl
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.
- imgui_debugger/__init__.py +88 -0
- imgui_debugger/_assets.py +125 -0
- imgui_debugger/debugger.py +553 -0
- imgui_debugger/edit.py +125 -0
- imgui_debugger/format.py +130 -0
- imgui_debugger/py.typed +0 -0
- imgui_debugger/runner.py +91 -0
- imgui_debugger/scopes.py +734 -0
- imgui_debugger/search.py +170 -0
- imgui_debugger/theme.py +129 -0
- imgui_debugger/tree.py +285 -0
- imgui_debugger-0.1.0.dist-info/METADATA +225 -0
- imgui_debugger-0.1.0.dist-info/RECORD +16 -0
- imgui_debugger-0.1.0.dist-info/WHEEL +5 -0
- imgui_debugger-0.1.0.dist-info/licenses/LICENSE +21 -0
- imgui_debugger-0.1.0.dist-info/top_level.txt +1 -0
imgui_debugger/search.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""The tree's filter: a bounded, memoized match over a value and its children.
|
|
2
|
+
|
|
3
|
+
The walk is capped in width and depth so a multi-megabyte attribute dict cannot
|
|
4
|
+
cost milliseconds per frame, and results are cached for the lifetime of one
|
|
5
|
+
filter string.
|
|
6
|
+
|
|
7
|
+
Examples
|
|
8
|
+
--------
|
|
9
|
+
>>> from imgui_debugger.search import matches
|
|
10
|
+
>>> matches("metadata", {"fs": 9.6}, "fs")
|
|
11
|
+
True
|
|
12
|
+
>>> matches("metadata", {"fs": 9.6}, "dz")
|
|
13
|
+
False
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from collections.abc import Mapping, Sequence
|
|
19
|
+
|
|
20
|
+
MAX_ITEMS = 64
|
|
21
|
+
MAX_DEPTH = 6
|
|
22
|
+
CACHE_MAX = 4096
|
|
23
|
+
|
|
24
|
+
_cache: dict = {}
|
|
25
|
+
_cache_filter: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def clear_cache() -> None:
|
|
29
|
+
"""Drop every memoized match result.
|
|
30
|
+
|
|
31
|
+
Examples
|
|
32
|
+
--------
|
|
33
|
+
>>> from imgui_debugger.search import clear_cache, matches
|
|
34
|
+
>>> matches("a", 1, "a")
|
|
35
|
+
True
|
|
36
|
+
>>> clear_cache()
|
|
37
|
+
"""
|
|
38
|
+
global _cache_filter
|
|
39
|
+
_cache.clear()
|
|
40
|
+
_cache_filter = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def matches_shallow(name, value, text: str) -> bool:
|
|
44
|
+
"""Whether a name, or a non-container value, contains ``text``.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
name : object
|
|
49
|
+
The row label.
|
|
50
|
+
value : object
|
|
51
|
+
The row value.
|
|
52
|
+
text : str
|
|
53
|
+
Filter text; an empty string matches everything.
|
|
54
|
+
|
|
55
|
+
Examples
|
|
56
|
+
--------
|
|
57
|
+
>>> from imgui_debugger.search import matches_shallow
|
|
58
|
+
>>> matches_shallow("frame_rate", 30, "rate")
|
|
59
|
+
True
|
|
60
|
+
>>> matches_shallow("frame_rate", 30, "30")
|
|
61
|
+
True
|
|
62
|
+
>>> matches_shallow("frame_rate", [30], "30")
|
|
63
|
+
False
|
|
64
|
+
"""
|
|
65
|
+
if not text:
|
|
66
|
+
return True
|
|
67
|
+
low = text.lower()
|
|
68
|
+
if low in str(name).lower():
|
|
69
|
+
return True
|
|
70
|
+
if isinstance(value, (Mapping, list, tuple)):
|
|
71
|
+
return False
|
|
72
|
+
try:
|
|
73
|
+
return low in str(value).lower()
|
|
74
|
+
except Exception:
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def matches(name, value, text: str, depth: int = 0) -> bool:
|
|
79
|
+
"""Whether a row or any of its bounded descendants matches ``text``.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
name : object
|
|
84
|
+
The row label.
|
|
85
|
+
value : object
|
|
86
|
+
The row value.
|
|
87
|
+
text : str
|
|
88
|
+
Filter text; an empty string matches everything.
|
|
89
|
+
depth : int
|
|
90
|
+
Current recursion depth, used internally.
|
|
91
|
+
|
|
92
|
+
Examples
|
|
93
|
+
--------
|
|
94
|
+
>>> from imgui_debugger.search import matches
|
|
95
|
+
>>> matches("arr", {"meta": {"dz": 5}}, "dz")
|
|
96
|
+
True
|
|
97
|
+
>>> matches("arr", [1, 2, 3], "2")
|
|
98
|
+
True
|
|
99
|
+
>>> matches("arr", "hello", "ell")
|
|
100
|
+
True
|
|
101
|
+
"""
|
|
102
|
+
global _cache_filter
|
|
103
|
+
if not text:
|
|
104
|
+
return True
|
|
105
|
+
if text != _cache_filter:
|
|
106
|
+
_cache.clear()
|
|
107
|
+
_cache_filter = text
|
|
108
|
+
key = (str(name), id(value), depth)
|
|
109
|
+
hit = _cache.get(key)
|
|
110
|
+
if hit is not None and hit[0] is value:
|
|
111
|
+
return hit[1]
|
|
112
|
+
result = _walk(name, value, text, depth)
|
|
113
|
+
if len(_cache) >= CACHE_MAX:
|
|
114
|
+
_cache.clear()
|
|
115
|
+
_cache[key] = (value, result)
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _walk(name, value, text: str, depth: int) -> bool:
|
|
120
|
+
"""Do the bounded recursive match behind :func:`matches`.
|
|
121
|
+
|
|
122
|
+
Examples
|
|
123
|
+
--------
|
|
124
|
+
>>> from imgui_debugger.search import _walk
|
|
125
|
+
>>> _walk("cfg", {"a": {"b": 1}}, "b", 0)
|
|
126
|
+
True
|
|
127
|
+
>>> _walk("cfg", {"a": {"b": 1}}, "b", 6)
|
|
128
|
+
False
|
|
129
|
+
"""
|
|
130
|
+
if matches_shallow(name, value, text):
|
|
131
|
+
return True
|
|
132
|
+
if depth >= MAX_DEPTH:
|
|
133
|
+
return False
|
|
134
|
+
if isinstance(value, Mapping):
|
|
135
|
+
for i, (k, v) in enumerate(value.items()):
|
|
136
|
+
if i >= MAX_ITEMS:
|
|
137
|
+
break
|
|
138
|
+
if matches(k, v, text, depth + 1):
|
|
139
|
+
return True
|
|
140
|
+
return False
|
|
141
|
+
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
|
|
142
|
+
for i, v in enumerate(value):
|
|
143
|
+
if i >= MAX_ITEMS:
|
|
144
|
+
break
|
|
145
|
+
if matches(f"[{i}]", v, text, depth + 1):
|
|
146
|
+
return True
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def filter_children(children, text: str):
|
|
151
|
+
"""Keep only the rows that match ``text``.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
children : list[imgui_debugger.scopes.Child]
|
|
156
|
+
Rows to filter.
|
|
157
|
+
text : str
|
|
158
|
+
Filter text; an empty string keeps everything.
|
|
159
|
+
|
|
160
|
+
Examples
|
|
161
|
+
--------
|
|
162
|
+
>>> from imgui_debugger.scopes import Child
|
|
163
|
+
>>> from imgui_debugger.search import filter_children
|
|
164
|
+
>>> rows = [Child("fs", 9.6), Child("dz", 5)]
|
|
165
|
+
>>> [c.name for c in filter_children(rows, "dz")]
|
|
166
|
+
['dz']
|
|
167
|
+
"""
|
|
168
|
+
if not text:
|
|
169
|
+
return list(children)
|
|
170
|
+
return [c for c in children if matches(c.name, c.value, text)]
|
imgui_debugger/theme.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Colors and rounding knobs for the debugger tree.
|
|
2
|
+
|
|
3
|
+
Examples
|
|
4
|
+
--------
|
|
5
|
+
>>> from imgui_debugger import Theme
|
|
6
|
+
>>> Theme.dark().replace(value=(1.0, 1.0, 1.0, 1.0)).value
|
|
7
|
+
(1.0, 1.0, 1.0, 1.0)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, replace
|
|
13
|
+
from typing import Tuple
|
|
14
|
+
|
|
15
|
+
from imgui_bundle import imgui
|
|
16
|
+
|
|
17
|
+
Color = Tuple[float, float, float, float]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def to_vec4(color) -> "imgui.ImVec4":
|
|
21
|
+
"""Coerce an ``(r, g, b[, a])`` tuple (or an ImVec4) to ``imgui.ImVec4``.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
color : tuple | imgui.ImVec4
|
|
26
|
+
Components in the 0..1 range.
|
|
27
|
+
|
|
28
|
+
Examples
|
|
29
|
+
--------
|
|
30
|
+
>>> from imgui_debugger import to_vec4
|
|
31
|
+
>>> to_vec4((1.0, 0.0, 0.0)).w
|
|
32
|
+
1.0
|
|
33
|
+
"""
|
|
34
|
+
if isinstance(color, imgui.ImVec4):
|
|
35
|
+
return color
|
|
36
|
+
r, g, b = color[0], color[1], color[2]
|
|
37
|
+
a = color[3] if len(color) > 3 else 1.0
|
|
38
|
+
return imgui.ImVec4(r, g, b, a)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class Theme:
|
|
43
|
+
"""Palette for the debugger, one ``(r, g, b, a)`` float tuple per role.
|
|
44
|
+
|
|
45
|
+
Examples
|
|
46
|
+
--------
|
|
47
|
+
>>> from imgui_debugger import Theme
|
|
48
|
+
>>> t = Theme.light()
|
|
49
|
+
>>> t.node != Theme.dark().node
|
|
50
|
+
True
|
|
51
|
+
>>> Theme.dark().replace(accent=(1.0, 0.5, 0.0, 1.0)).accent
|
|
52
|
+
(1.0, 0.5, 0.0, 1.0)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
bg: Color = (0.11, 0.11, 0.12, 1.0)
|
|
56
|
+
text: Color = (0.90, 0.90, 0.92, 1.0)
|
|
57
|
+
text_dim: Color = (0.55, 0.55, 0.58, 1.0)
|
|
58
|
+
accent: Color = (0.20, 0.50, 0.85, 1.0)
|
|
59
|
+
border: Color = (0.35, 0.35, 0.37, 0.7)
|
|
60
|
+
separator: Color = (0.35, 0.35, 0.37, 0.6)
|
|
61
|
+
frame_bg: Color = (0.18, 0.18, 0.20, 1.0)
|
|
62
|
+
# tree roles
|
|
63
|
+
node: Color = (0.40, 0.80, 0.95, 1.0)
|
|
64
|
+
name: Color = (0.95, 0.80, 0.30, 1.0)
|
|
65
|
+
index: Color = (0.60, 0.95, 0.40, 1.0)
|
|
66
|
+
value: Color = (0.85, 0.85, 0.85, 1.0)
|
|
67
|
+
prop: Color = (0.70, 0.75, 1.00, 1.0)
|
|
68
|
+
cls: Color = (0.85, 0.65, 1.00, 1.0)
|
|
69
|
+
local: Color = (0.95, 0.80, 0.30, 1.0)
|
|
70
|
+
glob: Color = (0.55, 0.85, 0.80, 1.0)
|
|
71
|
+
runtime: Color = (0.90, 0.60, 0.40, 1.0)
|
|
72
|
+
error: Color = (0.95, 0.40, 0.40, 1.0)
|
|
73
|
+
changed: Color = (1.00, 0.85, 0.35, 1.0)
|
|
74
|
+
frame_rounding: float = 4.0
|
|
75
|
+
child_rounding: float = 4.0
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def dark() -> "Theme":
|
|
79
|
+
"""The default dark palette.
|
|
80
|
+
|
|
81
|
+
Examples
|
|
82
|
+
--------
|
|
83
|
+
>>> from imgui_debugger import Theme
|
|
84
|
+
>>> Theme.dark() == Theme()
|
|
85
|
+
True
|
|
86
|
+
"""
|
|
87
|
+
return Theme()
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def light() -> "Theme":
|
|
91
|
+
"""A light palette with the same role names.
|
|
92
|
+
|
|
93
|
+
Examples
|
|
94
|
+
--------
|
|
95
|
+
>>> from imgui_debugger import Theme
|
|
96
|
+
>>> Theme.light().bg[0] > 0.5
|
|
97
|
+
True
|
|
98
|
+
"""
|
|
99
|
+
return Theme(
|
|
100
|
+
bg=(0.94, 0.94, 0.95, 1.0),
|
|
101
|
+
text=(0.10, 0.10, 0.12, 1.0),
|
|
102
|
+
text_dim=(0.42, 0.42, 0.46, 1.0),
|
|
103
|
+
accent=(0.10, 0.45, 0.80, 1.0),
|
|
104
|
+
border=(0.70, 0.70, 0.74, 0.9),
|
|
105
|
+
separator=(0.70, 0.70, 0.74, 0.6),
|
|
106
|
+
frame_bg=(0.88, 0.88, 0.90, 1.0),
|
|
107
|
+
node=(0.05, 0.40, 0.60, 1.0),
|
|
108
|
+
name=(0.55, 0.38, 0.05, 1.0),
|
|
109
|
+
index=(0.15, 0.45, 0.10, 1.0),
|
|
110
|
+
value=(0.18, 0.18, 0.20, 1.0),
|
|
111
|
+
prop=(0.25, 0.30, 0.65, 1.0),
|
|
112
|
+
cls=(0.45, 0.20, 0.60, 1.0),
|
|
113
|
+
local=(0.55, 0.38, 0.05, 1.0),
|
|
114
|
+
glob=(0.10, 0.45, 0.42, 1.0),
|
|
115
|
+
runtime=(0.65, 0.35, 0.10, 1.0),
|
|
116
|
+
error=(0.75, 0.15, 0.15, 1.0),
|
|
117
|
+
changed=(0.60, 0.42, 0.00, 1.0),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def replace(self, **changes) -> "Theme":
|
|
121
|
+
"""Return a copy with the given fields overridden.
|
|
122
|
+
|
|
123
|
+
Examples
|
|
124
|
+
--------
|
|
125
|
+
>>> from imgui_debugger import Theme
|
|
126
|
+
>>> Theme.dark().replace(name=(1.0, 0.0, 0.0, 1.0)).name
|
|
127
|
+
(1.0, 0.0, 0.0, 1.0)
|
|
128
|
+
"""
|
|
129
|
+
return replace(self, **changes)
|
imgui_debugger/tree.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""The collapsible tree: one scope header per root, one row per value below it.
|
|
2
|
+
|
|
3
|
+
Examples
|
|
4
|
+
--------
|
|
5
|
+
>>> from imgui_debugger.tree import TreeStyle
|
|
6
|
+
>>> TreeStyle(filter="fs").filter
|
|
7
|
+
'fs'
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from imgui_bundle import imgui
|
|
16
|
+
|
|
17
|
+
from .edit import can_edit, edit_value
|
|
18
|
+
from .format import fmt_error, fmt_value, type_label
|
|
19
|
+
from .scopes import Child, Scope, children_of, has_children
|
|
20
|
+
from .search import filter_children, matches
|
|
21
|
+
from .theme import Theme, to_vec4
|
|
22
|
+
|
|
23
|
+
ROLE_COLORS = {
|
|
24
|
+
"attr": "name",
|
|
25
|
+
"prop": "prop",
|
|
26
|
+
"class": "cls",
|
|
27
|
+
"item": "name",
|
|
28
|
+
"index": "index",
|
|
29
|
+
"local": "local",
|
|
30
|
+
"global": "glob",
|
|
31
|
+
"runtime": "runtime",
|
|
32
|
+
"error": "error",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class TreeStyle:
|
|
38
|
+
"""Everything the row renderer needs besides the rows themselves.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
theme : Theme
|
|
43
|
+
Palette.
|
|
44
|
+
filter : str
|
|
45
|
+
Case-insensitive filter; empty shows everything.
|
|
46
|
+
editable : bool
|
|
47
|
+
Draw inline editors for writable leaves.
|
|
48
|
+
private : bool
|
|
49
|
+
Expand ``_name`` attributes of nested values.
|
|
50
|
+
properties : bool
|
|
51
|
+
Expand ``property`` descriptors of nested values.
|
|
52
|
+
max_depth : int
|
|
53
|
+
Deepest level expanded before a row is shown as a summary only.
|
|
54
|
+
max_items : int
|
|
55
|
+
Rows rendered per container before a "+N more" line.
|
|
56
|
+
value_col : float
|
|
57
|
+
Pixel column the value is drawn at; ``0`` packs it after the name.
|
|
58
|
+
force_open : bool | None
|
|
59
|
+
For one frame, expand (True) or collapse (False) every node.
|
|
60
|
+
|
|
61
|
+
Examples
|
|
62
|
+
--------
|
|
63
|
+
>>> from imgui_debugger.tree import TreeStyle
|
|
64
|
+
>>> from imgui_debugger import Theme
|
|
65
|
+
>>> TreeStyle(theme=Theme.light(), value_col=220.0).value_col
|
|
66
|
+
220.0
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
theme: Theme = field(default_factory=Theme.dark)
|
|
70
|
+
filter: str = ""
|
|
71
|
+
editable: bool = True
|
|
72
|
+
private: bool = False
|
|
73
|
+
properties: bool = True
|
|
74
|
+
max_depth: int = 8
|
|
75
|
+
max_items: int = 200
|
|
76
|
+
value_col: float = 0.0
|
|
77
|
+
force_open: Optional[bool] = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def role_color(style: TreeStyle, kind: str):
|
|
81
|
+
"""The theme color a row of the given kind is drawn in.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
style : TreeStyle
|
|
86
|
+
Active style.
|
|
87
|
+
kind : str
|
|
88
|
+
A :class:`~imgui_debugger.scopes.Child` kind, or a theme field name.
|
|
89
|
+
|
|
90
|
+
Examples
|
|
91
|
+
--------
|
|
92
|
+
>>> from imgui_debugger.tree import TreeStyle, role_color
|
|
93
|
+
>>> role_color(TreeStyle(), "index") == TreeStyle().theme.index
|
|
94
|
+
True
|
|
95
|
+
"""
|
|
96
|
+
name = ROLE_COLORS.get(kind, kind)
|
|
97
|
+
return getattr(style.theme, name, style.theme.name)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def draw_value(child: Child, path: str, style: TreeStyle) -> None:
|
|
101
|
+
"""Draw a leaf's value, as an editor when it is writable and editable.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
child : Child
|
|
106
|
+
The row.
|
|
107
|
+
path : str
|
|
108
|
+
Dotted path, used as the imgui id.
|
|
109
|
+
style : TreeStyle
|
|
110
|
+
Active style.
|
|
111
|
+
|
|
112
|
+
Examples
|
|
113
|
+
--------
|
|
114
|
+
>>> from imgui_debugger.scopes import Child
|
|
115
|
+
>>> from imgui_debugger.tree import TreeStyle, draw_value
|
|
116
|
+
>>> draw_value(Child("fs", 9.6), "arr.fs", TreeStyle()) # doctest: +SKIP
|
|
117
|
+
"""
|
|
118
|
+
if style.value_col > 0:
|
|
119
|
+
imgui.same_line(style.value_col)
|
|
120
|
+
else:
|
|
121
|
+
imgui.same_line(spacing=16)
|
|
122
|
+
if child.kind == "error":
|
|
123
|
+
imgui.text_colored(to_vec4(style.theme.error), fmt_error(child.value))
|
|
124
|
+
return
|
|
125
|
+
if style.editable and child.editable and can_edit(child.value):
|
|
126
|
+
edit_value(path, child.value, child.setter, style.theme)
|
|
127
|
+
return
|
|
128
|
+
imgui.text_colored(to_vec4(style.theme.value), fmt_value(child.value))
|
|
129
|
+
if imgui.is_item_hovered():
|
|
130
|
+
imgui.set_tooltip(f"{path}\n{type_label(child.value)}")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def draw_child(child: Child, prefix: str, depth: int, style: TreeStyle) -> None:
|
|
134
|
+
"""Draw one row, recursing into its children when it expands.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
child : Child
|
|
139
|
+
The row.
|
|
140
|
+
prefix : str
|
|
141
|
+
Dotted path of the parent, ``""`` at a scope root.
|
|
142
|
+
depth : int
|
|
143
|
+
Current depth, compared against ``style.max_depth``.
|
|
144
|
+
style : TreeStyle
|
|
145
|
+
Active style.
|
|
146
|
+
|
|
147
|
+
Examples
|
|
148
|
+
--------
|
|
149
|
+
>>> from imgui_debugger.scopes import Child
|
|
150
|
+
>>> from imgui_debugger.tree import TreeStyle, draw_child
|
|
151
|
+
>>> draw_child(Child("md", {"fs": 9.6}), "", 0, TreeStyle()) # doctest: +SKIP
|
|
152
|
+
"""
|
|
153
|
+
path = f"{prefix}{child.name}" if child.name.startswith("[") else f"{prefix}.{child.name}"
|
|
154
|
+
path = path.lstrip(".")
|
|
155
|
+
color = role_color(style, child.kind)
|
|
156
|
+
expandable = (
|
|
157
|
+
child.kind != "error"
|
|
158
|
+
and depth < style.max_depth
|
|
159
|
+
and has_children(child.value, style.private, style.properties)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if not expandable:
|
|
163
|
+
imgui.text_colored(to_vec4(color), child.name)
|
|
164
|
+
draw_value(child, path, style)
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
if style.force_open is not None:
|
|
168
|
+
imgui.set_next_item_open(style.force_open)
|
|
169
|
+
imgui.push_style_color(imgui.Col_.text, to_vec4(style.theme.node))
|
|
170
|
+
open_ = imgui.tree_node(f"{child.name}##{path}")
|
|
171
|
+
imgui.pop_style_color()
|
|
172
|
+
if imgui.is_item_hovered():
|
|
173
|
+
imgui.set_tooltip(f"{path}\n{type_label(child.value)}")
|
|
174
|
+
imgui.same_line(style.value_col if style.value_col > 0 else 0.0)
|
|
175
|
+
imgui.text_colored(to_vec4(style.theme.text_dim), type_label(child.value))
|
|
176
|
+
if not open_:
|
|
177
|
+
return
|
|
178
|
+
try:
|
|
179
|
+
rows = children_of(child.value, style.private, style.properties, style.max_items + 1)
|
|
180
|
+
except Exception as exc:
|
|
181
|
+
imgui.text_colored(to_vec4(style.theme.error), fmt_error(exc))
|
|
182
|
+
imgui.tree_pop()
|
|
183
|
+
return
|
|
184
|
+
draw_children(rows, path, depth + 1, style)
|
|
185
|
+
imgui.tree_pop()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def draw_children(rows, prefix: str, depth: int, style: TreeStyle) -> None:
|
|
189
|
+
"""Draw a list of rows, filtered and capped at ``style.max_items``.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
rows : list[Child]
|
|
194
|
+
Rows to draw.
|
|
195
|
+
prefix : str
|
|
196
|
+
Dotted path of the parent.
|
|
197
|
+
depth : int
|
|
198
|
+
Current depth.
|
|
199
|
+
style : TreeStyle
|
|
200
|
+
Active style.
|
|
201
|
+
|
|
202
|
+
Examples
|
|
203
|
+
--------
|
|
204
|
+
>>> from imgui_debugger.scopes import Child
|
|
205
|
+
>>> from imgui_debugger.tree import TreeStyle, draw_children
|
|
206
|
+
>>> draw_children([Child("n", 1)], "cfg", 1, TreeStyle()) # doctest: +SKIP
|
|
207
|
+
"""
|
|
208
|
+
shown = filter_children(rows, style.filter)
|
|
209
|
+
for child in shown[: style.max_items]:
|
|
210
|
+
draw_child(child, prefix, depth, style)
|
|
211
|
+
hidden = len(shown) - style.max_items
|
|
212
|
+
if hidden > 0:
|
|
213
|
+
imgui.text_disabled(f"... +{hidden} more")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def draw_scope(scope: Scope, style: TreeStyle) -> str:
|
|
217
|
+
"""Draw one scope header and its rows, returning the scope name.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
scope : Scope
|
|
222
|
+
The scope to draw.
|
|
223
|
+
style : TreeStyle
|
|
224
|
+
Active style.
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
str
|
|
229
|
+
``scope.name``, so a caller can key per-scope state off the return.
|
|
230
|
+
|
|
231
|
+
Examples
|
|
232
|
+
--------
|
|
233
|
+
>>> from imgui_debugger.scopes import Scope, Child
|
|
234
|
+
>>> from imgui_debugger.tree import TreeStyle, draw_scope
|
|
235
|
+
>>> s = Scope("counters", lambda: [Child("frames", 1)])
|
|
236
|
+
>>> draw_scope(s, TreeStyle()) # doctest: +SKIP
|
|
237
|
+
'counters'
|
|
238
|
+
"""
|
|
239
|
+
try:
|
|
240
|
+
rows = scope.children()
|
|
241
|
+
except Exception as exc:
|
|
242
|
+
imgui.text_colored(to_vec4(style.theme.error), f"{scope.name}: {fmt_error(exc)}")
|
|
243
|
+
return scope.name
|
|
244
|
+
shown = filter_children(rows, style.filter)
|
|
245
|
+
if style.filter and not shown:
|
|
246
|
+
return scope.name
|
|
247
|
+
|
|
248
|
+
if style.force_open is not None:
|
|
249
|
+
imgui.set_next_item_open(style.force_open)
|
|
250
|
+
elif style.filter:
|
|
251
|
+
imgui.set_next_item_open(True)
|
|
252
|
+
else:
|
|
253
|
+
imgui.set_next_item_open(scope.start_open, imgui.Cond_.first_use_ever)
|
|
254
|
+
imgui.push_style_color(imgui.Col_.text, to_vec4(role_color(style, scope.role)))
|
|
255
|
+
open_ = imgui.collapsing_header(f"{scope.name}##scope_{scope.name}")
|
|
256
|
+
imgui.pop_style_color()
|
|
257
|
+
if scope.hint and imgui.is_item_hovered():
|
|
258
|
+
imgui.set_tooltip(scope.hint)
|
|
259
|
+
imgui.same_line()
|
|
260
|
+
imgui.text_disabled(f"({len(shown)})")
|
|
261
|
+
if open_:
|
|
262
|
+
imgui.indent()
|
|
263
|
+
draw_children(shown, scope.name, 1, style)
|
|
264
|
+
imgui.unindent()
|
|
265
|
+
return scope.name
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def row_matches(child: Child, text: str) -> bool:
|
|
269
|
+
"""Whether one row survives the filter, children included.
|
|
270
|
+
|
|
271
|
+
Parameters
|
|
272
|
+
----------
|
|
273
|
+
child : Child
|
|
274
|
+
The row.
|
|
275
|
+
text : str
|
|
276
|
+
Filter text.
|
|
277
|
+
|
|
278
|
+
Examples
|
|
279
|
+
--------
|
|
280
|
+
>>> from imgui_debugger.scopes import Child
|
|
281
|
+
>>> from imgui_debugger.tree import row_matches
|
|
282
|
+
>>> row_matches(Child("md", {"fs": 9.6}), "fs")
|
|
283
|
+
True
|
|
284
|
+
"""
|
|
285
|
+
return matches(child.name, child.value, text)
|