imgui_data_loader 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Flynn OConnell
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,382 @@
1
+ Metadata-Version: 2.4
2
+ Name: imgui_data_loader
3
+ Version: 0.1.0
4
+ Summary: A themed, configurable imgui file/folder open dialog widget built on imgui-bundle.
5
+ Author-email: Flynn OConnell <flynnoconnell@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/FlynnOConnell/imgui_data_loader
8
+ Project-URL: Source, https://github.com/FlynnOConnell/imgui_data_loader
9
+ Project-URL: Issues, https://github.com/FlynnOConnell/imgui_data_loader/issues
10
+ Keywords: imgui,imgui-bundle,hello-imgui,file-dialog,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 :: User Interfaces
16
+ Classifier: Topic :: Multimedia :: Graphics
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
+ # imgui_data_loader
29
+
30
+ A themed, configurable **file / folder open dialog** for
31
+ [imgui-bundle](https://github.com/pthom/imgui_bundle). Drop it into a
32
+ [hello_imgui](https://github.com/pthom/hello_imgui) / `immapp` app, or use the
33
+ one-shot helper to pop up a launcher window and get back the path the user
34
+ picked.
35
+
36
+ It's a small, styled **launcher window** — a title, your help/info content, and
37
+ buttons that open the **OS-native** file picker (via
38
+ `portable_file_dialogs`). Everything is configurable: buttons, file types,
39
+ theme, the info card, and an options popup.
40
+
41
+ <p align="center">
42
+ <img src="docs/_images/examples/02_buttons_filetypes.png" alt="imgui_data_loader file dialog" width="360">
43
+ </p>
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install imgui_data_loader
49
+ ```
50
+
51
+ The only dependency is `imgui-bundle` (which provides imgui, hello_imgui,
52
+ immapp, portable_file_dialogs and the FontAwesome icon font).
53
+
54
+ ## Quick start
55
+
56
+ ```python
57
+ from imgui_data_loader import run_file_dialog, FileDialogConfig
58
+
59
+ result = run_file_dialog(FileDialogConfig()) # default Open File(s) / Select Folder
60
+
61
+ if result: # truthy only for a real selection
62
+ print(result.paths) # list[str]
63
+ print(result.path) # first path, or None
64
+ else:
65
+ print("cancelled")
66
+ ```
67
+
68
+ `run_file_dialog` opens the window, blocks until the user picks something or
69
+ quits, and returns a `DialogResult`.
70
+
71
+ <p align="center">
72
+ <img src="docs/_images/examples/01_minimal.png" alt="minimal dialog" width="320">
73
+ </p>
74
+
75
+ ## Examples
76
+
77
+ Full runnable scripts are in [`examples/`](examples/) — a ladder from the
78
+ one-liner above to a full embedded app; each adds options the previous one
79
+ didn't. The snippets below show the essential config; the screenshots are
80
+ captured from the **real** dialogs by `python scripts/capture_docs.py`
81
+ (`pip install -e ".[docs]"`, needs a desktop session).
82
+
83
+ ### Buttons & file types
84
+
85
+ Branding, a hand-built `buttons` list covering every `PickKind`, per-button
86
+ `FileType` filters (overriding the dialog-wide default), and a `default_dir`.
87
+ `result.kind` tells you which button produced the selection.
88
+ [`examples/02_buttons_filetypes.py`](examples/02_buttons_filetypes.py)
89
+
90
+ ```python
91
+ from imgui_bundle import icons_fontawesome_6 as fa
92
+ from imgui_data_loader import (
93
+ run_file_dialog, FileDialogConfig, ButtonSpec, PickKind, FileType,
94
+ )
95
+
96
+ tiff = [FileType("TIFF", "*.tif *.tiff"), FileType("All Files", "*")]
97
+ result = run_file_dialog(FileDialogConfig(
98
+ title="Importer",
99
+ subtitle="open anything",
100
+ buttons=[
101
+ ButtonSpec("Open one image", PickKind.OPEN_FILE, icon=fa.ICON_FA_FILE_IMAGE,
102
+ filetypes=tiff),
103
+ ButtonSpec("Open many images", PickKind.OPEN_FILE, icon=fa.ICON_FA_CLONE,
104
+ multiselect=True, filetypes=tiff),
105
+ ButtonSpec("Open a folder", PickKind.SELECT_FOLDER, icon=fa.ICON_FA_FOLDER_OPEN),
106
+ ButtonSpec("Export as…", PickKind.SAVE_FILE, icon=fa.ICON_FA_FLOPPY_DISK,
107
+ filetypes=[FileType("Zarr", "*.zarr")]),
108
+ ],
109
+ ))
110
+ print(result.kind, result.paths) # which button fired + what it returned
111
+ ```
112
+
113
+ <p align="center">
114
+ <img src="docs/_images/examples/02_buttons_filetypes.png" alt="custom buttons and file types" width="360">
115
+ </p>
116
+
117
+ ### Theme & info card
118
+
119
+ A custom `Theme` (start from `dark()`/`light()` and `.replace()` colors) and an
120
+ `info` card. `info` is one callback or a **list** of them — each drawn as its own
121
+ section — using the library's themed helpers (`center_text`,
122
+ `text_wrapped_colored`, `icon_button`). Each callback may take the `FileDialog`
123
+ or nothing. [`examples/03_theme_info.py`](examples/03_theme_info.py)
124
+
125
+ ```python
126
+ from imgui_bundle import imgui
127
+ from imgui_data_loader import (
128
+ run_file_dialog, FileDialogConfig, Theme, center_text, text_wrapped_colored,
129
+ )
130
+
131
+ def formats(dlg):
132
+ center_text("Supported formats", dlg.theme.accent)
133
+ imgui.bullet_text("TIFF / Zarr / HDF5")
134
+
135
+ def help_note(dlg):
136
+ text_wrapped_colored(dlg.theme.text_dim,
137
+ "Folders load every supported file inside, sorted by name.")
138
+
139
+ run_file_dialog(FileDialogConfig(
140
+ title="Themed Loader",
141
+ theme=Theme.dark().replace(accent=(0.20, 0.75, 0.70, 1.0)),
142
+ info=[formats, help_note], # two sections in the card
143
+ ))
144
+ ```
145
+
146
+ <p align="center">
147
+ <img src="docs/_images/examples/03_theme_info.png" alt="themed dialog with info card" width="340">
148
+ </p>
149
+
150
+ ### Content slots & options popup
151
+
152
+ `top_draw` puts a widget row between the header and the buttons; `options_draw`
153
+ adds an **Options** button that opens a popup (shown open below); `on_select` /
154
+ `on_cancel` react to the outcome without inspecting the return value. Each
155
+ imgui widget returns `(changed, new_value)` — keep the value.
156
+ [`examples/04_slots_options.py`](examples/04_slots_options.py)
157
+
158
+ ```python
159
+ from imgui_bundle import imgui
160
+ from imgui_data_loader import run_file_dialog, FileDialogConfig
161
+
162
+ state = {"mode": 0, "recurse": True, "downsample": 1}
163
+ MODES = ["2D", "3D volume", "Time series"]
164
+
165
+ def mode_row(dlg):
166
+ imgui.set_next_item_width(imgui.get_content_region_avail().x)
167
+ _, state["mode"] = imgui.combo("##mode", state["mode"], MODES)
168
+
169
+ def options(dlg):
170
+ _, state["recurse"] = imgui.checkbox("Recurse into subfolders", state["recurse"])
171
+ _, state["downsample"] = imgui.slider_int("Downsample", state["downsample"], 1, 8)
172
+
173
+ run_file_dialog(FileDialogConfig(
174
+ title="Volume Loader",
175
+ top_draw=mode_row,
176
+ options_draw=options,
177
+ options_label="Load options",
178
+ quit_label="Close",
179
+ on_select=lambda r: print(MODES[state["mode"]], r.paths),
180
+ on_cancel=lambda: print("user quit"),
181
+ ))
182
+ ```
183
+
184
+ <p align="center">
185
+ <img src="docs/_images/examples/04_slots_options_popup.png" alt="options popup open" width="360">
186
+ </p>
187
+
188
+ ### Recent files
189
+
190
+ Pass a `PreferenceStore`; the included `JsonPreferenceStore` seeds the picker's
191
+ start directory from the last-used one and records selections to a JSON file.
192
+ Draw the recents wherever you like — here in the info card.
193
+ [`examples/05_recent_files.py`](examples/05_recent_files.py)
194
+
195
+ ```python
196
+ from imgui_bundle import imgui
197
+ from imgui_data_loader import run_file_dialog, FileDialogConfig, JsonPreferenceStore
198
+
199
+ store = JsonPreferenceStore() # ~/.config/imgui_data_loader/recent.json
200
+
201
+ def recents(dlg):
202
+ imgui.text_colored(dlg.theme.accent, "Recent")
203
+ for path in store.recent()[:6]:
204
+ imgui.bullet_text(path)
205
+
206
+ run_file_dialog(FileDialogConfig(title="Recent Files", persistence=store, info=recents))
207
+ print(store.recent())
208
+ ```
209
+
210
+ <p align="center">
211
+ <img src="docs/_images/examples/05_recent_files.png" alt="recent files list" width="340">
212
+ </p>
213
+
214
+ ### Embed in your own app
215
+
216
+ Render the `FileDialog` **widget** inside your own hello_imgui / immapp loop as
217
+ one panel of a bigger UI. Set `close_on_select=False` and poll `take_result()`
218
+ each frame (or use `on_select`); `header_draw` / `footer_draw` replace the
219
+ default header/footer; `ensure_assets()` makes the icon font available in your
220
+ runner. [`examples/06_embedded_app.py`](examples/06_embedded_app.py)
221
+
222
+ ```python
223
+ from imgui_bundle import hello_imgui, imgui, immapp
224
+ from imgui_data_loader import FileDialog, FileDialogConfig, ensure_assets
225
+
226
+ ensure_assets()
227
+ dlg = FileDialog(FileDialogConfig(close_on_select=False, show_quit_button=False))
228
+ loaded = []
229
+
230
+ def gui():
231
+ dlg.render() # the dialog is one panel…
232
+ result = dlg.take_result() # None until the user picks
233
+ if result:
234
+ loaded.extend(result.paths)
235
+ imgui.separator() # …the rest of your app follows
236
+ for p in loaded[-8:]:
237
+ imgui.bullet_text(p)
238
+
239
+ params = hello_imgui.RunnerParams()
240
+ params.callbacks.show_gui = gui
241
+ immapp.run(params)
242
+ ```
243
+
244
+ <p align="center">
245
+ <img src="docs/_images/examples/06_embedded_app.png" alt="embedded in a larger app" width="380">
246
+ </p>
247
+
248
+ ## More specific cases
249
+
250
+ Things you may want that the examples above don't cover.
251
+
252
+ ### Your own `PreferenceStore`
253
+
254
+ `persistence` is just a three-method protocol (`default_dir` / `recent` /
255
+ `record_selection`), so back "recent files" with anything — a database, your
256
+ app's settings, a project-local file:
257
+
258
+ ```python
259
+ from pathlib import Path
260
+ import json
261
+
262
+ class ProjectStore: # implements the PreferenceStore protocol
263
+ def __init__(self, project_dir):
264
+ self.dir = Path(project_dir)
265
+ self.file = self.dir / ".loader_history.json"
266
+ self._recent = json.loads(self.file.read_text()) if self.file.exists() else []
267
+
268
+ def default_dir(self): return str(self.dir) # always start in the project
269
+ def recent(self): return list(self._recent)
270
+ def record_selection(self, result):
271
+ self._recent = (result.paths + self._recent)[:10]
272
+ self.file.write_text(json.dumps(self._recent))
273
+
274
+ run_file_dialog(FileDialogConfig(title="Project Loader", persistence=ProjectStore(".")))
275
+ ```
276
+
277
+ ### Persist options across launches
278
+
279
+ hello_imgui ships a key/value preference store that writes into the **same
280
+ layout `.ini`** this library already manages, so an Options toggle survives
281
+ across runs with no extra files. It stores **strings**, and the calls must run
282
+ in the runner's `post_init` (load) and `before_exit` (save) callbacks — pass
283
+ your own `RunnerParams` and `run_file_dialog` fills in the title/size/ini
284
+ around them:
285
+
286
+ ```python
287
+ from imgui_bundle import hello_imgui, imgui
288
+
289
+ state = {"recursive": True}
290
+ load = lambda: state.update(recursive=hello_imgui.load_user_pref("recursive") == "1")
291
+ save = lambda: hello_imgui.save_user_pref("recursive", "1" if state["recursive"] else "0")
292
+
293
+ def options(dlg):
294
+ _, state["recursive"] = imgui.checkbox("Search subfolders", state["recursive"])
295
+
296
+ params = hello_imgui.RunnerParams()
297
+ params.callbacks.post_init = load
298
+ params.callbacks.before_exit = save
299
+ run_file_dialog(FileDialogConfig(title="Loader", options_draw=options), runner_params=params)
300
+ ```
301
+
302
+ This is separate from `PreferenceStore`: use `PreferenceStore` for recent-files
303
+ history that *your code* reads back, and `load_user_pref` / `save_user_pref` for
304
+ small UI settings the dialog owns.
305
+
306
+ ### Fancier widgets from imgui-bundle
307
+
308
+ The callback slots run inside a live imgui frame, so any widget **bundled with
309
+ imgui-bundle** works — not just base imgui. A few worth reaching for from an
310
+ `options_draw` / `info` callback:
311
+
312
+ ```python
313
+ from imgui_bundle import imgui_toggle, imgui_knobs, imspinner, imgui_md, imgui
314
+
315
+ def options(dlg):
316
+ _, opts["recursive"] = imgui_toggle.toggle( # animated on/off switch
317
+ "Search subfolders", opts["recursive"], imgui_toggle.ToggleFlags_.animated)
318
+ _, opts["gain"] = imgui_knobs.knob("Gain", opts["gain"], 0.0, 4.0) # rotary knob
319
+ if opts["busy"]: # busy indicator
320
+ imspinner.spinner_ang("scan", 12.0, 4.0, color=imgui.ImColor(*dlg.theme.accent))
321
+
322
+ def formats(dlg):
323
+ imgui_md.render("**Supported**: TIFF · Zarr · HDF5") # markdown in the info card
324
+ ```
325
+
326
+ `imgui_command_palette`, `im_cool_bar`, and the rest of the bundle work the same
327
+ way. Pair them with the library's helpers (`center_text`, `icon_button`,
328
+ `push_button_style`, …) and `dlg.theme` to match the styling.
329
+
330
+ ### Where the layout `.ini` goes
331
+
332
+ hello_imgui persists window layout to a small `.ini`. By default `run_file_dialog`
333
+ writes it to an **absolute** path under your config dir —
334
+ `~/.config/imgui_data_loader/file_dialog.ini` (honoring `$XDG_CONFIG_HOME`) — so it
335
+ never lands in the current working directory. Change it with
336
+ `FileDialogConfig(ini_path="/path/to/my_dialog.ini")` (the parent dir is created).
337
+ When you **embed** the dialog with your own runner, set `params.ini_filename`
338
+ (and optionally `params.ini_folder_type`) yourself — `run_file_dialog` only fills
339
+ it in when you leave it unset.
340
+
341
+ ## Configuration reference
342
+
343
+ `FileDialogConfig` fields:
344
+
345
+ | field | default | purpose |
346
+ |-------|---------|---------|
347
+ | `title`, `subtitle` | `"Open Data"`, `""` | header text |
348
+ | `buttons` | Open File(s) + Select Folder | list of `ButtonSpec` |
349
+ | `filetypes` | `[All Files]` | default filters for file/save buttons |
350
+ | `default_dir` | `""` | picker start dir (else persistence, else `~`) |
351
+ | `theme` | `Theme.dark()` | colors |
352
+ | `header_draw` | `None` | replace the title/subtitle block |
353
+ | `top_draw` | `None` | content between header and buttons |
354
+ | `info` | `None` | callback(s) drawn in the info card |
355
+ | `options_draw` | `None` | Options popup content (also toggles the button) |
356
+ | `footer_draw` | `None` | replace the Options/Quit row |
357
+ | `options_label` | `"Options"` | popup + button label |
358
+ | `show_options_button` | `True` | show Options (needs `options_draw`) |
359
+ | `show_quit_button`, `quit_label` | `True`, `"Quit"` | Quit button |
360
+ | `quit_on_escape` | `True` | Esc cancels |
361
+ | `close_on_select` | `True` | exit the run loop after a pick (one-shot mode) |
362
+ | `window_title`, `window_size`, `resizable` | — | OS window (one-shot) |
363
+ | `ini_path` | `~/.config/imgui_data_loader/…` | where the layout `.ini` is saved |
364
+ | `assets_folder` | imgui-bundle's | folder providing the icon font |
365
+ | `persistence` | `None` | a `PreferenceStore` |
366
+ | `on_select`, `on_cancel` | `None` | result callbacks |
367
+
368
+ ## Notes
369
+
370
+ - Buttons open the **OS-native** dialog, so a desktop session is required (no
371
+ in-window file browser).
372
+ - Icons come from FontAwesome 6, which ships inside imgui-bundle;
373
+ `run_file_dialog` points hello_imgui at that font automatically. When
374
+ embedding, make sure your app's assets folder provides it (or pass
375
+ `assets_folder`). The bundled build is **Solid** only, so a few glyphs (e.g.
376
+ `ICON_FA_IMAGES`, `ICON_FA_LAYER_GROUP`) render as a blank box — pick a solid
377
+ icon if one shows empty.
378
+ - Draw callbacks run inside an active imgui frame — only call imgui from them.
379
+
380
+ ## License
381
+
382
+ MIT