2 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
11 public static class FileSelection
13 private static bool open;
14 public static bool Open
22 if (value) { InitIfNecessary(); }
25 fileSystemWatcher?.Dispose();
26 fileSystemWatcher =
null;
32 private static GUIFrame? backgroundFrame;
33 private static GUIFrame? window;
34 private static GUIListBox? sidebar;
35 private static GUIListBox? fileList;
36 private static GUITextBox? directoryBox;
37 private static GUITextBox? filterBox;
38 private static GUITextBox? fileBox;
39 private static GUIDropDown? fileTypeDropdown;
40 private static GUIButton? openButton;
42 private static System.IO.FileSystemWatcher? fileSystemWatcher;
44 private enum ItemIsDirectory
49 private static string? currentFileTypePattern;
51 private static readonly
string[] ignoredDrivePrefixes =
56 private static string currentDirectory =
"";
57 public static string CurrentDirectory
61 return currentDirectory;
65 string[] dirSplit = value.Replace(
'\\',
'/').Split(
'/');
66 List<string> dirs =
new List<string>();
67 for (
int i = 0; i < dirSplit.Length; i++)
69 if (dirSplit[i].Trim() ==
"..")
73 dirs.RemoveAt(dirs.Count - 1);
76 else if (dirSplit[i].Trim() !=
".")
78 dirs.Add(dirSplit[i]);
81 currentDirectory =
string.Join(
"/", dirs);
82 if (!currentDirectory.EndsWith(
"/"))
84 currentDirectory +=
"/";
88 fileSystemWatcher?.Dispose();
89 fileSystemWatcher =
new System.IO.FileSystemWatcher(currentDirectory)
92 NotifyFilter = System.IO.NotifyFilters.LastWrite | System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.DirectoryName
94 fileSystemWatcher.Created += OnFileSystemChanges;
95 fileSystemWatcher.Deleted += OnFileSystemChanges;
96 fileSystemWatcher.Renamed += OnFileSystemChanges;
97 fileSystemWatcher.EnableRaisingEvents =
true;
99 catch (System.IO.FileNotFoundException exception)
101 DebugConsole.ThrowError(
"Failed to set the current directory, possibly due to insufficient access permissions.", exception);
103 catch (ArgumentException exception)
105 DebugConsole.ThrowError(
"Failed to set the current directory, possibly because it was deleted.", exception);
107 catch (Exception exception)
109 DebugConsole.ThrowError(
"Failed to set the current directory for an unknown reason.", exception);
115 public static Action<string>? OnFileSelected
121 private static void OnFileSystemChanges(
object sender, System.IO.FileSystemEventArgs e)
123 if (fileList is
null) {
return; }
124 switch (e.ChangeType)
126 case System.IO.WatcherChangeTypes.Created:
128 var itemFrame =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), e.Name ??
string.Empty)
130 UserData = Directory.Exists(e.FullPath) ? ItemIsDirectory.Yes : ItemIsDirectory.No
132 if (itemFrame.UserData is ItemIsDirectory.Yes)
134 itemFrame.Text +=
"/";
136 fileList.Content.RectTransform.SortChildren(SortFiles);
139 case System.IO.WatcherChangeTypes.Deleted:
141 var itemFrame = fileList.Content.FindChild(c => (c is GUITextBlock tb) && (tb.Text == e.Name || tb.Text == e.Name +
"/"));
142 if (itemFrame !=
null) { fileList.RemoveChild(itemFrame); }
145 case System.IO.WatcherChangeTypes.Renamed:
147 System.IO.RenamedEventArgs renameArgs = e as System.IO.RenamedEventArgs ??
throw new InvalidCastException($
"Unable to cast {nameof(System.IO.FileSystemEventArgs)} to {nameof(System.IO.RenamedEventArgs)}.");
149 fileList.Content.FindChild(c => (c is GUITextBlock tb) && (tb.Text == renameArgs.OldName || tb.Text == renameArgs.OldName +
"/")) as GUITextBlock
150 ??
throw new Exception($
"Could not find file list item with name \"{renameArgs.OldName}\"");
151 itemFrame.UserData = Directory.Exists(e.FullPath) ? ItemIsDirectory.Yes : ItemIsDirectory.No;
152 itemFrame.Text = renameArgs.Name ??
string.Empty;
153 if (itemFrame.UserData is ItemIsDirectory.Yes)
155 itemFrame.Text +=
"/";
157 fileList.Content.RectTransform.SortChildren(SortFiles);
163 private static int SortFiles(RectTransform r1, RectTransform r2)
165 string file1 = (r1.GUIComponent as GUITextBlock)?.Text?.SanitizedValue ??
"";
166 string file2 = (r2.GUIComponent as GUITextBlock)?.Text?.SanitizedValue ??
"";
167 bool dir1 = r1.GUIComponent.UserData is ItemIsDirectory.Yes;
168 bool dir2 = r2.GUIComponent.UserData is ItemIsDirectory.Yes;
173 else if (!dir1 && dir2)
178 return string.Compare(file1, file2, StringComparison.OrdinalIgnoreCase);
181 private static void InitIfNecessary()
183 if (backgroundFrame ==
null) { Init(); }
186 public static void Init()
188 backgroundFrame =
new GUIFrame(
new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas), style:
null)
190 Color = Color.Black * 0.5f,
191 HoverColor = Color.Black * 0.5f,
192 SelectedColor = Color.Black * 0.5f,
193 PressedColor = Color.Black * 0.5f,
196 window =
new GUIFrame(
new RectTransform(Vector2.One * 0.8f, backgroundFrame.RectTransform,
Anchor.Center));
198 var horizontalLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One * 0.9f, window.RectTransform,
Anchor.Center),
true);
199 sidebar =
new GUIListBox(
new RectTransform(
new Vector2(0.29f, 1.0f), horizontalLayout.RectTransform))
201 PlaySoundOnSelect =
true
204 var drives = System.IO.DriveInfo.GetDrives();
205 foreach (var drive
in drives)
207 if (drive.DriveType == System.IO.DriveType.Ram) {
continue; }
208 if (ignoredDrivePrefixes.Any(p => drive.Name.StartsWith(p))) {
continue; }
209 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.05f), sidebar.Content.RectTransform), drive.Name.Replace(
'\\',
'/'));
212 sidebar.OnSelected = (child, userdata) =>
214 CurrentDirectory = (child as GUITextBlock)?.Text.SanitizedValue ??
throw new Exception(
"Sidebar selection is invalid");
220 new GUIFrame(
new RectTransform(
new Vector2(0.01f, 1.0f), horizontalLayout.RectTransform), style:
null);
222 var fileListLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.7f, 1.0f), horizontalLayout.RectTransform));
223 var firstRow =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
224 new GUIButton(
new RectTransform(
new Vector2(0.05f, 1.0f), firstRow.RectTransform),
"^")
226 OnClicked = MoveToParentDirectory
228 directoryBox =
new GUITextBox(
new RectTransform(
new Vector2(0.7f, 1.0f), firstRow.RectTransform))
231 OnEnterPressed = (tb, txt) =>
233 if (Directory.Exists(txt))
235 var attributes = System.IO.File.GetAttributes(txt);
236 if (attributes.HasAnyFlag(System.IO.FileAttributes.System) || attributes.HasAnyFlag(System.IO.FileAttributes.Hidden))
240 tb.Text = CurrentDirectory;
243 CurrentDirectory = txt;
248 tb.Text = CurrentDirectory;
253 filterBox =
new GUITextBox(
new RectTransform(
new Vector2(0.25f, 1.0f), firstRow.RectTransform))
257 firstRow.RectTransform.MinSize =
new Point(0, firstRow.RectTransform.Children.Max(c => c.MinSize.Y));
259 filterBox.OnTextChanged += (txtbox, txt) =>
265 new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style:
null);
267 fileList =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 0.85f), fileListLayout.RectTransform))
269 PlaySoundOnSelect =
true,
270 OnSelected = (child, userdata) =>
272 if (userdata is
null) {
return false; }
273 if (fileBox is
null) {
return false; }
275 var fileName = (child as GUITextBlock)!.Text.SanitizedValue;
276 fileBox.Text = fileName;
277 if (PlayerInput.DoubleClicked())
279 bool isDir = userdata is ItemIsDirectory.Yes;
282 CurrentDirectory += fileName;
286 OnFileSelected?.Invoke(CurrentDirectory + fileName);
296 new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style:
null);
298 var thirdRow =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.04f), fileListLayout.RectTransform),
true);
299 fileBox =
new GUITextBox(
new RectTransform(
new Vector2(0.7f, 1.0f), thirdRow.RectTransform))
301 OnEnterPressed = (tb, txt) => openButton?.OnClicked?.Invoke(openButton,
null) ??
false
304 fileTypeDropdown =
new GUIDropDown(
new RectTransform(
new Vector2(0.3f, 1.0f), thirdRow.RectTransform), dropAbove:
true)
306 OnSelected = (child, userdata) =>
308 currentFileTypePattern = (child as GUITextBlock)!.UserData as
string;
315 fileTypeDropdown.Select(4);
318 new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style:
null);
319 var fourthRow =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.04f), fileListLayout.RectTransform),
true);
322 new GUIFrame(
new RectTransform(
new Vector2(0.7f, 1.0f), fourthRow.RectTransform), style:
null);
324 openButton =
new GUIButton(
new RectTransform(
new Vector2(0.15f, 1.0f), fourthRow.RectTransform), TextManager.Get(
"opensubbutton"))
326 OnClicked = (btn, obj) =>
328 if (Directory.Exists(Path.Combine(CurrentDirectory, fileBox.Text)))
330 CurrentDirectory += fileBox.Text;
332 if (!File.Exists(CurrentDirectory + fileBox.Text)) {
return false; }
333 OnFileSelected?.Invoke(CurrentDirectory + fileBox.Text);
338 new GUIButton(
new RectTransform(
new Vector2(0.15f, 1.0f), fourthRow.RectTransform), TextManager.Get(
"cancel"))
340 OnClicked = (btn, obj) =>
347 CurrentDirectory = Directory.GetCurrentDirectory();
350 public static void ClearFileTypeFilters()
353 fileTypeDropdown!.ClearChildren();
356 public static void AddFileTypeFilter(
string name,
string pattern)
359 fileTypeDropdown!.AddItem(name +
" (" + pattern +
")", pattern);
362 public static void SelectFileTypeFilter(
string pattern)
365 fileTypeDropdown!.SelectItem(pattern);
368 public static void RefreshFileList()
371 fileList!.Content.ClearChildren();
372 fileList.BarScroll = 0.0f;
376 var directories = Directory.EnumerateDirectories(currentDirectory,
"*" + filterBox!.Text +
"*");
377 foreach (var directory
in directories)
382 System.IO.Directory.GetDirectories(directory);
384 catch (UnauthorizedAccessException)
389 string txt = directory;
390 if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
391 if (!txt.EndsWith(
"/")) { txt +=
"/"; }
392 var itemFrame =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
394 UserData = ItemIsDirectory.Yes
396 var folderIcon =
new GUIImage(
new RectTransform(
new Point((
int)(itemFrame.Rect.Height * 0.8f)), itemFrame.RectTransform,
Anchor.CenterLeft)
398 AbsoluteOffset = new Point((int)(itemFrame.Rect.Height * 0.25f), 0)
399 }, style:
"OpenButton", scaleToFit:
true);
400 itemFrame.Padding =
new Vector4(folderIcon.Rect.Width * 1.5f, itemFrame.Padding.Y, itemFrame.Padding.Z, itemFrame.Padding.W);
403 IEnumerable<string> files = Enumerable.Empty<
string>();
404 if (currentFileTypePattern.IsNullOrEmpty())
406 files = Directory.GetFiles(currentDirectory);
410 foreach (
string pattern
in currentFileTypePattern!.Split(
','))
412 string patternTrimmed = pattern.Trim();
413 patternTrimmed =
"*" + filterBox.Text +
"*" + patternTrimmed;
416 files = Directory.EnumerateFiles(currentDirectory, patternTrimmed);
420 files = files.Concat(Directory.EnumerateFiles(currentDirectory, patternTrimmed));
425 foreach (var file
in files)
428 if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
429 var itemFrame =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
431 UserData = ItemIsDirectory.No
437 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.05f), fileList.Content.RectTransform),
"Could not list items in directory: " + e.Message)
443 fileList.Content.RectTransform.SortChildren(SortFiles);
445 directoryBox!.Text = currentDirectory;
450 public static bool MoveToParentDirectory(GUIButton button,
object userdata)
452 string dir = CurrentDirectory;
453 if (dir.EndsWith(
"/")) { dir = dir.Substring(0, dir.Length - 1); }
454 int index = dir.LastIndexOf(
"/");
455 if (index < 0) {
return false; }
456 CurrentDirectory = CurrentDirectory.Substring(0, index + 1);
461 public static void AddToGUIUpdateList()
463 if (!Open) {
return; }
464 backgroundFrame?.AddToGUIUpdateList();