From d9dc8d91723831f8f95016db9385b39bed761843 Mon Sep 17 00:00:00 2001 From: onepiecefreak3 Date: Sun, 29 Sep 2024 18:09:53 +0200 Subject: [PATCH] Add native windows file dialogs; Bump to version 1.1.2; --- ImGui.Forms/ImGui.Forms.nuspec | 2 +- .../IO/Windows/WindowsOpenFileDialog.cs | 163 ++++++++++++++++++ .../IO/Windows/WindowsSaveFileDialog.cs | 154 +++++++++++++++++ 3 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 ImGui.Forms/Modals/IO/Windows/WindowsOpenFileDialog.cs create mode 100644 ImGui.Forms/Modals/IO/Windows/WindowsSaveFileDialog.cs diff --git a/ImGui.Forms/ImGui.Forms.nuspec b/ImGui.Forms/ImGui.Forms.nuspec index 722ebf1..da4169a 100644 --- a/ImGui.Forms/ImGui.Forms.nuspec +++ b/ImGui.Forms/ImGui.Forms.nuspec @@ -2,7 +2,7 @@ Imgui.Forms - 1.1.1 + 1.1.2 A WinForms-inspired object-oriented framework around Dear ImGui (https://github.com/ocornut/imgui) onepiecefreak diff --git a/ImGui.Forms/Modals/IO/Windows/WindowsOpenFileDialog.cs b/ImGui.Forms/Modals/IO/Windows/WindowsOpenFileDialog.cs new file mode 100644 index 0000000..bb56646 --- /dev/null +++ b/ImGui.Forms/Modals/IO/Windows/WindowsOpenFileDialog.cs @@ -0,0 +1,163 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using ImGui.Forms.Localization; + +namespace ImGui.Forms.Modals.IO.Windows +{ + public class WindowsOpenFileDialog + { + public LocalizedString Title { get; set; } + public bool Multiselect { get; set; } = false; + public string InitialDirectory { get; set; } = null; + public IList Filters { get; set; } = new List { new("All Files", "*") }; + public bool ShowHidden { get; set; } = false; + public bool Success { get; private set; } + public string[] Files { get; private set; } + + public async Task ShowAsync() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return await ShowDefaultAsync(); + + await Task.Run(ShowOpenFileDialog); + + return Success ? DialogResult.Ok : DialogResult.Cancel; + } + + private async Task ShowDefaultAsync() + { + var ofd = new OpenFileDialog + { + Caption = Title, + InitialDirectory = InitialDirectory + }; + + foreach (FileFilter filter in Filters) + ofd.FileFilters.Add(filter); + + DialogResult result = await ofd.ShowAsync(); + if (result == DialogResult.Ok) + Files = new[] { ofd.SelectedPath }; + + Success = result == DialogResult.Ok; + return result; + } + + private void ShowOpenFileDialog() + { + const int MAX_FILE_LENGTH = 2048; + + Success = false; + Files = null; + + OpenFileName ofn = new OpenFileName(); + + ofn.structSize = Marshal.SizeOf(ofn); + ofn.filter = string.Join('\0', Filters.Select(f => f.Name + " (" + string.Join(';', f.Extensions.Select(e => "*." + e)) + ")\0"+ string.Join(';', f.Extensions.Select(e => "*." + e)))) + "\0"; + ofn.fileTitle = new string(new char[MAX_FILE_LENGTH]); + ofn.maxFileTitle = ofn.fileTitle.Length; + ofn.initialDir = InitialDirectory; + ofn.title = Title; + ofn.flags = (int)OpenFileNameFlags.OFN_HIDEREADONLY | (int)OpenFileNameFlags.OFN_EXPLORER | (int)OpenFileNameFlags.OFN_FILEMUSTEXIST | (int)OpenFileNameFlags.OFN_PATHMUSTEXIST; + + // Create buffer for file names + ofn.file = Marshal.AllocHGlobal(MAX_FILE_LENGTH * Marshal.SystemDefaultCharSize); + ofn.maxFile = MAX_FILE_LENGTH; + + // Initialize buffer with NULL bytes + for (int i = 0; i < MAX_FILE_LENGTH * Marshal.SystemDefaultCharSize; i++) + { + Marshal.WriteByte(ofn.file, i, 0); + } + + if (ShowHidden) + { + ofn.flags |= (int)OpenFileNameFlags.OFN_FORCESHOWHIDDEN; + } + + if (Multiselect) + { + ofn.flags |= (int)OpenFileNameFlags.OFN_ALLOWMULTISELECT; + } + + Success = GetOpenFileName(ofn); + + if (Success) + { + nint filePointer = ofn.file; + long pointer = (long)filePointer; + string file = Marshal.PtrToStringAuto(filePointer); + List strList = new List(); + + // Retrieve file names + while (file.Length > 0) + { + strList.Add(file); + + pointer += file.Length * Marshal.SystemDefaultCharSize + Marshal.SystemDefaultCharSize; + filePointer = (nint)pointer; + file = Marshal.PtrToStringAuto(filePointer); + } + + if (strList.Count > 1) + { + Files = new string[strList.Count - 1]; + for (int i = 1; i < strList.Count; i++) + { + Files[i - 1] = Path.Combine(strList[0], strList[i]); + } + } + else + { + Files = strList.ToArray(); + } + } + + Marshal.FreeHGlobal(ofn.file); + } + + [DllImport("comdlg32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern bool GetOpenFileName([In, Out] OpenFileName ofn); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private class OpenFileName + { + public int structSize = 0; + public nint dlgOwner = nint.Zero; + public nint instance = nint.Zero; + public string filter; + public string customFilter; + public int maxCustFilter = 0; + public int filterIndex = 0; + public nint file; + public int maxFile = 0; + public string fileTitle; + public int maxFileTitle = 0; + public string initialDir; + public string title; + public int flags = 0; + public short fileOffset = 0; + public short fileExtension = 0; + public string defExt; + public nint custData = nint.Zero; + public nint hook = nint.Zero; + public string templateName; + public nint reservedPtr = nint.Zero; + public int reservedInt = 0; + public int flagsEx = 0; + } + + private enum OpenFileNameFlags + { + OFN_HIDEREADONLY = 0x4, + OFN_FORCESHOWHIDDEN = 0x10000000, + OFN_ALLOWMULTISELECT = 0x200, + OFN_EXPLORER = 0x80000, + OFN_FILEMUSTEXIST = 0x1000, + OFN_PATHMUSTEXIST = 0x800 + } + } +} diff --git a/ImGui.Forms/Modals/IO/Windows/WindowsSaveFileDialog.cs b/ImGui.Forms/Modals/IO/Windows/WindowsSaveFileDialog.cs new file mode 100644 index 0000000..1592874 --- /dev/null +++ b/ImGui.Forms/Modals/IO/Windows/WindowsSaveFileDialog.cs @@ -0,0 +1,154 @@ +using ImGui.Forms.Localization; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace ImGui.Forms.Modals.IO.Windows +{ + public class WindowsSaveFileDialog + { + public LocalizedString Title { get; set; } + public string InitialDirectory { get; set; } = null; + public IList Filters { get; set; } = new List { new("All Files", "*") }; + public bool ShowHidden { get; set; } = false; + public bool Success { get; private set; } + public string[] Files { get; private set; } + + public async Task ShowAsync() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return await ShowDefaultAsync(); + + await Task.Run(ShowSaveFileDialog); + + return Success ? DialogResult.Ok : DialogResult.Cancel; + } + + private async Task ShowDefaultAsync() + { + var sfd = new SaveFileDialog + { + Caption = Title, + InitialDirectory = InitialDirectory + }; + + DialogResult result = await sfd.ShowAsync(); + if (result == DialogResult.Ok) + Files = new[] { sfd.SelectedPath }; + + Success = result == DialogResult.Ok; + return result; + } + + private void ShowSaveFileDialog() + { + const int MAX_FILE_LENGTH = 2048; + + Success = false; + Files = null; + + OpenFileName ofn = new OpenFileName(); + + ofn.structSize = Marshal.SizeOf(ofn); + ofn.filter = string.Join('\0', Filters.Select(f => f.Name + " (" + string.Join(';', f.Extensions.Select(e => "*." + e)) + ")\0" + string.Join(';', f.Extensions.Select(e => "*." + e)))) + "\0"; + ofn.fileTitle = new string(new char[MAX_FILE_LENGTH]); + ofn.maxFileTitle = ofn.fileTitle.Length; + ofn.initialDir = InitialDirectory; + ofn.title = Title; + ofn.flags = (int)OpenFileNameFlags.OFN_HIDEREADONLY | (int)OpenFileNameFlags.OFN_EXPLORER | (int)OpenFileNameFlags.OFN_FILEMUSTEXIST | (int)OpenFileNameFlags.OFN_PATHMUSTEXIST; + + // Create buffer for file names + ofn.file = Marshal.AllocHGlobal(MAX_FILE_LENGTH * Marshal.SystemDefaultCharSize); + ofn.maxFile = MAX_FILE_LENGTH; + + // Initialize buffer with NULL bytes + for (int i = 0; i < MAX_FILE_LENGTH * Marshal.SystemDefaultCharSize; i++) + { + Marshal.WriteByte(ofn.file, i, 0); + } + + if (ShowHidden) + { + ofn.flags |= (int)OpenFileNameFlags.OFN_FORCESHOWHIDDEN; + } + + Success = GetSaveFileName(ofn); + + if (Success) + { + nint filePointer = ofn.file; + long pointer = (long)filePointer; + string file = Marshal.PtrToStringAuto(filePointer); + List strList = new List(); + + // Retrieve file names + while (file.Length > 0) + { + strList.Add(file); + + pointer += file.Length * Marshal.SystemDefaultCharSize + Marshal.SystemDefaultCharSize; + filePointer = (nint)pointer; + file = Marshal.PtrToStringAuto(filePointer); + } + + if (strList.Count > 1) + { + Files = new string[strList.Count - 1]; + for (int i = 1; i < strList.Count; i++) + { + Files[i - 1] = Path.Combine(strList[0], strList[i]); + } + } + else + { + Files = strList.ToArray(); + } + } + + Marshal.FreeHGlobal(ofn.file); + } + + [DllImport("comdlg32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern bool GetSaveFileName([In, Out] OpenFileName ofn); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private class OpenFileName + { + public int structSize = 0; + public nint dlgOwner = nint.Zero; + public nint instance = nint.Zero; + public string filter; + public string customFilter; + public int maxCustFilter = 0; + public int filterIndex = 0; + public nint file; + public int maxFile = 0; + public string fileTitle; + public int maxFileTitle = 0; + public string initialDir; + public string title; + public int flags = 0; + public short fileOffset = 0; + public short fileExtension = 0; + public string defExt; + public nint custData = nint.Zero; + public nint hook = nint.Zero; + public string templateName; + public nint reservedPtr = nint.Zero; + public int reservedInt = 0; + public int flagsEx = 0; + } + + private enum OpenFileNameFlags + { + OFN_HIDEREADONLY = 0x4, + OFN_FORCESHOWHIDDEN = 0x10000000, + OFN_ALLOWMULTISELECT = 0x200, + OFN_EXPLORER = 0x80000, + OFN_FILEMUSTEXIST = 0x1000, + OFN_PATHMUSTEXIST = 0x800 + } + } +}