GalleryLoad.cs 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. using Avalonia.Threading;
  2. using PicView.Avalonia.ImageHandling;
  3. using PicView.Avalonia.UI;
  4. using PicView.Avalonia.ViewModels;
  5. using PicView.Avalonia.Views.UC;
  6. using PicView.Core.Gallery;
  7. using PicView.Core.Localization;
  8. namespace PicView.Avalonia.Gallery;
  9. public static class GalleryLoad
  10. {
  11. public static bool IsLoading { get; private set; }
  12. private static string? _currentDirectory;
  13. private static CancellationTokenSource? _cancellationTokenSource;
  14. public static async Task LoadGallery(MainViewModel vm, string currentDirectory)
  15. {
  16. // TODO: Lazy load this when scrolling instead. Figure out how to support virtualization.
  17. if (vm.ImageIterator?.ImagePaths.Count == 0 || IsLoading || vm.ImageIterator is null)
  18. {
  19. return;
  20. }
  21. var mainView = UIHelper.GetMainView;
  22. var galleryListBox = mainView.GalleryView.GalleryListBox;
  23. var toReturn = false;
  24. if (!string.IsNullOrEmpty(_currentDirectory))
  25. {
  26. if (_currentDirectory == currentDirectory)
  27. {
  28. return;
  29. }
  30. return;
  31. }
  32. await Dispatcher.UIThread.InvokeAsync(() =>
  33. {
  34. if (galleryListBox is null)
  35. {
  36. toReturn = true;
  37. return;
  38. }
  39. if (galleryListBox.Items.Count > 0)
  40. {
  41. // Make sure to not run consecutively
  42. toReturn = true;
  43. }
  44. });
  45. if (toReturn)
  46. {
  47. return;
  48. }
  49. _cancellationTokenSource = new CancellationTokenSource();
  50. _currentDirectory = currentDirectory;
  51. IsLoading = true;
  52. var index = vm.ImageIterator.CurrentIndex;
  53. var galleryItemSize = Math.Max(vm.GetBottomGalleryItemHeight, vm.GetFullGalleryItemHeight);
  54. var loading = TranslationHelper.Translation.Loading;
  55. var endIndex = vm.ImageIterator.ImagePaths.Count;
  56. // Set priority low when loading excess images to ensure app responsiveness
  57. var priority = endIndex switch
  58. {
  59. >= 3000 => DispatcherPriority.ApplicationIdle,
  60. >= 2000 => DispatcherPriority.Background,
  61. >= 1000 => DispatcherPriority.Input,
  62. >= 500 => DispatcherPriority.Render,
  63. _ => DispatcherPriority.Normal
  64. };
  65. GalleryStretchMode.SetSquareFillStretch(vm);
  66. var fileInfos = new FileInfo[endIndex];
  67. try
  68. {
  69. for (var i = 0; i < endIndex; i++)
  70. {
  71. if (currentDirectory != _currentDirectory || _cancellationTokenSource.IsCancellationRequested || vm.ImageIterator is null)
  72. {
  73. await _cancellationTokenSource.CancelAsync();
  74. return;
  75. }
  76. _cancellationTokenSource.Token.ThrowIfCancellationRequested();
  77. fileInfos[i] = new FileInfo(vm.ImageIterator.ImagePaths[i]);
  78. await Dispatcher.UIThread.InvokeAsync(() =>
  79. {
  80. var galleryItem = new GalleryItem
  81. {
  82. DataContext = vm,
  83. FileName = { Text = loading },
  84. FileSize = { Text = loading },
  85. FileDate = { Text = loading },
  86. FileLocation = { Text = fileInfos[i].FullName },
  87. };
  88. var i1 = i;
  89. galleryItem.PointerPressed += async (_, _) =>
  90. {
  91. if (GalleryFunctions.IsFullGalleryOpen)
  92. {
  93. await GalleryFunctions.ToggleGallery(vm);
  94. }
  95. await vm.ImageIterator.IterateToIndex(vm.ImageIterator.ImagePaths.IndexOf(fileInfos[i1].FullName)).ConfigureAwait(false);
  96. };
  97. galleryListBox.Items.Add(galleryItem);
  98. if (i != vm.ImageIterator?.CurrentIndex)
  99. {
  100. return;
  101. }
  102. vm.SelectedGalleryItemIndex = i;
  103. galleryListBox.SelectedItem = galleryItem;
  104. }, priority, _cancellationTokenSource.Token);
  105. }
  106. await Dispatcher.UIThread.InvokeAsync(() =>
  107. {
  108. if (galleryListBox.Items.Count == 0)
  109. {
  110. return;
  111. }
  112. if (galleryListBox.Items[0] is not GalleryItem galleryItem)
  113. {
  114. return;
  115. }
  116. var horizontalItems = (int)Math.Floor(galleryListBox.Bounds.Width / galleryItem.ImageBorder.MinWidth);
  117. index = (vm.ImageIterator.CurrentIndex - horizontalItems) % vm.ImageIterator.ImagePaths.Count;
  118. });
  119. index = index < 0 ? 0 : index;
  120. var maxDegreeOfParallelism = Environment.ProcessorCount > 4 ? Environment.ProcessorCount - 2 : 2;
  121. ParallelOptions options = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
  122. await AsyncLoop(index, vm.ImageIterator.ImagePaths.Count, options, _cancellationTokenSource.Token);
  123. await AsyncLoop(0, index, options, _cancellationTokenSource.Token);
  124. GalleryStretchMode.DetermineStretchMode(vm);
  125. GalleryNavigation.CenterScrollToSelectedItem(vm);
  126. }
  127. catch (OperationCanceledException)
  128. {
  129. await Dispatcher.UIThread.InvokeAsync(() =>
  130. {
  131. galleryListBox?.Items.Clear();
  132. });
  133. }
  134. catch (Exception e)
  135. {
  136. #if DEBUG
  137. Console.WriteLine($"GalleryLoad exception:\n{e.Message}");
  138. #endif
  139. }
  140. finally
  141. {
  142. IsLoading = false;
  143. _cancellationTokenSource.Dispose();
  144. _cancellationTokenSource = null;
  145. _currentDirectory = null;
  146. }
  147. return;
  148. async Task AsyncLoop(int startIndex, int endIndex, ParallelOptions options, CancellationToken ct)
  149. {
  150. await Parallel.ForAsync(startIndex, endIndex, options, async (i, _) =>
  151. {
  152. if (currentDirectory != _currentDirectory || _cancellationTokenSource.IsCancellationRequested || vm.ImageIterator is null)
  153. {
  154. await _cancellationTokenSource.CancelAsync();
  155. return;
  156. }
  157. ct.ThrowIfCancellationRequested();
  158. if (i < 0 || i >= vm.ImageIterator.ImagePaths.Count)
  159. {
  160. return;
  161. }
  162. var thumb = await GetThumbnails.GetThumbAsync(fileInfos[i].FullName, (uint)galleryItemSize, fileInfos[i]);
  163. var thumbData = GalleryThumbInfo.GalleryThumbHolder.GetThumbData(fileInfos[i]);
  164. await Dispatcher.UIThread.InvokeAsync(() =>
  165. {
  166. if (i < 0 || i >= galleryListBox.Items.Count)
  167. {
  168. return;
  169. }
  170. if (galleryListBox.Items[i] is not GalleryItem galleryItem)
  171. {
  172. return;
  173. }
  174. if (thumb is not null)
  175. {
  176. ImageFunctions.SetImage(thumb, galleryItem.GalleryImage,
  177. fileInfos[i].Extension.Equals("svg", StringComparison.OrdinalIgnoreCase) ||
  178. fileInfos[i].Extension.Equals("svgz", StringComparison.OrdinalIgnoreCase) ? ImageType.Svg : ImageType.Bitmap);
  179. }
  180. galleryItem.FileLocation.Text = thumbData.FileLocation;
  181. galleryItem.FileDate.Text = thumbData.FileDate;
  182. galleryItem.FileSize.Text = thumbData.FileSize;
  183. galleryItem.FileName.Text = thumbData.FileName;
  184. if (vm.ImageIterator is null)
  185. {
  186. ct.ThrowIfCancellationRequested();
  187. galleryListBox?.Items.Clear();
  188. if (GalleryFunctions.IsBottomGalleryOpen)
  189. {
  190. mainView.GalleryView.GalleryMode = GalleryMode.BottomToClosed;
  191. }
  192. return;
  193. }
  194. if (i == vm.ImageIterator.CurrentIndex)
  195. {
  196. galleryListBox.ScrollToCenterOfItem(galleryItem);
  197. }
  198. }, priority, ct);
  199. });
  200. }
  201. }
  202. public static async Task ReloadGalleryAsync(MainViewModel vm, string currentDirectory)
  203. {
  204. if (_cancellationTokenSource is not null)
  205. {
  206. await _cancellationTokenSource.CancelAsync();
  207. await Dispatcher.UIThread.InvokeAsync(() =>
  208. {
  209. try
  210. {
  211. var mainView = UIHelper.GetMainView;
  212. var galleryListBox = mainView.GalleryView.GalleryListBox;
  213. galleryListBox?.Items.Clear();
  214. }
  215. catch (Exception e)
  216. {
  217. #if DEBUG
  218. Console.WriteLine($"GalleryLoad exception:\n{e.Message}");
  219. #endif
  220. }
  221. });
  222. }
  223. var x = 0;
  224. while (IsLoading)
  225. {
  226. await Task.Delay(200).ConfigureAwait(false);
  227. x++;
  228. if (x > 100)
  229. {
  230. break;
  231. }
  232. }
  233. await Dispatcher.UIThread.InvokeAsync(() =>
  234. {
  235. try
  236. {
  237. var mainView = UIHelper.GetMainView;
  238. var galleryListBox = mainView.GalleryView.GalleryListBox;
  239. galleryListBox?.Items.Clear();
  240. }
  241. catch (Exception e)
  242. {
  243. #if DEBUG
  244. Console.WriteLine($"GalleryLoad exception:\n{e.Message}");
  245. #endif
  246. }
  247. });
  248. await LoadGallery(vm, currentDirectory).ConfigureAwait(false);
  249. }
  250. }