CVR Manager 1.0.0
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
Reference in New Issue
Block a user