Client LuaCsForBarotrauma
FileSelection.cs
1 #nullable enable
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using Barotrauma.IO;
6 using System.Linq;
7 using System.Text;
9 
10 namespace Barotrauma
11 {
12  public static class FileSelection
13  {
14  private static bool open;
15  public static bool Open
16  {
17  get
18  {
19  return open;
20  }
21  set
22  {
23  if (value) { InitIfNecessary(); }
24  if (!value)
25  {
26  fileSystemWatcher?.Dispose();
27  fileSystemWatcher = null;
28  }
29  open = value;
30  }
31  }
32 
33  private static GUIFrame? backgroundFrame;
34  private static GUIFrame? window;
35  private static GUIListBox? sidebar;
36  private static GUIListBox? fileList;
37  private static GUITextBox? directoryBox;
38  private static GUITextBox? filterBox;
39  private static GUITextBox? fileBox;
40  private static GUIDropDown? fileTypeDropdown;
41  private static GUIButton? openButton;
42 
43  private static System.IO.FileSystemWatcher? fileSystemWatcher;
44 
45  private enum ItemIsDirectory
46  {
47  Yes, No
48  }
49 
50  private static string? currentFileTypePattern;
51 
52  private static readonly string[] ignoredDrivePrefixes =
53  {
54  "/sys/", "/snap/"
55  };
56 
57  private static string currentDirectory = "";
58  public static string CurrentDirectory
59  {
60  get
61  {
62  return currentDirectory;
63  }
64  set
65  {
66  string[] dirSplit = value.Replace('\\', '/').Split('/');
67  List<string> dirs = new List<string>();
68  for (int i = 0; i < dirSplit.Length; i++)
69  {
70  if (dirSplit[i].Trim() == "..")
71  {
72  if (dirs.Count > 1)
73  {
74  dirs.RemoveAt(dirs.Count - 1);
75  }
76  }
77  else if (dirSplit[i].Trim() != ".")
78  {
79  dirs.Add(dirSplit[i]);
80  }
81  }
82  currentDirectory = string.Join("/", dirs);
83  if (!currentDirectory.EndsWith("/"))
84  {
85  currentDirectory += "/";
86  }
87  fileSystemWatcher?.Dispose();
88  fileSystemWatcher = new System.IO.FileSystemWatcher(currentDirectory)
89  {
90  Filter = "*",
91  NotifyFilter = System.IO.NotifyFilters.LastWrite | System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.DirectoryName
92  };
93  fileSystemWatcher.Created += OnFileSystemChanges;
94  fileSystemWatcher.Deleted += OnFileSystemChanges;
95  fileSystemWatcher.Renamed += OnFileSystemChanges;
96  fileSystemWatcher.EnableRaisingEvents = true;
97  RefreshFileList();
98  }
99  }
100 
101  public static Action<string>? OnFileSelected
102  {
103  get;
104  set;
105  }
106 
107  private static void OnFileSystemChanges(object sender, System.IO.FileSystemEventArgs e)
108  {
109  if (fileList is null) { return; }
110  switch (e.ChangeType)
111  {
112  case System.IO.WatcherChangeTypes.Created:
113  {
114  var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), e.Name ?? string.Empty)
115  {
116  UserData = Directory.Exists(e.FullPath) ? ItemIsDirectory.Yes : ItemIsDirectory.No
117  };
118  if (itemFrame.UserData is ItemIsDirectory.Yes)
119  {
120  itemFrame.Text += "/";
121  }
122  fileList.Content.RectTransform.SortChildren(SortFiles);
123  }
124  break;
125  case System.IO.WatcherChangeTypes.Deleted:
126  {
127  var itemFrame = fileList.Content.FindChild(c => (c is GUITextBlock tb) && (tb.Text == e.Name || tb.Text == e.Name + "/"));
128  if (itemFrame != null) { fileList.RemoveChild(itemFrame); }
129  }
130  break;
131  case System.IO.WatcherChangeTypes.Renamed:
132  {
133  System.IO.RenamedEventArgs renameArgs = e as System.IO.RenamedEventArgs ?? throw new InvalidCastException($"Unable to cast {nameof(System.IO.FileSystemEventArgs)} to {nameof(System.IO.RenamedEventArgs)}.");
134  var itemFrame =
135  fileList.Content.FindChild(c => (c is GUITextBlock tb) && (tb.Text == renameArgs.OldName || tb.Text == renameArgs.OldName + "/")) as GUITextBlock
136  ?? throw new Exception($"Could not find file list item with name \"{renameArgs.OldName}\"");
137  itemFrame.UserData = Directory.Exists(e.FullPath) ? ItemIsDirectory.Yes : ItemIsDirectory.No;
138  itemFrame.Text = renameArgs.Name ?? string.Empty;
139  if (itemFrame.UserData is ItemIsDirectory.Yes)
140  {
141  itemFrame.Text += "/";
142  }
143  fileList.Content.RectTransform.SortChildren(SortFiles);
144  }
145  break;
146  }
147  }
148 
149  private static int SortFiles(RectTransform r1, RectTransform r2)
150  {
151  string file1 = (r1.GUIComponent as GUITextBlock)?.Text?.SanitizedValue ?? "";
152  string file2 = (r2.GUIComponent as GUITextBlock)?.Text?.SanitizedValue ?? "";
153  bool dir1 = r1.GUIComponent.UserData is ItemIsDirectory.Yes;
154  bool dir2 = r2.GUIComponent.UserData is ItemIsDirectory.Yes;
155  if (dir1 && !dir2)
156  {
157  return -1;
158  }
159  else if (!dir1 && dir2)
160  {
161  return 1;
162  }
163 
164  return string.Compare(file1, file2, StringComparison.OrdinalIgnoreCase);
165  }
166 
167  private static void InitIfNecessary()
168  {
169  if (backgroundFrame == null) { Init(); }
170  }
171 
172  public static void Init()
173  {
174  backgroundFrame = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas), style: null)
175  {
176  Color = Color.Black * 0.5f,
177  HoverColor = Color.Black * 0.5f,
178  SelectedColor = Color.Black * 0.5f,
179  PressedColor = Color.Black * 0.5f,
180  };
181 
182  window = new GUIFrame(new RectTransform(Vector2.One * 0.8f, backgroundFrame.RectTransform, Anchor.Center));
183 
184  var horizontalLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, window.RectTransform, Anchor.Center), true);
185  sidebar = new GUIListBox(new RectTransform(new Vector2(0.29f, 1.0f), horizontalLayout.RectTransform))
186  {
187  PlaySoundOnSelect = true
188  };
189 
190  var drives = System.IO.DriveInfo.GetDrives();
191  foreach (var drive in drives)
192  {
193  if (drive.DriveType == System.IO.DriveType.Ram) { continue; }
194  if (ignoredDrivePrefixes.Any(p => drive.Name.StartsWith(p))) { continue; }
195  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), sidebar.Content.RectTransform), drive.Name.Replace('\\','/'));
196  }
197 
198  sidebar.OnSelected = (child, userdata) =>
199  {
200  CurrentDirectory = (child as GUITextBlock)?.Text.SanitizedValue ?? throw new Exception("Sidebar selection is invalid");
201 
202  return false;
203  };
204 
205  //spacing between sidebar and fileListLayout
206  new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), horizontalLayout.RectTransform), style: null);
207 
208  var fileListLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 1.0f), horizontalLayout.RectTransform));
209  var firstRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
210  new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), firstRow.RectTransform), "^")
211  {
212  OnClicked = MoveToParentDirectory
213  };
214  directoryBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), firstRow.RectTransform))
215  {
216  OverflowClip = true,
217  OnEnterPressed = (tb, txt) =>
218  {
219  if (Directory.Exists(txt))
220  {
221  CurrentDirectory = txt;
222  return true;
223  }
224  else
225  {
226  tb.Text = CurrentDirectory;
227  return false;
228  }
229  }
230  };
231  filterBox = new GUITextBox(new RectTransform(new Vector2(0.25f, 1.0f), firstRow.RectTransform))
232  {
233  OverflowClip = true
234  };
235  firstRow.RectTransform.MinSize = new Point(0, firstRow.RectTransform.Children.Max(c => c.MinSize.Y));
236 
237  filterBox.OnTextChanged += (txtbox, txt) =>
238  {
239  RefreshFileList();
240  return true;
241  };
242  //spacing between rows
243  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
244 
245  fileList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.85f), fileListLayout.RectTransform))
246  {
247  PlaySoundOnSelect = true,
248  OnSelected = (child, userdata) =>
249  {
250  if (userdata is null) { return false; }
251  if (fileBox is null) { return false; }
252 
253  var fileName = (child as GUITextBlock)!.Text.SanitizedValue;
254  fileBox.Text = fileName;
255  if (PlayerInput.DoubleClicked())
256  {
257  bool isDir = userdata is ItemIsDirectory.Yes;
258  if (isDir)
259  {
260  CurrentDirectory += fileName;
261  }
262  else
263  {
264  OnFileSelected?.Invoke(CurrentDirectory + fileName);
265  Open = false;
266  }
267  }
268 
269  return true;
270  }
271  };
272 
273  //spacing between rows
274  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
275 
276  var thirdRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), true);
277  fileBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), thirdRow.RectTransform))
278  {
279  OnEnterPressed = (tb, txt) => openButton?.OnClicked?.Invoke(openButton, null) ?? false
280  };
281 
282  fileTypeDropdown = new GUIDropDown(new RectTransform(new Vector2(0.3f, 1.0f), thirdRow.RectTransform), dropAbove: true)
283  {
284  OnSelected = (child, userdata) =>
285  {
286  currentFileTypePattern = (child as GUITextBlock)!.UserData as string;
287  RefreshFileList();
288 
289  return true;
290  }
291  };
292 
293  fileTypeDropdown.Select(4);
294 
295  //spacing between rows
296  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
297  var fourthRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), true);
298 
299  //padding for open/cancel buttons
300  new GUIFrame(new RectTransform(new Vector2(0.7f, 1.0f), fourthRow.RectTransform), style: null);
301 
302  openButton = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), fourthRow.RectTransform), TextManager.Get("opensubbutton"))
303  {
304  OnClicked = (btn, obj) =>
305  {
306  if (Directory.Exists(Path.Combine(CurrentDirectory, fileBox.Text)))
307  {
308  CurrentDirectory += fileBox.Text;
309  }
310  if (!File.Exists(CurrentDirectory + fileBox.Text)) { return false; }
311  OnFileSelected?.Invoke(CurrentDirectory + fileBox.Text);
312  Open = false;
313  return false;
314  }
315  };
316  new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), fourthRow.RectTransform), TextManager.Get("cancel"))
317  {
318  OnClicked = (btn, obj) =>
319  {
320  Open = false;
321  return false;
322  }
323  };
324 
325  CurrentDirectory = Directory.GetCurrentDirectory();
326  }
327 
328  public static void ClearFileTypeFilters()
329  {
330  InitIfNecessary();
331  fileTypeDropdown!.ClearChildren();
332  }
333 
334  public static void AddFileTypeFilter(string name, string pattern)
335  {
336  InitIfNecessary();
337  fileTypeDropdown!.AddItem(name + " (" + pattern + ")", pattern);
338  }
339 
340  public static void SelectFileTypeFilter(string pattern)
341  {
342  InitIfNecessary();
343  fileTypeDropdown!.SelectItem(pattern);
344  }
345 
346  public static void RefreshFileList()
347  {
348  InitIfNecessary();
349  fileList!.Content.ClearChildren();
350  fileList.BarScroll = 0.0f;
351 
352  try
353  {
354  var directories = Directory.EnumerateDirectories(currentDirectory, "*" + filterBox!.Text + "*");
355  foreach (var directory in directories)
356  {
357  string txt = directory;
358  if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
359  if (!txt.EndsWith("/")) { txt += "/"; }
360  //get directory info
361  DirectoryInfo dirInfo = new DirectoryInfo(directory);
362  try
363  {
364  //this will throw an exception if the directory can't be opened
365  Directory.GetDirectories(directory);
366  }
367  catch (UnauthorizedAccessException)
368  {
369  continue;
370  }
371  var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
372  {
373  UserData = ItemIsDirectory.Yes
374  };
375  var folderIcon = new GUIImage(new RectTransform(new Point((int)(itemFrame.Rect.Height * 0.8f)), itemFrame.RectTransform, Anchor.CenterLeft)
376  {
377  AbsoluteOffset = new Point((int)(itemFrame.Rect.Height * 0.25f), 0)
378  }, style: "OpenButton", scaleToFit: true);
379  itemFrame.Padding = new Vector4(folderIcon.Rect.Width * 1.5f, itemFrame.Padding.Y, itemFrame.Padding.Z, itemFrame.Padding.W);
380  }
381 
382  IEnumerable<string> files = Enumerable.Empty<string>();
383  if (currentFileTypePattern.IsNullOrEmpty())
384  {
385  files = Directory.GetFiles(currentDirectory);
386  }
387  else
388  {
389  foreach (string pattern in currentFileTypePattern!.Split(','))
390  {
391  string patternTrimmed = pattern.Trim();
392  patternTrimmed = "*" + filterBox.Text + "*" + patternTrimmed;
393  if (files.None())
394  {
395  files = Directory.EnumerateFiles(currentDirectory, patternTrimmed);
396  }
397  else
398  {
399  files = files.Concat(Directory.EnumerateFiles(currentDirectory, patternTrimmed));
400  }
401  }
402  }
403 
404  foreach (var file in files)
405  {
406  string txt = file;
407  if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
408  var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
409  {
410  UserData = ItemIsDirectory.No
411  };
412  }
413  }
414  catch (Exception e)
415  {
416  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), "Could not list items in directory: " + e.Message)
417  {
418  CanBeFocused = false
419  };
420  }
421 
422  fileList.Content.RectTransform.SortChildren(SortFiles);
423 
424  directoryBox!.Text = currentDirectory;
425  fileBox!.Text = "";
426  fileList.Deselect();
427  }
428 
429  public static bool MoveToParentDirectory(GUIButton button, object userdata)
430  {
431  string dir = CurrentDirectory;
432  if (dir.EndsWith("/")) { dir = dir.Substring(0, dir.Length - 1); }
433  int index = dir.LastIndexOf("/");
434  if (index < 0) { return false; }
435  CurrentDirectory = CurrentDirectory.Substring(0, index + 1);
436 
437  return true;
438  }
439 
440  public static void AddToGUIUpdateList()
441  {
442  if (!Open) { return; }
443  backgroundFrame?.AddToGUIUpdateList();
444  }
445  }
446 }