CVR Manager 1.0.0

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Exterrata
2026-10-25 02:46:35 -04:00
commit 9bb4f85802
59 changed files with 7913 additions and 0 deletions
+154
View File
@@ -0,0 +1,154 @@
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 });
}