Singularity/Assets/Plugins/crosstales/FileBrowser/Scripts/Wrapper/FileBrowserLinux.cs
2024-05-06 11:45:45 -07:00

345 lines
11 KiB
C#

#if UNITY_STANDALONE_LINUX || CT_DEVELOP
using System;
using UnityEngine;
namespace Crosstales.FB.Wrapper
{
/// <summary>File browser implementation for Linux (GTK).</summary>
public class FileBrowserLinux : BaseFileBrowserStandalone
{
#region Variables
private static FileBrowserLinux instance;
private static Action<string[]> _openFileCb;
private static Action<string[]> _openFolderCb;
private static Action<string> _saveFileCb;
private const char splitChar = (char)28;
#endregion
#region Constructor
public FileBrowserLinux()
{
instance = this;
Linux.NativeMethods.DialogInit();
}
#endregion
#region Implemented methods
public override bool canOpenMultipleFolders => true;
public override bool isPlatformSupported => Crosstales.FB.Util.Helper.isLinuxPlatform; // || Util.Helper.isLinuxEditor;
public override bool isWorkingInEditor => false;
public override string[] OpenFiles(string title, string directory, string defaultName, bool multiselect, params ExtensionFilter[] extensions)
{
if (!string.IsNullOrEmpty(defaultName))
Debug.LogWarning("'defaultName' is not supported under Linux.");
//resetOpenFiles();
return openFiles(title, directory, defaultName, multiselect, false, extensions);
}
public override string[] OpenFolders(string title, string directory, bool multiselect)
{
//resetOpenFolders();
return openFolders(title, directory, multiselect, false);
}
public override string SaveFile(string title, string directory, string defaultName, params ExtensionFilter[] extensions)
{
//resetSaveFile();
return saveFile(title, directory, defaultName, false, extensions);
}
public override void OpenFilesAsync(string title, string directory, string defaultName, bool multiselect, ExtensionFilter[] extensions, Action<string[]> cb)
{
if (!string.IsNullOrEmpty(defaultName))
Debug.LogWarning("'defaultName' is not supported under Linux.");
//resetOpenFiles();
_openFileCb = cb;
openFiles(title, directory, defaultName, multiselect, true, extensions);
}
public override void OpenFoldersAsync(string title, string directory, bool multiselect, Action<string[]> cb)
{
//resetOpenFolders();
_openFolderCb = cb;
openFolders(title, directory, multiselect, true);
}
public override void SaveFileAsync(string title, string directory, string defaultName, ExtensionFilter[] extensions, Action<string> cb)
{
//resetSaveFile();
_saveFileCb = cb;
saveFile(title, directory, defaultName, true, extensions);
}
#endregion
#region Private methods
private static string getFilterFromFileExtensionList(ExtensionFilter[] extensions)
{
if (extensions?.Length > 0)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (int xx = 0; xx < extensions.Length; xx++)
{
ExtensionFilter filter = extensions[xx];
sb.Append(filter.Name);
sb.Append(";");
for (int ii = 0; ii < filter.Extensions.Length; ii++)
{
sb.Append(filter.Extensions[ii]);
if (ii + 1 < filter.Extensions.Length)
sb.Append(",");
}
if (xx + 1 < extensions.Length)
sb.Append("|");
}
if (Crosstales.FB.Util.Config.DEBUG)
Debug.Log($"getFilterFromFileExtensionList: {sb}");
return sb.ToString();
}
return string.Empty;
}
#endregion
private static string[] openFiles(string title, string directory, string defaultName, bool multiselect, bool isAsync, params ExtensionFilter[] extensions)
{
try
{
if (isAsync)
{
//TODO change to the same method as for macOS?
Linux.NativeMethods.DialogOpenFilePanelAsync(title, directory, getFilterFromFileExtensionList(extensions), multiselect, paths =>
{
if (string.IsNullOrEmpty(paths))
{
instance.CurrentOpenFiles = System.Array.Empty<string>();
instance.CurrentOpenSingleFile = string.Empty;
_openFileCb?.Invoke(null);
}
else
{
string[] pathArray = paths.Split(splitChar);
instance.CurrentOpenFiles = pathArray;
instance.CurrentOpenSingleFile = pathArray[0];
_openFileCb?.Invoke(pathArray);
}
});
}
else
{
string paths = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(Linux.NativeMethods.DialogOpenFilePanel(title, directory, getFilterFromFileExtensionList(extensions), multiselect));
if (string.IsNullOrEmpty(paths))
{
instance.CurrentOpenFiles = System.Array.Empty<string>();
instance.CurrentOpenSingleFile = string.Empty;
return null;
}
string[] pathArray = paths.Split(splitChar);
instance.CurrentOpenFiles = pathArray;
instance.CurrentOpenSingleFile = pathArray[0];
return instance.CurrentOpenFiles;
}
}
catch (Exception ex)
{
instance.CurrentOpenFiles = System.Array.Empty<string>();
instance.CurrentOpenSingleFile = string.Empty;
Debug.LogError($"Open file dialog threw an error: {ex}");
}
return null;
}
private static string[] openFolders(string title, string directory, bool multiselect, bool isAsync)
{
try
{
if (isAsync)
{
//TODO change to the same method as for macOS?
Linux.NativeMethods.DialogOpenFolderPanelAsync(title, directory, multiselect, paths =>
{
if (string.IsNullOrEmpty(paths))
{
instance.CurrentOpenFolders = System.Array.Empty<string>();
instance.CurrentOpenSingleFolder = string.Empty;
_openFolderCb?.Invoke(null);
}
else
{
string[] pathArray = paths.Split(splitChar);
instance.CurrentOpenFolders = pathArray;
instance.CurrentOpenSingleFolder = pathArray[0];
_openFolderCb?.Invoke(pathArray);
}
});
}
else
{
string paths = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(Linux.NativeMethods.DialogOpenFolderPanel(title, directory, multiselect));
if (string.IsNullOrEmpty(paths))
{
instance.CurrentOpenFolders = System.Array.Empty<string>();
instance.CurrentOpenSingleFolder = string.Empty;
return null;
}
string[] pathArray = paths.Split(splitChar);
instance.CurrentOpenFolders = pathArray;
instance.CurrentOpenSingleFolder = pathArray[0];
return instance.CurrentOpenFolders;
}
}
catch (Exception ex)
{
instance.CurrentOpenFolders = System.Array.Empty<string>();
instance.CurrentOpenSingleFolder = string.Empty;
Debug.LogError($"Folder dialog threw an error: {ex}");
}
return null;
}
private static string saveFile(string title, string directory, string defaultName, bool isAsync, params ExtensionFilter[] extensions)
{
bool useFallback = extensions != null && extensions.Length == 1 && extensions[0].Extensions.Length == 1;
string fallbackExtension = useFallback ? $".{extensions[0].Extensions[0]}" : string.Empty;
try
{
if (isAsync)
{
//TODO change to the same method as for macOS?
Linux.NativeMethods.DialogSaveFilePanelAsync(title, directory, defaultName, getFilterFromFileExtensionList(extensions), path =>
{
if (string.IsNullOrEmpty(path))
{
instance.CurrentSaveFile = string.Empty;
_saveFileCb?.Invoke(null);
}
else
{
if (useFallback && !path.EndsWith(fallbackExtension))
path += fallbackExtension;
instance.CurrentSaveFile = path;
_saveFileCb?.Invoke(path);
}
});
}
else
{
string path = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(Linux.NativeMethods.DialogSaveFilePanel(title, directory, defaultName, getFilterFromFileExtensionList(extensions)));
if (string.IsNullOrEmpty(path))
{
instance.CurrentSaveFile = string.Empty;
return null;
}
if (useFallback && !path.EndsWith(fallbackExtension))
path += fallbackExtension;
instance.CurrentSaveFile = path;
return instance.CurrentSaveFile;
}
}
catch (Exception ex)
{
instance.CurrentSaveFile = string.Empty;
Debug.LogError($"Save file dialog threw an error: {ex}");
}
return null;
}
}
}
namespace Crosstales.FB.Wrapper.Linux
{
/// <summary>Native methods (bridge to Linux).</summary>
internal static class NativeMethods
{
[System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
public delegate void AsyncCallback(string path);
[System.Runtime.InteropServices.DllImport("FileBrowser")]
internal static extern void DialogInit();
[System.Runtime.InteropServices.DllImport("FileBrowser")]
internal static extern IntPtr DialogOpenFilePanel(string title, string directory, string extension, bool multiselect);
[System.Runtime.InteropServices.DllImport("FileBrowser")]
internal static extern IntPtr DialogOpenFolderPanel(string title, string directory, bool multiselect);
[System.Runtime.InteropServices.DllImport("FileBrowser")]
internal static extern IntPtr DialogSaveFilePanel(string title, string directory, string defaultName, string extension);
[System.Runtime.InteropServices.DllImport("FileBrowser")]
internal static extern void DialogOpenFilePanelAsync(string title, string directory, string extension, bool multiselect, AsyncCallback callback);
[System.Runtime.InteropServices.DllImport("FileBrowser")]
internal static extern void DialogOpenFolderPanelAsync(string title, string directory, bool multiselect, AsyncCallback callback);
[System.Runtime.InteropServices.DllImport("FileBrowser")]
internal static extern void DialogSaveFilePanelAsync(string title, string directory, string defaultName, string extension, AsyncCallback callback);
}
}
#endif
// © 2019-2023 crosstales LLC (https://www.crosstales.com)