using System; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using PsdParser; using YukkuriMovieMaker.Commons; using YukkuriMovieMaker.Plugin.FileSource.Psd; using YukkuriMovieMaker.Resources.Localization; namespace YukkuriMovieMaker.Plugin.Tachie.Psd; public class PsdLayerEditorViewModel : ValidatableBindable, IDisposable { private readonly DisposeCollector disposer = new DisposeCollector(); private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1); private readonly IPsdTachieParameter? psdTachieParameter; private readonly PsdTachieCharacterParameter? psdTachieCharacterParameter; private readonly PsdShapeParameter? psdShapeParameter; private string? psdFilePath; private PsdFile? psdFile; private IEnumerable defaultEnableLayers = Array.Empty(); private PsdFolder? psdFolderRoot; private PsdFolderViewModel? root; private bool disposedValue; public PsdFolderViewModel? Root { get { return root; } set { Set(ref root, value, "Root"); } } public event EventHandler? BeginEdit; public event EventHandler? EndEdit; public PsdLayerEditorViewModel(IPsdTachieParameter psdTachieParameter, PsdTachieCharacterParameter psdTachieCharacterParameter) { this.psdTachieParameter = psdTachieParameter; this.psdTachieCharacterParameter = psdTachieCharacterParameter; psdTachieCharacterParameter.PropertyChanged += PsdTachieCharacterParameter_PropertyChanged; psdTachieParameter.PropertyChanged += PsdTachieParameter_PropertyChanged; LoadItemsAsync(psdTachieCharacterParameter.FilePath); } public PsdLayerEditorViewModel(PsdShapeParameter psdShapeParameter) { this.psdShapeParameter = psdShapeParameter; psdShapeParameter.PropertyChanged += PsdShapeParameter_PropertyChanged; LoadItemsAsync(psdShapeParameter.FilePath); } private async void PsdShapeParameter_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == "FilePath") { LoadItemsAsync(psdShapeParameter.FilePath); } string propertyName = e.PropertyName; if ((propertyName == "EnableLayers" || propertyName == "EnableLayersFilePath") ? true : false) { await LoadItemsAsync(psdShapeParameter.FilePath); UpdateViewModels(); } } private void PsdTachieCharacterParameter_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == "FilePath") { LoadItemsAsync(psdTachieCharacterParameter.FilePath); } } private async void PsdTachieParameter_PropertyChanged(object? sender, PropertyChangedEventArgs e) { string propertyName = e.PropertyName; if ((propertyName == "FilePath" || propertyName == "EnableLayers") ? true : false) { await LoadItemsAsync(psdTachieCharacterParameter.FilePath); UpdateViewModels(); } } private async Task LoadItemsAsync(string? file) { await semaphore.WaitAsync(); try { if (psdFilePath == file) { return; } if (Root != null) { UnsubscribeFolder(Root); } if (psdFile != null) { disposer.RemoveAndDispose(ref psdFile); } Root = null; psdFolderRoot = null; defaultEnableLayers = Array.Empty(); psdFilePath = file; if (File.Exists(psdFilePath)) { psdFile = await Task.Run(() => new PsdFile(psdFilePath)); disposer.Collect(psdFile); PsdFolder root = PsdFolder.Parse(psdFile); psdFolderRoot = root; defaultEnableLayers = root.GetEnableItems().ToList(); Root = await Task.Run(() => new PsdFolderViewModel(root, PsdFileSettings.LoadFromPsdFilePath(psdFilePath), new ShiftLayerCommand(), new SwitchLayerCommand())); UpdateViewModels(); SubscribeFolder(Root); } } catch (Exception ex) { psdFilePath = string.Empty; if (psdFile != null) { disposer.RemoveAndDispose(ref psdFile); } defaultEnableLayers = Array.Empty(); Root = null; AddError("Root", Texts.LoadPsdFileFailedMessage + "\r\n" + ex.Message); } finally { semaphore.Release(); } } private void UpdateViewModels() { if (psdTachieParameter != null && psdTachieParameter.FilePath == psdFilePath) { Root?.UpdateEnable(Resolve(psdTachieParameter.EnableLayers, psdTachieParameter.EnableLayerPaths)); } else if (psdShapeParameter != null && psdShapeParameter.EnableLayersFilePath == psdFilePath) { Root?.UpdateEnable(Resolve(psdShapeParameter.EnableLayers, psdShapeParameter.EnableLayerPaths)); } else { Root?.UpdateEnable(defaultEnableLayers); } } private IEnumerable Resolve(ImmutableList ids, ImmutableList paths) { if (psdFolderRoot == null) { return ids; } return psdFolderRoot.ResolveLayers(ids, paths); } private void SubscribeFolder(PsdFolderViewModel folder) { folder.EnableChanged += Item_EnableChanged; foreach (IPsdItemViewModel item in folder.Items) { if (item is PsdLayerViewModel psdLayerViewModel) { psdLayerViewModel.EnableChanged += Item_EnableChanged; } else if (item is PsdFolderViewModel folder2) { SubscribeFolder(folder2); } } } private void UnsubscribeFolder(PsdFolderViewModel folder) { folder.EnableChanged -= Item_EnableChanged; foreach (IPsdItemViewModel item in folder.Items) { if (item is PsdLayerViewModel psdLayerViewModel) { psdLayerViewModel.EnableChanged -= Item_EnableChanged; } else if (item is PsdFolderViewModel folder2) { UnsubscribeFolder(folder2); } } } private void Item_EnableChanged(object? sender, EventArgs e) { if (Root == null) { return; } this.BeginEdit?.Invoke(this, EventArgs.Empty); List<(string, string)> source = Root.GetEnableItemsWithPath().ToList(); if (psdTachieParameter != null) { psdTachieParameter.EnableLayerPaths = ImmutableList.Create(new ReadOnlySpan(source.Select<(string, string), string>(((string Id, string Path) x) => x.Path).ToArray())); psdTachieParameter.EnableLayers = ImmutableList.Create(new ReadOnlySpan(source.Select<(string, string), string>(((string Id, string Path) x) => x.Id).ToArray())); psdTachieParameter.FilePath = psdFilePath; } else if (psdShapeParameter != null) { psdShapeParameter.EnableLayerPaths = ImmutableList.Create(new ReadOnlySpan(source.Select<(string, string), string>(((string Id, string Path) x) => x.Path).ToArray())); psdShapeParameter.EnableLayers = ImmutableList.Create(new ReadOnlySpan(source.Select<(string, string), string>(((string Id, string Path) x) => x.Id).ToArray())); psdShapeParameter.EnableLayersFilePath = psdFilePath; } this.EndEdit?.Invoke(this, EventArgs.Empty); } private void Release() { PsdTachieCharacterParameter? obj = psdTachieCharacterParameter; if (obj != null) { obj.PropertyChanged -= PsdTachieCharacterParameter_PropertyChanged; } IPsdTachieParameter? obj2 = psdTachieParameter; if (obj2 != null) { obj2.PropertyChanged -= PsdTachieParameter_PropertyChanged; } PsdShapeParameter? obj3 = psdShapeParameter; if (obj3 != null) { obj3.PropertyChanged -= PsdShapeParameter_PropertyChanged; } if (Root != null) { UnsubscribeFolder(Root); } } protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { disposer.Dispose(); Release(); } disposedValue = true; } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } }