9bb4f85802
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
155 lines
5.3 KiB
C#
155 lines
5.3 KiB
C#
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<string> 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<GalleryItemVM> 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<string>();
|
|
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 });
|
|
}
|