957 lines
35 KiB
C#
957 lines
35 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using Spine;
|
|
using Spine.Interfaces;
|
|
using SpineViewer.Models;
|
|
using System.Collections;
|
|
using System.Collections.Immutable;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Windows;
|
|
|
|
namespace SpineViewer.ViewModels.MainWindow
|
|
{
|
|
public class SpineObjectTabViewModel : ObservableObject
|
|
{
|
|
private SpineObjectModel[] _selectedObjects = [];
|
|
private readonly ObservableCollection<SkinViewModel> _skins = [];
|
|
private readonly ObservableCollection<SlotViewModel> _slots = [];
|
|
private readonly ObservableCollection<AnimationTrackViewModel> _animationTracks = [];
|
|
|
|
public static ImmutableArray<ISkeleton.Physics> PhysicsOptions { get; } = Enum.GetValues<ISkeleton.Physics>().ToImmutableArray();
|
|
|
|
public SpineObjectModel[] SelectedObjects
|
|
{
|
|
get => _selectedObjects;
|
|
set
|
|
{
|
|
if (ReferenceEquals(_selectedObjects, value)) return;
|
|
|
|
// 清空之前的所有内容
|
|
foreach (var obj in _selectedObjects)
|
|
{
|
|
obj.PropertyChanged -= SingleModel_PropertyChanged;
|
|
obj.TrackPropertyChanged -= SingleModel_TrackPropChanged;
|
|
}
|
|
_skins.Clear();
|
|
_slots.Clear();
|
|
_animationTracks.Clear();
|
|
|
|
// 生成新的内容
|
|
_selectedObjects = value ?? [];
|
|
if (_selectedObjects.Length > 0)
|
|
{
|
|
foreach (var obj in _selectedObjects)
|
|
{
|
|
obj.PropertyChanged += SingleModel_PropertyChanged;
|
|
obj.TrackPropertyChanged += SingleModel_TrackPropChanged;
|
|
}
|
|
|
|
IEnumerable<string> commonSkinNames = _selectedObjects[0].Skins;
|
|
foreach (var obj in _selectedObjects.Skip(1)) commonSkinNames = commonSkinNames.Intersect(obj.Skins);
|
|
foreach (var name in commonSkinNames.Order()) _skins.Add(new(name, _selectedObjects));
|
|
|
|
IEnumerable<string> commonSlotNames = _selectedObjects[0].SlotAttachments.Keys;
|
|
foreach (var obj in _selectedObjects.Skip(1)) commonSlotNames = commonSlotNames.Intersect(obj.SlotAttachments.Keys);
|
|
foreach (var name in commonSlotNames.Order()) _slots.Add(new(name, _selectedObjects));
|
|
|
|
IEnumerable<int> commonTrackIndices = _selectedObjects[0].GetTrackIndices();
|
|
foreach (var obj in _selectedObjects.Skip(1)) commonTrackIndices = commonTrackIndices.Intersect(obj.GetTrackIndices());
|
|
foreach (var idx in commonTrackIndices.Order()) _animationTracks.Add(new(idx, _selectedObjects));
|
|
}
|
|
|
|
OnPropertyChanged();
|
|
|
|
Cmd_AppendTrack.NotifyCanExecuteChanged();
|
|
|
|
OnPropertyChanged(nameof(Version));
|
|
OnPropertyChanged(nameof(AssetsDir));
|
|
OnPropertyChanged(nameof(SkelPath));
|
|
OnPropertyChanged(nameof(AtlasPath));
|
|
OnPropertyChanged(nameof(Name));
|
|
OnPropertyChanged(nameof(FileVersion));
|
|
|
|
OnPropertyChanged(nameof(IsShown));
|
|
OnPropertyChanged(nameof(UsePma));
|
|
OnPropertyChanged(nameof(Physics));
|
|
OnPropertyChanged(nameof(TimeScale));
|
|
|
|
OnPropertyChanged(nameof(Scale));
|
|
OnPropertyChanged(nameof(FlipX));
|
|
OnPropertyChanged(nameof(FlipY));
|
|
OnPropertyChanged(nameof(X));
|
|
OnPropertyChanged(nameof(Y));
|
|
|
|
OnPropertyChanged(nameof(DebugTexture));
|
|
OnPropertyChanged(nameof(DebugBounds));
|
|
OnPropertyChanged(nameof(DebugBones));
|
|
OnPropertyChanged(nameof(DebugRegions));
|
|
OnPropertyChanged(nameof(DebugMeshHulls));
|
|
OnPropertyChanged(nameof(DebugMeshes));
|
|
OnPropertyChanged(nameof(DebugBoundingBoxes));
|
|
OnPropertyChanged(nameof(DebugPaths));
|
|
OnPropertyChanged(nameof(DebugPoints));
|
|
OnPropertyChanged(nameof(DebugClippings));
|
|
}
|
|
}
|
|
|
|
public SpineVersion? Version
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].Version;
|
|
if (_selectedObjects.Skip(1).Any(it => it.Version != val)) return null;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
public string? AssetsDir
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].AssetsDir;
|
|
if (_selectedObjects.Skip(1).Any(it => it.AssetsDir != val)) return null;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
public string? SkelPath
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].SkelPath;
|
|
if (_selectedObjects.Skip(1).Any(it => it.SkelPath != val)) return null;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
public string? AtlasPath
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].AtlasPath;
|
|
if (_selectedObjects.Skip(1).Any(it => it.AtlasPath != val)) return null;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
public string? Name
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].Name;
|
|
if (_selectedObjects.Skip(1).Any(it => it.Name != val)) return null;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
public string? FileVersion
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].FileVersion;
|
|
if (_selectedObjects.Skip(1).Any(it => it.FileVersion != val)) return null;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
public bool? IsShown
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].IsShown;
|
|
if (_selectedObjects.Skip(1).Any(it => it.IsShown != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.IsShown = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? UsePma
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].UsePma;
|
|
if (_selectedObjects.Skip(1).Any(it => it.UsePma != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.UsePma = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public ISkeleton.Physics? Physics
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].Physics;
|
|
if (_selectedObjects.Skip(1).Any(it => it.Physics != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.Physics = (ISkeleton.Physics)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public float? TimeScale
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].TimeScale;
|
|
if (_selectedObjects.Skip(1).Any(it => it.TimeScale != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.TimeScale = (float)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public float? Scale
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].Scale;
|
|
if (_selectedObjects.Skip(1).Any(it => it.Scale != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.Scale = (float)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? FlipX
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].FlipX;
|
|
if (_selectedObjects.Skip(1).Any(it => it.FlipX != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.FlipX = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? FlipY
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].FlipY;
|
|
if (_selectedObjects.Skip(1).Any(it => it.FlipY != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.FlipY = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public float? X
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].X;
|
|
if (_selectedObjects.Skip(1).Any(it => it.X != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.X = (float)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public float? Y
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].Y;
|
|
if (_selectedObjects.Skip(1).Any(it => it.Y != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.Y = (float)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public ObservableCollection<SkinViewModel> Skins => _skins;
|
|
|
|
public RelayCommand<IList?> Cmd_EnableSkins => _cmd_EnableSkins ??= new (
|
|
args => { if (args is null) return; foreach (var s in args.OfType<SkinViewModel>()) s.Status = true; },
|
|
args => { return args is not null && args.OfType<SkinViewModel>().Any(); }
|
|
);
|
|
private RelayCommand<IList?> _cmd_EnableSkins;
|
|
|
|
public RelayCommand<IList?> Cmd_DisableSkins => _cmd_DisableSkins ??= new (
|
|
args => { if (args is null) return; foreach (var s in args.OfType<SkinViewModel>()) s.Status = false; },
|
|
args => { return args is not null && args.OfType<SkinViewModel>().Any(); }
|
|
);
|
|
private RelayCommand<IList?> _cmd_DisableSkins;
|
|
|
|
public ObservableCollection<SlotViewModel> Slots => _slots;
|
|
|
|
public RelayCommand<IList?> Cmd_EnableSlots => _cmd_EnableSlots ??= new (
|
|
args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = true; },
|
|
args => { return args is not null && args.OfType<SlotViewModel>().Any(); }
|
|
);
|
|
private RelayCommand<IList?> _cmd_EnableSlots;
|
|
|
|
public RelayCommand<IList?> Cmd_DisableSlots => _cmd_DisableSlots ??= new (
|
|
args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = false; },
|
|
args => { return args is not null && args.OfType<SlotViewModel>().Any(); }
|
|
);
|
|
private RelayCommand<IList?> _cmd_DisableSlots;
|
|
|
|
public ObservableCollection<AnimationTrackViewModel> AnimationTracks => _animationTracks;
|
|
|
|
public RelayCommand Cmd_AppendTrack => _cmd_AppendTrack ??= new(
|
|
() =>
|
|
{
|
|
if (_selectedObjects.Length != 1) return;
|
|
var sp = _selectedObjects[0];
|
|
if (sp.Animations.Length <= 0) return;
|
|
sp.SetAnimation(sp.GetTrackIndices().LastOrDefault(-1) + 1, sp.Animations[0]);
|
|
},
|
|
() => { return _selectedObjects.Length == 1; }
|
|
);
|
|
private RelayCommand? _cmd_AppendTrack;
|
|
|
|
public RelayCommand<IList?> Cmd_InsertTrack => _cmd_InsertTrack ??= new(
|
|
args =>
|
|
{
|
|
if (_selectedObjects.Length != 1) return;
|
|
var sp = _selectedObjects[0];
|
|
|
|
if (sp.Animations.Length <= 0) return;
|
|
if (args is null) return;
|
|
if (args.Count != 1) return;
|
|
if (args[0] is not AnimationTrackViewModel vm) return;
|
|
var idx = vm.TrackIndex;
|
|
|
|
if (idx <= 0) return;
|
|
if (sp.GetTrackIndices().Contains(idx - 1)) return;
|
|
sp.SetAnimation(idx - 1, sp.Animations[0]);
|
|
},
|
|
args =>
|
|
{
|
|
if (_selectedObjects.Length != 1) return false;
|
|
var sp = _selectedObjects[0];
|
|
|
|
if (sp.Animations.Length <= 0) return false;
|
|
if (args is null) return false;
|
|
if (args.Count != 1) return false;
|
|
if (args[0] is not AnimationTrackViewModel vm) return false;
|
|
var idx = vm.TrackIndex;
|
|
|
|
if (idx <= 0) return false;
|
|
if (sp.GetTrackIndices().Contains(idx - 1)) return false;
|
|
return true;
|
|
}
|
|
);
|
|
private RelayCommand<IList?>? _cmd_InsertTrack;
|
|
|
|
public RelayCommand<IList?>? Cmd_ClearTrack => _cmd_ClearTrack ??= new(
|
|
args =>
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (args is null) return;
|
|
if (args.Count <= 0) return;
|
|
|
|
foreach (var vm in args.OfType<AnimationTrackViewModel>())
|
|
foreach (var sp in _selectedObjects)
|
|
sp.ClearTrack(vm.TrackIndex);
|
|
},
|
|
args =>
|
|
{
|
|
if (_selectedObjects.Length <= 0) return false;
|
|
if (args is null) return false;
|
|
if (args.Count <= 0) return false;
|
|
return true;
|
|
}
|
|
);
|
|
private RelayCommand<IList?>? _cmd_ClearTrack;
|
|
|
|
public bool? DebugTexture
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].DebugTexture;
|
|
if (_selectedObjects.Skip(1).Any(it => it.DebugTexture != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.DebugTexture = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? DebugBounds
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].DebugBounds;
|
|
if (_selectedObjects.Skip(1).Any(it => it.DebugBounds != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.DebugBounds = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? DebugBones
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].DebugBones;
|
|
if (_selectedObjects.Skip(1).Any(it => it.DebugBones != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.DebugBones = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? DebugRegions
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].DebugRegions;
|
|
if (_selectedObjects.Skip(1).Any(it => it.DebugRegions != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.DebugRegions = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? DebugMeshHulls
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].DebugMeshHulls;
|
|
if (_selectedObjects.Skip(1).Any(it => it.DebugMeshHulls != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.DebugMeshHulls = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? DebugMeshes
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].DebugMeshes;
|
|
if (_selectedObjects.Skip(1).Any(it => it.DebugMeshes != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.DebugMeshes = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? DebugBoundingBoxes
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].DebugBoundingBoxes;
|
|
if (_selectedObjects.Skip(1).Any(it => it.DebugBoundingBoxes != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.DebugBoundingBoxes = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? DebugPaths
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].DebugPaths;
|
|
if (_selectedObjects.Skip(1).Any(it => it.DebugPaths != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.DebugPaths = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? DebugPoints
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].DebugPoints;
|
|
if (_selectedObjects.Skip(1).Any(it => it.DebugPoints != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.DebugPoints = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? DebugClippings
|
|
{
|
|
get
|
|
{
|
|
if (_selectedObjects.Length <= 0) return null;
|
|
var val = _selectedObjects[0].DebugClippings;
|
|
if (_selectedObjects.Skip(1).Any(it => it.DebugClippings != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_selectedObjects.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _selectedObjects) sp.DebugClippings = (bool)value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
private static readonly Dictionary<string, string> _singleModelPropertyMap = new()
|
|
{
|
|
{ nameof(SpineObjectModel.IsShown), nameof(IsShown) },
|
|
{ nameof(SpineObjectModel.UsePma), nameof(UsePma) },
|
|
{ nameof(SpineObjectModel.Physics), nameof(Physics) },
|
|
{ nameof(SpineObjectModel.TimeScale), nameof(TimeScale) },
|
|
|
|
{ nameof(SpineObjectModel.Scale), nameof(Scale) },
|
|
{ nameof(SpineObjectModel.FlipX), nameof(FlipX) },
|
|
{ nameof(SpineObjectModel.FlipY), nameof(FlipY) },
|
|
{ nameof(SpineObjectModel.X), nameof(X) },
|
|
{ nameof(SpineObjectModel.Y), nameof(Y) },
|
|
|
|
// Skins 变化在 SkinViewModel 中监听
|
|
// Slots 变化在 SlotAttachmentViewModel 中监听
|
|
// AnimationTracks 变化在 AnimationTrackViewModel 中监听
|
|
|
|
{ nameof(SpineObjectModel.DebugTexture), nameof(DebugTexture) },
|
|
{ nameof(SpineObjectModel.DebugBounds), nameof(DebugBounds) },
|
|
{ nameof(SpineObjectModel.DebugBones), nameof(DebugBones) },
|
|
{ nameof(SpineObjectModel.DebugRegions), nameof(DebugRegions) },
|
|
{ nameof(SpineObjectModel.DebugMeshHulls), nameof(DebugMeshHulls) },
|
|
{ nameof(SpineObjectModel.DebugMeshes), nameof(DebugMeshes) },
|
|
{ nameof(SpineObjectModel.DebugBoundingBoxes), nameof(DebugBoundingBoxes) },
|
|
{ nameof(SpineObjectModel.DebugPaths), nameof(DebugPaths) },
|
|
{ nameof(SpineObjectModel.DebugPoints), nameof(DebugPoints) },
|
|
{ nameof(SpineObjectModel.DebugClippings), nameof(DebugClippings) },
|
|
};
|
|
|
|
/// <summary>
|
|
/// 监听单个模型属性发生变化, 则更新聚合属性值
|
|
/// </summary>
|
|
private void SingleModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (_singleModelPropertyMap.TryGetValue(e.PropertyName, out var targetProperty))
|
|
{
|
|
OnPropertyChanged(targetProperty);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 监听单个模型动画轨道发生变化, 则重建聚合后的动画列表
|
|
/// </summary>
|
|
private void SingleModel_TrackPropChanged(object? sender, TrackPropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.AnimationName))
|
|
{
|
|
// XXX: 这里应该有更好的实现, 当 e.AnimationName == null 的时候代表删除轨道需要重新构建列表
|
|
// 但是目前无法识别是否增加了轨道, 因此总是重建列表
|
|
|
|
// 由于某些原因, 直接使用 Clear 会和 UI 逻辑冲突产生报错, 因此需要放到 Dispatcher 里延迟执行
|
|
Application.Current.Dispatcher.BeginInvoke(
|
|
() =>
|
|
{
|
|
_animationTracks.Clear();
|
|
IEnumerable<int> commonTrackIndices = _selectedObjects[0].GetTrackIndices();
|
|
foreach (var obj in _selectedObjects.Skip(1)) commonTrackIndices = commonTrackIndices.Intersect(obj.GetTrackIndices());
|
|
foreach (var idx in commonTrackIndices) _animationTracks.Add(new(idx, _selectedObjects));
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
public class SkinViewModel : ObservableObject
|
|
{
|
|
private readonly SpineObjectModel[] _spines;
|
|
private readonly string _name;
|
|
|
|
public SkinViewModel(string name, SpineObjectModel[] spines)
|
|
{
|
|
_spines = spines;
|
|
_name = name;
|
|
|
|
// 使用弱引用, 则此 ViewModel 被释放时无需显式退订事件
|
|
foreach (var sp in _spines)
|
|
{
|
|
WeakEventManager<SpineObjectModel, SkinStatusChangedEventArgs>.AddHandler(
|
|
sp,
|
|
nameof(sp.SkinStatusChanged),
|
|
SingleModel_SkinStatusChanged
|
|
);
|
|
}
|
|
}
|
|
|
|
public string Name => _name;
|
|
|
|
public bool? Status
|
|
{
|
|
get
|
|
{
|
|
if (_spines.Length <= 0) return null;
|
|
var val = _spines[0].GetSkinStatus(_name);
|
|
if (_spines.Skip(1).Any(it => it.GetSkinStatus(_name) != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_spines.Length <= 0) return;
|
|
if (value is null) return;
|
|
bool changed = false;
|
|
foreach (var sp in _spines) if (sp.SetSkinStatus(_name, (bool)value)) changed = true;
|
|
if (changed) OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
private void SingleModel_SkinStatusChanged(object? sender, SkinStatusChangedEventArgs e)
|
|
{
|
|
if (e.Name == _name) OnPropertyChanged(nameof(Status));
|
|
}
|
|
}
|
|
|
|
public class SlotViewModel : ObservableObject
|
|
{
|
|
private readonly SpineObjectModel[] _spines;
|
|
private readonly string[] _attachmentNames = [];
|
|
private readonly string _slotName;
|
|
|
|
public SlotViewModel(string slotName, SpineObjectModel[] spines)
|
|
{
|
|
_spines = spines;
|
|
_slotName = slotName;
|
|
|
|
if (_spines.Length > 0)
|
|
{
|
|
IEnumerable<string> attachmentNames = _spines[0].SlotAttachments[_slotName];
|
|
foreach (var sp in _spines.Skip(1))
|
|
attachmentNames = attachmentNames.Union(sp.SlotAttachments[_slotName]);
|
|
_attachmentNames = attachmentNames.ToArray();
|
|
}
|
|
|
|
// 使用弱引用, 则此 ViewModel 被释放时无需显式退订事件
|
|
foreach (var sp in _spines)
|
|
{
|
|
WeakEventManager<SpineObjectModel, SlotVisibleChangedEventArgs>.AddHandler(
|
|
sp,
|
|
nameof(sp.SlotVisibleChanged),
|
|
SingleModel_SlotVisibleChanged
|
|
);
|
|
WeakEventManager<SpineObjectModel, SlotAttachmentChangedEventArgs>.AddHandler(
|
|
sp,
|
|
nameof(sp.SlotAttachmentChanged),
|
|
SingleModel_SlotAttachmentChanged
|
|
);
|
|
WeakEventManager<SpineObjectModel, SkinStatusChangedEventArgs>.AddHandler(
|
|
sp,
|
|
nameof(sp.SkinStatusChanged),
|
|
SingleModel_SkinStatusChanged
|
|
);
|
|
}
|
|
}
|
|
|
|
public ReadOnlyCollection<string> AttachmentNames => _attachmentNames.AsReadOnly();
|
|
|
|
public string SlotName => _slotName;
|
|
|
|
public string? AttachmentName
|
|
{
|
|
get
|
|
{
|
|
if (_spines.Length <= 0) return null;
|
|
var val = _spines[0].GetAttachment(_slotName);
|
|
if (_spines.Skip(1).Any(it => it.GetAttachment(_slotName) != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_spines.Length <= 0) return;
|
|
bool changed = false;
|
|
foreach (var sp in _spines) if (sp.SetAttachment(_slotName, value)) changed = true;
|
|
if (changed) OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public bool? Visible
|
|
{
|
|
get
|
|
{
|
|
if (_spines.Length <= 0) return null;
|
|
var val = _spines[0].GetSlotVisible(_slotName);
|
|
if (_spines.Skip(1).Any(it => it.GetSlotVisible(_slotName) != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_spines.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _spines) sp.SetSlotVisible(_slotName, (bool)value);
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
private void SingleModel_SlotVisibleChanged(object? sender, SlotVisibleChangedEventArgs e)
|
|
{
|
|
if (e.SlotName == _slotName) OnPropertyChanged(nameof(Visible));
|
|
}
|
|
|
|
private void SingleModel_SlotAttachmentChanged(object? sender, SlotAttachmentChangedEventArgs e)
|
|
{
|
|
if (e.SlotName == _slotName) OnPropertyChanged(nameof(AttachmentName));
|
|
}
|
|
|
|
private void SingleModel_SkinStatusChanged(object? sender, SkinStatusChangedEventArgs e)
|
|
{
|
|
// 如果皮肤发生改变, 则直接触发附件属性变化事件
|
|
OnPropertyChanged(nameof(AttachmentName));
|
|
}
|
|
}
|
|
|
|
public class AnimationTrackViewModel : ObservableObject
|
|
{
|
|
private readonly SpineObjectModel[] _spines;
|
|
private readonly string[] _animationNames = [];
|
|
private readonly int _trackIndex;
|
|
|
|
public AnimationTrackViewModel(int trackIndex, SpineObjectModel[] spines)
|
|
{
|
|
_spines = spines;
|
|
_trackIndex = trackIndex;
|
|
|
|
if (_spines.Length > 0)
|
|
{
|
|
IEnumerable<string> animationNames = _spines[0].Animations;
|
|
foreach (var sp in _spines.Skip(1))
|
|
animationNames = animationNames.Union(sp.Animations);
|
|
_animationNames = animationNames.ToArray();
|
|
}
|
|
|
|
// 使用弱引用, 则此 ViewModel 被释放时无需显式退订事件
|
|
foreach (var sp in _spines)
|
|
{
|
|
WeakEventManager<SpineObjectModel, TrackPropertyChangedEventArgs>.AddHandler(
|
|
sp,
|
|
nameof(sp.TrackPropertyChanged),
|
|
SingleModel_TrackPropChanged
|
|
);
|
|
}
|
|
}
|
|
|
|
public ReadOnlyCollection<string> AnimationNames => _animationNames.AsReadOnly();
|
|
|
|
public int TrackIndex => _trackIndex;
|
|
|
|
public float? AnimationDuration
|
|
{
|
|
get
|
|
{
|
|
if (_spines.Length <= 0) return null;
|
|
var ani = _spines[0].GetAnimation(_trackIndex);
|
|
if (ani is null) return null;
|
|
var val = _spines[0].GetAnimationDuration(ani);
|
|
foreach (var sp in _spines.Skip(1))
|
|
{
|
|
var a = sp.GetAnimation(_trackIndex);
|
|
if (a is null) return null;
|
|
if (sp.GetAnimationDuration(a) != val) return null;
|
|
}
|
|
return val;
|
|
}
|
|
}
|
|
|
|
public string? AnimationName
|
|
{
|
|
get
|
|
{
|
|
// XXX: 空轨道和多选不相同都会返回 null
|
|
if (_spines.Length <= 0) return null;
|
|
var val = _spines[0].GetAnimation(_trackIndex);
|
|
if (_spines.Skip(1).Any(it => it.GetAnimation(_trackIndex) != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_spines.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _spines) sp.SetAnimation(_trackIndex, value);
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(AnimationDuration));
|
|
}
|
|
}
|
|
|
|
public float? TrackTimeScale
|
|
{
|
|
get
|
|
{
|
|
// XXX: 空轨道和多选不相同都会返回 null
|
|
if (_spines.Length <= 0) return null;
|
|
var val = _spines[0].GetTrackTimeScale(_trackIndex);
|
|
if (_spines.Skip(1).Any(it => it.GetTrackTimeScale(_trackIndex) != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_spines.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _spines) sp.SetTrackTimeScale(_trackIndex, (float)value);
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
public float? TrackAlpha
|
|
{
|
|
get
|
|
{
|
|
// XXX: 空轨道和多选不相同都会返回 null
|
|
if (_spines.Length <= 0) return null;
|
|
var val = _spines[0].GetTrackAlpha(_trackIndex);
|
|
if (_spines.Skip(1).Any(it => it.GetTrackAlpha(_trackIndex) != val)) return null;
|
|
return val;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (_spines.Length <= 0) return;
|
|
if (value is null) return;
|
|
foreach (var sp in _spines) sp.SetTrackAlpha(_trackIndex, (float)value);
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
private void SingleModel_TrackPropChanged(object? sender, TrackPropertyChangedEventArgs e)
|
|
{
|
|
if (e.TrackIndex == _trackIndex)
|
|
{
|
|
if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.AnimationName)) OnPropertyChanged(nameof(AnimationName));
|
|
else if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.TimeScale)) OnPropertyChanged(nameof(TrackTimeScale));
|
|
else if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.Alpha)) OnPropertyChanged(nameof(TrackAlpha));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |