#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using IFSEngine.Generation;
using IFSEngine.Model;
using IFSEngine.Rendering;
using IFSEngine.Utility;
using OpenTK.Windowing.Desktop;
using WpfDisplay.Properties;
using WpfDisplay.Serialization;
using WpfDisplay.Services;
namespace WpfDisplay.ViewModels;
public sealed partial class WelcomeViewModel : ObservableObject
{
private readonly IReadOnlyCollection<string> _includeSources;
private readonly IReadOnlyCollection<TransformPlugin> _loadedTransforms;
private readonly IReadOnlyCollection<EffectPlugin> _loadedEffects;
public RelayCommand? ContinueCommand { get; set; }
public WelcomeWorkflow SelectedWorkflow { get; private set; } = WelcomeWorkflow.FromScratch;
public IFS ExploreParams { get; private set; } = new IFS();
public string? SelectedFilePath { get; private set; }
public string SelectedExpander
{
get => Settings.Default.WelcomeExpanderState;
set
{
Settings.Default.WelcomeExpanderState = value;
Settings.Default.Save();
OnPropertyChanged(nameof(SelectedExpander));
}
}
[ObservableProperty] public partial ImageSource? ExploreThumbnail { get; set; }
[ObservableProperty] public partial List<KeyValuePair<IFS, ImageSource>> Templates { get; set; } = [];
[ObservableProperty] public partial List<KeyValuePair<IFS, ImageSource>> RecentFiles { get; set; } = [];
public WelcomeViewModel(IReadOnlyCollection<string> includeSources, IReadOnlyCollection<TransformPlugin> loadedTransforms, IReadOnlyCollection<EffectPlugin> loadedEffects)
{
_includeSources = includeSources;
_loadedTransforms = loadedTransforms;
_loadedEffects = loadedEffects;
}
public async Task Initialize()
{
//init thumbnail renderer
GameWindow hw = new(new GameWindowSettings(), new NativeWindowSettings
{
Flags = OpenTK.Windowing.Common.ContextFlags.Offscreen,
IsEventDriven = true,
StartFocused = false,
StartVisible = false,
WindowState = OpenTK.Windowing.Common.WindowState.Minimized
});
var renderer = new RendererGL(hw.Context);
renderer.SetDisplayResolution(100, 100);
await renderer.Initialize(_includeSources, _loadedTransforms, _loadedEffects);
await renderer.SetWorkgroupCount(100);
ExploreParams = new Generator(_loadedTransforms).GenerateOne(new());
ExploreThumbnail = RenderThumbnail(renderer, ExploreParams);
//load template files and recent files.
List<Task<IFS>> templateFilesTasks = [];
List<Task<IFS>> recentFilesTasks = [];
try
{
templateFilesTasks = Directory
.GetFiles(App.TemplatesDirectoryPath, "*.ifsjson")
.Select(templatePath => IfsNodesSerializer.LoadJsonFileAsync(templatePath, _loadedTransforms, _loadedEffects, true))
.ToList();
var recentFilePaths = Settings.Default.RecentFiles.Cast<string>();
recentFilesTasks = recentFilePaths
.Select(recentPath => IfsNodesSerializer.LoadJsonFileAsync(recentPath, _loadedTransforms, _loadedEffects, true))
.Reverse()
.ToList();
await Task.WhenAll(templateFilesTasks.Concat(recentFilesTasks));
}
catch (System.Runtime.Serialization.SerializationException) { /*Ignore broken files*/ }
catch (FileNotFoundException) { /*Ignore deleted recent files*/ }
//render thumbnails
Templates = templateFilesTasks
.Where(t => t.IsCompletedSuccessfully).Select(t => t.Result)
.ToDictionary(p => p, p => RenderThumbnail(renderer, p))
.ToList();
RecentFiles = recentFilesTasks
.Where(t => t.IsCompletedSuccessfully).Select(t => t.Result)
.ToDictionary(p => p, p => RenderThumbnail(renderer, p))
.ToList();
}
private static ImageSource RenderThumbnail(RendererGL renderer, IFS ifs)
{
//modify settings to be optimal for thumbnail rendering
var previewIfs = ifs.DeepClone();
previewIfs.ImageResolution = new System.Drawing.Size(100, 100);
previewIfs.Entropy = 1.0 / 100;
previewIfs.Warmup = 0;
//render image after 1 compute pass
renderer.LoadParams(previewIfs);
renderer.SetHistogramScaleToDisplay();
renderer.DispatchCompute();
renderer.RenderImage();
//read rendered image data
WriteableBitmap wbm = new WriteableBitmap(renderer.HistogramWidth, renderer.HistogramHeight, 96, 96, PixelFormats.Bgra32, null);
renderer.CopyPixelDataToBitmap(wbm.BackBuffer).Wait();
wbm.Freeze();
//save thumbnail image
var thumbnail = new FormatConvertedBitmap(wbm, PixelFormats.Bgr32, null, 0);
thumbnail.Freeze();
return thumbnail;
}
[RelayCommand]
private void SelectWorkflow(WelcomeWorkflow selection)
{
SelectedWorkflow = selection;
ContinueCommand?.Execute(null);
}
[RelayCommand]
private void SelectExploreWorkflow(IFS? ifs)
{
if (RecentFiles.Any(f => f.Key == ifs))
{
SelectedWorkflow = WelcomeWorkflow.LoadRecent;
SelectedFilePath = Settings.Default.RecentFiles[RecentFiles.Count - 1 - RecentFiles.IndexOf(RecentFiles.First(f => f.Key == ifs))];//TODO: ugh
}
else
SelectedWorkflow = WelcomeWorkflow.Explore;
if (ifs is not null)
ExploreParams = ifs;
ContinueCommand?.Execute(null);
}
[RelayCommand]
private void PasteFromClipboard()
{
try
{
string jsonData = System.Windows.Clipboard.GetText();
IFS ifs = IfsNodesSerializer.DeserializeJsonString(jsonData, _loadedTransforms, _loadedEffects, true);
SelectExploreWorkflow(ifs);
}
catch (SerializationException) { /* Ignore when Clipboard contains no params */ }
}
[RelayCommand]
private void DropFile(IDataObject dataObject)
{
if (FileDropHandler.IsDropSupported(dataObject, out var fileType, out var filePath) && fileType == Models.DroppedFile.Image || fileType == Models.DroppedFile.Params)
{//only params and params in an image can be dropped to the welcome window
SelectedWorkflow = WelcomeWorkflow.LoadFromFile;
SelectedFilePath = filePath;
ContinueCommand?.Execute(null);
}
}
[RelayCommand]
private void DisableStartup()
{
Settings.Default.IsWelcomeShownOnStartup = false;
Settings.Default.Save();
ContinueCommand?.Execute(null);
}
}