Skip to content

Commit d489f1c

Browse files
committed
Single class for opening files
1 parent c4a5029 commit d489f1c

File tree

3 files changed

+108
-122
lines changed

3 files changed

+108
-122
lines changed

AssetRipper.NativeDialogs.Example/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ static async Task Main(string[] args)
1717
}
1818
else if (arguments.OpenFiles)
1919
{
20-
string[]? files = await OpenFilesDialog.OpenFilesAsync();
20+
string[]? files = await OpenFileDialog.OpenFilesAsync();
2121
if (files is null || files.Length == 0)
2222
{
2323
Console.WriteLine("No files selected.");

AssetRipper.NativeDialogs/OpenFileDialog.cs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,111 @@ public static class OpenFileDialog
110110
return Task.FromResult<string?>(null);
111111
}
112112
}
113+
114+
public static Task<string[]?> OpenFilesAsync()
115+
{
116+
if (OperatingSystem.IsWindows())
117+
{
118+
return OpenFilesAsyncWindows();
119+
}
120+
else if (OperatingSystem.IsMacOS())
121+
{
122+
return OpenFilesAsyncMacOS();
123+
}
124+
else if (OperatingSystem.IsLinux())
125+
{
126+
return OpenFilesAsyncLinux();
127+
}
128+
else
129+
{
130+
return Task.FromResult<string[]?>(null);
131+
}
132+
}
133+
134+
[SupportedOSPlatform("windows")]
135+
private unsafe static Task<string[]?> OpenFilesAsyncWindows()
136+
{
137+
// https://learn.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamew
138+
139+
char[] buffer = ArrayPool<char>.Shared.Rent(ushort.MaxValue + 1); // Should be enough for the overwhelming majority of cases.
140+
new Span<char>(buffer).Clear();
141+
142+
string filter = "All Files\0*.*\0\0";
143+
144+
fixed (char* bufferPtr = buffer)
145+
fixed (char* filterPtr = filter)
146+
{
147+
OPENFILENAMEW ofn = default;
148+
ofn.lStructSize = (uint)Unsafe.SizeOf<OPENFILENAMEW>();
149+
ofn.hwndOwner = default; // No owner window.
150+
ofn.lpstrFile = bufferPtr;
151+
ofn.nMaxFile = (uint)buffer.Length;
152+
ofn.lpstrFilter = filterPtr;
153+
ofn.nFilterIndex = 1; // The first pair of strings has an index value of 1.
154+
ofn.Flags = OFN.OFN_PATHMUSTEXIST | OFN.OFN_FILEMUSTEXIST | OFN.OFN_ALLOWMULTISELECT | OFN.OFN_EXPLORER;
155+
if (Windows.GetOpenFileNameW(&ofn) && buffer[^1] == 0)
156+
{
157+
List<string> files = [];
158+
159+
int directoryLength = Array.IndexOf(buffer, '\0');
160+
string directory = new(buffer, 0, directoryLength);
161+
162+
int startIndex = directoryLength + 1;
163+
while (startIndex < buffer.Length && buffer[startIndex] != '\0')
164+
{
165+
int endIndex = Array.IndexOf(buffer, '\0', startIndex);
166+
string fileName = new(buffer, startIndex, endIndex - startIndex);
167+
files.Add(Path.Combine(directory, fileName));
168+
startIndex = endIndex + 1; // Move to the next file name
169+
}
170+
171+
ArrayPool<char>.Shared.Return(buffer);
172+
if (files.Count > 0)
173+
{
174+
return Task.FromResult<string[]?>(files.ToArray());
175+
}
176+
else
177+
{
178+
// If a single file was selected, the system appends it to the directory path.
179+
return Task.FromResult<string[]?>([directory]);
180+
}
181+
}
182+
}
183+
184+
ArrayPool<char>.Shared.Return(buffer);
185+
return Task.FromResult<string[]?>(null);
186+
}
187+
188+
[SupportedOSPlatform("macos")]
189+
private static async Task<string[]?> OpenFilesAsyncMacOS()
190+
{
191+
ReadOnlySpan<string> arguments =
192+
[
193+
"-e", "set theFiles to choose file with multiple selections allowed",
194+
"-e", "set filePaths to {}",
195+
"-e", "repeat with aFile in theFiles",
196+
"-e", "set end of filePaths to POSIX path of aFile",
197+
"-e", "end repeat",
198+
"-e", "set text item delimiters to \":\"",
199+
"-e", "return filePaths as string",
200+
];
201+
string? output = await ProcessExecutor.TryRun("osascript", arguments);
202+
if (string.IsNullOrEmpty(output))
203+
{
204+
return null; // User canceled the dialog
205+
}
206+
return output.Split(':');
207+
}
208+
209+
[SupportedOSPlatform("linux")]
210+
private static async Task<string[]?> OpenFilesAsyncLinux()
211+
{
212+
// Todo: proper Linux implementation
213+
string? path = await OpenFileDialog.OpenFileAsync();
214+
if (string.IsNullOrEmpty(path))
215+
{
216+
return null; // User canceled the dialog
217+
}
218+
return [path];
219+
}
113220
}

AssetRipper.NativeDialogs/OpenFilesDialog.cs

Lines changed: 0 additions & 121 deletions
This file was deleted.

0 commit comments

Comments
 (0)