FileBrowser.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using System;
  4. using System.IO;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. namespace SimpleFileBrowser
  8. {
  9. public class FileBrowser : MonoBehaviour, IListViewAdapter
  10. {
  11. public enum Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };
  12. #region Structs
  13. #pragma warning disable 0649
  14. [Serializable]
  15. private struct FiletypeIcon
  16. {
  17. public string extension;
  18. public Sprite icon;
  19. }
  20. [Serializable]
  21. private struct QuickLink
  22. {
  23. #if UNITY_EDITOR || ( !UNITY_WSA && !UNITY_WSA_10_0 )
  24. public Environment.SpecialFolder target;
  25. #endif
  26. public string name;
  27. public Sprite icon;
  28. }
  29. #pragma warning restore 0649
  30. #endregion
  31. #region Inner Classes
  32. public class Filter
  33. {
  34. public readonly string name;
  35. public readonly HashSet<string> extensions;
  36. public readonly string defaultExtension;
  37. internal Filter( string name )
  38. {
  39. this.name = name;
  40. extensions = null;
  41. defaultExtension = null;
  42. }
  43. public Filter( string name, string extension )
  44. {
  45. this.name = name;
  46. extension = extension.ToLowerInvariant();
  47. extensions = new HashSet<string>() { extension };
  48. defaultExtension = extension;
  49. }
  50. public Filter( string name, params string[] extensions )
  51. {
  52. this.name = name;
  53. for( int i = 0; i < extensions.Length; i++ )
  54. extensions[i] = extensions[i].ToLowerInvariant();
  55. this.extensions = new HashSet<string>( extensions );
  56. defaultExtension = extensions[0];
  57. }
  58. public override string ToString()
  59. {
  60. string result = "";
  61. if( name != null )
  62. result += name;
  63. if( extensions != null )
  64. {
  65. if( name != null )
  66. result += " (";
  67. int index = 0;
  68. foreach( string extension in extensions )
  69. {
  70. if( index++ > 0 )
  71. result += ", " + extension;
  72. else
  73. result += extension;
  74. }
  75. if( name != null )
  76. result += ")";
  77. }
  78. return result;
  79. }
  80. }
  81. #endregion
  82. #region Constants
  83. private const string ALL_FILES_FILTER_TEXT = "All Files (.*)";
  84. private const string FOLDERS_FILTER_TEXT = "Folders";
  85. private string DEFAULT_PATH;
  86. #if !UNITY_EDITOR && UNITY_ANDROID
  87. private const string SAF_PICK_FOLDER_QUICK_LINK_TEXT = "Pick Folder";
  88. private const string SAF_PICK_FOLDER_QUICK_LINK_PATH = "SAF_PICK_FOLDER";
  89. #endif
  90. #endregion
  91. #region Static Variables
  92. public static bool IsOpen { get; private set; }
  93. public static bool Success { get; private set; }
  94. public static string Result { get; private set; }
  95. private static bool m_askPermissions = true;
  96. public static bool AskPermissions
  97. {
  98. get { return m_askPermissions; }
  99. set { m_askPermissions = value; }
  100. }
  101. private static bool m_singleClickMode = false;
  102. public static bool SingleClickMode
  103. {
  104. get { return m_singleClickMode; }
  105. set { m_singleClickMode = value; }
  106. }
  107. private static FileBrowser m_instance = null;
  108. private static FileBrowser Instance
  109. {
  110. get
  111. {
  112. if( m_instance == null )
  113. {
  114. m_instance = Instantiate( Resources.Load<GameObject>( "SimpleFileBrowserCanvas" ) ).GetComponent<FileBrowser>();
  115. DontDestroyOnLoad( m_instance.gameObject );
  116. m_instance.gameObject.SetActive( false );
  117. }
  118. return m_instance;
  119. }
  120. }
  121. #endregion
  122. #region Variables
  123. #pragma warning disable 0649
  124. [Header( "References" )]
  125. [SerializeField]
  126. private FileBrowserMovement window;
  127. private RectTransform windowTR;
  128. [SerializeField]
  129. private FileBrowserItem itemPrefab;
  130. [SerializeField]
  131. private FileBrowserQuickLink quickLinkPrefab;
  132. [SerializeField]
  133. private Text titleText;
  134. [SerializeField]
  135. private Button backButton;
  136. [SerializeField]
  137. private Button forwardButton;
  138. [SerializeField]
  139. private Button upButton;
  140. [SerializeField]
  141. private InputField pathInputField;
  142. [SerializeField]
  143. private InputField searchInputField;
  144. [SerializeField]
  145. private RectTransform quickLinksContainer;
  146. [SerializeField]
  147. private RectTransform filesContainer;
  148. [SerializeField]
  149. private ScrollRect filesScrollRect;
  150. [SerializeField]
  151. private RecycledListView listView;
  152. [SerializeField]
  153. private InputField filenameInputField;
  154. [SerializeField]
  155. private Image filenameImage;
  156. [SerializeField]
  157. private Dropdown filtersDropdown;
  158. [SerializeField]
  159. private RectTransform filtersDropdownContainer;
  160. [SerializeField]
  161. private Text filterItemTemplate;
  162. [SerializeField]
  163. private Toggle showHiddenFilesToggle;
  164. [SerializeField]
  165. private Text submitButtonText;
  166. [Header( "Icons" )]
  167. [SerializeField]
  168. private Sprite folderIcon;
  169. [SerializeField]
  170. private Sprite driveIcon;
  171. [SerializeField]
  172. private Sprite defaultIcon;
  173. [SerializeField]
  174. private FiletypeIcon[] filetypeIcons;
  175. private Dictionary<string, Sprite> filetypeToIcon;
  176. [Header( "Other" )]
  177. public Color normalFileColor = Color.white;
  178. public Color hoveredFileColor = new Color32( 225, 225, 255, 255 );
  179. public Color selectedFileColor = new Color32( 0, 175, 255, 255 );
  180. public Color wrongFilenameColor = new Color32( 255, 100, 100, 255 );
  181. public int minWidth = 380;
  182. public int minHeight = 300;
  183. [SerializeField]
  184. private string[] excludeExtensions;
  185. #pragma warning disable 0414
  186. [SerializeField]
  187. private QuickLink[] quickLinks;
  188. private static bool quickLinksInitialized;
  189. #pragma warning restore 0414
  190. private readonly HashSet<string> excludedExtensionsSet = new HashSet<string>();
  191. private readonly HashSet<string> addedQuickLinksSet = new HashSet<string>();
  192. [SerializeField]
  193. private bool generateQuickLinksForDrives = true;
  194. #pragma warning restore 0649
  195. private RectTransform rectTransform;
  196. private FileAttributes ignoredFileAttributes = FileAttributes.System;
  197. private FileSystemEntry[] allFileEntries;
  198. private readonly List<FileSystemEntry> validFileEntries = new List<FileSystemEntry>();
  199. private readonly List<Filter> filters = new List<Filter>();
  200. private Filter allFilesFilter;
  201. private bool showAllFilesFilter = true;
  202. private int currentPathIndex = -1;
  203. private readonly List<string> pathsFollowed = new List<string>();
  204. private bool canvasDimensionsChanged;
  205. // Required in RefreshFiles() function
  206. private UnityEngine.EventSystems.PointerEventData nullPointerEventData;
  207. #endregion
  208. #region Properties
  209. private string m_currentPath = string.Empty;
  210. private string CurrentPath
  211. {
  212. get { return m_currentPath; }
  213. set
  214. {
  215. #if !UNITY_EDITOR && UNITY_ANDROID
  216. if( !FileBrowserHelpers.ShouldUseSAF )
  217. #endif
  218. if( value != null )
  219. value = GetPathWithoutTrailingDirectorySeparator( value.Trim() );
  220. if( value == null )
  221. return;
  222. if( m_currentPath != value )
  223. {
  224. if( !FileBrowserHelpers.DirectoryExists( value ) )
  225. return;
  226. m_currentPath = value;
  227. pathInputField.text = m_currentPath;
  228. if( currentPathIndex == -1 || pathsFollowed[currentPathIndex] != m_currentPath )
  229. {
  230. currentPathIndex++;
  231. if( currentPathIndex < pathsFollowed.Count )
  232. {
  233. pathsFollowed[currentPathIndex] = value;
  234. for( int i = pathsFollowed.Count - 1; i >= currentPathIndex + 1; i-- )
  235. pathsFollowed.RemoveAt( i );
  236. }
  237. else
  238. pathsFollowed.Add( m_currentPath );
  239. }
  240. backButton.interactable = currentPathIndex > 0;
  241. forwardButton.interactable = currentPathIndex < pathsFollowed.Count - 1;
  242. #if !UNITY_EDITOR && UNITY_ANDROID
  243. if( !FileBrowserHelpers.ShouldUseSAF )
  244. #endif
  245. upButton.interactable = Directory.GetParent( m_currentPath ) != null;
  246. m_searchString = string.Empty;
  247. searchInputField.text = m_searchString;
  248. filesScrollRect.verticalNormalizedPosition = 1;
  249. filenameImage.color = Color.white;
  250. if( m_folderSelectMode )
  251. filenameInputField.text = string.Empty;
  252. }
  253. RefreshFiles( true );
  254. }
  255. }
  256. private string m_searchString = string.Empty;
  257. private string SearchString
  258. {
  259. get
  260. {
  261. return m_searchString;
  262. }
  263. set
  264. {
  265. if( m_searchString != value )
  266. {
  267. m_searchString = value;
  268. searchInputField.text = m_searchString;
  269. RefreshFiles( false );
  270. }
  271. }
  272. }
  273. private int m_selectedFilePosition = -1;
  274. public int SelectedFilePosition { get { return m_selectedFilePosition; } }
  275. private FileBrowserItem m_selectedFile;
  276. private FileBrowserItem SelectedFile
  277. {
  278. get
  279. {
  280. return m_selectedFile;
  281. }
  282. set
  283. {
  284. if( value == null )
  285. {
  286. if( m_selectedFile != null )
  287. m_selectedFile.Deselect();
  288. m_selectedFilePosition = -1;
  289. m_selectedFile = null;
  290. }
  291. else if( m_selectedFilePosition != value.Position )
  292. {
  293. if( m_selectedFile != null )
  294. m_selectedFile.Deselect();
  295. m_selectedFile = value;
  296. m_selectedFilePosition = value.Position;
  297. if( m_folderSelectMode || !m_selectedFile.IsDirectory )
  298. filenameInputField.text = m_selectedFile.Name;
  299. m_selectedFile.Select();
  300. }
  301. }
  302. }
  303. private bool m_acceptNonExistingFilename = false;
  304. private bool AcceptNonExistingFilename
  305. {
  306. get { return m_acceptNonExistingFilename; }
  307. set { m_acceptNonExistingFilename = value; }
  308. }
  309. private bool m_folderSelectMode = false;
  310. private bool FolderSelectMode
  311. {
  312. get
  313. {
  314. return m_folderSelectMode;
  315. }
  316. set
  317. {
  318. if( m_folderSelectMode != value )
  319. {
  320. m_folderSelectMode = value;
  321. if( m_folderSelectMode )
  322. {
  323. filtersDropdown.options[0].text = FOLDERS_FILTER_TEXT;
  324. filtersDropdown.value = 0;
  325. filtersDropdown.RefreshShownValue();
  326. filtersDropdown.interactable = false;
  327. }
  328. else
  329. {
  330. filtersDropdown.options[0].text = filters[0].ToString();
  331. filtersDropdown.interactable = true;
  332. }
  333. Text placeholder = filenameInputField.placeholder as Text;
  334. if( placeholder != null )
  335. placeholder.text = m_folderSelectMode ? "" : "Filename";
  336. }
  337. }
  338. }
  339. private string Title
  340. {
  341. get { return titleText.text; }
  342. set { titleText.text = value; }
  343. }
  344. private string SubmitButtonText
  345. {
  346. get { return submitButtonText.text; }
  347. set { submitButtonText.text = value; }
  348. }
  349. #endregion
  350. #region Delegates
  351. public delegate void OnSuccess( string path );
  352. public delegate void OnCancel();
  353. #if !UNITY_EDITOR && UNITY_ANDROID
  354. public delegate void DirectoryPickCallback( string rawUri, string name );
  355. #endif
  356. private OnSuccess onSuccess;
  357. private OnCancel onCancel;
  358. #endregion
  359. #region Messages
  360. private void Awake()
  361. {
  362. m_instance = this;
  363. rectTransform = (RectTransform) transform;
  364. windowTR = (RectTransform) window.transform;
  365. ItemHeight = ( (RectTransform) itemPrefab.transform ).sizeDelta.y;
  366. nullPointerEventData = new UnityEngine.EventSystems.PointerEventData( null );
  367. #if !UNITY_EDITOR && ( UNITY_ANDROID || UNITY_IOS || UNITY_WSA || UNITY_WSA_10_0 )
  368. DEFAULT_PATH = Application.persistentDataPath;
  369. #else
  370. DEFAULT_PATH = Environment.GetFolderPath( Environment.SpecialFolder.MyDocuments );
  371. #endif
  372. #if !UNITY_EDITOR && UNITY_ANDROID
  373. if( FileBrowserHelpers.ShouldUseSAF )
  374. {
  375. // These UI elements have no use in Storage Access Framework mode (Android 10+)
  376. upButton.gameObject.SetActive( false );
  377. pathInputField.gameObject.SetActive( false );
  378. showHiddenFilesToggle.gameObject.SetActive( false );
  379. }
  380. #endif
  381. InitializeFiletypeIcons();
  382. filetypeIcons = null;
  383. SetExcludedExtensions( excludeExtensions );
  384. excludeExtensions = null;
  385. backButton.interactable = false;
  386. forwardButton.interactable = false;
  387. upButton.interactable = false;
  388. filenameInputField.onValidateInput += OnValidateFilenameInput;
  389. allFilesFilter = new Filter( ALL_FILES_FILTER_TEXT );
  390. filters.Add( allFilesFilter );
  391. window.Initialize( this );
  392. listView.SetAdapter( this );
  393. }
  394. private void OnRectTransformDimensionsChange()
  395. {
  396. canvasDimensionsChanged = true;
  397. }
  398. private void LateUpdate()
  399. {
  400. if( canvasDimensionsChanged )
  401. {
  402. canvasDimensionsChanged = false;
  403. EnsureWindowIsWithinBounds();
  404. }
  405. }
  406. private void OnApplicationFocus( bool focus )
  407. {
  408. if( focus )
  409. RefreshFiles( true );
  410. }
  411. #endregion
  412. #region Interface Methods
  413. public OnItemClickedHandler OnItemClicked { get { return null; } set { } }
  414. public int Count { get { return validFileEntries.Count; } }
  415. public float ItemHeight { get; private set; }
  416. public ListItem CreateItem()
  417. {
  418. FileBrowserItem item = (FileBrowserItem) Instantiate( itemPrefab, filesContainer, false );
  419. item.SetFileBrowser( this );
  420. return item;
  421. }
  422. public void SetItemContent( ListItem item )
  423. {
  424. FileBrowserItem file = (FileBrowserItem) item;
  425. FileSystemEntry fileInfo = validFileEntries[item.Position];
  426. bool isDirectory = fileInfo.IsDirectory;
  427. Sprite icon;
  428. if( isDirectory )
  429. icon = folderIcon;
  430. else if( !filetypeToIcon.TryGetValue( fileInfo.Extension.ToLowerInvariant(), out icon ) )
  431. icon = defaultIcon;
  432. file.SetFile( icon, fileInfo.Name, isDirectory );
  433. file.SetHidden( ( fileInfo.Attributes & FileAttributes.Hidden ) == FileAttributes.Hidden );
  434. if( item.Position == m_selectedFilePosition )
  435. {
  436. m_selectedFile = file;
  437. file.Select();
  438. }
  439. else
  440. file.Deselect();
  441. }
  442. #endregion
  443. #region Initialization Functions
  444. private void InitializeFiletypeIcons()
  445. {
  446. filetypeToIcon = new Dictionary<string, Sprite>();
  447. for( int i = 0; i < filetypeIcons.Length; i++ )
  448. {
  449. FiletypeIcon thisIcon = filetypeIcons[i];
  450. filetypeToIcon[thisIcon.extension] = thisIcon.icon;
  451. }
  452. }
  453. private void InitializeQuickLinks()
  454. {
  455. Vector2 anchoredPos = new Vector2( 0f, -quickLinksContainer.sizeDelta.y );
  456. #if !UNITY_EDITOR && UNITY_ANDROID
  457. if( !FileBrowserHelpers.ShouldUseSAF )
  458. {
  459. #endif
  460. if( generateQuickLinksForDrives )
  461. {
  462. #if !UNITY_EDITOR && UNITY_ANDROID
  463. string drivesList = FileBrowserHelpers.AJC.CallStatic<string>( "GetExternalDrives" );
  464. if( drivesList != null && drivesList.Length > 0 )
  465. {
  466. bool defaultPathInitialized = false;
  467. int driveIndex = 1;
  468. string[] drives = drivesList.Split( ':' );
  469. for( int i = 0; i < drives.Length; i++ )
  470. {
  471. try
  472. {
  473. //string driveName = new DirectoryInfo( drives[i] ).Name;
  474. //if( driveName.Length <= 1 )
  475. //{
  476. // try
  477. // {
  478. // driveName = Directory.GetParent( drives[i] ).Name + "/" + driveName;
  479. // }
  480. // catch
  481. // {
  482. // driveName = "Drive " + driveIndex++;
  483. // }
  484. //}
  485. string driveName;
  486. if( !defaultPathInitialized )
  487. {
  488. DEFAULT_PATH = drives[i];
  489. defaultPathInitialized = true;
  490. driveName = "Primary Drive";
  491. }
  492. else
  493. {
  494. if( driveIndex == 1 )
  495. driveName = "External Drive";
  496. else
  497. driveName = "External Drive " + driveIndex;
  498. driveIndex++;
  499. }
  500. AddQuickLink( driveIcon, driveName, drives[i], ref anchoredPos );
  501. }
  502. catch { }
  503. }
  504. }
  505. #elif !UNITY_EDITOR && ( UNITY_IOS || UNITY_WSA || UNITY_WSA_10_0 )
  506. AddQuickLink( driveIcon, "Files", Application.persistentDataPath, ref anchoredPos );
  507. #else
  508. string[] drives = Directory.GetLogicalDrives();
  509. for( int i = 0; i < drives.Length; i++ )
  510. AddQuickLink( driveIcon, drives[i], drives[i], ref anchoredPos );
  511. #endif
  512. }
  513. #if UNITY_EDITOR || ( !UNITY_ANDROID && !UNITY_WSA && !UNITY_WSA_10_0 )
  514. for( int i = 0; i < quickLinks.Length; i++ )
  515. {
  516. QuickLink quickLink = quickLinks[i];
  517. string quickLinkPath = Environment.GetFolderPath( quickLink.target );
  518. AddQuickLink( quickLink.icon, quickLink.name, quickLinkPath, ref anchoredPos );
  519. }
  520. quickLinks = null;
  521. #endif
  522. #if !UNITY_EDITOR && UNITY_ANDROID
  523. }
  524. else
  525. {
  526. AddQuickLink( driveIcon, SAF_PICK_FOLDER_QUICK_LINK_TEXT, SAF_PICK_FOLDER_QUICK_LINK_PATH, ref anchoredPos );
  527. try
  528. {
  529. FetchPersistedSAFQuickLinks( ref anchoredPos );
  530. }
  531. catch( Exception e )
  532. {
  533. Debug.LogException( e );
  534. }
  535. }
  536. #endif
  537. quickLinksContainer.sizeDelta = new Vector2( 0f, -anchoredPos.y );
  538. }
  539. #endregion
  540. #region Button Events
  541. public void OnBackButtonPressed()
  542. {
  543. if( currentPathIndex > 0 )
  544. CurrentPath = pathsFollowed[--currentPathIndex];
  545. }
  546. public void OnForwardButtonPressed()
  547. {
  548. if( currentPathIndex < pathsFollowed.Count - 1 )
  549. CurrentPath = pathsFollowed[++currentPathIndex];
  550. }
  551. public void OnUpButtonPressed()
  552. {
  553. #if !UNITY_EDITOR && UNITY_ANDROID
  554. if( FileBrowserHelpers.ShouldUseSAF )
  555. return;
  556. #endif
  557. DirectoryInfo parentPath = Directory.GetParent( m_currentPath );
  558. if( parentPath != null )
  559. CurrentPath = parentPath.FullName;
  560. }
  561. public void OnSubmitButtonClicked()
  562. {
  563. string path = m_currentPath;
  564. string filenameInput = filenameInputField.text.Trim();
  565. #if !UNITY_EDITOR && UNITY_ANDROID
  566. if( FileBrowserHelpers.ShouldUseSAF )
  567. {
  568. if( filenameInput.Length == 0 )
  569. {
  570. if( m_folderSelectMode )
  571. OnOperationSuccessful( path );
  572. else
  573. filenameImage.color = wrongFilenameColor;
  574. return;
  575. }
  576. for( int i = 0; i < validFileEntries.Count; i++ )
  577. {
  578. FileSystemEntry fileInfo = validFileEntries[i];
  579. if( fileInfo.Name == filenameInput )
  580. {
  581. if( fileInfo.IsDirectory == m_folderSelectMode )
  582. OnOperationSuccessful( fileInfo.Path );
  583. else if( fileInfo.IsDirectory )
  584. CurrentPath = fileInfo.Path;
  585. else
  586. filenameImage.color = wrongFilenameColor;
  587. return;
  588. }
  589. }
  590. if( m_acceptNonExistingFilename )
  591. {
  592. if( !m_folderSelectMode && filters[filtersDropdown.value].defaultExtension != null )
  593. filenameInput = Path.ChangeExtension( filenameInput, filters[filtersDropdown.value].defaultExtension );
  594. if( m_folderSelectMode )
  595. OnOperationSuccessful( FileBrowserHelpers.CreateFolderInDirectory( path, filenameInput ) );
  596. else
  597. OnOperationSuccessful( FileBrowserHelpers.CreateFileInDirectory( path, filenameInput ) );
  598. }
  599. else
  600. filenameImage.color = wrongFilenameColor;
  601. return;
  602. }
  603. #endif
  604. if( filenameInput.Length > 0 )
  605. path = Path.Combine( path, filenameInput );
  606. if( File.Exists( path ) )
  607. {
  608. if( !m_folderSelectMode )
  609. OnOperationSuccessful( path );
  610. else
  611. filenameImage.color = wrongFilenameColor;
  612. }
  613. else if( Directory.Exists( path ) )
  614. {
  615. if( m_folderSelectMode )
  616. OnOperationSuccessful( path );
  617. else
  618. {
  619. if( m_currentPath == path )
  620. filenameImage.color = wrongFilenameColor;
  621. else
  622. CurrentPath = path;
  623. }
  624. }
  625. else
  626. {
  627. if( m_acceptNonExistingFilename )
  628. {
  629. if( !m_folderSelectMode && filters[filtersDropdown.value].defaultExtension != null )
  630. path = Path.ChangeExtension( path, filters[filtersDropdown.value].defaultExtension );
  631. OnOperationSuccessful( path );
  632. }
  633. else
  634. filenameImage.color = wrongFilenameColor;
  635. }
  636. }
  637. public void OnCancelButtonClicked()
  638. {
  639. OnOperationCanceled( true );
  640. }
  641. #endregion
  642. #region Other Events
  643. private void OnOperationSuccessful( string path )
  644. {
  645. Success = true;
  646. Result = path;
  647. Hide();
  648. OnSuccess _onSuccess = onSuccess;
  649. onSuccess = null;
  650. onCancel = null;
  651. if( _onSuccess != null )
  652. _onSuccess( path );
  653. }
  654. private void OnOperationCanceled( bool invokeCancelCallback )
  655. {
  656. Success = false;
  657. Result = null;
  658. Hide();
  659. OnCancel _onCancel = onCancel;
  660. onSuccess = null;
  661. onCancel = null;
  662. if( invokeCancelCallback && _onCancel != null )
  663. _onCancel();
  664. }
  665. public void OnPathChanged( string newPath )
  666. {
  667. CurrentPath = newPath;
  668. }
  669. public void OnSearchStringChanged( string newSearchString )
  670. {
  671. SearchString = newSearchString;
  672. }
  673. public void OnFilterChanged()
  674. {
  675. RefreshFiles( false );
  676. }
  677. public void OnShowHiddenFilesToggleChanged()
  678. {
  679. RefreshFiles( false );
  680. }
  681. public void OnQuickLinkSelected( FileBrowserQuickLink quickLink )
  682. {
  683. if( quickLink != null )
  684. {
  685. #if !UNITY_EDITOR && UNITY_ANDROID
  686. if( quickLink.TargetPath == SAF_PICK_FOLDER_QUICK_LINK_PATH )
  687. FileBrowserHelpers.AJC.CallStatic( "PickSAFFolder", FileBrowserHelpers.Context, new FBDirectoryReceiveCallbackAndroid( OnSAFDirectoryPicked ) );
  688. else
  689. #endif
  690. CurrentPath = quickLink.TargetPath;
  691. }
  692. }
  693. public void OnItemSelected( FileBrowserItem item )
  694. {
  695. SelectedFile = item;
  696. }
  697. public void OnItemOpened( FileBrowserItem item )
  698. {
  699. if( item.IsDirectory )
  700. {
  701. #if !UNITY_EDITOR && UNITY_ANDROID
  702. if( FileBrowserHelpers.ShouldUseSAF )
  703. {
  704. for( int i = 0; i < validFileEntries.Count; i++ )
  705. {
  706. FileSystemEntry fileInfo = validFileEntries[i];
  707. if( fileInfo.IsDirectory && fileInfo.Name == item.Name )
  708. {
  709. CurrentPath = fileInfo.Path;
  710. return;
  711. }
  712. }
  713. }
  714. else
  715. #endif
  716. CurrentPath = Path.Combine( m_currentPath, item.Name );
  717. }
  718. else
  719. OnSubmitButtonClicked();
  720. }
  721. #if !UNITY_EDITOR && UNITY_ANDROID
  722. private void OnSAFDirectoryPicked( string rawUri, string name )
  723. {
  724. if( !string.IsNullOrEmpty( rawUri ) )
  725. {
  726. Vector2 anchoredPos = new Vector2( 0f, -quickLinksContainer.sizeDelta.y );
  727. if( AddQuickLink( folderIcon, name, rawUri, ref anchoredPos ) )
  728. {
  729. quickLinksContainer.sizeDelta = new Vector2( 0f, -anchoredPos.y );
  730. CurrentPath = rawUri;
  731. }
  732. }
  733. }
  734. private void FetchPersistedSAFQuickLinks( ref Vector2 anchoredPos )
  735. {
  736. string resultRaw = FileBrowserHelpers.AJC.CallStatic<string>( "FetchSAFQuickLinks", FileBrowserHelpers.Context );
  737. if( resultRaw == "0" )
  738. return;
  739. int separatorIndex = resultRaw.LastIndexOf( "<>" );
  740. if( separatorIndex <= 0 )
  741. {
  742. Debug.LogError( "Entry count does not exist" );
  743. return;
  744. }
  745. int entryCount = 0;
  746. for( int i = separatorIndex + 2; i < resultRaw.Length; i++ )
  747. {
  748. char ch = resultRaw[i];
  749. if( ch < '0' && ch > '9' )
  750. {
  751. Debug.LogError( "Couldn't parse entry count" );
  752. return;
  753. }
  754. entryCount = entryCount * 10 + ( ch - '0' );
  755. }
  756. if( entryCount <= 0 )
  757. return;
  758. bool defaultPathInitialized = false;
  759. separatorIndex = 0;
  760. for( int i = 0; i < entryCount; i++ )
  761. {
  762. int nextSeparatorIndex = resultRaw.IndexOf( "<>", separatorIndex );
  763. if( nextSeparatorIndex <= 0 )
  764. {
  765. Debug.LogError( "Entry name is empty" );
  766. return;
  767. }
  768. string entryName = resultRaw.Substring( separatorIndex, nextSeparatorIndex - separatorIndex );
  769. separatorIndex = nextSeparatorIndex + 2;
  770. nextSeparatorIndex = resultRaw.IndexOf( "<>", separatorIndex );
  771. if( nextSeparatorIndex <= 0 )
  772. {
  773. Debug.LogError( "Entry rawUri is empty" );
  774. return;
  775. }
  776. string rawUri = resultRaw.Substring( separatorIndex, nextSeparatorIndex - separatorIndex );
  777. separatorIndex = nextSeparatorIndex + 2;
  778. if( AddQuickLink( folderIcon, entryName, rawUri, ref anchoredPos ) && !defaultPathInitialized )
  779. {
  780. DEFAULT_PATH = rawUri;
  781. defaultPathInitialized = true;
  782. }
  783. }
  784. }
  785. #endif
  786. public char OnValidateFilenameInput( string text, int charIndex, char addedChar )
  787. {
  788. if( addedChar == '\n' )
  789. {
  790. OnSubmitButtonClicked();
  791. return '\0';
  792. }
  793. return addedChar;
  794. }
  795. #endregion
  796. #region Helper Functions
  797. public void Show( string initialPath )
  798. {
  799. if( AskPermissions )
  800. RequestPermission();
  801. if( !quickLinksInitialized )
  802. {
  803. quickLinksInitialized = true;
  804. InitializeQuickLinks();
  805. }
  806. SelectedFile = null;
  807. m_searchString = string.Empty;
  808. searchInputField.text = m_searchString;
  809. filesScrollRect.verticalNormalizedPosition = 1;
  810. filenameInputField.text = string.Empty;
  811. filenameImage.color = Color.white;
  812. IsOpen = true;
  813. Success = false;
  814. Result = null;
  815. gameObject.SetActive( true );
  816. CurrentPath = GetInitialPath( initialPath );
  817. }
  818. public void Hide()
  819. {
  820. IsOpen = false;
  821. currentPathIndex = -1;
  822. pathsFollowed.Clear();
  823. backButton.interactable = false;
  824. forwardButton.interactable = false;
  825. upButton.interactable = false;
  826. gameObject.SetActive( false );
  827. }
  828. public void RefreshFiles( bool pathChanged )
  829. {
  830. if( pathChanged )
  831. {
  832. if( !string.IsNullOrEmpty( m_currentPath ) )
  833. allFileEntries = FileBrowserHelpers.GetEntriesInDirectory( m_currentPath );
  834. else
  835. allFileEntries = null;
  836. }
  837. SelectedFile = null;
  838. if( !showHiddenFilesToggle.isOn )
  839. ignoredFileAttributes |= FileAttributes.Hidden;
  840. else
  841. ignoredFileAttributes &= ~FileAttributes.Hidden;
  842. string searchStringLowercase = m_searchString.ToLower();
  843. validFileEntries.Clear();
  844. if( allFileEntries != null )
  845. {
  846. for( int i = 0; i < allFileEntries.Length; i++ )
  847. {
  848. try
  849. {
  850. FileSystemEntry item = allFileEntries[i];
  851. if( !item.IsDirectory )
  852. {
  853. if( m_folderSelectMode )
  854. continue;
  855. if( ( item.Attributes & ignoredFileAttributes ) != 0 )
  856. continue;
  857. string extension = item.Extension.ToLowerInvariant();
  858. if( excludedExtensionsSet.Contains( extension ) )
  859. continue;
  860. HashSet<string> extensions = filters[filtersDropdown.value].extensions;
  861. if( extensions != null && !extensions.Contains( extension ) )
  862. continue;
  863. }
  864. else
  865. {
  866. if( ( item.Attributes & ignoredFileAttributes ) != 0 )
  867. continue;
  868. }
  869. if( m_searchString.Length == 0 || item.Name.ToLower().Contains( searchStringLowercase ) )
  870. validFileEntries.Add( item );
  871. }
  872. catch( Exception e )
  873. {
  874. Debug.LogException( e );
  875. }
  876. }
  877. }
  878. listView.UpdateList();
  879. // Prevent the case where all the content stays offscreen after changing the search string
  880. filesScrollRect.OnScroll( nullPointerEventData );
  881. }
  882. private bool AddQuickLink( Sprite icon, string name, string path, ref Vector2 anchoredPos )
  883. {
  884. if( string.IsNullOrEmpty( path ) )
  885. return false;
  886. #if !UNITY_EDITOR && UNITY_ANDROID
  887. if( !FileBrowserHelpers.ShouldUseSAF )
  888. #endif
  889. if( !Directory.Exists( path ) )
  890. return false;
  891. // Don't add quick link if it already exists
  892. if( addedQuickLinksSet.Contains( path ) )
  893. return false;
  894. FileBrowserQuickLink quickLink = (FileBrowserQuickLink) Instantiate( quickLinkPrefab, quickLinksContainer, false );
  895. quickLink.SetFileBrowser( this );
  896. if( icon != null )
  897. quickLink.SetQuickLink( icon, name, path );
  898. else
  899. quickLink.SetQuickLink( folderIcon, name, path );
  900. quickLink.TransformComponent.anchoredPosition = anchoredPos;
  901. anchoredPos.y -= ItemHeight;
  902. addedQuickLinksSet.Add( path );
  903. return true;
  904. }
  905. public void EnsureWindowIsWithinBounds()
  906. {
  907. Vector2 canvasSize = rectTransform.sizeDelta;
  908. Vector2 windowSize = windowTR.sizeDelta;
  909. if( windowSize.x > canvasSize.x )
  910. windowSize.x = canvasSize.x;
  911. if( windowSize.y > canvasSize.y )
  912. windowSize.y = canvasSize.y;
  913. Vector2 windowPos = windowTR.anchoredPosition;
  914. Vector2 canvasHalfSize = canvasSize * 0.5f;
  915. Vector2 windowHalfSize = windowSize * 0.5f;
  916. Vector2 windowBottomLeft = windowPos - windowHalfSize + canvasHalfSize;
  917. Vector2 windowTopRight = windowPos + windowHalfSize + canvasHalfSize;
  918. if( windowBottomLeft.x < 0f )
  919. windowPos.x -= windowBottomLeft.x;
  920. else if( windowTopRight.x > canvasSize.x )
  921. windowPos.x -= windowTopRight.x - canvasSize.x;
  922. if( windowBottomLeft.y < 0f )
  923. windowPos.y -= windowBottomLeft.y;
  924. else if( windowTopRight.y > canvasSize.y )
  925. windowPos.y -= windowTopRight.y - canvasSize.y;
  926. windowTR.anchoredPosition = windowPos;
  927. windowTR.sizeDelta = windowSize;
  928. }
  929. private string GetPathWithoutTrailingDirectorySeparator( string path )
  930. {
  931. if( string.IsNullOrEmpty( path ) )
  932. return null;
  933. // Credit: http://stackoverflow.com/questions/6019227/remove-the-last-character-if-its-directoryseparatorchar-with-c-sharp
  934. try
  935. {
  936. if( Path.GetDirectoryName( path ) != null )
  937. {
  938. char lastChar = path[path.Length - 1];
  939. if( lastChar == Path.DirectorySeparatorChar || lastChar == Path.AltDirectorySeparatorChar )
  940. path = path.Substring( 0, path.Length - 1 );
  941. }
  942. }
  943. catch
  944. {
  945. return null;
  946. }
  947. return path;
  948. }
  949. // Credit: http://answers.unity3d.com/questions/898770/how-to-get-the-width-of-ui-text-with-horizontal-ov.html
  950. private int CalculateLengthOfDropdownText( string str )
  951. {
  952. int totalLength = 0;
  953. Font myFont = filterItemTemplate.font;
  954. CharacterInfo characterInfo = new CharacterInfo();
  955. myFont.RequestCharactersInTexture( str, filterItemTemplate.fontSize, filterItemTemplate.fontStyle );
  956. for( int i = 0; i < str.Length; i++ )
  957. {
  958. if( !myFont.GetCharacterInfo( str[i], out characterInfo, filterItemTemplate.fontSize ) )
  959. totalLength += 5;
  960. totalLength += characterInfo.advance;
  961. }
  962. return totalLength;
  963. }
  964. private string GetInitialPath( string initialPath )
  965. {
  966. if( string.IsNullOrEmpty( initialPath ) || !Directory.Exists( initialPath ) )
  967. {
  968. if( CurrentPath.Length == 0 )
  969. initialPath = DEFAULT_PATH;
  970. else
  971. initialPath = CurrentPath;
  972. }
  973. m_currentPath = string.Empty; // Needed to correctly reset the pathsFollowed
  974. return initialPath;
  975. }
  976. #endregion
  977. #region File Browser Functions (static)
  978. public static bool ShowSaveDialog( OnSuccess onSuccess, OnCancel onCancel,
  979. bool folderMode = false, string initialPath = null,
  980. string title = "Save", string saveButtonText = "Save" )
  981. {
  982. if( Instance.gameObject.activeSelf )
  983. {
  984. Debug.LogError( "Error: Multiple dialogs are not allowed!" );
  985. return false;
  986. }
  987. Instance.onSuccess = onSuccess;
  988. Instance.onCancel = onCancel;
  989. Instance.FolderSelectMode = folderMode;
  990. Instance.Title = title;
  991. Instance.SubmitButtonText = saveButtonText;
  992. Instance.AcceptNonExistingFilename = !folderMode;
  993. Instance.Show( initialPath );
  994. return true;
  995. }
  996. public static bool ShowLoadDialog( OnSuccess onSuccess, OnCancel onCancel,
  997. bool folderMode = false, string initialPath = null,
  998. string title = "Load", string loadButtonText = "Select" )
  999. {
  1000. if( Instance.gameObject.activeSelf )
  1001. {
  1002. Debug.LogError( "Error: Multiple dialogs are not allowed!" );
  1003. return false;
  1004. }
  1005. Instance.onSuccess = onSuccess;
  1006. Instance.onCancel = onCancel;
  1007. Instance.FolderSelectMode = folderMode;
  1008. Instance.Title = title;
  1009. Instance.SubmitButtonText = loadButtonText;
  1010. Instance.AcceptNonExistingFilename = false;
  1011. Instance.Show( initialPath );
  1012. return true;
  1013. }
  1014. public static void HideDialog( bool invokeCancelCallback = false )
  1015. {
  1016. Instance.OnOperationCanceled( invokeCancelCallback );
  1017. }
  1018. public static IEnumerator WaitForSaveDialog( bool folderMode = false, string initialPath = null,
  1019. string title = "Save", string saveButtonText = "Save" )
  1020. {
  1021. if( !ShowSaveDialog( null, null, folderMode, initialPath, title, saveButtonText ) )
  1022. yield break;
  1023. while( Instance.gameObject.activeSelf )
  1024. yield return null;
  1025. }
  1026. public static IEnumerator WaitForLoadDialog( bool folderMode = false, string initialPath = null,
  1027. string title = "Load", string loadButtonText = "Select" )
  1028. {
  1029. if( !ShowLoadDialog( null, null, folderMode, initialPath, title, loadButtonText ) )
  1030. yield break;
  1031. while( Instance.gameObject.activeSelf )
  1032. yield return null;
  1033. }
  1034. public static bool AddQuickLink( string name, string path, Sprite icon = null )
  1035. {
  1036. #if !UNITY_EDITOR && UNITY_ANDROID
  1037. if( FileBrowserHelpers.ShouldUseSAF )
  1038. return false;
  1039. #endif
  1040. if( !quickLinksInitialized )
  1041. {
  1042. quickLinksInitialized = true;
  1043. // Fetching the list of external drives is only possible with the READ_EXTERNAL_STORAGE permission granted on Android
  1044. if( AskPermissions )
  1045. RequestPermission();
  1046. Instance.InitializeQuickLinks();
  1047. }
  1048. Vector2 anchoredPos = new Vector2( 0f, -Instance.quickLinksContainer.sizeDelta.y );
  1049. if( Instance.AddQuickLink( icon, name, path, ref anchoredPos ) )
  1050. {
  1051. Instance.quickLinksContainer.sizeDelta = new Vector2( 0f, -anchoredPos.y );
  1052. return true;
  1053. }
  1054. return false;
  1055. }
  1056. public static void SetExcludedExtensions( params string[] excludedExtensions )
  1057. {
  1058. Instance.excludedExtensionsSet.Clear();
  1059. if( excludedExtensions != null )
  1060. {
  1061. for( int i = 0; i < excludedExtensions.Length; i++ )
  1062. Instance.excludedExtensionsSet.Add( excludedExtensions[i].ToLowerInvariant() );
  1063. }
  1064. }
  1065. public static void SetFilters( bool showAllFilesFilter, IEnumerable<string> filters )
  1066. {
  1067. SetFiltersPreProcessing( showAllFilesFilter );
  1068. if( filters != null )
  1069. {
  1070. foreach( string filter in filters )
  1071. {
  1072. if( filter != null && filter.Length > 0 )
  1073. Instance.filters.Add( new Filter( null, filter ) );
  1074. }
  1075. }
  1076. SetFiltersPostProcessing();
  1077. }
  1078. public static void SetFilters( bool showAllFilesFilter, params string[] filters )
  1079. {
  1080. SetFiltersPreProcessing( showAllFilesFilter );
  1081. if( filters != null )
  1082. {
  1083. for( int i = 0; i < filters.Length; i++ )
  1084. {
  1085. if( filters[i] != null && filters[i].Length > 0 )
  1086. Instance.filters.Add( new Filter( null, filters[i] ) );
  1087. }
  1088. }
  1089. SetFiltersPostProcessing();
  1090. }
  1091. public static void SetFilters( bool showAllFilesFilter, IEnumerable<Filter> filters )
  1092. {
  1093. SetFiltersPreProcessing( showAllFilesFilter );
  1094. if( filters != null )
  1095. {
  1096. foreach( Filter filter in filters )
  1097. {
  1098. if( filter != null && filter.defaultExtension.Length > 0 )
  1099. Instance.filters.Add( filter );
  1100. }
  1101. }
  1102. SetFiltersPostProcessing();
  1103. }
  1104. public static void SetFilters( bool showAllFilesFilter, params Filter[] filters )
  1105. {
  1106. SetFiltersPreProcessing( showAllFilesFilter );
  1107. if( filters != null )
  1108. {
  1109. for( int i = 0; i < filters.Length; i++ )
  1110. {
  1111. if( filters[i] != null && filters[i].defaultExtension.Length > 0 )
  1112. Instance.filters.Add( filters[i] );
  1113. }
  1114. }
  1115. SetFiltersPostProcessing();
  1116. }
  1117. private static void SetFiltersPreProcessing( bool showAllFilesFilter )
  1118. {
  1119. Instance.showAllFilesFilter = showAllFilesFilter;
  1120. Instance.filters.Clear();
  1121. if( showAllFilesFilter )
  1122. Instance.filters.Add( Instance.allFilesFilter );
  1123. }
  1124. private static void SetFiltersPostProcessing()
  1125. {
  1126. List<Filter> filters = Instance.filters;
  1127. if( filters.Count == 0 )
  1128. filters.Add( Instance.allFilesFilter );
  1129. int maxFilterStrLength = 100;
  1130. List<string> dropdownValues = new List<string>( filters.Count );
  1131. for( int i = 0; i < filters.Count; i++ )
  1132. {
  1133. string filterStr = filters[i].ToString();
  1134. dropdownValues.Add( filterStr );
  1135. maxFilterStrLength = Mathf.Max( maxFilterStrLength, Instance.CalculateLengthOfDropdownText( filterStr ) );
  1136. }
  1137. Vector2 size = Instance.filtersDropdownContainer.sizeDelta;
  1138. size.x = maxFilterStrLength + 28;
  1139. Instance.filtersDropdownContainer.sizeDelta = size;
  1140. Instance.filtersDropdown.ClearOptions();
  1141. Instance.filtersDropdown.AddOptions( dropdownValues );
  1142. Instance.filtersDropdown.value = 0;
  1143. }
  1144. public static bool SetDefaultFilter( string defaultFilter )
  1145. {
  1146. if( defaultFilter == null )
  1147. {
  1148. if( Instance.showAllFilesFilter )
  1149. {
  1150. Instance.filtersDropdown.value = 0;
  1151. Instance.filtersDropdown.RefreshShownValue();
  1152. return true;
  1153. }
  1154. return false;
  1155. }
  1156. defaultFilter = defaultFilter.ToLowerInvariant();
  1157. for( int i = 0; i < Instance.filters.Count; i++ )
  1158. {
  1159. HashSet<string> extensions = Instance.filters[i].extensions;
  1160. if( extensions != null && extensions.Contains( defaultFilter ) )
  1161. {
  1162. Instance.filtersDropdown.value = i;
  1163. Instance.filtersDropdown.RefreshShownValue();
  1164. return true;
  1165. }
  1166. }
  1167. return false;
  1168. }
  1169. public static Permission CheckPermission()
  1170. {
  1171. #if !UNITY_EDITOR && UNITY_ANDROID
  1172. Permission result = (Permission) FileBrowserHelpers.AJC.CallStatic<int>( "CheckPermission", FileBrowserHelpers.Context );
  1173. if( result == Permission.Denied && (Permission) PlayerPrefs.GetInt( "FileBrowserPermission", (int) Permission.ShouldAsk ) == Permission.ShouldAsk )
  1174. result = Permission.ShouldAsk;
  1175. return result;
  1176. #else
  1177. return Permission.Granted;
  1178. #endif
  1179. }
  1180. public static Permission RequestPermission()
  1181. {
  1182. #if !UNITY_EDITOR && UNITY_ANDROID
  1183. object threadLock = new object();
  1184. lock( threadLock )
  1185. {
  1186. FBPermissionCallbackAndroid nativeCallback = new FBPermissionCallbackAndroid( threadLock );
  1187. FileBrowserHelpers.AJC.CallStatic( "RequestPermission", FileBrowserHelpers.Context, nativeCallback, PlayerPrefs.GetInt( "FileBrowserPermission", (int) Permission.ShouldAsk ) );
  1188. if( nativeCallback.Result == -1 )
  1189. System.Threading.Monitor.Wait( threadLock );
  1190. if( (Permission) nativeCallback.Result != Permission.ShouldAsk && PlayerPrefs.GetInt( "FileBrowserPermission", -1 ) != nativeCallback.Result )
  1191. {
  1192. PlayerPrefs.SetInt( "FileBrowserPermission", nativeCallback.Result );
  1193. PlayerPrefs.Save();
  1194. }
  1195. return (Permission) nativeCallback.Result;
  1196. }
  1197. #else
  1198. return Permission.Granted;
  1199. #endif
  1200. }
  1201. #endregion
  1202. }
  1203. }