dear-file-browser 0.11.0

File dialogs and in-UI file browser for dear-imgui-rs
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
# Fearless Refactor: File Browser Architecture (IGFD Reference-Driven)

This document proposes and documents a "fearless refactor" of `dear-file-browser` to reach feature parity (or near-parity) with ImGuiFileDialog (IGFD) while staying idiomatic to Rust and compatible with `dear-imgui-rs`.

Key policy: **IGFD is the reference implementation for UI/behavior** in the ImGui backend. We keep a copy of IGFD source under `repo-ref/ImGuiFileDialog` and prefer source-diff driven parity work.

See also:

- `docs/IGFD_PARITY_AND_DEVIATIONS.md` (capability parity tracking)
- `docs/IGFD_SOURCE_REFERENCE_MAP.md` (IGFD function → our code map for 1:1 UI/behavior alignment)

Scope: **the ImGui-embedded browser/dialog** (not OS-native dialogs via `rfd`, which remain a separate backend).

---

## 1. Context & Motivation

`dear-file-browser` started lightweight but is actively evolving toward an IGFD-grade dialog:

- `FileDialogCore` (domain logic) + `FileDialogUiState` (transient UI state).
- `DialogManager` for multi-instance Open/Display-style workflows.
- Places (devices + bookmarks) with export/import, custom panes, file styles, and thumbnails.
- Advanced filters (multi-layer extensions, wildcard `*`/`?`, `((...))` regex tokens) and natural sorting.

IGFD, in contrast, is a full dialog system with:

- Open/Display separation (many opens, one display per key).
- Multiple instances and strong configurability.
- Places/bookmarks with persistence.
- Custom pane callbacks that can block validation.
- File style rules (color/icon/font) by type/ext/regex/… and via callback.
- Thumbnails with GPU lifecycle callbacks.
- Rich selection and keyboard navigation behavior (Ctrl/Shift selection, Ctrl+A, type-to-select).
- Advanced filter syntax (collections, regex, multi-layer extensions, asterisk semantics).

This refactor aims to adopt IGFD’s **capabilities and concepts** while keeping:

- A Rust-first API surface.
- Renderer/backend agnostic thumbnail handling.
- Testable business logic (core) without depending on Dear ImGui types.

---

## 1.1 IGFD Parity Snapshot

This table maps IGFD’s headline feature list to the current ImGui backend of `dear-file-browser`.
The goal is *capability parity*, not necessarily API/flag parity.

Legend: ✅ done, 🟡 partial, ❌ missing / planned.

| IGFD capability | dear-file-browser | Notes / gaps |
|---|---:|---|
| Call/Display split + multi instances || `DialogManager` + stable ids. |
| Custom pane that can block confirm || `CustomPane` + `ConfirmGate` (supports bottom or right dock). |
| File styles (type/ext/name/contains/regex/…) || Rule-based registry + callback-based provider + optional `font_token -> FontId` mapping in UI state. |
| Multi-selection (Ctrl/Shift) + Ctrl+A || Supports optional “max N” cap (IGFD-style). |
| Keyboard navigation + type-to-select || Works in list & grid. |
| Places: devices + bookmarks + custom groups || Devices + bookmarks + user groups are editable (add/remove/rename) and support per-group metadata (order/default-open) + optional separators; System group remains read-only. |
| Directory manual entry (right-click breadcrumb) || Supported. |
| “Parallel directory” popup on path separator || Supported via breadcrumb separator popup. |
| Confirm overwrite (Save) || `SavePolicy` + modal prompt. |
| Result modes (path+name+selection, ext policies) || `Selection { paths }` + convenience helpers: `file_path_name()`, `file_name()`, `selection_named_paths()`; extension policy matches IGFD intent. |
| Thumbnails + GPU lifecycle hooks || Pipeline + LRU + optional `thumbnails-image` decoding + backend upload/destroy. |
| Embedded / custom host || `draw_contents*()` + window host config + `show_modal*()` convenience. |
| Validation buttons tuning (placement/width/invert) || `ValidationButtonsConfig` (align/order/width/labels). |
| Filter groups / collection syntax (`Name{...}`) || `FileFilter::parse_igfd()` / `FileDialog::filters_igfd()` support `Name{...}` collections and simple lists. |
| Natural sort for extensions on demand || “Ext” column supports sorting by full extension using natural order. |
| File operations (rename/delete/copy/paste, etc.) || Rename + delete + copy/cut/paste exist (delete supports optional recursive directories; paste supports conflict modal with Overwrite/Skip/Keep Both). |

---

Parity status note (2026-02-07, non-C-API scope):

- Closed recently: explicit lifecycle helpers (`open/reopen/close/is_open`), ID-first rename/delete target model, selected path/count readback in core, scan-time entry hook (`set_scan_hook` / `clear_scan_hook`), link/symlink metadata+style parity (`EntryKind::Link`), host min/max constraints, and parity/deviation baseline docs.
- Remaining high-priority gaps: none in non-C-API core parity scope; only post-parity API polish remains.
- Product scope note: C API parity is intentionally excluded for this refactor wave.

---
## 2. Design Principles

1. **Decouple core logic from ImGui rendering**
   - Core should be unit-testable and deterministic.
2. **Decouple dialog contents from its host (window/modal/popup/embed)**
   - The same dialog can be embedded or hosted as a window/modal/popup.
3. **Open/Display split + multi-instance by default**
   - Enable multiple dialogs concurrently without singleton global state.
4. **Explicit extension points**
   - Custom panes, file styles, filesystem backends, thumbnails, places persistence.
5. **Feature-gated complexity**
   - Regex parsing, thumbnail decoding, async scanning should be optional cargo features.
6. **Performance by design**
   - Caching, incremental scanning, and an "asynchronous enumeration" path.

Non-goals (for early phases):

- Perfect pixel-identical UI to IGFD.
- Re-implement every IGFD flag 1:1; instead provide equivalent capabilities.

---

## 3. High-Level Architecture

### 3.1 Layering

```
┌─────────────────────────────────────────────────────────┐
│                      Host Layer                          │
│  WindowHost / ModalHost / PopupHost / EmbedHost          │
│  - sizes, focus rules, open/close, docking integration    │
└─────────────────────────────────────────────────────────┘
                ↓ calls
┌─────────────────────────────────────────────────────────┐
│                      UI Layer (ImGui)                    │
│  - translates ImGui input → core::Event                  │
│  - draws ViewModel → ImGui widgets                        │
│  - calls thumbnail renderer hooks (optional)              │
└─────────────────────────────────────────────────────────┘
                ↓ drives
┌─────────────────────────────────────────────────────────┐
│                      Core Layer (no ImGui)               │
│  DialogManager + FileDialogCore                          │
│  - state machine, selection model, filtering, sorting     │
│  - places store, persistence I/O model                    │
│  - filesystem abstraction                                │
│  - thumbnail pipeline state (decode/upload/destroy)       │
└─────────────────────────────────────────────────────────┘
```

### 3.2 Open/Display Split

`DialogManager` owns multiple dialog instances. You "open" a request and later "display" (drive + draw) per dialog.

- `open(req) -> DialogId`
- each frame: `dialog_mut(id)` + `ui.draw(dialog.view())` + `dialog.handle_event(event)`
- `take_result()` emits `Selection` only when finished.

This mirrors IGFD’s `OpenDialog(key)` + `Display(key)` pattern, but Rust-idiomatic.

---

## 4. Public API Sketch

### 4.1 Types

- `DialogId`: stable handle used by UI/host.
- `DialogMode`: OpenFile/OpenFiles/PickFolder/SaveFile.
- `OpenRequest`: initial configuration snapshot (path, filters, flags, etc.).
- `Selection`: selected paths + optional metadata.
- `DialogResult`: Ok(selection) or Err(cancelled/validation/…).

### 4.2 Manager

```rust
pub struct DialogManager {
    // multiple dialogs + shared resources (places store, thumbnail cache, etc.)
}

impl DialogManager {
    pub fn open(&mut self, req: OpenRequest) -> DialogId;
    pub fn close(&mut self, id: DialogId);
    pub fn dialog_mut(&mut self, id: DialogId) -> Option<&mut FileDialogCore>;
}
```

### 4.3 Core Dialog

```rust
pub struct FileDialogCore { /* ... */ }

impl FileDialogCore {
    pub fn handle_event(&mut self, ev: Event) -> Vec<Action>;
    pub fn view(&self) -> ViewModel;
    pub fn take_result(&mut self) -> Option<DialogResult>;
}
```

UI code becomes "thin": it translates ImGui input into `Event` and renders from `ViewModel`.

---

## 5. Core Modules (Proposed)

Suggested module layout (within `extensions/dear-file-browser/src/`):

- `core/manager.rs``DialogManager`, `DialogId`
- `core/dialog.rs``FileDialogCore` state machine
- `core/model.rs``Entry`, `EntryId`, `SelectionModel`, `SortSpec`, `FilterSpec`
- `core/fs.rs``FileSystem` trait + default `StdFileSystem`
- `core/places.rs``PlacesStore` + `PlacesGroup/PlaceItem` + (de)serialization models
- `core/style.rs``FileStyle` + `StyleProvider` + rule engine
- `core/pane.rs``CustomPane` trait + glue types + can-confirm semantics
- `core/thumbs.rs` — thumbnail state machine, caching, decode queue
- `ui/mod.rs` — ImGui-specific drawing + input mapping
- `hosts/*.rs` — window/modal/popup/embed hosts

Note: this can start inside the existing `src/ui/mod.rs` by extraction and gradual migration.

---

## 6. Data Model

### 6.1 Directory Entries

Represent each entry with stable identity and metadata.

- `EntryId`:
  - stable within a directory snapshot.
  - recommended: hash of `(full_path, file_type, size, modified)` or a `PathBuf`-based key with a per-snapshot counter.
- `FileMeta`:
  - `name`, `path`, `is_dir`, `is_symlink`, `size`, `modified`, `kind`, `extension_layers`, etc.

Avoid storing "selected = Vec<String>" by name. Use `EntryId` + map to `FileMeta`.

### 6.2 Selection Model (IGFD-grade)

Maintain:

- `selected: IndexSet<EntryId>`
- `focused: Option<EntryId>`
- `anchor: Option<EntryId>` (for shift-range)
- `last_click_index: Option<usize>` (optional)

Operations:

- `toggle(id)` (Ctrl-click)
- `select_range(anchor, id)` (Shift-click)
- `select_all(visible_entries)` (Ctrl+A)
- `clear()`

### 6.3 Dialog State Machine

Core states (minimum):

- `Open` (normal browsing)
- `ConfirmOverwrite { target: PathBuf }` (save mode)
- `Finished(DialogResult)`

Transitions are driven via `Event`s.

---

## 7. Event Model (UI → Core)

ImGui input should translate into domain events:

- navigation:
  - `NavigateUp`, `NavigateInto(EntryId)`, `NavigateTo(PathBuf)`
- selection:
  - `ClickEntry { id, modifiers }`, `DoubleClickEntry { id }`
  - `SelectAll`
- filtering/search:
  - `SetSearch(String)`, `SetActiveFilter(FilterId)`, `SetShowHidden(bool)`
- sorting:
  - `SetSort(SortSpec)`
- confirm/cancel:
  - `Confirm`, `Cancel`
  - `ConfirmOverwriteYes`, `ConfirmOverwriteNo`
- keyboard helper:
  - `TypeToSelectChar(char)` (optional)

This makes core testable without Dear ImGui.

---

## 8. Hosts (Window / Modal / Popup / Embed)

### 8.1 Why hosts?

Currently, `show()` always creates a window. IGFD supports:

- modal / non-modal
- embedding into a parent frame
- "NoDialog" mode (only content; host provided by caller)

### 8.2 Host responsibilities

- Decide whether/how to call `ui::draw_contents()`
- Provide min/max size constraints and default sizes
- Coordinate focus rules (initial focus, path edit focus, search focus)
- Provide unique ImGui IDs / keys

Minimal API sketch:

```rust
pub trait DialogHost {
    fn draw(&mut self, ui: &Ui, dialog: &mut FileDialogCore) -> Option<DialogResult>;
}
```

---

## 9. Places System (Bookmarks / Devices / Custom Groups)

### 9.1 Data model

- `PlacesStore` contains ordered groups.
- Each group has:
  - `user_editable`
  - `open_by_default`
  - `display_order`
  - items: (label, path, optional icon)

### 9.2 Persistence

Core provides:

- `serialize() -> String` (JSON or RON recommended)
- `deserialize(&str) -> Result<()>`

Caller decides where to store (app config file, etc.).

### 9.3 System devices

Expose via `FileSystem::drives()` / `FileSystem::devices()` to populate a default group.

---

## 10. Custom Pane (Extension Widgets)

### 10.1 Requirements

- Render arbitrary ImGui widgets based on current filter / selection / user data.
- Pane can set `can_confirm = false` to block validation.
- Pane can display inline validation errors/hints.

### 10.2 Rust API shape

Provide both:

- trait object: `Box<dyn CustomPane>`
- function callback: `FnMut(&PaneCtx, &Ui) -> PaneResult`

Core stores "pane outputs" each frame to decide confirm enablement.

---

## 11. File Styles (Color / Icon / Font / Tooltip)

### 11.1 Supported matchers

Should cover IGFD equivalents:

- by type: file/dir/link
- by extension (multi-layer extension support)
- by full name
- by substring
- by regex (feature-gated)
- by callback (dynamic)

### 11.2 Rendering integration

Core decides style metadata (logical). UI maps:

- icon string to displayed label or icon font glyph
- font id to actual ImGui font pointer (if app provides mapping)

---

## 12. Thumbnails (Agnostic Pipeline)

### 12.1 Separation of concerns

- Core:
  - decides which entries need thumbnails
  - stores decode state and LRU
  - requests decode work
- UI/Host:
  - owns GPU texture creation/destruction via a `ThumbnailRenderer`

### 12.2 States

Per entry:

- `None`
- `QueuedDecode`
- `Decoded { rgba, w, h }`
- `Uploaded { tex_id }`
- `Failed`

### 12.3 GPU lifecycle

UI/Host calls `thumbs.maintain(renderer)` each frame:

- upload decoded images that are visible
- destroy evicted textures

This matches IGFD’s `ManageGPUThumbnails()` concept.

---

## 13. Filtering Engine (IGFD-Compatible, Rust-Friendly)

Provide:

1) "simple" filters (extensions list)
2) optional parser for IGFD-like syntax:
   - collections: `Name{.png,.jpg}`
   - regex: `((...))`
   - multi-layer extensions: `.vcxproj.filters`
   - advanced `*` handling (prefer globset; fallback to regex if enabled)

Core compiles filters into matchers for fast evaluation.

---

## 14. Save Mode Policies (Confirm Overwrite + Extension Policy)

Introduce `SavePolicy`:

- `confirm_overwrite: bool`
- `extension_policy: ExtensionPolicy`
  - `KeepUser`
  - `AddIfMissing`
  - `OverwriteByFilter` (IGFD-like result modes)

When confirming in save mode:

- compute target path (including extension policy)
- if exists and policy requires confirm → enter `ConfirmOverwrite` state

---

## 15. Performance Considerations

Detailed post-parity implementation design (scan coordinator/runtime, generation invalidation,
partial batches, bounded per-frame work, observability) is documented in:

- `docs/FEARLESS_REFACTOR_P2_PERF_ASYNC_DESIGN.md`

### 15.1 Directory enumeration

Baseline:

- keep a cached snapshot of `Vec<FileMeta>` for current dir
- recompute only on:
  - dir changed
  - refresh requested
  - show_hidden toggled (could be a filtered view instead)

Advanced (optional):

- background enumeration / incremental fill:
  - return partial results to keep UI responsive
  - ensure deterministic ordering by sorting once complete (or stable insert)

### 15.2 Large directories

- avoid allocating strings repeatedly (interning / reuse buffers)
- avoid `to_lowercase()` on every frame; precompute lowercase name or use case-folding cache

---

## 16. Migration Strategy (Breaking Changes Allowed)

This refactor assumes **no backward compatibility requirement**. The goal is to converge quickly on an IGFD-grade mental model (core/state/host separation) without maintaining legacy wrappers.

Phase the migration:

1) extract current rendering into `ui::draw_contents_*()` with a host wrapper
2) introduce `FileDialogCore` + `FileDialogState` (core + UI-only state)
3) add `DialogManager` and a stable open/display API surface
4) iterate on parity features (custom pane, file styles, thumbnails, advanced filters)

### 16.2 Mapping from current code

Current:

- `extensions/dear-file-browser/src/ui/mod.rs`
  - contains state + rendering + filesystem scanning

Proposed:

- keep UI code in `ui/` but remove filesystem and domain logic to `core/`
- convert `selected: Vec<String>` to selection model

---

## 17. Testing Strategy

Core-only tests (no ImGui):

- selection model (Ctrl/Shift behavior, Ctrl+A)
- filter matching (extensions, multi-layer, regex feature)
- save policy (extension policy + overwrite confirm state)
- places (serialize/deserialize)
- event-driven state machine (confirm/cancel transitions)

UI smoke tests:

- minimal: build an ImGui context and call `draw_contents()` to ensure no panic

Performance tests (optional):

- benchmark directory scanning and matching on large synthetic sets

---

## 18. Risks & Trade-offs

- Thumbnail abstraction is the hardest part: requires careful API to stay backend-agnostic.
- Regex/glob compatibility with IGFD is complex; should be feature-gated and added incrementally.
- Full IGFD parity is large; the milestone plan must prioritize user-visible wins (Places, selection UX, custom pane).

---

## 19. Acceptance Criteria (Definition of Done)

This architecture is considered successful when:

- Dialog contents can be hosted as window/modal/popup/embed without duplicating logic.
- Multiple dialogs can be open concurrently via `DialogManager`.
- Places are persistent, editable, and can show system devices.
- Custom pane + file styles exist and can block confirmation.
- Thumbnail pipeline exists and does not hard-depend on any specific renderer backend.
- Core logic is unit-test covered and does not depend on ImGui types.