8.18.1.33. file_dialog
A native, reusable New / Open / Save / Pick-Folder file dialog, built entirely
from the boost DSL and living in imgui/imgui_file_dialog. Because it is composed from
boost widgets (popup_modal / data_table / selectable_label /
input_text / combo / button) rather than a bound C++ file-dialog
library, every element registers on the snapshot registry — the whole dialog
is imgui_playwright-drivable (a bound C++
dialog renders raw ImGui and would be invisible to it).
It mirrors the core UX of a desktop file picker: a clickable directory breadcrumb
plus .. and an editable path bar (type a path or a different drive, press
Enter), a New Folder button, a sortable Name / Size / Type / Modified
table, a multi-extension filter dropdown, multi-select in open mode, a filename
field (with an optional pre-filled default) in save / create mode, an
overwrite-confirm step, a directory-pick mode, and keyboard shortcuts
(Enter confirms, Backspace goes up a directory).
Source: modules/dasImgui/examples/tutorial/file_dialog.das.
The recording opens the dialog in Save mode: the name field is pre-filled from
default_name, the breadcrumb and editable path bar sit above a sortable
Name / Size / Type / Modified table, the Images filter matches several extensions
at once, and New Folder makes a directory in place.
8.18.1.33.1. Walkthrough
The dialog is immediate-mode — there are no stored callbacks (daslang blocks can’t be stored across frames). The flow is three calls:
a trigger (button / menu item) calls
file_dialog_open(cfg)with a mode, a start directory and a list of extension filters;file_dialog_draw()is called every frame, at the top level ofupdate()(outside any window, so the registry path stays a cleanFILEDLG/...). It renders the modal when open and returns this frame’sFileDialogResult;on
confirmed, the chosen path(s) are read withfile_dialog_selection()/file_dialog_selected_path().
1options gen2
2options _comment_hygiene = true
3
4require imgui/imgui_harness
5require imgui/imgui_file_dialog
6
7// =============================================================================
8// TUTORIAL: file_dialog — native New / Open / Save dialog (boost DSL).
9//
10// `imgui/imgui_file_dialog` is a reusable, fully `imgui_playwright`-drivable
11// file dialog built entirely from boost widgets (popup_modal + data_table +
12// selectable_label + input_text + combo + buttons) — no raw-ImGui C++ library,
13// so every element shows up in the snapshot registry.
14//
15// Immediate-mode usage:
16// 1. a trigger (button / menu) calls `file_dialog_open(cfg)` with a mode,
17// a start directory, and a list of extension filters;
18// 2. `file_dialog_draw()` runs EVERY frame at the top level of update()
19// (outside any window, so the registry path stays a clean `FILEDLG/...`)
20// and returns this frame's `FileDialogResult`;
21// 3. on `confirmed`, read `file_dialog_selection()` / `file_dialog_selected_path()`.
22//
23// open mode multi-selects (Ctrl / Shift, `max_selection`); save / create modes
24// take a typed name and confirm-overwrite if it already exists.
25//
26// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/file_dialog.das
27// LIVE: daslang-live modules/dasImgui/examples/tutorial/file_dialog.das
28// =============================================================================
29
30var private g_status = "(nothing selected yet)"
31
32// Two equivalent ways to build the filter list. The typed array is explicit;
33// `file_filters` parses the terse C#-style "caption|ext;ext|caption|ext|..."
34// pipe spec (';'-separated extensions = a multi-extension filter, "" = all).
35def private das_filters() : array<FileDialogFilter> {
36 return <- file_filters("daslang (*.das)|das|Images (*.png *.jpg)|png;jpg;jpeg|All files|")
37}
38
39[export]
40def init() {
41 harness_init("file_dialog — native New / Open / Save", 760, 560)
42}
43
44[export]
45def update() {
46 if (!harness_begin_frame()) {
47 return
48 }
49 harness_new_frame()
50
51 SetNextWindowSize(ImVec2(520.0f, 200.0f), ImGuiCond.FirstUseEver)
52 window(FDLG_WIN, (text = "file_dialog demo", closable = false, flags = ImGuiWindowFlags.None)) {
53 text(LEAD, (text = "Open the native file dialog in each mode:"))
54 if (button(BTN_OPEN, (text = "Open..."))) {
55 file_dialog_open(FileDialogConfig(title = "Open File", start_dir = "",
56 filters <- das_filters(), mode = FileDialogMode.open, max_selection = 0))
57 }
58 same_line()
59 if (button(BTN_SAVE, (text = "Save As..."))) {
60 // default_name pre-fills the filename field
61 file_dialog_open(FileDialogConfig(title = "Save File As", start_dir = "",
62 filters <- das_filters(), mode = FileDialogMode.save, max_selection = 1,
63 default_name = "untitled.das"))
64 }
65 same_line()
66 if (button(BTN_CREATE, (text = "New..."))) {
67 file_dialog_open(FileDialogConfig(title = "New File", start_dir = "",
68 filters <- das_filters(), mode = FileDialogMode.create, max_selection = 1))
69 }
70 same_line()
71 if (button(BTN_PICKDIR, (text = "Pick Folder..."))) {
72 file_dialog_open(FileDialogConfig(title = "Select Folder", start_dir = "",
73 mode = FileDialogMode.pick_dir))
74 }
75 separator(SEP)
76 text(RESULT, (text = g_status))
77 }
78
79 // The dialog renders at the top level so its registry path is `FILEDLG/...`
80 // independent of any window. Poll the result every frame.
81 let r = file_dialog_draw()
82 if (r == FileDialogResult.confirmed) {
83 var sel <- file_dialog_selection()
84 g_status = "picked {length(sel)} item(s): {file_dialog_selected_path()}"
85 delete sel
86 } elif (r == FileDialogResult.cancelled) {
87 g_status = "cancelled"
88 }
89
90 harness_end_frame()
91}
92
93[export]
94def shutdown() {
95 harness_shutdown()
96}
97
98[export]
99def main() {
100 init()
101 while (!exit_requested()) {
102 update()
103 }
104 shutdown()
105}
8.18.1.33.1.1. Requires
One module on top of the harness:
imgui/imgui_file_dialog— the dialog component. It pulls inimgui_table_builtin(the sortabledata_table),imgui_widgets_builtinandimgui_containers_builtin(the leaf widgets +popup_modal), anddaslib/fio(directory listing) transitively.
8.18.1.33.1.2. Public API
enum FileDialogResult { none; confirmed; cancelled }
enum FileDialogMode { open; save; create; pick_dir } // 'new' is a reserved keyword
struct FileDialogFilter { caption : string; ext : string } // ext: ';'-separated ("png;jpg"); "" = all
struct FileDialogConfig {
title : string = "Select File"
start_dir : string = "" // "" => current working directory
filters : array<FileDialogFilter> // empty => all files
mode : FileDialogMode = FileDialogMode.open
max_selection : int = 1 // open mode; 0 = unlimited
default_name : string = "" // save/create: pre-filled filename
}
def public file_dialog_open(cfg : FileDialogConfig) : void
def public file_dialog_draw() : FileDialogResult
def public file_dialog_selection() : array<string> // confirmed absolute path(s)
def public file_dialog_selected_path() : string // first selection (convenience)
def public file_dialog_current_dir() : string
def public file_dialog_is_open() : bool
def public file_dialog_last_dir() : string // last confirmed folder this session
def public file_dialog_set_last_dir(dir : string) : void // seed it (persist across runs)
// convenience: parse a C#-style "caption|ext;ext|caption|ext|..." pipe spec
def public file_filters(spec : string) : array<FileDialogFilter>
The result is polled, not pushed: file_dialog_draw() returns confirmed
exactly on the frame the user commits, cancelled on the frame they close /
press Escape, and none otherwise. The accessors stay valid from the
confirmed frame until the dialog is reopened.
A blank start_dir opens the dialog at the last folder a selection was
confirmed in this session, then the working directory. file_dialog_set_last_dir
seeds that folder (e.g. from saved app config on startup) and
file_dialog_last_dir reads it back to persist across runs; an explicit
start_dir always wins.
8.18.1.33.1.3. Modes
open — pick one or more existing files. Selection follows the usual modifiers: plain click selects, Ctrl+click toggles, Shift+click selects a range, clamped to
max_selection(0= unlimited). Double-clicking a file confirms it; double-clicking a directory enters it.save / create — type a name in the filename field (clicking a row fills it;
default_namepre-fills it on open). The active filter’s first extension is appended if missing. If the name already exists, an overwrite-confirm sub-modal (FILEDLG/OVERWRITE) gates the commit. (createis the “New” flavor — same name-entry path;newitself is a reserved keyword, hence the enum name.)pick_dir — choose a directory. The list shows folders only, the filter and name field are hidden, and Select Folder confirms the highlighted folder (or, with nothing selected, the directory you are browsing).
A filter’s ext is a ;-separated pattern list, so "png;jpg;jpeg" is one
“Images” filter matching all three. file_filters(...) builds the typed list
from the terse C# pipe form: "daslang|das|Images|png;jpg|All files|".
Keyboard: Enter / Keypad-Enter confirms (or, when the path bar has focus, navigates to it); Backspace goes up a directory when no text field is being edited; Escape cancels.
8.18.1.33.1.5. Drivability
Every interactive element is a registry-backed boost widget, so a driver can operate the dialog by id:
Element |
Path |
Verb |
|---|---|---|
Modal |
|
snapshot / |
Parent / breadcrumb |
|
|
Path bar / Go |
|
|
New Folder |
|
|
Filter dropdown |
|
|
File table |
|
sort via header click |
File row i |
|
|
Filename field |
|
|
OK / Cancel |
|
|
Overwrite confirm |
|
|
8.18.1.33.1.6. Standalone vs live
Same convention as the other tutorials. daslang.exe runs the demo once and
exits at exit_requested(); daslang-live keeps the window open and reloads
on source edits.
See also
Full source: modules/dasImgui/examples/tutorial/file_dialog.das
Integration test: modules/dasImgui/tests/test_file_dialog.das — drives the
dialog through imgui_playwright (open → cancel, save → type → confirm).
data_table — the sortable table the file list is built on. popup_modal — the modal container.