#+build windows package file_dialog import "core:strings" import "core:c" import "core:fmt" foreign import comdlg32 "system:comdlg32.lib" foreign import ole32 "system:ole32.lib" foreign import shell32 "system:shell32.lib" OPENFILENAME :: struct { lStructSize: c.ulong, hwndOwner: rawptr, hInstance: rawptr, lpstrFilter: cstring, lpstrCustomFilter: cstring, nMaxCustFilter: c.ulong, nFilterIndex: c.ulong, lpstrFile: cstring, nMaxFile: c.ulong, lpstrFileTitle: cstring, nMaxFileTitle: c.ulong, lpstrInitialDir: cstring, lpstrTitle: cstring, Flags: c.ulong, nFileOffset: c.ushort, nFileExtension: c.ushort, lpstrDefExt: cstring, lCustData: c.ulong_ptr, lpfnHook: rawptr, lpTemplateName: cstring, pvReserved: rawptr, dwReserved: c.ulong, FlagsEx: c.ulong, } OFN_PATHMUSTEXIST :: 0x00000800 OFN_FILEMUSTEXIST :: 0x00001000 OFN_HIDEREADONLY :: 0x00000004 OFN_ALLOWMULTISELECT :: 0x00000200 @(default_calling_convention="stdcall") foreign comdlg32 { GetOpenFileNameA :: proc(lpofn: ^OPENFILENAME) -> c.int --- GetSaveFileNameA :: proc(lpofn: ^OPENFILENAME) -> c.int --- } show_open_file_dialog_windows :: proc(title: string, filters: []File_Filter, allow_multiple: bool) -> File_Dialog_Result { MAX_PATH :: 260 BUFFER_SIZE :: 32768 // Larger buffer for multiple files file_buffer: [BUFFER_SIZE]c.char // Build filter string (format: "Description\0*.ext\0Description2\0*.ext2\0\0") filter_str := strings.builder_make() defer strings.builder_destroy(&filter_str) if len(filters) > 0 { for filter in filters { strings.write_string(&filter_str, filter.name) strings.write_byte(&filter_str, 0) strings.write_string(&filter_str, filter.pattern) strings.write_byte(&filter_str, 0) } } else { strings.write_string(&filter_str, "All Files") strings.write_byte(&filter_str, 0) strings.write_string(&filter_str, "*.*") strings.write_byte(&filter_str, 0) } strings.write_byte(&filter_str, 0) // Double null terminator filter_cstr := strings.clone_to_cstring(strings.to_string(filter_str)) defer delete(filter_cstr) title_cstr := strings.clone_to_cstring(title) defer delete(title_cstr) flags := OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY if allow_multiple { flags |= OFN_ALLOWMULTISELECT } ofn := OPENFILENAME{ lStructSize = size_of(OPENFILENAME), lpstrFile = &file_buffer[0], nMaxFile = BUFFER_SIZE, lpstrFilter = filter_cstr, nFilterIndex = 1, lpstrTitle = title_cstr, Flags = flags, } if GetOpenFileNameA(&ofn) != 0 { buffer_str := string(cstring(&file_buffer[0])) if allow_multiple { // Parse multiple files format: "directory\0file1\0file2\0\0" parts := strings.split(buffer_str, "\x00") defer delete(parts) if len(parts) > 1 && parts[1] != "" { // Multiple files selected directory := parts[0] file_paths := make([]string, len(parts) - 1) for i := 1; i < len(parts); i+=1 { if parts[i] != "" { full_path := strings.concatenate({directory, "\\", parts[i]}) file_paths[i-1] = full_path } } return { success = true, file_path = file_paths[0], // First file for compatibility file_paths = file_paths, } } else { // Single file selected (even with multiple selection enabled) return { success = true, file_path = strings.clone(buffer_str), file_paths = {strings.clone(buffer_str)}, } } } else { // Single file selection return { success = true, file_path = strings.clone(buffer_str), file_paths = {strings.clone(buffer_str)}, } } } return { success = false, error_message = "User cancelled or dialog failed", } } show_save_file_dialog_macos :: proc(title: string, filters: []File_Filter, default_filename: string = "", default_directory: string = "") -> File_Dialog_Result { panel := NSSavePanel_savePanel() title_nsstr := NSStringFromCString(strings.clone_to_cstring(title)) NSSavePanel_setTitle(panel, title_nsstr) // Set default filename if provided if default_filename != "" { filename_nsstr := NSStringFromCString(strings.clone_to_cstring(default_filename)) NSSavePanel_setNameFieldStringValue(panel, filename_nsstr) } // Set default directory if provided if default_directory != "" { dir_nsstr := NSStringFromCString(strings.clone_to_cstring(default_directory)) dir_url := NSURL_fileURLWithPath(dir_nsstr) if dir_url != nil { NSSavePanel_setDirectoryURL(panel, dir_url) } } // Note: File filters in NSSavePanel require more complex setup // This is a simplified version - full implementation would use UTType response := NSSavePanel_runModal(panel) if response == NSModalResponseOK { url := NSSavePanel_URL(panel) if url != nil { path_nsstr := NSURL_path(url) path_cstr := NSString_UTF8String(path_nsstr) file_path := strings.clone(string(path_cstr)) return { success = true, file_path = file_path, file_paths = {file_path}, } } } return { success = false, error_message = "User cancelled or dialog failed", } } show_save_file_dialog_windows :: proc( title: string, filters: []File_Filter, default_filename: string = "", default_directory: string = "" ) -> File_Dialog_Result { MAX_PATH :: 260 file_buffer: [MAX_PATH]c.char // Set default filename if provided if default_filename != "" { filename_cstr := strings.clone_to_cstring(default_filename) defer delete(filename_cstr) // Copy to buffer (ensure null termination) filename_len := min(len(default_filename), MAX_PATH - 1) for i in 0.. 0 { for filter in filters { strings.write_string(&filter_str, filter.name) strings.write_byte(&filter_str, 0) strings.write_string(&filter_str, filter.pattern) strings.write_byte(&filter_str, 0) } } else { strings.write_string(&filter_str, "All Files") strings.write_byte(&filter_str, 0) strings.write_string(&filter_str, "*.*") strings.write_byte(&filter_str, 0) } strings.write_byte(&filter_str, 0) // Double null terminator filter_cstr := strings.clone_to_cstring(strings.to_string(filter_str)) defer delete(filter_cstr) title_cstr := strings.clone_to_cstring(title) defer delete(title_cstr) // Default directory (optional) dir_cstr: cstring = nil if default_directory != "" { dir_cstr = strings.clone_to_cstring(default_directory) defer delete(dir_cstr) } ofn := OPENFILENAME{ lStructSize = size_of(OPENFILENAME), lpstrFile = &file_buffer[0], nMaxFile = MAX_PATH, lpstrFilter = filter_cstr, nFilterIndex = 1, lpstrTitle = title_cstr, lpstrInitialDir = dir_cstr, Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY, } if GetSaveFileNameA(&ofn) != 0 { return { success = true, file_path = strings.clone(string(cstring(&file_buffer[0]))), file_paths = {strings.clone(string(cstring(&file_buffer[0])))}, } } return { success = false, error_message = "User cancelled or dialog failed", } }