using System.Collections.ObjectModel; using System.Windows.Input; using Avalonia.Media.Imaging; using Avalonia.Threading; using Microsoft.VisualBasic.FileIO; namespace CVRManager.ViewModels; public class GalleryItemVM : ObservableObject { Bitmap? thumb; public GalleryItemVM(GalleryViewModel owner, string path) { Path = path; Name = System.IO.Path.GetFileName(path); DateText = File.GetLastWriteTime(path).ToString("yyyy-MM-dd HH:mm"); CopyCommand = new RelayCommand(() => owner.CopyAsync(this)); DeleteCommand = new RelayCommand(() => owner.DeleteAsync(this)); OpenCommand = new RelayCommand(() => owner.Open(this)); ShowCommand = new RelayCommand(() => owner.Show(this)); } public string Path { get; } public string Name { get; } public string DateText { get; } public Bitmap? Thumb { get => thumb; set => Set(ref thumb, value); } public ICommand CopyCommand { get; } public ICommand DeleteCommand { get; } public ICommand OpenCommand { get; } public ICommand ShowCommand { get; } } public class GalleryViewModel : ObservableObject { const int PageSize = 48; readonly MainViewModel main; List all = new(); int shown; string status = ""; bool loading; public GalleryViewModel(MainViewModel main) { this.main = main; RefreshCommand = new RelayCommand(RefreshAsync); MoreCommand = new RelayCommand(LoadMore); OpenFolderCommand = new RelayCommand(() => { Directory.CreateDirectory(Root); System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("explorer.exe", $"\"{Root}\"") { UseShellExecute = true }); }); } public static string Root => System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "ChilloutVR"); public ObservableCollection Items { get; } = new(); public ICommand RefreshCommand { get; } public ICommand MoreCommand { get; } public ICommand OpenFolderCommand { get; } public string Status { get => status; private set => Set(ref status, value); } public bool HasMore => shown < all.Count; public bool IsEmpty => !loading && all.Count == 0; public string FolderText => Root; public async Task RefreshAsync() { loading = true; Status = "Looking for screenshots..."; var found = await Task.Run(() => { if (!Directory.Exists(Root)) return new List(); return Directory.EnumerateFiles(Root, "*.png", System.IO.SearchOption.AllDirectories) .Select(f => (f, t: File.GetLastWriteTimeUtc(f))) .OrderByDescending(x => x.t) .Select(x => x.f) .ToList(); }); loading = false; all = found; shown = 0; Items.Clear(); LoadMore(); Raise(nameof(IsEmpty)); } void LoadMore() { var batch = all.Skip(shown).Take(PageSize).ToList(); shown += batch.Count; foreach (var path in batch) { var item = new GalleryItemVM(this, path); Items.Add(item); _ = LoadThumbAsync(item); } Status = all.Count == 0 ? "No screenshots yet." : $"Showing {shown} of {all.Count} screenshots"; Raise(nameof(HasMore)); } static readonly SemaphoreSlim DecodeGate = new(3); static async Task LoadThumbAsync(GalleryItemVM item) { await DecodeGate.WaitAsync(); try { var bmp = await Task.Run(() => { try { using var fs = File.OpenRead(item.Path); return Bitmap.DecodeToWidth(fs, 360); } catch { return null; } }); if (bmp != null) Dispatcher.UIThread.Post(() => item.Thumb = bmp); } finally { DecodeGate.Release(); } } public async Task CopyAsync(GalleryItemVM item) { if (main.CopyImage == null) return; try { await main.CopyImage(item.Path); main.ReportStatus($"Copied {item.Name} to the clipboard."); } catch (Exception e) { main.ReportStatus($"Could not copy the image: {e.Message}"); } } public async Task DeleteAsync(GalleryItemVM item) { if (main.Confirm != null && !await main.Confirm($"Delete {item.Name}?\nIt is moved to the Recycle Bin.")) return; try { await Task.Run(() => FileSystem.DeleteFile(item.Path, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin)); Items.Remove(item); all.Remove(item.Path); shown = Math.Max(0, shown - 1); Status = $"Showing {shown} of {all.Count} screenshots"; Raise(nameof(HasMore)); Raise(nameof(IsEmpty)); } catch (Exception e) { main.ReportStatus($"Could not delete the image: {e.Message}"); } } public void Open(GalleryItemVM item) => System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(item.Path) { UseShellExecute = true }); public void Show(GalleryItemVM item) => System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("explorer.exe", $"/select,\"{item.Path}\"") { UseShellExecute = true }); }