using System.Linq;
using UnityEngine;
using Crosstales.FB.Util;
using Crosstales.FB.Wrapper;
namespace Crosstales.FB
{
/// Native file browser various actions like open file, open folder and save file.
[ExecuteInEditMode]
[DisallowMultipleComponent]
[HelpURL("https://www.crosstales.com/media/data/assets/FileBrowser/api/class_crosstales_1_1_f_b_1_1_file_browser.html")]
public class FileBrowser : Crosstales.Common.Util.Singleton
{
#region Variables
[Header("Custom Wrapper"), Tooltip("Custom wrapper for File Browser."), SerializeField] private BaseCustomFileBrowser customWrapper;
[Tooltip("Enable or disable the custom wrapper (default: false)."), SerializeField] private bool customMode;
[Header("Titles")] [Tooltip("Title for the 'Open File'-dialog."), SerializeField] private string titleOpenFile = "Open File";
[Tooltip("Title for the 'Open Files'-dialog."), SerializeField] private string titleOpenFiles = "Open Files";
[Tooltip("Title for the 'Open Folder'-dialog."), SerializeField] private string titleOpenFolder = "Select Folder";
[Tooltip("Title for the 'Open Folders'-dialog."), SerializeField] private string titleOpenFolders = "Select Folders";
[Tooltip("Title for the 'Save File'-dialog."), SerializeField] private string titleSaveFile = "Save File";
[Header("Labels")] [Tooltip("Text for 'All Files'-filter (*)"), SerializeField] private string textAllFiles = "All Files";
[Tooltip("Default name of the save-file."), SerializeField] private string nameSaveFile = "MySaveFile";
[UnityEngine.Serialization.FormerlySerializedAsAttribute("LegacyFolderBrowser")] [Header("Windows Settings"), Tooltip("Use the legacy folder browser under Windows (default: false)."), SerializeField]
private bool legacyFolderBrowser;
[Tooltip("Ask to overwrite existing file in save dialog (default: true)."), SerializeField] private bool askOverwriteFile = true;
[Header("UWP (WSA) Settings"), Tooltip("Always read the file data under UWP (default: false)."), SerializeField]
private bool alwaysReadFile;
private static string lastOpenSingleFile;
private static string[] lastOpenFiles;
private static string lastOpenSingleFolder;
private static string[] lastOpenFolders;
private static string lastSaveFile;
private WrapperHolder wrapperHolder;
#endregion
#region Properties
/// Custom wrapper for File Browser.
public BaseCustomFileBrowser CustomWrapper
{
get => customWrapper;
set
{
if (customWrapper == value) return;
customWrapper = value;
wrapperHolder = new WrapperHolder();
}
}
/// Enables or disables the custom wrapper.
public bool CustomMode
{
get => customMode;
set
{
if (customMode == value) return;
customMode = value;
wrapperHolder = new WrapperHolder();
}
}
/// Use the legacy folder browser (Windows).
public bool LegacyFolderBrowser
{
get => legacyFolderBrowser;
set => legacyFolderBrowser = value;
}
/// Ask to overwrite existing file in save dialog (Windows).
public bool AskOverwriteFile
{
get => askOverwriteFile;
set => askOverwriteFile = value;
}
/// Always read the file data (UWP).
public bool AlwaysReadFile
{
get => alwaysReadFile;
set => alwaysReadFile = value;
}
/// Title for the 'Open File'-dialog.
public string TitleOpenFile
{
get => titleOpenFile;
set => titleOpenFile = value;
}
/// Title for the 'Open Files'-dialog.
public string TitleOpenFiles
{
get => titleOpenFiles;
set => titleOpenFiles = value;
}
/// Title for the 'Open Folder'-dialog.
public string TitleOpenFolder
{
get => titleOpenFolder;
set => titleOpenFolder = value;
}
/// Title for the 'Open Folders'-dialog.
public string TitleOpenFolders
{
get => titleOpenFolders;
set => titleOpenFolders = value;
}
/// Title for the 'Save File'-dialog.
public string TitleSaveFile
{
get => titleSaveFile;
set => titleSaveFile = value;
}
/// Text for 'All Files'-filter (*).
public string TextAllFiles
{
get => textAllFiles;
set => textAllFiles = value;
}
/// Default name of the save-file.
public string NameSaveFile
{
get => nameSaveFile;
set => nameSaveFile = value;
}
/// Returns the file from the last "OpenSingleFile"-action.
/// File from the last "OpenSingleFile"-action.
public string CurrentOpenSingleFile
{
get => wrapperHolder?.PlatformWrapper?.CurrentOpenSingleFile;
set => wrapperHolder.PlatformWrapper.CurrentOpenSingleFile = value;
}
/// Returns the file name (without path) from the last "OpenSingleFile"-action.
/// File name from the last "OpenSingleFile"-action.
public string CurrentOpenSingleFileName => getNameFromPath(CurrentOpenSingleFile);
/// Returns the array of files from the last "OpenFiles"-action.
/// Array of files from the last "OpenFiles"-action.
public string[] CurrentOpenFiles
{
get => wrapperHolder?.PlatformWrapper?.CurrentOpenFiles;
set => wrapperHolder.PlatformWrapper.CurrentOpenFiles = value;
}
/// Returns the folder from the last "OpenSingleFolder"-action.
/// Folder from the last "OpenSingleFolder"-action.
public string CurrentOpenSingleFolder
{
get => wrapperHolder?.PlatformWrapper?.CurrentOpenSingleFolder;
set => wrapperHolder.PlatformWrapper.CurrentOpenSingleFolder = value;
}
/// Returns the folder name (without path) from the last "OpenSingleFolder"-action.
/// Folder name from the last "OpenSingleFolder"-action.
public string CurrentOpenSingleFolderName => getNameFromPath(CurrentOpenSingleFolder);
/// Returns the array of folders from the last "OpenFolders"-action.
/// Array of folders from the last "OpenFolders"-action.
public string[] CurrentOpenFolders
{
get => wrapperHolder?.PlatformWrapper?.CurrentOpenFolders;
set => wrapperHolder.PlatformWrapper.CurrentOpenFolders = value;
}
/// Returns the file from the last "SaveFile"-action.
/// File from the last "SaveFile"-action.
public string CurrentSaveFile
{
get => wrapperHolder?.PlatformWrapper?.CurrentSaveFile;
set => wrapperHolder.PlatformWrapper.CurrentSaveFile = value;
}
/// Returns the file name (without path) from the last "SaveFile"-action.
/// File name from the last "SaveFile"-action.
public string CurrentSaveFileName => getNameFromPath(CurrentSaveFile);
/// Returns the data of the file from the last "OpenSingleFile"-action.
/// Data of the file from the last "OpenSingleFile"-action.
public byte[] CurrentOpenSingleFileData => wrapperHolder?.PlatformWrapper?.CurrentOpenSingleFileData;
/// The data for the "SaveFile"-action.
public byte[] CurrentSaveFileData
{
get => wrapperHolder?.PlatformWrapper?.CurrentSaveFileData;
set => wrapperHolder.PlatformWrapper.CurrentSaveFileData = value;
}
#region Wrapper delegates
/// Indicates if this wrapper can open a file.
/// Wrapper can open a file.
public bool canOpenFile => wrapperHolder?.PlatformWrapper.canOpenFile ?? false;
/// Indicates if this wrapper can open a folder.
/// Wrapper can open a folder.
public bool canOpenFolder => wrapperHolder?.PlatformWrapper.canOpenFolder ?? false;
/// Indicates if this wrapper can save a file.
/// Wrapper can save a file.
public bool canSaveFile => wrapperHolder?.PlatformWrapper.canSaveFile ?? false;
/// Indicates if this wrapper can open multiple files.
/// Wrapper can open multiple files.
public bool canOpenMultipleFiles => wrapperHolder?.PlatformWrapper.canOpenMultipleFiles ?? false;
/// Indicates if this wrapper can open multiple folders.
/// Wrapper can open multiple folders.
public bool canOpenMultipleFolders => wrapperHolder?.PlatformWrapper.canOpenMultipleFolders ?? false;
/// Indicates if this wrapper is supporting the current platform.
/// True if this wrapper supports current platform.
public bool isPlatformSupported => wrapperHolder?.PlatformWrapper.isPlatformSupported ?? true;
/// Indicates if this wrapper is working directly inside the Unity Editor (without 'Play'-mode).
/// True if this wrapper is working directly inside the Unity Editor.
public bool isWorkingInEditor => wrapperHolder?.PlatformWrapper.isWorkingInEditor ?? false;
#endregion
#endregion
#region Events
[Header("Events")] public OnOpenFilesCompleted OnOpenFilesCompleted;
public OnOpenFoldersCompleted OnOpenFoldersCompleted;
public OnSaveFileCompleted OnSaveFileCompleted;
public delegate void OpenFilesStart();
public delegate void OpenFilesComplete(bool selected, string singleFile, string[] files);
public delegate void OpenFoldersStart();
public delegate void OpenFoldersComplete(bool selected, string singleFolder, string[] folders);
public delegate void SaveFileStart();
public delegate void SaveFileComplete(bool selected, string file);
/// An event triggered whenever "OpenFiles" is started.
public event OpenFilesStart OnOpenFilesStart;
/// An event triggered whenever "OpenFiles" is completed.
public event OpenFilesComplete OnOpenFilesComplete;
/// An event triggered whenever "OpenFolders" is started.
public event OpenFoldersStart OnOpenFoldersStart;
/// An event triggered whenever "OpenFolders" is completed.
public event OpenFoldersComplete OnOpenFoldersComplete;
/// An event triggered whenever "SaveFile" is started.
public event SaveFileStart OnSaveFileStart;
/// An event triggered whenever "SaveFile" is completed.
public event SaveFileComplete OnSaveFileComplete;
#endregion
#region MonoBehaviour methods
protected override void Awake()
{
base.Awake();
//if (!Helper.isEditorMode && DontDestroy && CustomMode && CustomWrapper != null)
// CustomWrapper.transform.parent = transform;
wrapperHolder = new WrapperHolder();
}
private void Update()
{
if (lastOpenFiles != CurrentOpenFiles || lastOpenSingleFile != CurrentOpenSingleFile)
{
lastOpenFiles = CurrentOpenFiles;
lastOpenSingleFile = CurrentOpenSingleFile;
bool selected = false;
string singleFile = null;
if (lastOpenFiles?.Length > 0)
{
selected = true;
singleFile = lastOpenFiles[0];
}
onOpenFilesComplete(selected, singleFile, lastOpenFiles);
}
if (lastOpenFolders != CurrentOpenFolders || lastOpenSingleFolder != CurrentOpenSingleFolder)
{
lastOpenFolders = CurrentOpenFolders;
lastOpenSingleFolder = CurrentOpenSingleFolder;
bool selected = false;
string singleFolder = null;
if (lastOpenFolders?.Length > 0)
{
selected = !string.IsNullOrEmpty(lastOpenFolders[0]);
singleFolder = lastOpenFolders[0];
}
onOpenFoldersComplete(selected, singleFolder, lastOpenFolders);
}
if (lastSaveFile != CurrentSaveFile)
{
lastSaveFile = CurrentSaveFile;
bool selected = !string.IsNullOrEmpty(lastSaveFile);
onSaveFileComplete(selected, lastSaveFile);
}
}
#endregion
#region Public methods
/// Open native file browser for a single file.
/// Allowed extension, e.g. "png" (optional)
/// Returns a string of the chosen file. Empty string when cancelled
public string OpenSingleFile(string extension = "*")
{
return OpenSingleFile(string.Empty, string.Empty, string.Empty, getFilter(extension));
}
/// Open native file browser for a single file.
/// Dialog title
/// Root directory
/// Default file name (currently only supported under Windows standalone)
/// Allowed extensions, e.g. "png" (optional)
/// Returns a string of the chosen file. Empty string when cancelled
public string OpenSingleFile(string title, string directory, string defaultName, params string[] extensions)
{
return OpenSingleFile(title, directory, defaultName, getFilter(extensions));
}
/// Open native file browser for a single file.
/// Dialog title
/// Root directory
/// Default file name (currently only supported under Windows standalone)
/// List of extension filters (optional)
/// Returns a string of the chosen file. Empty string when cancelled
public string OpenSingleFile(string title, string directory, string defaultName, params ExtensionFilter[] extensions)
{
if (this != null && !isActiveAndEnabled)
return "disabled";
if (Crosstales.Common.Util.FileHelper.HasFileInvalidChars(defaultName))
{
Debug.LogWarning("'defaultName' contains invalid characters - can not show 'OpenSingleFile' dialog!", this);
return null;
}
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'OpenSingleFile' dialog!", this);
return null;
}
if (canOpenFile)
{
onOpenFilesStart();
wrapperHolder.PlatformWrapper.OpenSingleFile(string.IsNullOrEmpty(title) ? titleOpenFile : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), defaultName, extensions);
return CurrentOpenSingleFile;
}
Debug.LogWarning("'OpenSingleFile' is currently not supported for the current platform!", this);
return null;
}
/// Open native file browser for multiple files.
/// Allowed extension, e.g. "png" (optional)
/// Returns array of chosen files. Zero length array when cancelled
public string[] OpenFiles(string extension = "*")
{
return OpenFiles(string.Empty, string.Empty, string.Empty, getFilter(extension));
}
/// Open native file browser for multiple files.
/// Dialog title
/// Root directory
/// Default file name (currently only supported under Windows standalone)
/// Allowed extensions, e.g. "png" (optional)
/// Returns array of chosen files. Zero length array when cancelled
public string[] OpenFiles(string title, string directory, string defaultName, params string[] extensions)
{
return OpenFiles(title, directory, defaultName, getFilter(extensions));
}
/// Open native file browser for multiple files.
/// Dialog title
/// Root directory
/// Default file name (currently only supported under Windows standalone)
/// List of extension filters (optional)
/// Returns array of chosen files. Zero length array when cancelled
public string[] OpenFiles(string title, string directory, string defaultName, params ExtensionFilter[] extensions)
{
if (this != null && !isActiveAndEnabled)
return null;
if (Crosstales.Common.Util.FileHelper.HasFileInvalidChars(defaultName))
{
Debug.LogWarning("'defaultName' contains invalid characters - can not show 'OpenFiles' dialog!", this);
return null;
}
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'OpenFiles' dialog!", this);
return null;
}
if (canOpenFile)
{
onOpenFilesStart();
wrapperHolder.PlatformWrapper.OpenFiles(string.IsNullOrEmpty(title) ? titleOpenFiles : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), defaultName, true, extensions);
return CurrentOpenFiles;
}
Debug.LogWarning("'OpenFiles' is currently not supported for the current platform!", this);
return null;
}
/// Open native folder browser for a single folder.
/// Returns a string of the chosen folder. Empty string when cancelled
public string OpenSingleFolder()
{
return OpenSingleFolder(string.Empty);
}
///
/// Open native folder browser for a single folder.
/// NOTE: Title is not supported under Windows and UWP (WSA)!
///
/// Dialog title
/// Root directory (default: current, optional)
/// Returns a string of the chosen folder. Empty string when cancelled
public string OpenSingleFolder(string title, string directory = "")
{
if (this != null && !isActiveAndEnabled)
return "disabled";
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'OpenSingleFolder' dialog!", this);
return null;
}
if (canOpenFolder)
{
onOpenFoldersStart();
wrapperHolder.PlatformWrapper.OpenSingleFolder(string.IsNullOrEmpty(title) ? titleOpenFolder : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false));
return CurrentOpenSingleFolder;
}
Debug.LogWarning("'OpenSingleFolder' is currently not supported for the current platform!", this);
return null;
}
///
/// Open native folder browser for multiple folders.
/// NOTE: Title and multiple folder selection are not supported under Windows and UWP (WSA)!
///
/// Returns array of chosen folders. Zero length array when cancelled
public string[] OpenFolders()
{
return OpenFolders(string.Empty);
}
///
/// Open native folder browser for multiple folders.
/// NOTE: Title and multiple folder selection are not supported under Windows and UWP (WSA)!
///
/// Dialog title
/// Root directory (default: current, optional)
/// Returns array of chosen folders. Zero length array when cancelled
public string[] OpenFolders(string title, string directory = "")
{
if (this != null && !isActiveAndEnabled)
return null;
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'OpenFolders' dialog!", this);
return null;
}
if (canOpenFolder)
{
onOpenFoldersStart();
wrapperHolder.PlatformWrapper.OpenFolders(string.IsNullOrEmpty(title) ? titleOpenFolders : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), true);
return CurrentOpenFolders;
}
Debug.LogWarning("'OpenFolders' is currently not supported for the current platform!", this);
return null;
}
/// Open native save file browser.
/// Default file name (optional)
/// File extensions, e.g. "png" (optional)
/// Returns chosen file. Empty string when cancelled
public string SaveFile(string defaultName = "", string extension = "*")
{
return SaveFile(string.Empty, string.Empty, defaultName, getFilter(extension));
}
/// Open native save file browser.
/// Dialog title
/// Root directory
/// Default file name
/// File extensions, e.g. "png" (optional)
/// Returns chosen file. Empty string when cancelled
public string SaveFile(string title, string directory, string defaultName, params string[] extensions)
{
return SaveFile(title, directory, defaultName, getFilter(extensions));
}
/// Open native save file browser
/// Dialog title
/// Root directory
/// Default file name
/// List of extension filters (optional)
/// Returns chosen file. Empty string when cancelled
public string SaveFile(string title, string directory, string defaultName, params ExtensionFilter[] extensions)
{
if (this != null && !isActiveAndEnabled)
return "disabled";
if (Crosstales.Common.Util.FileHelper.HasFileInvalidChars(defaultName))
{
Debug.LogWarning("'defaultName' contains invalid characters - can not show 'SaveFile' dialog!", this);
return null;
}
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'SaveFile' dialog!", this);
return null;
}
if (canSaveFile)
{
onSaveFileStart();
wrapperHolder.PlatformWrapper.SaveFile(string.IsNullOrEmpty(title) ? titleSaveFile : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), string.IsNullOrEmpty(defaultName) ? NameSaveFile : defaultName, extensions);
return CurrentSaveFile;
}
Debug.LogWarning("'SaveFile' is currently not supported for the current platform!", this);
return null;
}
/// Asynchronously opens native file browser for a single file.
/// Allowed extension, e.g. "png" (optional)
/// Returns a string of the chosen file. Empty string when cancelled
public void OpenSingleFileAsync(string extension = "*")
{
OpenSingleFileAsync(string.Empty, string.Empty, string.Empty, getFilter(extension));
}
/// Asynchronously opens native file browser for a single file.
/// Dialog title
/// Root directory
/// Default file name (currently only supported under Windows standalone)
/// Allowed extensions, e.g. "png" (optional)
/// Returns a string of the chosen file. Empty string when cancelled
public void OpenSingleFileAsync(string title, string directory, string defaultName, params string[] extensions)
{
OpenSingleFileAsync(title, directory, defaultName, getFilter(extensions));
}
/// Asynchronously opens native file browser for a single file.
/// Dialog title
/// Root directory
/// Default file name (currently only supported under Windows standalone)
/// List of extension filters (optional)
/// Returns a string of the chosen file. Empty string when cancelled
public void OpenSingleFileAsync(string title, string directory, string defaultName, params ExtensionFilter[] extensions)
{
if (this != null && !isActiveAndEnabled)
return;
if (Crosstales.Common.Util.FileHelper.HasFileInvalidChars(defaultName))
{
Debug.LogWarning("'defaultName' contains invalid characters - can not show 'OpenSingleFileAsync' dialog!", this);
return;
}
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'OpenSingleFileAsync' dialog!", this);
return;
}
if (canOpenFile)
{
onOpenFilesStart();
wrapperHolder.PlatformWrapper.OpenFilesAsync(string.IsNullOrEmpty(title) ? titleOpenFile : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), defaultName, false, extensions, resetOpenFiles);
}
else
{
Debug.LogWarning("'OpenSingleFileAsync' is currently not supported for the current platform!", this);
}
}
/// Asynchronously opens native file browser for multiple files.
/// Allow multiple file selection (default: true, optional)
/// Allowed extensions, e.g. "png" (optional)
/// Returns array of chosen files. Zero length array when cancelled
public void OpenFilesAsync(bool multiselect = true, params string[] extensions)
{
OpenFilesAsync(string.Empty, string.Empty, string.Empty, multiselect, getFilter(extensions));
}
/// Asynchronously opens native file browser for multiple files.
/// Dialog title
/// Root directory
/// Default file name (currently only supported under Windows standalone)
/// Allow multiple file selection (default: true, optional)
/// Allowed extensions, e.g. "png" (optional)
/// Returns array of chosen files. Zero length array when cancelled
public void OpenFilesAsync(string title, string directory, string defaultName, bool multiselect = true, params string[] extensions)
{
OpenFilesAsync(title, directory, defaultName, multiselect, getFilter(extensions));
}
/// Asynchronously opens native file browser for multiple files.
/// Dialog title
/// Root directory
/// Default file name (currently only supported under Windows standalone)
/// Allow multiple file selection (default: true, optional)
/// List of extension filters (optional)
/// Returns array of chosen files. Zero length array when cancelled
public void OpenFilesAsync(string title, string directory, string defaultName, bool multiselect = true, params ExtensionFilter[] extensions)
{
if (this != null && !isActiveAndEnabled)
return;
if (Crosstales.Common.Util.FileHelper.HasFileInvalidChars(defaultName))
{
Debug.LogWarning("'defaultName' contains invalid characters - can not show 'OpenFilesAsync' dialog!", this);
return;
}
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'OpenFilesAsync' dialog!", this);
return;
}
if (canOpenFile)
{
onOpenFilesStart();
wrapperHolder.PlatformWrapper.OpenFilesAsync(string.IsNullOrEmpty(title) ? multiselect ? titleOpenFiles : titleOpenFile : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), defaultName, multiselect, extensions, resetOpenFiles);
}
else
{
Debug.LogWarning("'OpenFilesAsync' is currently not supported for the current platform!", this);
}
}
/// Asynchronously opens native folder browser for a single folder.
/// Returns a string of the chosen folder. Empty string when cancelled
public void OpenSingleFolderAsync()
{
OpenSingleFolderAsync(string.Empty);
}
///
/// Asynchronously opens native folder browser for a single folder.
/// NOTE: Title is not supported under Windows and UWP (WSA)!
///
/// Dialog title
/// Root directory (default: current, optional)
/// Returns a string of the chosen folder. Empty string when cancelled
public void OpenSingleFolderAsync(string title, string directory = "")
{
if (this != null && !isActiveAndEnabled)
return;
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'OpenSingleFolderAsync' dialog!", this);
return;
}
if (canOpenFolder)
{
onOpenFoldersStart();
wrapperHolder.PlatformWrapper.OpenFoldersAsync(string.IsNullOrEmpty(title) ? titleOpenFolder : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), false, resetOpenFolders);
}
else
{
Debug.LogWarning("'OpenSingleFolderAsync' is currently not supported for the current platform!", this);
}
}
/// Asynchronously opens native folder browser for multiple folders.
/// Allow multiple folder selection (default: true, optional)
/// Returns array of chosen folders. Zero length array when cancelled
public void OpenFoldersAsync(bool multiselect = true)
{
OpenFoldersAsync(string.Empty, string.Empty, multiselect);
}
/// Asynchronously opens native folder browser for multiple folders.
/// Dialog title
/// Root directory (default: current, optional)
/// Allow multiple folder selection (default: true, optional)
/// Returns array of chosen folders. Zero length array when cancelled
public void OpenFoldersAsync(string title, string directory = "", bool multiselect = true)
{
if (this != null && !isActiveAndEnabled)
return;
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'OpenFoldersAsync' dialog!", this);
return;
}
if (canOpenFolder)
{
onOpenFoldersStart();
wrapperHolder.PlatformWrapper.OpenFoldersAsync(string.IsNullOrEmpty(title) ? multiselect ? titleOpenFolders : titleOpenFolder : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), multiselect, resetOpenFolders);
}
else
{
Debug.LogWarning("'OpenFoldersAsync' is currently not supported for the current platform!", this);
}
}
/// Asynchronously opens native save file browser.
/// Default file name (optional)
/// File extension, e.g. "png" (optional)
/// Returns chosen file. Empty string when cancelled
public void SaveFileAsync(string defaultName = "", string extension = "*")
{
SaveFileAsync(string.Empty, string.Empty, defaultName, getFilter(extension));
}
/// Asynchronously opens native save file browser.
/// Dialog title
/// Root directory
/// Default file name
/// File extensions, e.g. "png" (optional)
/// Returns chosen file. Empty string when cancelled
public void SaveFileAsync(string title, string directory, string defaultName, params string[] extensions)
{
SaveFileAsync(title, directory, defaultName, getFilter(extensions));
}
/// Asynchronously opens native save file browser (async)
/// Dialog title
/// Root directory
/// Default file name
/// List of extension filters (optional)
/// Returns chosen file. Empty string when cancelled
public void SaveFileAsync(string title, string directory, string defaultName, params ExtensionFilter[] extensions)
{
if (this != null && !isActiveAndEnabled)
return;
if (Crosstales.Common.Util.FileHelper.HasFileInvalidChars(defaultName))
{
Debug.LogWarning("'defaultName' contains invalid characters - can not show 'SaveFileAsync' dialog!", this);
return;
}
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'SaveFileAsync' dialog!", this);
return;
}
if (canSaveFile)
{
onSaveFileStart();
wrapperHolder.PlatformWrapper.SaveFileAsync(string.IsNullOrEmpty(title) ? titleSaveFile : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), string.IsNullOrEmpty(defaultName) ? NameSaveFile : defaultName, extensions, paths => resetSaveFile(paths));
}
else
{
Debug.LogWarning("'SaveFileAsync' is currently not supported for the current platform!", this);
}
}
/// Find files inside a path.
/// Path to find the files
/// Recursive search
/// List of extension filters for the search (optional)
/// Returns array of the found files inside the path. Zero length array when an error occured.
public string[] GetFiles(string path, bool isRecursive, params ExtensionFilter[] extensions)
{
return Crosstales.Common.Util.FileHelper.GetFiles(path, isRecursive, extensions.SelectMany(extensionFilter => extensionFilter.Extensions).ToArray());
}
#region Legacy
/// Find files inside a path.
/// Path to find the files
/// Recursive search (default: false, optional)
/// Extensions for the file search, e.g. "png" (optional)
/// Returns array of the found files inside the path (alphabetically ordered). Zero length array when an error occured.
[System.Obsolete("Please use 'Crosstales.Common.Util.FileHelper.GetFiles' instead.")]
public string[] GetFiles(string path, bool isRecursive = false, params string[] extensions)
{
return Crosstales.Common.Util.FileHelper.GetFiles(path, isRecursive, extensions);
}
/// Find folders inside.
/// Path to find the directories
/// Recursive search (default: false, optional)
/// Returns array of the found directories inside the path. Zero length array when an error occured.
[System.Obsolete("Please use 'Crosstales.Common.Util.FileHelper.GetDirectories' instead.")]
public string[] GetFolders(string path, bool isRecursive = false)
{
return Crosstales.Common.Util.FileHelper.GetDirectories(path, isRecursive);
}
///
/// Find all logical drives.
///
/// Returns array of the found drives. Zero length array when an error occured.
[System.Obsolete("Please use 'Crosstales.Common.Util.FileHelper.GetDrives' instead.")]
public string[] GetDrives()
{
return Crosstales.Common.Util.FileHelper.GetDrives();
}
/// Copy or move a file.
/// Source file path
/// Destination file path
/// Move file instead of copy (default: false, optional)
[System.Obsolete("Please use 'Crosstales.Common.Util.FileHelper.CopyFile' instead.")]
public static void CopyFile(string sourceFile, string destFile, bool move = false)
{
Crosstales.Common.Util.FileHelper.CopyFile(sourceFile, destFile, move);
}
/// Copy or move a folder.
/// Source folder path
/// Destination folder path
/// Move folder instead of copy (default: false, optional)
[System.Obsolete("Please use 'Crosstales.Common.Util.FileHelper.CopyDirectory' instead.")]
public static void CopyFolder(string sourcePath, string destPath, bool move = false)
{
Crosstales.Common.Util.FileHelper.CopyDirectory(sourcePath, destPath, move);
}
///
/// Shows the location of a file (or folder) in OS file explorer.
/// NOTE: only works on standalone platforms
///
[System.Obsolete("Please use 'Crosstales.Common.Util.FileHelper.ShowFile' instead.")]
public static void ShowFile(string file)
{
Crosstales.Common.Util.FileHelper.ShowFile(file);
}
///
/// Shows the location of a folder (or file) in OS file explorer.
/// NOTE: only works on standalone platforms
///
[System.Obsolete("Please use 'Crosstales.Common.Util.FileHelper.ShowPath' instead.")]
public static void ShowFolder(string path)
{
Crosstales.Common.Util.FileHelper.ShowPath(path);
}
///
/// Opens a file with the OS default application.
/// NOTE: only works for standalone platforms
///
/// File path
[System.Obsolete("Please use 'Crosstales.Common.Util.FileHelper.OpenFile' instead.")]
public static void OpenFile(string file)
{
Crosstales.Common.Util.FileHelper.OpenFile(file);
}
/// Open native file browser for multiple files.
/// Callback for the async operation.
/// Allow multiple file selection (default: true, optional)
/// Allowed extensions, e.g. "png" (optional)
/// Returns array of chosen files. Zero length array when cancelled
//[System.Obsolete("This method is deprecated, please use it without the callback.")]
public void OpenFilesAsync(System.Action cb, bool multiselect = true, params string[] extensions)
{
OpenFilesAsync(cb, multiselect ? titleOpenFiles : titleOpenFile, string.Empty, string.Empty, multiselect, getFilter(extensions));
}
/// Open native file browser for multiple files.
/// Callback for the async operation.
/// Dialog title
/// Root directory
/// Default file name (currently only supported under Windows standalone)
/// Allow multiple file selection (default: true, optional)
/// Allowed extensions, e.g. "png" (optional)
/// Returns array of chosen files. Zero length array when cancelled
//[System.Obsolete("This method is deprecated, please use it without the callback.")]
public void OpenFilesAsync(System.Action cb, string title, string directory, string defaultName, bool multiselect = true, params string[] extensions)
{
OpenFilesAsync(cb, title, directory, defaultName, multiselect, getFilter(extensions));
}
/// Open native file browser for multiple files (async).
/// Callback for the async operation.
/// Dialog title
/// Root directory
/// Default file name (currently only supported under Windows standalone)
/// Allow multiple file selection (default: true, optional)
/// List of extension filters (optional)
/// Returns array of chosen files. Zero length array when cancelled
//[System.Obsolete("This method is deprecated, please use it without the callback.")]
public void OpenFilesAsync(System.Action cb, string title, string directory, string defaultName, bool multiselect = true, params ExtensionFilter[] extensions)
{
if (this != null && !isActiveAndEnabled)
return;
if (cb != null)
{
if (canOpenFile)
{
if (Crosstales.Common.Util.FileHelper.HasFileInvalidChars(defaultName))
{
Debug.LogWarning("'defaultName' contains invalid characters - can not show 'OpenFiles' dialog!", this);
return;
}
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'OpenFiles' dialog!", this);
return;
}
wrapperHolder.PlatformWrapper.OpenFilesAsync(string.IsNullOrEmpty(title) ? titleOpenFiles : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), defaultName, multiselect, extensions, cb);
}
else
{
Debug.LogWarning("'OpenFilesAsync' is currently not supported for the current platform!", this);
}
}
else
{
Debug.LogWarning("'cb' (callback)is null - can not show dialog!", this);
}
}
/// Open native folder browser for multiple folders (async).
/// Callback for the async operation.
/// Allow multiple folder selection (default: true, optional)
/// Returns array of chosen folders. Zero length array when cancelled
//[System.Obsolete("This method is deprecated, please use it without the callback.")]
public void OpenFoldersAsync(System.Action cb, bool multiselect = true)
{
OpenFoldersAsync(cb, titleOpenFolders, string.Empty, multiselect);
}
/// Open native folder browser for multiple folders (async).
/// Callback for the async operation.
/// Dialog title
/// Root directory (default: current, optional)
/// Allow multiple folder selection (default: true, optional)
/// Returns array of chosen folders. Zero length array when cancelled
//[System.Obsolete("This method is deprecated, please use it without the callback.")]
public void OpenFoldersAsync(System.Action cb, string title, string directory = "", bool multiselect = true)
{
if (this != null && !isActiveAndEnabled)
return;
if (cb != null)
{
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'OpenFolders' dialog!", this);
return;
}
if (canOpenFolder)
{
wrapperHolder.PlatformWrapper.OpenFoldersAsync(string.IsNullOrEmpty(title) ? titleOpenFolders : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), multiselect, cb);
}
else
{
Debug.LogWarning("'OpenFoldersAsync' is currently not supported for the current platform!", this);
}
}
else
{
Debug.LogWarning("'cb' (callback) is null - can not show dialog!", this);
}
}
/// Open native save file browser
/// Callback for the async operation.
/// Default file name (optional)
/// File extension, e.g. "png" (optional)
/// Returns chosen file. Empty string when cancelled
//[System.Obsolete("This method is deprecated, please use it without the callback.")]
public void SaveFileAsync(System.Action cb, string defaultName = "", string extension = "*")
{
SaveFileAsync(cb, titleSaveFile, string.Empty, defaultName, getFilter(extension));
}
/// Open native save file browser
/// Callback for the async operation.
/// Dialog title
/// Root directory
/// Default file name
/// File extensions, e.g. "png" (optional)
/// Returns chosen file. Empty string when cancelled
//[System.Obsolete("This method is deprecated, please use it without the callback.")]
public void SaveFileAsync(System.Action cb, string title, string directory, string defaultName, params string[] extensions)
{
SaveFileAsync(cb, title, directory, defaultName, getFilter(extensions));
}
/// Open native save file browser (async).
/// Callback for the async operation.
/// Dialog title
/// Root directory
/// Default file name
/// List of extension filters (optional)
/// Returns chosen file. Empty string when cancelled
//[System.Obsolete("This method is deprecated, please use it without the callback.")]
public void SaveFileAsync(System.Action cb, string title, string directory, string defaultName, params ExtensionFilter[] extensions)
{
if (this != null && !isActiveAndEnabled)
return;
if (cb != null)
{
if (Crosstales.Common.Util.FileHelper.HasFileInvalidChars(defaultName))
{
Debug.LogWarning("'defaultName' contains invalid characters - can not show 'SaveFileAsync' dialog!", this);
return;
}
if (Crosstales.Common.Util.FileHelper.HasPathInvalidChars(directory))
{
Debug.LogWarning("'directory' contains invalid characters - can not show 'SaveFileAsync' dialog!", this);
return;
}
if (canSaveFile)
{
wrapperHolder.PlatformWrapper.SaveFileAsync(string.IsNullOrEmpty(title) ? titleSaveFile : title, Crosstales.Common.Util.FileHelper.ValidatePath(directory, true, false), string.IsNullOrEmpty(defaultName) ? NameSaveFile : defaultName, extensions, cb);
}
else
{
Debug.LogWarning("'SaveFileAsync' is currently not supported for the current platform!", this);
}
}
else
{
Debug.LogWarning("'cb' (callback) is null - can not show dialog!", this);
}
}
#endregion
#endregion
#region Private methods
private string getNameFromPath(string path) //TODO move to BaseHelper?
{
if (string.IsNullOrEmpty(path))
return null;
int nameIndex = path.LastIndexOf("/");
if (nameIndex < 0)
nameIndex = path.LastIndexOf(@"\");
if (nameIndex < 0)
nameIndex = -1;
return path.Substring(nameIndex + 1);
}
private void resetOpenFiles(params string[] paths)
{
//do nothing
//lastOpenFiles = System.Array.Empty();
//lastOpenSingleFile = string.Empty;
}
private void resetOpenFolders(params string[] paths)
{
//do nothing
//lastOpenFolders = System.Array.Empty();
//lastOpenSingleFolder = string.Empty;
}
private void resetSaveFile(params string[] paths)
{
//do nothing
//lastSaveFile = string.Empty;
}
private ExtensionFilter[] getFilter(params string[] extensions)
{
if (extensions?.Length > 0)
{
if (extensions.Length == 1 && "*".Equals(extensions[0]))
return null;
ExtensionFilter[] filter = new ExtensionFilter[extensions.Length];
for (int ii = 0; ii < extensions.Length; ii++)
{
string extension = string.IsNullOrEmpty(extensions[ii]) ? "*" : extensions[ii];
if (extension.Equals("*"))
{
filter[ii] = new ExtensionFilter(TextAllFiles, Helper.isMacOSEditor ? string.Empty : extension);
}
else
{
filter[ii] = new ExtensionFilter(extension, extension);
}
}
if (Config.DEBUG)
Debug.Log($"getFilter: {filter.CTDump()}", this);
return filter;
}
return null;
}
#endregion
#region Event-trigger methods
private void onOpenFilesStart()
{
if (Config.DEBUG)
Debug.Log("onOpenFilesStart", this);
lastOpenFiles = CurrentOpenFiles = null;
lastOpenSingleFile = CurrentOpenSingleFile = null;
OnOpenFilesStart?.Invoke();
}
private void onOpenFilesComplete(bool selected, string singleFile, string[] files)
{
if (Config.DEBUG)
Debug.Log($"onOpenFilesComplete: {selected} - {singleFile} - {CurrentOpenSingleFileData?.Length}", this);
if (!Helper.isEditorMode)
{
string fileList = files?.Length > 0 ? string.Join(";", files) : null;
OnOpenFilesCompleted?.Invoke(selected, singleFile, fileList);
}
OnOpenFilesComplete?.Invoke(selected, singleFile, files);
}
private void onOpenFoldersStart()
{
if (Config.DEBUG)
Debug.Log("onOpenFoldersStart", this);
lastOpenFolders = CurrentOpenFolders = null;
lastOpenSingleFolder = CurrentOpenSingleFolder = null;
OnOpenFoldersStart?.Invoke();
}
private void onOpenFoldersComplete(bool selected, string singleFolder, string[] folders)
{
if (Config.DEBUG)
Debug.Log($"onOpenFoldersComplete: {selected} - {singleFolder}", this);
if (!Helper.isEditorMode)
{
string folderList = folders?.Length > 0 ? string.Join(";", folders) : null;
OnOpenFoldersCompleted?.Invoke(selected, singleFolder, folderList);
}
OnOpenFoldersComplete?.Invoke(selected, singleFolder, folders);
}
private void onSaveFileStart()
{
if (Config.DEBUG)
Debug.Log("onSaveFileStart", this);
lastSaveFile = CurrentSaveFile = null;
OnSaveFileStart?.Invoke();
}
private void onSaveFileComplete(bool selected, string file)
{
if (Config.DEBUG)
Debug.Log($"onSaveFileComplete: {selected} - {file} - {CurrentSaveFileData?.Length}", this);
if (selected && (!Helper.isWebPlatform || Helper.isEditor) && CurrentSaveFileData != null)
{
try
{
Crosstales.Common.Util.FileHelper.WriteAllBytes(file, CurrentSaveFileData);
}
catch (System.Exception ex)
{
//if (Config.DEBUG)
Debug.LogWarning($"Could not write file: {file} - {ex}", this);
}
}
if (!Helper.isEditorMode)
OnSaveFileCompleted?.Invoke(selected, file);
OnSaveFileComplete?.Invoke(selected, file);
}
#endregion
}
/// Filter for extensions.
public struct ExtensionFilter
{
public string Name;
public string[] Extensions;
public ExtensionFilter(string filterName, params string[] filterExtensions)
{
Name = filterName;
Extensions = filterExtensions;
}
public override string ToString()
{
System.Text.StringBuilder result = new System.Text.StringBuilder();
result.Append(GetType().Name);
result.Append(Constants.TEXT_TOSTRING_START);
result.Append("Name='");
result.Append(Name);
result.Append(Constants.TEXT_TOSTRING_DELIMITER);
result.Append("Extensions='");
result.Append(Extensions.CTDump());
result.Append(Constants.TEXT_TOSTRING_DELIMITER_END);
result.Append(Constants.TEXT_TOSTRING_END);
return result.ToString();
}
}
[System.Serializable]
public class OnOpenFilesCompleted : UnityEngine.Events.UnityEvent
{
}
[System.Serializable]
public class OnOpenFoldersCompleted : UnityEngine.Events.UnityEvent
{
}
[System.Serializable]
public class OnSaveFileCompleted : UnityEngine.Events.UnityEvent
{
}
internal class WrapperHolder
{
#region Variables
public IFileBrowser PlatformWrapper { get; private set; }
#endregion
#region Constructor
public WrapperHolder()
{
bool useCustom = FileBrowser.Instance.CustomWrapper != null && FileBrowser.Instance.CustomMode && FileBrowser.Instance.CustomWrapper.enabled;
if (useCustom)
{
PlatformWrapper = FileBrowser.Instance.CustomWrapper;
}
else
{
#if UNITY_EDITOR_WIN
if (Helper.isEditor && !Config.NATIVE_WINDOWS)
#else
if (Helper.isEditor)
#endif
{
#if UNITY_EDITOR
PlatformWrapper = new FileBrowserEditor();
#endif
}
#if UNITY_STANDALONE_OSX && !UNITY_EDITOR_WIN
else if (Helper.isMacOSPlatform)
{
PlatformWrapper = new FileBrowserMac();
}
#endif
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
else if (Helper.isWindowsPlatform || Helper.isWindowsEditor)
{
PlatformWrapper = new FileBrowserWindows();
}
#endif
#if UNITY_STANDALONE_LINUX && !UNITY_EDITOR_WIN
else if (Helper.isLinuxPlatform)
{
PlatformWrapper = new FileBrowserLinux();
}
#endif
#if UNITY_WSA && !UNITY_EDITOR && ENABLE_WINMD_SUPPORT
else if (Helper.isWSAPlatform)
{
PlatformWrapper = new FileBrowserWSA();
}
#endif
else
{
PlatformWrapper = new FileBrowserGeneric();
}
}
if (Config.DEBUG)
Debug.Log(PlatformWrapper);
}
#endregion
}
}
// © 2017-2023 crosstales LLC (https://www.crosstales.com)