imgui_debugger 0.1.0__tar.gz → 0.2.0__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 (41) hide show
  1. imgui_debugger-0.2.0/PKG-INFO +398 -0
  2. imgui_debugger-0.2.0/README.md +371 -0
  3. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/pyproject.toml +1 -1
  4. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger/__init__.py +132 -88
  5. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger/debugger.py +20 -97
  6. imgui_debugger-0.2.0/src/imgui_debugger/native.py +200 -0
  7. imgui_debugger-0.2.0/src/imgui_debugger/panel.py +342 -0
  8. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger/runner.py +132 -66
  9. imgui_debugger-0.2.0/src/imgui_debugger/style.py +593 -0
  10. imgui_debugger-0.2.0/src/imgui_debugger/tools.py +254 -0
  11. imgui_debugger-0.2.0/src/imgui_debugger.egg-info/PKG-INFO +398 -0
  12. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger.egg-info/SOURCES.txt +8 -1
  13. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/tests/test_doctests.py +31 -27
  14. imgui_debugger-0.2.0/tests/test_headless_render.py +264 -0
  15. imgui_debugger-0.2.0/tests/test_panel.py +99 -0
  16. imgui_debugger-0.2.0/tests/test_style.py +88 -0
  17. imgui_debugger-0.2.0/tests/test_tools.py +89 -0
  18. imgui_debugger-0.1.0/PKG-INFO +0 -225
  19. imgui_debugger-0.1.0/README.md +0 -198
  20. imgui_debugger-0.1.0/src/imgui_debugger.egg-info/PKG-INFO +0 -225
  21. imgui_debugger-0.1.0/tests/test_headless_render.py +0 -123
  22. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/LICENSE +0 -0
  23. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/setup.cfg +0 -0
  24. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger/_assets.py +0 -0
  25. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger/edit.py +0 -0
  26. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger/format.py +0 -0
  27. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger/py.typed +0 -0
  28. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger/scopes.py +0 -0
  29. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger/search.py +0 -0
  30. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger/theme.py +0 -0
  31. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger/tree.py +0 -0
  32. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger.egg-info/dependency_links.txt +0 -0
  33. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger.egg-info/requires.txt +0 -0
  34. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/src/imgui_debugger.egg-info/top_level.txt +0 -0
  35. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/tests/test_assets.py +0 -0
  36. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/tests/test_debugger.py +0 -0
  37. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/tests/test_edit.py +0 -0
  38. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/tests/test_format.py +0 -0
  39. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/tests/test_scopes.py +0 -0
  40. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/tests/test_search.py +0 -0
  41. {imgui_debugger-0.1.0 → imgui_debugger-0.2.0}/tests/test_theme.py +0 -0
@@ -0,0 +1,398 @@
1
+ Metadata-Version: 2.4
2
+ Name: imgui_debugger
3
+ Version: 0.2.0
4
+ Summary: A live variable inspector you can drop into any imgui-bundle widget.
5
+ Author-email: Flynn OConnell <flynnoconnell@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/FlynnOConnell/imgui_debugger
8
+ Project-URL: Source, https://github.com/FlynnOConnell/imgui_debugger
9
+ Project-URL: Issues, https://github.com/FlynnOConnell/imgui_debugger/issues
10
+ Keywords: imgui,imgui-bundle,hello-imgui,debugger,inspector,gui,widget
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Topic :: Software Development :: Debuggers
16
+ Classifier: Topic :: Software Development :: User Interfaces
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: imgui-bundle<2,>=1.92
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7; extra == "dev"
23
+ Provides-Extra: docs
24
+ Requires-Dist: pillow>=9; extra == "docs"
25
+ Requires-Dist: numpy>=1.21; extra == "docs"
26
+ Dynamic: license-file
27
+
28
+ <h1 align="center">imgui_debugger</h1>
29
+
30
+ <p align="center">
31
+ <a href="https://pypi.org/project/imgui_debugger/"><img src="https://img.shields.io/pypi/v/imgui_debugger.svg" alt="PyPI version"></a>
32
+ <a href="https://pypi.org/project/imgui_debugger/"><img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10+"></a>
33
+ <a href="LICENSE"><img src="https://img.shields.io/pypi/l/imgui_debugger.svg" alt="License: MIT"></a>
34
+ </p>
35
+
36
+ <samp>
37
+ <p align="center">
38
+ Standalone, configurable <b>debug panels</b> for imgui-bundle apps
39
+ <br>
40
+ <br>
41
+ <a href="#install">install</a> ·
42
+ <a href="#the-panels">panels</a> ·
43
+ <a href="#one-menu-for-everything">one menu</a> ·
44
+ <a href="#variable-inspector">inspector</a> ·
45
+ <a href="#style-editor">style editor</a> ·
46
+ <a href="#examples">examples</a> ·
47
+ <a href="#configuration-reference">configuration</a> ·
48
+ <a href="https://github.com/FlynnOConnell/imgui_debugger/issues">issues</a>
49
+ </p>
50
+ </samp>
51
+
52
+ ## About
53
+
54
+ Every debugging tool imgui offers normally hangs off the demo window's Tools
55
+ menu. This package unbundles them: each tool is an independent, configurable
56
+ panel you can drop into your own app, on your own menu, with your own
57
+ shortcuts — plus a live variable inspector and a style editor whose Save and
58
+ Load are yours.
59
+
60
+ Nothing here needs the demo window, and nothing here depends on anything else
61
+ here: import one panel, or take the whole set.
62
+
63
+ | panel | what it shows | comes from |
64
+ |-------|---------------|------------|
65
+ | `Debugger` | every variable your widget can see, by scope, live | this package |
66
+ | `StyleEditor` | sizes, colors, rendering, with pluggable Save / Load | this package |
67
+ | `MetricsPanel` | windows, draw lists, viewports, internal state | imgui |
68
+ | `DebugLogPanel` | focus, nav, docking and IO events as they happen | imgui |
69
+ | `IdStackPanel` | what an item's id is built from, for id collisions | imgui |
70
+ | `AboutPanel` | version and the build's enabled features | imgui |
71
+ | `UserGuidePanel` | imgui's built-in control reference | imgui |
72
+ | `DemoPanel` | the demo window, if you ever want it — opt-in only | imgui |
73
+
74
+ Built for widgets like the ones in
75
+ [mbo_utilities](https://github.com/MillerBrainObservatory/mbo_utilities) and
76
+ [masknmf-toolbox](https://github.com/apasarkar/masknmf-toolbox), but it knows
77
+ nothing about them — a target is any Python object.
78
+
79
+ ## Install
80
+
81
+ ```bash
82
+ pip install imgui_debugger
83
+ ```
84
+
85
+ The only dependency is `imgui-bundle` (which provides imgui, hello_imgui,
86
+ immapp and the FontAwesome icon font).
87
+
88
+ ## The panels
89
+
90
+ Every panel — including ones you write — has the same four-part surface:
91
+
92
+ ```python
93
+ panel.visible # bool you can set, bind, or persist
94
+ panel.menu_item() # a checked menu entry bound to visible
95
+ panel.render() # the body, at the current cursor, no window
96
+ panel.render_window() # the body in its own window; polls the hotkey
97
+ ```
98
+
99
+ so wiring one into a host app is two lines:
100
+
101
+ ```python
102
+ def draw_menu(self):
103
+ if imgui.begin_menu("View"):
104
+ self.editor.menu_item() # or menu_item("Theme", "Ctrl+T")
105
+ imgui.end_menu()
106
+
107
+ def draw(self):
108
+ ...
109
+ self.editor.render_window()
110
+ ```
111
+
112
+ `render()` is there for when you would rather host the body yourself — in a tab,
113
+ a dock node, or a sidebar you already own. imgui's own windows (`MetricsPanel`
114
+ and friends) are window-only and say so by raising from `render()`.
115
+
116
+ Configuration is a dataclass per panel, all sharing `PanelConfig`:
117
+
118
+ ```python
119
+ from imgui_bundle import imgui
120
+ from imgui_debugger import Hotkey, StyleEditor, StyleEditorConfig
121
+
122
+ editor = StyleEditor(StyleEditorConfig(
123
+ title="Theme", # window title + menu label
124
+ visible=False, # starts closed
125
+ hotkey=Hotkey(imgui.Key.t, ctrl=True), # Ctrl+T toggles it; the menu says so
126
+ window_size=(520, 700),
127
+ show_sizes=False, # StyleEditor's own knobs
128
+ on_save=my_app.save_style,
129
+ ))
130
+ ```
131
+
132
+ To show one on its own, with no host app at all:
133
+
134
+ ```python
135
+ from imgui_debugger import run_panel
136
+ run_panel(StyleEditor())
137
+ run_panel(MetricsPanel())
138
+ ```
139
+
140
+ Writing your own panel is a subclass and a `render`:
141
+
142
+ ```python
143
+ from imgui_debugger import Panel, PanelConfig
144
+
145
+ class Timings(Panel):
146
+ config_class = PanelConfig
147
+
148
+ def render(self):
149
+ imgui.text(f"{imgui.get_io().framerate:.0f} fps")
150
+ ```
151
+
152
+ ## One menu for everything
153
+
154
+ `DebugTools` is an ordered set of panels with one menu and one per-frame call:
155
+
156
+ ```python
157
+ from imgui_debugger import DebugTools, Hotkey
158
+
159
+ self.tools = DebugTools.default(self, menu_label="Debug")
160
+ self.tools["Debugger"].config.hotkey = Hotkey(imgui.Key.f12)
161
+
162
+ def draw_menu(self):
163
+ self.tools.draw_menu() # or draw_menu_items() inside your own menu
164
+
165
+ def draw(self):
166
+ ...
167
+ self.tools.render() # every open panel, every hotkey
168
+ ```
169
+
170
+ `DebugTools.default()` gives the inspector, the style editor, metrics, the debug
171
+ log and the ID stack tool, all starting closed. The demo window is deliberately
172
+ left out. The set is editable — `add`, `remove`, `tools["title"]`, `show_all`,
173
+ `hide_all`, `visible()` — so you can swap the style editor for one configured
174
+ your way, or register a panel of your own beside them.
175
+
176
+ ## Variable inspector
177
+
178
+ Point it at a widget and it draws every variable that widget can see, grouped by
179
+ scope, in a collapsible tree that re-reads its values every frame:
180
+
181
+ - **instance** — the object's own `__dict__` and `__slots__`
182
+ - **properties** — `property` descriptors, evaluated live; one that raises shows
183
+ the exception instead of blanking the panel
184
+ - **class** — class attributes from the whole MRO
185
+ - **locals / globals** — the call frame you captured, so you can follow a draw
186
+ method's own variables
187
+ - **imgui** — io, mouse, keyboard, the window rect and the style metrics that
188
+ explain most layout bugs
189
+ - **watches** — anything else you promote to a top-level scope
190
+
191
+ Leaves that are a bool, number, string or color tuple get an inline editor that
192
+ writes straight back onto the object.
193
+
194
+ ```python
195
+ from imgui_debugger import attach
196
+
197
+ class RoiWidget:
198
+ def __init__(self):
199
+ self.threshold = 0.4
200
+ self.debugger = attach(self, title="ROIs widget")
201
+
202
+ def update(self): # your per-frame draw
203
+ ...
204
+ self.debugger.capture() # follow this method's locals
205
+ self.debugger.render_window()
206
+ ```
207
+
208
+ Or inspect one object with no host app:
209
+
210
+ ```python
211
+ from imgui_debugger import run_debugger
212
+ run_debugger({"fs": 9.6, "dz": 5.0, "planes": [1, 2, 3]})
213
+ ```
214
+
215
+ ### Scopes
216
+
217
+ `Debugger.scopes()` returns them in display order: the target's
218
+ `instance` / `properties` / `class`, then your watches, then `locals` /
219
+ `globals`, then `imgui`.
220
+
221
+ | scope | source | writable |
222
+ |-------|--------|----------|
223
+ | `instance` | `vars(obj)` + `__slots__` | yes |
224
+ | `properties` | `property` / `cached_property` on the MRO | only with an `fset` |
225
+ | `class` | class attributes, no methods or descriptors | yes |
226
+ | `locals` | the captured frame's `f_locals` | no (writes do not stick) |
227
+ | `globals` | the captured frame's `f_globals` | yes |
228
+ | `imgui` | `io`, `mouse`, `keyboard`, `window`, `style` | no |
229
+ | watches | `watch(name, value_or_callable)` | depends on the value |
230
+
231
+ A watch takes a value or a zero-argument callable; the callable is re-read every
232
+ frame, so it survives the attribute being reassigned:
233
+
234
+ ```python
235
+ dbg.watch("metadata", lambda: self.metadata, role="prop")
236
+ dbg.watch("fps", lambda: {"now": imgui.get_io().framerate}, role="runtime")
237
+ ```
238
+
239
+ `watch_all(obj, ["metadata", "indices"])` does the same for several attributes
240
+ in one call.
241
+
242
+ ### Toolbar
243
+
244
+ | control | what it does |
245
+ |---------|--------------|
246
+ | filter | case-insensitive match over names and leaf values, recursing into children (bounded to 6 levels and 64 items per container, memoized per filter string) |
247
+ | expand / collapse | force every node open or shut for one frame |
248
+ | private | include `_name` attributes |
249
+ | edit | turn the inline editors off and read only |
250
+
251
+ ## Style editor
252
+
253
+ `StyleEditor` is the imgui demo's style editor with its Save Ref / Revert Ref /
254
+ Export buttons replaced by two of yours. `imgui.show_style_editor()` cannot be
255
+ drawn without those buttons, and they only manage an in-memory reference style —
256
+ nothing they do touches disk — so this draws the tabs itself.
257
+
258
+ ```python
259
+ from imgui_debugger import StyleEditor, StyleEditorConfig
260
+
261
+ editor = StyleEditor(StyleEditorConfig(
262
+ title="Theme",
263
+ visible=False,
264
+ save_label="Save to settings",
265
+ load_label="Load from settings",
266
+ on_save=my_app.save_style, # on_save(data: dict)
267
+ on_load=my_app.load_style, # on_load() -> dict | None
268
+ ))
269
+ ```
270
+
271
+ With no `on_save` / `on_load`, the buttons fall back to reading and writing
272
+ `config.path` as JSON (`~/.imgui_debugger/style.json` when that is unset too).
273
+ `on_load` returning `None` is a cancel — that is how a native file dialog the
274
+ user dismissed reports back. Errors from either hook land on the editor's status
275
+ line instead of raising inside a frame.
276
+
277
+ The body is configurable: `show_sizes`, `show_colors` and `show_rendering` pick
278
+ the tabs (one tab alone is drawn without a tab bar), `show_font_selector` and
279
+ `show_style_selector` add imgui's own pickers, `size_groups` replaces the Sizes
280
+ tab's fields entirely, and `extra_draw(editor)` slots your own controls under the
281
+ toolbar. Presets apply `style_colors_dark` / `_light` / `_classic`; Revert
282
+ restores the style as it was when the editor was constructed.
283
+
284
+ The serialization is usable on its own:
285
+
286
+ | function | does |
287
+ |----------|------|
288
+ | `style_to_dict(style=None)` | `{"sizes": {...}, "colors": {name: [r,g,b,a]}}` |
289
+ | `apply_style_dict(data, style=None)` | writes it back, returns how many fields landed |
290
+ | `save_style(path=None, style=None)` | `style_to_dict` to JSON |
291
+ | `load_style(path=None, style=None)` | JSON to `apply_style_dict` |
292
+
293
+ Colors are keyed by imgui's own names (`Text`, `FrameBg`, ...), and unknown keys
294
+ are ignored, so a file written against an older imgui still loads. Two fields are
295
+ deliberately not serialized: `font_scale_dpi` and `font_size_base` are derived
296
+ from the screen at runtime, and restoring another machine's values resizes every
297
+ font for the wrong display.
298
+
299
+ ## Examples
300
+
301
+ See all examples in [`examples/`](examples/).
302
+
303
+ | name | file | what it shows |
304
+ |------|------|---------------|
305
+ | debug_minimal | [`debug_minimal.py`](examples/debug_minimal.py) | one-shot window over a settings dataclass |
306
+ | debug_widget | [`debug_widget.py`](examples/debug_widget.py) | a widget that owns its inspector, captures its own locals, and toggles it with F12 |
307
+ | debug_edge_window | [`debug_edge_window.py`](examples/debug_edge_window.py) | a fastplotlib `EdgeWindow`, the widget shape pml_utilities and masknmf-toolbox use |
308
+ | style_editor_menu | [`style_editor_menu.py`](examples/style_editor_menu.py) | the style editor alone, on a menu, saving into the app's own settings file |
309
+ | debug_tools_menu | [`debug_tools_menu.py`](examples/debug_tools_menu.py) | the whole set behind one menu, with hotkeys and a replaced style editor |
310
+
311
+ ## Configuration reference
312
+
313
+ ### `PanelConfig` — every panel takes these
314
+
315
+ | field | default | purpose |
316
+ |-------|---------|---------|
317
+ | `title` | per panel | window title and default menu label |
318
+ | `visible` | `True` | whether it starts open |
319
+ | `window_id` | `""` | stable imgui id suffix; the class name when empty, so renaming the title keeps the saved layout |
320
+ | `window_size` | `(0, 0)` | first-use size; `(0, 0)` sizes to content |
321
+ | `window_pos` | `None` | first-use position |
322
+ | `window_flags` | `0` | `imgui.WindowFlags_` bits |
323
+ | `closable` | `True` | draw the close button and clear `visible` with it |
324
+ | `shortcut` | `""` | menu shortcut text; the hotkey's text when empty |
325
+ | `hotkey` | `None` | `Hotkey(key, ctrl=, shift=, alt=)` that toggles the panel |
326
+ | `theme` | `Theme.dark()` | palette for what the panel draws itself |
327
+
328
+ ### `DebuggerConfig` — plus the above
329
+
330
+ | field | default | purpose |
331
+ |-------|---------|---------|
332
+ | `target` | `None` | the object whose scopes come first |
333
+ | `private` | `False` | show `_name` attributes |
334
+ | `properties` | `True` | show the `properties` scope and expand nested properties |
335
+ | `class_attrs` | `True` | show the `class` scope |
336
+ | `editable` | `True` | inline editors for writable leaves |
337
+ | `show_frame` | `True` | show `locals` / `globals` |
338
+ | `show_runtime` | `True` | show the live `imgui` scope |
339
+ | `max_depth` | `8` | deepest level the tree expands |
340
+ | `max_items` | `200` | rows per container before "+N more" |
341
+ | `value_col` | `0.0` | pixel column values align at; `0` packs them after the name |
342
+ | `show_toolbar` | `True` | draw the filter box and toggles |
343
+ | `show_title` | `True` | draw the title line inside the body |
344
+ | `os_window_title`, `resizable`, `ini_path`, `assets_folder` | — | one-shot `run_debugger` only |
345
+
346
+ ### `StyleEditorConfig` — plus `PanelConfig`
347
+
348
+ | field | default | purpose |
349
+ |-------|---------|---------|
350
+ | `on_save` | `None` | `on_save(data)`; falls back to writing `path` |
351
+ | `on_load` | `None` | `on_load() -> dict \| None`; falls back to reading `path` |
352
+ | `path` | `None` | file used by the fallback Save / Load |
353
+ | `save_label`, `load_label` | `"Save"`, `"Load"` | button text |
354
+ | `show_presets`, `show_revert` | `True` | the preset buttons and Revert |
355
+ | `show_sizes`, `show_colors`, `show_rendering` | `True` | which tabs to draw |
356
+ | `show_font_selector`, `show_style_selector` | `False` | imgui's own pickers above the tabs |
357
+ | `size_groups` | `None` | replace the Sizes tab's field groups |
358
+ | `extra_draw` | `None` | `extra_draw(editor)` under the toolbar |
359
+
360
+ `attach(target, **kwargs)` and `run_debugger(target, **kwargs)` take any
361
+ `DebuggerConfig` field as a keyword argument.
362
+
363
+ ## Files on disk
364
+
365
+ Everything the library writes lives under `~/.imgui_debugger/` (override with
366
+ the `IMGUI_DEBUGGER_HOME` env var):
367
+
368
+ | path | written by | purpose |
369
+ |------|-----------|---------|
370
+ | `~/.imgui_debugger/debugger.ini`, `panel.ini` | `run_debugger`, `run_panel` | hello_imgui window layout. Override per call; an embedding app's own `ini_filename` always wins. |
371
+ | `~/.imgui_debugger/style.json` | `save_style` with no path | the fallback style file |
372
+ | `~/.imgui_debugger/assets/` | you (optional) | user assets folder, added to hello_imgui's search path when the icon font cannot be resolved |
373
+
374
+ Embedded use writes nothing: `render()` and `render_window()` only draw.
375
+
376
+ ## Notes
377
+
378
+ - Reads are guarded. A property that raises, a `__repr__` that raises, and a
379
+ container that changes size mid-frame all render as a row rather than crashing
380
+ the frame.
381
+ - Big containers are capped, not truncated silently: a "+N more" line says how
382
+ many rows were left out.
383
+ - Editing writes through the same setter the row was built from — `setattr` for
384
+ an attribute, `__setitem__` for a dict or list entry, the property's `fset` for
385
+ a property.
386
+
387
+ ## Acknowledgements
388
+
389
+ The tree rendering, the bounded memoized filter and the value formatting come
390
+ from the metadata inspector in
391
+ [mbo_utilities](https://github.com/MillerBrainObservatory/mbo_utilities) at the
392
+ [Miller Brain Observatory](https://github.com/MillerBrainObservatory). The
393
+ packaging, theming and one-shot harness follow
394
+ [imgui_data_loader](https://github.com/FlynnOConnell/imgui_data_loader).
395
+
396
+ ## License
397
+
398
+ MIT