Files
XUniWindowController/Runtime/Scripts/LowLevel/FilePanel.cs
T
2026-06-14 22:49:28 +08:00

210 lines
7.5 KiB
C#

using AOT;
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Kirurobo
{
/// <summary>
/// 提供打开原生文件对话框的静态方法
/// </summary>
public class FilePanel
{
protected class LibUniWinC
{
[DllImport("LibUniWinC", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenFilePanel(in PanelSettings settings, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder buffer, UInt32 bufferSize);
[DllImport("LibUniWinC", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenSavePanel(in PanelSettings settings, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder buffer, UInt32 bufferSize);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PanelSettings : IDisposable {
public Int32 structSize;
public Int32 flags;
public IntPtr lpszTitle;
public IntPtr lpszFilter;
public IntPtr lpszInitialFile;
public IntPtr lpszInitialDir;
public IntPtr lpszDefaultExt;
public PanelSettings(Settings settings)
{
this.structSize = 0;
//this.structSize = 4 * 2 + Marshal.SizeOf<IntPtr>() * 3;
this.flags = (Int32)settings.flags;
//this.lpTitleText = IntPtr.Zero;
//this.lpFilterText = IntPtr.Zero;
//this.lpDefaultPath = IntPtr.Zero;
this.lpszTitle = Marshal.StringToHGlobalUni(settings.title);
this.lpszFilter = Marshal.StringToHGlobalUni(Filter.Join(settings.filters));
this.lpszInitialFile = Marshal.StringToHGlobalUni(settings.initialFile);
this.lpszInitialDir = Marshal.StringToHGlobalUni(settings.initialDirectory);
//this.lpszDefaultExt = Marshal.StringToHGlobalUni(settings.defaultExtension);
this.lpszDefaultExt = IntPtr.Zero;
//this.structSize = Marshal.SizeOf(this);
this.structSize = Marshal.SizeOf(this);
}
public void Dispose()
{
if (this.lpszTitle != IntPtr.Zero)
{
Marshal.FreeHGlobal(lpszTitle);
this.lpszTitle = IntPtr.Zero;
}
if (this.lpszFilter!= IntPtr.Zero)
{
Marshal.FreeHGlobal(lpszFilter);
this.lpszFilter= IntPtr.Zero;
}
if (this.lpszInitialFile!= IntPtr.Zero)
{
Marshal.FreeHGlobal(lpszInitialFile);
this.lpszInitialFile= IntPtr.Zero;
}
if (this.lpszInitialDir != IntPtr.Zero)
{
Marshal.FreeHGlobal(lpszInitialDir);
this.lpszInitialDir = IntPtr.Zero;
}
if (this.lpszDefaultExt != IntPtr.Zero)
{
Marshal.FreeHGlobal(lpszDefaultExt);
this.lpszDefaultExt = IntPtr.Zero;
}
}
}
}
/// <summary>
/// 对话框的设置标志
/// </summary>
[Flags]
public enum Flag
{
None = 0,
FileMustExist = 1, // 仅 Windows
FolderMustExist = 2, // 仅 Windows
AllowMultipleSelection = 4,
CanCreateDirectories = 16,
OverwritePrompt = 256, // macOS 上始终启用
CreatePrompt = 512, // macOS 上始终启用
ShowHiddenFiles = 4096,
RetrieveLink = 8192,
}
/// <summary>
/// 文件对话框的参数
/// </summary>
public struct Settings
{
public string title;
public Filter[] filters;
public string initialDirectory;
public string initialFile;
public string defaultExtension; // 未实现
public Flag flags;
}
/// <summary>
/// 文件过滤器
/// </summary>
public class Filter
{
protected string title;
protected string[] extensions;
/// <summary>
///
/// </summary>
/// <param name="title">过滤器标题(macOS 上暂不可用)</param>
/// <param name="extensions">扩展名数组,如 ["png", "jpg", "txt"]</param>
public Filter(string title, params string[] extensions)
{
this.title = title;
this.extensions = extensions;
}
public override string ToString()
{
return title + "\t" + String.Join("\t", extensions);
}
/// <summary>
/// 返回由 Filter 数组转换后的字符串
/// </summary>
/// <param name="filters"></param>
/// <returns></returns>
public static string Join(Filter[] filters)
{
if (filters == null) return "";
string result = "";
bool isFirstItem = true;
foreach (var filter in filters) {
if (!isFirstItem) result += "\n";
result += filter.ToString();
isFirstItem = false;
}
return result;
}
}
/// <summary>
/// 用于传递文件或文件夹路径的 UTF-16 缓冲区字符数
/// 因为多个路径以换行符分隔,260 字符不够用。
/// </summary>
private const int pathBufferSize = 2560;
/// <summary>
/// 打开文件选择对话框
/// </summary>
/// <param name="settings"></param>
/// <param name="action"></param>
public static void OpenFilePanel(Settings settings, Action<string[]> action)
{
LibUniWinC.PanelSettings ps = new LibUniWinC.PanelSettings(settings);
StringBuilder sb = new StringBuilder(pathBufferSize);
if (LibUniWinC.OpenFilePanel(in ps, sb, (uint)sb.Capacity))
{
string[] files = UniWinCore.parsePaths(sb.ToString());
action.Invoke(files);
}
ps.Dispose(); // 通过传入 Settings 的构造函数分配了内存,因此需要释放
}
/// <summary>
/// 打开保存文件选择对话框
/// </summary>
/// <param name="settings"></param>
/// <param name="action"></param>
public static void SaveFilePanel(Settings settings, Action<string[]> action)
{
LibUniWinC.PanelSettings ps = new LibUniWinC.PanelSettings(settings);
StringBuilder sb = new StringBuilder(pathBufferSize);
if (LibUniWinC.OpenSavePanel(in ps, sb, (uint)sb.Capacity))
{
string[] files = UniWinCore.parsePaths(sb.ToString());
action.Invoke(files);
}
ps.Dispose(); // 通过传入 Settings 的构造函数分配了内存,因此需要释放
}
}
}