整理结构
This commit is contained in:
@@ -34,14 +34,14 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
if (SpineHelper.CommonSkelSuffix.Contains(Path.GetExtension(path).ToLower()))
|
if (SpineUtils.CommonSkelSuffix.Contains(Path.GetExtension(path).ToLower()))
|
||||||
listBox.Items.Add(Path.GetFullPath(path));
|
listBox.Items.Add(Path.GetFullPath(path));
|
||||||
}
|
}
|
||||||
else if (Directory.Exists(path))
|
else if (Directory.Exists(path))
|
||||||
{
|
{
|
||||||
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
||||||
{
|
{
|
||||||
if (SpineHelper.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
|
if (SpineUtils.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
|
||||||
listBox.Items.Add(file);
|
listBox.Items.Add(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
||||||
{
|
{
|
||||||
if (SpineHelper.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
|
if (SpineUtils.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
|
||||||
listBox.Items.Add(file);
|
listBox.Items.Add(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace SpineViewer.Controls
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用于属性页显示模型参数的包装类
|
/// 用于属性页显示模型参数的包装类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Dictionary<string, SpineWrapper> spinePropertyWrappers = [];
|
private readonly Dictionary<string, SpineObjectProperty> spinePropertyWrappers = [];
|
||||||
|
|
||||||
public SpineListView()
|
public SpineListView()
|
||||||
{
|
{
|
||||||
@@ -205,14 +205,14 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
if (SpineHelper.CommonSkelSuffix.Contains(Path.GetExtension(path).ToLower()))
|
if (SpineUtils.CommonSkelSuffix.Contains(Path.GetExtension(path).ToLower()))
|
||||||
validPaths.Add(path);
|
validPaths.Add(path);
|
||||||
}
|
}
|
||||||
else if (Directory.Exists(path))
|
else if (Directory.Exists(path))
|
||||||
{
|
{
|
||||||
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
||||||
{
|
{
|
||||||
if (SpineHelper.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
|
if (SpineUtils.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
|
||||||
validPaths.Add(file);
|
validPaths.Add(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace SpineViewer.Controls
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置选中的对象列表, 可以赋值 null 来清空选中, 行为与 PropertyGrid.SelectedObjects 类似
|
/// 设置选中的对象列表, 可以赋值 null 来清空选中, 行为与 PropertyGrid.SelectedObjects 类似
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SpineWrapper[] SelectedSpines
|
public SpineObjectProperty[] SelectedSpines
|
||||||
{
|
{
|
||||||
get => selectedSpines ?? [];
|
get => selectedSpines ?? [];
|
||||||
set
|
set
|
||||||
@@ -49,14 +49,14 @@ namespace SpineViewer.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private SpineWrapper[]? selectedSpines = null;
|
private SpineObjectProperty[]? selectedSpines = null;
|
||||||
|
|
||||||
private void contextMenuStrip_Skin_Opening(object sender, CancelEventArgs e)
|
private void contextMenuStrip_Skin_Opening(object sender, CancelEventArgs e)
|
||||||
{
|
{
|
||||||
if (selectedSpines?.Length == 1)
|
if (selectedSpines?.Length == 1)
|
||||||
{
|
{
|
||||||
toolStripMenuItem_AddSkin.Enabled = true;
|
toolStripMenuItem_AddSkin.Enabled = true;
|
||||||
toolStripMenuItem_RemoveSkin.Enabled = propertyGrid_Skin.SelectedGridItem.Value is SkinWrapper;
|
toolStripMenuItem_RemoveSkin.Enabled = propertyGrid_Skin.SelectedGridItem.Value is SkinNameProperty;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -70,7 +70,7 @@ namespace SpineViewer.Controls
|
|||||||
if (selectedSpines?.Length == 1)
|
if (selectedSpines?.Length == 1)
|
||||||
{
|
{
|
||||||
toolStripMenuItem_AddAnimation.Enabled = true;
|
toolStripMenuItem_AddAnimation.Enabled = true;
|
||||||
toolStripMenuItem_RemoveAnimation.Enabled = propertyGrid_Animation.SelectedGridItem.Value is TrackWrapper;
|
toolStripMenuItem_RemoveAnimation.Enabled = propertyGrid_Animation.SelectedGridItem.Value is TrackAnimationProperty;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -99,7 +99,7 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
if (selectedSpines?.Length != 1) return;
|
if (selectedSpines?.Length != 1) return;
|
||||||
|
|
||||||
if (propertyGrid_Skin.SelectedGridItem.Value is SkinWrapper wrapper)
|
if (propertyGrid_Skin.SelectedGridItem.Value is SkinNameProperty wrapper)
|
||||||
{
|
{
|
||||||
selectedSpines[0].Skin.Spine.UnloadSkin(wrapper.Index);
|
selectedSpines[0].Skin.Spine.UnloadSkin(wrapper.Index);
|
||||||
propertyGrid_Skin.Refresh();
|
propertyGrid_Skin.Refresh();
|
||||||
@@ -119,7 +119,7 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
if (selectedSpines?.Length != 1) return;
|
if (selectedSpines?.Length != 1) return;
|
||||||
|
|
||||||
if (propertyGrid_Animation.SelectedGridItem.Value is TrackWrapper wrapper)
|
if (propertyGrid_Animation.SelectedGridItem.Value is TrackAnimationProperty wrapper)
|
||||||
{
|
{
|
||||||
selectedSpines[0].Animation.Spine.ClearTrack(wrapper.Index);
|
selectedSpines[0].Animation.Spine.ClearTrack(wrapper.Index);
|
||||||
propertyGrid_Animation.Refresh();
|
propertyGrid_Animation.Refresh();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace SpineViewer.Dialogs
|
|||||||
public BatchOpenSpineDialog()
|
public BatchOpenSpineDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
comboBox_Version.DataSource = SpineHelper.Names.ToList();
|
comboBox_Version.DataSource = SpineUtils.Names.ToList();
|
||||||
comboBox_Version.DisplayMember = "Value";
|
comboBox_Version.DisplayMember = "Value";
|
||||||
comboBox_Version.ValueMember = "Key";
|
comboBox_Version.ValueMember = "Key";
|
||||||
comboBox_Version.SelectedValue = SpineVersion.Auto;
|
comboBox_Version.SelectedValue = SpineVersion.Auto;
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
comboBox_SourceVersion.DataSource = SpineHelper.Names.ToList();
|
comboBox_SourceVersion.DataSource = SpineUtils.Names.ToList();
|
||||||
comboBox_SourceVersion.DisplayMember = "Value";
|
comboBox_SourceVersion.DisplayMember = "Value";
|
||||||
comboBox_SourceVersion.ValueMember = "Key";
|
comboBox_SourceVersion.ValueMember = "Key";
|
||||||
comboBox_SourceVersion.SelectedValue = SpineVersion.Auto;
|
comboBox_SourceVersion.SelectedValue = SpineVersion.Auto;
|
||||||
|
|
||||||
// 目标版本不包含自动
|
// 目标版本不包含自动
|
||||||
var versionsWithoutAuto = SpineHelper.Names.ToDictionary();
|
var versionsWithoutAuto = SpineUtils.Names.ToDictionary();
|
||||||
versionsWithoutAuto.Remove(SpineVersion.Auto);
|
versionsWithoutAuto.Remove(SpineVersion.Auto);
|
||||||
comboBox_TargetVersion.DataSource = versionsWithoutAuto.ToList();
|
comboBox_TargetVersion.DataSource = versionsWithoutAuto.ToList();
|
||||||
comboBox_TargetVersion.DisplayMember = "Value";
|
comboBox_TargetVersion.DisplayMember = "Value";
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace SpineViewer.Dialogs
|
|||||||
public OpenSpineDialog()
|
public OpenSpineDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
comboBox_Version.DataSource = SpineHelper.Names.ToList();
|
comboBox_Version.DataSource = SpineUtils.Names.ToList();
|
||||||
comboBox_Version.DisplayMember = "Value";
|
comboBox_Version.DisplayMember = "Value";
|
||||||
comboBox_Version.ValueMember = "Key";
|
comboBox_Version.ValueMember = "Key";
|
||||||
comboBox_Version.SelectedValue = SpineVersion.Auto;
|
comboBox_Version.SelectedValue = SpineVersion.Auto;
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ namespace SpineViewer
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
srcCvter = SkeletonConverter.New(SpineHelper.GetVersion(skelPath));
|
srcCvter = SkeletonConverter.New(SpineUtils.GetVersion(skelPath));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace SpineViewer.Spine
|
|||||||
skelPath = Path.GetFullPath(skelPath);
|
skelPath = Path.GetFullPath(skelPath);
|
||||||
atlasPath = Path.GetFullPath(atlasPath);
|
atlasPath = Path.GetFullPath(atlasPath);
|
||||||
|
|
||||||
if (version == SpineVersion.Auto) version = SpineHelper.GetVersion(skelPath);
|
if (version == SpineVersion.Auto) version = SpineUtils.GetVersion(skelPath);
|
||||||
if (!File.Exists(atlasPath)) throw new FileNotFoundException($"atlas file {atlasPath} not found");
|
if (!File.Exists(atlasPath)) throw new FileNotFoundException($"atlas file {atlasPath} not found");
|
||||||
return New(version, [skelPath, atlasPath]).PostInit();
|
return New(version, [skelPath, atlasPath]).PostInit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,35 +12,10 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace SpineViewer.Spine
|
namespace SpineViewer.Spine
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 支持的 Spine 版本
|
|
||||||
/// </summary>
|
|
||||||
public enum SpineVersion
|
|
||||||
{
|
|
||||||
[Description("<Auto>")] Auto = 0x0000,
|
|
||||||
[Description("2.1.x")] V21 = 0x0201,
|
|
||||||
[Description("3.6.x")] V36 = 0x0306,
|
|
||||||
[Description("3.7.x")] V37 = 0x0307,
|
|
||||||
[Description("3.8.x")] V38 = 0x0308,
|
|
||||||
[Description("4.0.x")] V40 = 0x0400,
|
|
||||||
[Description("4.1.x")] V41 = 0x0401,
|
|
||||||
[Description("4.2.x")] V42 = 0x0402,
|
|
||||||
[Description("4.3.x")] V43 = 0x0403,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Spine 实现类标记
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
|
||||||
public class SpineImplementationAttribute(SpineVersion version) : Attribute, IImplementationKey<SpineVersion>
|
|
||||||
{
|
|
||||||
public SpineVersion ImplementationKey { get; private set; } = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Spine 版本静态辅助类
|
/// Spine 版本静态辅助类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class SpineHelper
|
public static class SpineUtils
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 版本名称
|
/// 版本名称
|
||||||
@@ -53,7 +28,7 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Dictionary<SpineVersion, string> runtimes = [];
|
private static readonly Dictionary<SpineVersion, string> runtimes = [];
|
||||||
|
|
||||||
static SpineHelper()
|
static SpineUtils()
|
||||||
{
|
{
|
||||||
// 初始化缓存
|
// 初始化缓存
|
||||||
foreach (var value in Enum.GetValues(typeof(SpineVersion)))
|
foreach (var value in Enum.GetValues(typeof(SpineVersion)))
|
||||||
52
SpineViewer/Spine/SpineVersion.cs
Normal file
52
SpineViewer/Spine/SpineVersion.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using SpineViewer.Utils;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpineViewer.Spine
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 支持的 Spine 版本
|
||||||
|
/// </summary>
|
||||||
|
public enum SpineVersion
|
||||||
|
{
|
||||||
|
[Description("<Auto>")] Auto = 0x0000,
|
||||||
|
[Description("2.1.x")] V21 = 0x0201,
|
||||||
|
[Description("3.6.x")] V36 = 0x0306,
|
||||||
|
[Description("3.7.x")] V37 = 0x0307,
|
||||||
|
[Description("3.8.x")] V38 = 0x0308,
|
||||||
|
[Description("4.0.x")] V40 = 0x0400,
|
||||||
|
[Description("4.1.x")] V41 = 0x0401,
|
||||||
|
[Description("4.2.x")] V42 = 0x0402,
|
||||||
|
[Description("4.3.x")] V43 = 0x0403,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spine 实现类标记
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||||
|
public class SpineImplementationAttribute(SpineVersion version) : Attribute, IImplementationKey<SpineVersion>
|
||||||
|
{
|
||||||
|
public SpineVersion ImplementationKey { get; private set; } = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SpineVersionConverter : EnumConverter
|
||||||
|
{
|
||||||
|
public SpineVersionConverter() : base(typeof(SpineVersion)) { }
|
||||||
|
|
||||||
|
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type? destinationType)
|
||||||
|
{
|
||||||
|
if (destinationType == typeof(string) && value is SpineVersion version)
|
||||||
|
return version.GetName();
|
||||||
|
return base.ConvertTo(context, culture, value, destinationType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,85 +9,11 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace SpineViewer.Spine.SpineView
|
namespace SpineViewer.Spine.SpineView
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 对轨道索引属性的包装类, 能够在面板上显示例如时长的属性, 但是处理该属性时按字符串去处理, 例如 ToString 和判断对象相等都是用动画名称实现逻辑
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="spine"></param>
|
|
||||||
/// <param name="i"></param>
|
|
||||||
[TypeConverter(typeof(TrackWrapperConverter))]
|
|
||||||
public class TrackWrapper(SpineObject spine, int i)
|
|
||||||
{
|
|
||||||
private readonly SpineObject spine = spine;
|
|
||||||
|
|
||||||
[Browsable(false)]
|
|
||||||
public int Index { get; } = i;
|
|
||||||
|
|
||||||
[DisplayName("时长")]
|
|
||||||
public float Duration => spine.GetAnimationDuration(spine.GetAnimation(Index));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 实现了默认的转为字符串的方式
|
|
||||||
/// </summary>
|
|
||||||
public override string ToString() => spine.GetAnimation(Index);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 影响了属性面板的判断, 当动画名称相同的时候认为两个对象是相同的, 这样属性面板可以在多选的时候正确显示相同取值的内容
|
|
||||||
/// </summary>
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
if (obj is TrackWrapper) return ToString() == obj.ToString();
|
|
||||||
return base.Equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 哈希码需要和 Equals 行为类似
|
|
||||||
/// </summary>
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(TrackWrapper).FullName.GetHashCode(), ToString().GetHashCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用于在 PropertyGrid 上显示 Spine 动画列表的包装类
|
/// 用于在 PropertyGrid 上显示 Spine 动画列表的包装类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SpineAnimationWrapper(SpineObject spine) : ICustomTypeDescriptor
|
public class SpineAnimationProperty(SpineObject spine) : ICustomTypeDescriptor
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 轨道属性描述符, 实现对属性的读取和赋值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="i">轨道索引</param>
|
|
||||||
private class TrackWrapperPropertyDescriptor(int i, Attribute[]? attributes) : PropertyDescriptor($"Track{i}", attributes)
|
|
||||||
{
|
|
||||||
private readonly int idx = i;
|
|
||||||
|
|
||||||
public override Type ComponentType => typeof(SpineAnimationWrapper);
|
|
||||||
public override bool IsReadOnly => false;
|
|
||||||
public override Type PropertyType => typeof(TrackWrapper);
|
|
||||||
public override bool CanResetValue(object component) => false;
|
|
||||||
public override void ResetValue(object component) { }
|
|
||||||
public override bool ShouldSerializeValue(object component) => false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 得到一个轨道包装类, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性
|
|
||||||
/// </summary>
|
|
||||||
public override object? GetValue(object? component)
|
|
||||||
{
|
|
||||||
if (component is SpineAnimationWrapper tracks)
|
|
||||||
return tracks.GetTrackWrapper(idx);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 允许通过字符串赋值修改该轨道的动画, 这里决定了当其他地方的调用 (比如 Converter) 通过 value 来设置属性值的时候应该怎么处理
|
|
||||||
/// </summary>
|
|
||||||
public override void SetValue(object? component, object? value)
|
|
||||||
{
|
|
||||||
if (component is SpineAnimationWrapper tracks)
|
|
||||||
{
|
|
||||||
if (value is string s)
|
|
||||||
tracks.SetTrackWrapper(idx, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public SpineObject Spine { get; } = spine;
|
public SpineObject Spine { get; } = spine;
|
||||||
|
|
||||||
@@ -98,24 +24,24 @@ namespace SpineViewer.Spine.SpineView
|
|||||||
public float AnimationTracksMaxDuration => Spine.GetTrackIndices().Select(i => Spine.GetAnimationDuration(Spine.GetAnimation(i))).Max();
|
public float AnimationTracksMaxDuration => Spine.GetTrackIndices().Select(i => Spine.GetAnimationDuration(Spine.GetAnimation(i))).Max();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TrackWrapper 属性对象缓存
|
/// <see cref="TrackAnimationProperty"/> 属性对象缓存
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Dictionary<int, TrackWrapper> trackWrapperProperties = [];
|
private readonly Dictionary<int, TrackAnimationProperty> trackAnimationProperties = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 访问 TrackWrapper 属性 <c>AnimationTracks.Track{i}</c>
|
/// <c>this.Track{i}</c>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TrackWrapper GetTrackWrapper(int i)
|
public TrackAnimationProperty GetTrackAnimation(int i)
|
||||||
{
|
{
|
||||||
if (!trackWrapperProperties.ContainsKey(i))
|
if (!trackAnimationProperties.ContainsKey(i))
|
||||||
trackWrapperProperties[i] = new TrackWrapper(Spine, i);
|
trackAnimationProperties[i] = new TrackAnimationProperty(Spine, i);
|
||||||
return trackWrapperProperties[i];
|
return trackAnimationProperties[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置 TrackWrapper 属性 <c>AnimationTracks.Track{i} = <paramref name="value"/></c>
|
/// <c>this.Track{i} = <paramref name="value"/></c>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetTrackWrapper(int i, string value)
|
public void SetTrackAnimation(int i, string value)
|
||||||
{
|
{
|
||||||
Spine.SetAnimation(i, value);
|
Spine.SetAnimation(i, value);
|
||||||
TypeDescriptor.Refresh(this);
|
TypeDescriptor.Refresh(this);
|
||||||
@@ -128,11 +54,11 @@ namespace SpineViewer.Spine.SpineView
|
|||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
if (obj is SpineAnimationWrapper wrapper) return ToString() == wrapper.ToString();
|
if (obj is SpineAnimationProperty prop) return ToString() == prop.ToString();
|
||||||
return base.Equals(obj);
|
return base.Equals(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(SpineAnimationWrapper).FullName.GetHashCode(), ToString().GetHashCode());
|
public override int GetHashCode() => HashCode.Combine(typeof(SpineAnimationProperty).FullName.GetHashCode(), ToString().GetHashCode());
|
||||||
|
|
||||||
#region ICustomTypeDescriptor 接口实现
|
#region ICustomTypeDescriptor 接口实现
|
||||||
|
|
||||||
@@ -166,6 +92,113 @@ namespace SpineViewer.Spine.SpineView
|
|||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 轨道属性描述符, 实现对属性的读取和赋值
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="i">轨道索引</param>
|
||||||
|
private class TrackWrapperPropertyDescriptor(int i, Attribute[]? attributes) : PropertyDescriptor($"Track{i}", attributes)
|
||||||
|
{
|
||||||
|
private readonly int idx = i;
|
||||||
|
|
||||||
|
public override Type ComponentType => typeof(SpineAnimationProperty);
|
||||||
|
public override bool IsReadOnly => false;
|
||||||
|
public override Type PropertyType => typeof(TrackAnimationProperty);
|
||||||
|
public override bool CanResetValue(object component) => false;
|
||||||
|
public override void ResetValue(object component) { }
|
||||||
|
public override bool ShouldSerializeValue(object component) => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 得到一个轨道包装类, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性
|
||||||
|
/// </summary>
|
||||||
|
public override object? GetValue(object? component)
|
||||||
|
{
|
||||||
|
if (component is SpineAnimationProperty tracks)
|
||||||
|
return tracks.GetTrackAnimation(idx);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 允许通过字符串赋值修改该轨道的动画, 这里决定了当其他地方的调用 (比如 Converter) 通过 value 来设置属性值的时候应该怎么处理
|
||||||
|
/// </summary>
|
||||||
|
public override void SetValue(object? component, object? value)
|
||||||
|
{
|
||||||
|
if (component is SpineAnimationProperty tracks)
|
||||||
|
{
|
||||||
|
if (value is string s)
|
||||||
|
tracks.SetTrackAnimation(idx, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对 <c><see cref="SpineAnimationProperty"/>.Track{i}</c> 属性的包装类
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(TrackAnimationPropertyConverter))]
|
||||||
|
public class TrackAnimationProperty(SpineObject spine, int i)
|
||||||
|
{
|
||||||
|
private readonly SpineObject spine = spine;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
public int Index { get; } = i;
|
||||||
|
|
||||||
|
[DisplayName("时长")]
|
||||||
|
public float Duration => spine.GetAnimationDuration(spine.GetAnimation(Index));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实现了默认的转为字符串的方式
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString() => spine.GetAnimation(Index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 影响了属性面板的判断, 当动画名称相同的时候认为两个对象是相同的, 这样属性面板可以在多选的时候正确显示相同取值的内容
|
||||||
|
/// </summary>
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is TrackAnimationProperty) return ToString() == obj.ToString();
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 哈希码需要和 Equals 行为类似
|
||||||
|
/// </summary>
|
||||||
|
public override int GetHashCode() => HashCode.Combine(typeof(TrackAnimationProperty).FullName.GetHashCode(), ToString().GetHashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 轨道索引包装类转换器, 实现字符串和包装类的互相转换, 并且提供标准值列表对属性进行设置, 同时还提供在面板上显示包装类属性的能力
|
||||||
|
/// </summary>
|
||||||
|
public class TrackAnimationPropertyConverter : ExpandableObjectConverter
|
||||||
|
{
|
||||||
|
// NOTE: 可以不用实现 ConvertTo/ConvertFrom, 因为属性实现了与字符串之间的互转
|
||||||
|
// ToString 实现了 ConvertTo
|
||||||
|
// SetValue 实现了从字符串设置属性
|
||||||
|
|
||||||
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||||
|
|
||||||
|
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
||||||
|
|
||||||
|
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||||
|
{
|
||||||
|
if (context?.Instance is SpineAnimationProperty tracks)
|
||||||
|
{
|
||||||
|
return new StandardValuesCollection(tracks.Spine.AnimationNames);
|
||||||
|
}
|
||||||
|
else if (context?.Instance is object[] instances && instances.All(x => x is SpineAnimationProperty))
|
||||||
|
{
|
||||||
|
// XXX: 这里不知道为啥总是会得到 object[] 类型而不是具体的类型
|
||||||
|
var animTracks = instances.Cast<SpineAnimationProperty>().ToArray();
|
||||||
|
if (animTracks.Length > 0)
|
||||||
|
{
|
||||||
|
IEnumerable<string> common = animTracks[0].Spine.AnimationNames;
|
||||||
|
foreach (var t in animTracks.Skip(1))
|
||||||
|
common = common.Union(t.Spine.AnimationNames);
|
||||||
|
return new StandardValuesCollection(common.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base.GetStandardValues(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace SpineViewer.Spine.SpineView
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用于在 PropertyGrid 上显示 Spine 基本信息的包装类
|
/// 用于在 PropertyGrid 上显示 Spine 基本信息的包装类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SpineBaseInfoWrapper(SpineObject spine)
|
public class SpineBaseInfoProperty(SpineObject spine)
|
||||||
{
|
{
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public SpineObject Spine { get; } = spine;
|
public SpineObject Spine { get; } = spine;
|
||||||
@@ -11,7 +11,7 @@ namespace SpineViewer.Spine.SpineView
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用于在 PropertyGrid 上显示 Spine 调试属性的包装类
|
/// 用于在 PropertyGrid 上显示 Spine 调试属性的包装类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SpineDebugWrapper(SpineObject spine)
|
public class SpineDebugProperty(SpineObject spine)
|
||||||
{
|
{
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public SpineObject Spine { get; } = spine;
|
public SpineObject Spine { get; } = spine;
|
||||||
@@ -9,29 +9,29 @@ using SpineViewer.Spine;
|
|||||||
|
|
||||||
namespace SpineViewer.Spine.SpineView
|
namespace SpineViewer.Spine.SpineView
|
||||||
{
|
{
|
||||||
public class SpineWrapper(SpineObject spine)
|
public class SpineObjectProperty(SpineObject spine)
|
||||||
{
|
{
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public SpineObject Spine { get; } = spine;
|
public SpineObject Spine { get; } = spine;
|
||||||
|
|
||||||
[DisplayName("基本信息")]
|
[DisplayName("基本信息")]
|
||||||
public SpineBaseInfoWrapper BaseInfo { get; } = new(spine);
|
public SpineBaseInfoProperty BaseInfo { get; } = new(spine);
|
||||||
|
|
||||||
[DisplayName("渲染")]
|
[DisplayName("渲染")]
|
||||||
public SpineRenderWrapper Render { get; } = new(spine);
|
public SpineRenderProperty Render { get; } = new(spine);
|
||||||
|
|
||||||
[DisplayName("变换")]
|
[DisplayName("变换")]
|
||||||
public SpineTransformWrapper Transform { get; } = new(spine);
|
public SpineTransformProperty Transform { get; } = new(spine);
|
||||||
|
|
||||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||||
[DisplayName("皮肤")]
|
[DisplayName("皮肤")]
|
||||||
public SpineSkinWrapper Skin { get; } = new(spine);
|
public SpineSkinProperty Skin { get; } = new(spine);
|
||||||
|
|
||||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||||
[DisplayName("动画")]
|
[DisplayName("动画")]
|
||||||
public SpineAnimationWrapper Animation { get; } = new(spine);
|
public SpineAnimationProperty Animation { get; } = new(spine);
|
||||||
|
|
||||||
[DisplayName("调试")]
|
[DisplayName("调试")]
|
||||||
public SpineDebugWrapper Debug { get; } = new(spine);
|
public SpineDebugProperty Debug { get; } = new(spine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ namespace SpineViewer.Spine.SpineView
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用于在 PropertyGrid 上显示 Spine 渲染设置的包装类
|
/// 用于在 PropertyGrid 上显示 Spine 渲染设置的包装类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SpineRenderWrapper(SpineObject spine)
|
public class SpineRenderProperty(SpineObject spine)
|
||||||
{
|
{
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public SpineObject Spine { get; } = spine;
|
public SpineObject Spine { get; } = spine;
|
||||||
@@ -9,99 +9,34 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace SpineViewer.Spine.SpineView
|
namespace SpineViewer.Spine.SpineView
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 对皮肤属性的包装类
|
|
||||||
/// </summary>
|
|
||||||
[TypeConverter(typeof(SkinWrapperConverter))]
|
|
||||||
public class SkinWrapper(SpineObject spine, int i)
|
|
||||||
{
|
|
||||||
private readonly SpineObject spine = spine;
|
|
||||||
|
|
||||||
[Browsable(false)]
|
|
||||||
public int Index { get; } = i;
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
var loadedSkins = spine.GetLoadedSkins();
|
|
||||||
if (Index >= 0 && Index < loadedSkins.Length)
|
|
||||||
return loadedSkins[Index];
|
|
||||||
return "!NULL"; // XXX: 预期应该不会发生
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
if (obj is SkinWrapper) return ToString() == obj.ToString();
|
|
||||||
return base.Equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(SkinWrapper).FullName.GetHashCode(), ToString().GetHashCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 皮肤列表动态类型包装类, 用于提供对 Spine 皮肤列表的管理能力
|
/// 皮肤列表动态类型包装类, 用于提供对 Spine 皮肤列表的管理能力
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="spine">关联的 Spine 对象</param>
|
/// <param name="spine">关联的 Spine 对象</param>
|
||||||
public class SpineSkinWrapper(SpineObject spine) : ICustomTypeDescriptor
|
public class SpineSkinProperty(SpineObject spine) : ICustomTypeDescriptor
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 皮肤属性描述符, 实现对属性的读取和赋值
|
|
||||||
/// </summary>
|
|
||||||
private class SkinWrapperPropertyDescriptor(int i, Attribute[]? attributes) : PropertyDescriptor($"Skin{i}", attributes)
|
|
||||||
{
|
|
||||||
private readonly int idx = i;
|
|
||||||
|
|
||||||
public override Type ComponentType => typeof(SpineSkinWrapper);
|
|
||||||
public override bool IsReadOnly => false;
|
|
||||||
public override Type PropertyType => typeof(SkinWrapper);
|
|
||||||
public override bool CanResetValue(object component) => false;
|
|
||||||
public override void ResetValue(object component) { }
|
|
||||||
public override bool ShouldSerializeValue(object component) => false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 得到一个 SkinWrapper, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性
|
|
||||||
/// </summary>
|
|
||||||
public override object? GetValue(object? component)
|
|
||||||
{
|
|
||||||
if (component is SpineSkinWrapper manager)
|
|
||||||
return manager.GetSkinWrapper(idx);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 允许通过字符串赋值修改该位置的皮肤
|
|
||||||
/// </summary>
|
|
||||||
public override void SetValue(object? component, object? value)
|
|
||||||
{
|
|
||||||
if (component is SpineSkinWrapper manager)
|
|
||||||
{
|
|
||||||
if (value is string s)
|
|
||||||
manager.SetSkinWrapper(idx, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public SpineObject Spine { get; } = spine;
|
public SpineObject Spine { get; } = spine;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SkinWrapper 属性缓存
|
/// <see cref="SpineSkinProperty"/> 属性缓存
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Dictionary<int, SkinWrapper> skinWrapperProperties = [];
|
private readonly Dictionary<int, SkinNameProperty> skinNameProperties = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 访问 SkinWrapper 属性 <c>SkinManager.Skin{i}</c>
|
/// <c>this.Skin{i}</c>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SkinWrapper GetSkinWrapper(int i)
|
public SkinNameProperty GetSkinName(int i)
|
||||||
{
|
{
|
||||||
if (!skinWrapperProperties.ContainsKey(i))
|
if (!skinNameProperties.ContainsKey(i))
|
||||||
skinWrapperProperties[i] = new SkinWrapper(Spine, i);
|
skinNameProperties[i] = new SkinNameProperty(Spine, i);
|
||||||
return skinWrapperProperties[i];
|
return skinNameProperties[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置 SkinWrapper 属性 <c>SkinManager.Skin{i} = <paramref name="value"/></c>
|
/// <c>this.Skin{i} = <paramref name="value"/></c>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetSkinWrapper(int i, string value)
|
public void SetSkinName(int i, string value)
|
||||||
{
|
{
|
||||||
Spine.ReplaceSkin(i, value);
|
Spine.ReplaceSkin(i, value);
|
||||||
TypeDescriptor.Refresh(this);
|
TypeDescriptor.Refresh(this);
|
||||||
@@ -114,17 +49,17 @@ namespace SpineViewer.Spine.SpineView
|
|||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
if (obj is SpineSkinWrapper wrapper) return ToString() == wrapper.ToString();
|
if (obj is SpineSkinProperty prop) return ToString() == prop.ToString();
|
||||||
return base.Equals(obj);
|
return base.Equals(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(SpineSkinWrapper).FullName.GetHashCode(), ToString().GetHashCode());
|
public override int GetHashCode() => HashCode.Combine(typeof(SpineSkinProperty).FullName.GetHashCode(), ToString().GetHashCode());
|
||||||
|
|
||||||
#region ICustomTypeDescriptor 接口实现
|
#region ICustomTypeDescriptor 接口实现
|
||||||
|
|
||||||
// XXX: 必须实现 ICustomTypeDescriptor 接口, 不能继承 CustomTypeDescriptor, 似乎继承下来的东西会有问题, 导致某些调用不正确
|
// XXX: 必须实现 ICustomTypeDescriptor 接口, 不能继承 CustomTypeDescriptor, 似乎继承下来的东西会有问题, 导致某些调用不正确
|
||||||
|
|
||||||
private static readonly Dictionary<int, SkinWrapperPropertyDescriptor> pdCache = [];
|
private static readonly Dictionary<int, SkinNamePropertyDescriptor> pdCache = [];
|
||||||
|
|
||||||
public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
|
public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
|
||||||
public string? GetClassName() => TypeDescriptor.GetClassName(this, true);
|
public string? GetClassName() => TypeDescriptor.GetClassName(this, true);
|
||||||
@@ -143,12 +78,109 @@ namespace SpineViewer.Spine.SpineView
|
|||||||
for (var i = 0; i < Spine.GetLoadedSkins().Length; i++)
|
for (var i = 0; i < Spine.GetLoadedSkins().Length; i++)
|
||||||
{
|
{
|
||||||
if (!pdCache.ContainsKey(i))
|
if (!pdCache.ContainsKey(i))
|
||||||
pdCache[i] = new SkinWrapperPropertyDescriptor(i, [new DisplayNameAttribute($"皮肤 {i}")]);
|
pdCache[i] = new SkinNamePropertyDescriptor(i, [new DisplayNameAttribute($"皮肤 {i}")]);
|
||||||
props.Add(pdCache[i]);
|
props.Add(pdCache[i]);
|
||||||
}
|
}
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 皮肤属性描述符, 实现对属性的读取和赋值
|
||||||
|
/// </summary>
|
||||||
|
private class SkinNamePropertyDescriptor(int i, Attribute[]? attributes) : PropertyDescriptor($"Skin{i}", attributes)
|
||||||
|
{
|
||||||
|
private readonly int idx = i;
|
||||||
|
|
||||||
|
public override Type ComponentType => typeof(SpineSkinProperty);
|
||||||
|
public override bool IsReadOnly => false;
|
||||||
|
public override Type PropertyType => typeof(SkinNameProperty);
|
||||||
|
public override bool CanResetValue(object component) => false;
|
||||||
|
public override void ResetValue(object component) { }
|
||||||
|
public override bool ShouldSerializeValue(object component) => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 得到一个 <see cref="SpineSkinProperty"/>, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性
|
||||||
|
/// </summary>
|
||||||
|
public override object? GetValue(object? component)
|
||||||
|
{
|
||||||
|
if (component is SpineSkinProperty prop)
|
||||||
|
return prop.GetSkinName(idx);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 允许通过字符串赋值修改该位置的皮肤
|
||||||
|
/// </summary>
|
||||||
|
public override void SetValue(object? component, object? value)
|
||||||
|
{
|
||||||
|
if (component is SpineSkinProperty prop)
|
||||||
|
{
|
||||||
|
if (value is string s)
|
||||||
|
prop.SetSkinName(idx, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对 <c><see cref="SpineSkinProperty"/>.Skin{i}</c> 属性的包装类
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(SkinNamePropertyConverter))]
|
||||||
|
public class SkinNameProperty(SpineObject spine, int i)
|
||||||
|
{
|
||||||
|
private readonly SpineObject spine = spine;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
public int Index { get; } = i;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var loadedSkins = spine.GetLoadedSkins();
|
||||||
|
if (Index >= 0 && Index < loadedSkins.Length)
|
||||||
|
return loadedSkins[Index];
|
||||||
|
return "!NULL"; // XXX: 预期应该不会发生
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is SkinNameProperty) return ToString() == obj.ToString();
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode() => HashCode.Combine(typeof(SkinNameProperty).FullName.GetHashCode(), ToString().GetHashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SkinNamePropertyConverter : StringConverter
|
||||||
|
{
|
||||||
|
// NOTE: 可以不用实现 ConvertTo/ConvertFrom, 因为属性实现了与字符串之间的互转
|
||||||
|
// ToString 实现了 ConvertTo
|
||||||
|
// SetValue 实现了从字符串设置属性
|
||||||
|
|
||||||
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||||
|
|
||||||
|
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
||||||
|
|
||||||
|
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||||
|
{
|
||||||
|
if (context?.Instance is SpineSkinProperty manager)
|
||||||
|
{
|
||||||
|
return new StandardValuesCollection(manager.Spine.SkinNames);
|
||||||
|
}
|
||||||
|
else if (context?.Instance is object[] instances && instances.All(x => x is SpineSkinProperty))
|
||||||
|
{
|
||||||
|
// XXX: 这里不知道为啥总是会得到 object[] 类型而不是具体的 SpineSkinWrapper[] 类型
|
||||||
|
var managers = instances.Cast<SpineSkinProperty>().ToArray();
|
||||||
|
if (managers.Length > 0)
|
||||||
|
{
|
||||||
|
IEnumerable<string> common = managers[0].Spine.SkinNames;
|
||||||
|
foreach (var t in managers.Skip(1))
|
||||||
|
common = common.Union(t.Spine.SkinNames);
|
||||||
|
return new StandardValuesCollection(common.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base.GetStandardValues(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace SpineViewer.Spine.SpineView
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用于在 PropertyGrid 上显示 Spine 空间变换的包装类
|
/// 用于在 PropertyGrid 上显示 Spine 空间变换的包装类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SpineTransformWrapper(SpineObject spine)
|
public class SpineTransformProperty(SpineObject spine)
|
||||||
{
|
{
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public SpineObject Spine { get; } = spine;
|
public SpineObject Spine { get; } = spine;
|
||||||
@@ -100,140 +100,6 @@ namespace SpineViewer.Utils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SpineVersionConverter : EnumConverter
|
|
||||||
{
|
|
||||||
public SpineVersionConverter() : base(typeof(SpineVersion)) { }
|
|
||||||
|
|
||||||
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type? destinationType)
|
|
||||||
{
|
|
||||||
if (destinationType == typeof(string) && value is SpineVersion version)
|
|
||||||
return version.GetName();
|
|
||||||
return base.ConvertTo(context, culture, value, destinationType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SpineSkinNameConverter : StringConverter
|
|
||||||
{
|
|
||||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
|
||||||
|
|
||||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
|
||||||
|
|
||||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
|
||||||
{
|
|
||||||
if (context?.Instance is SpineObject obj)
|
|
||||||
{
|
|
||||||
return new StandardValuesCollection(obj.SkinNames);
|
|
||||||
}
|
|
||||||
else if (context?.Instance is SpineObject[] spines)
|
|
||||||
{
|
|
||||||
if (spines.Length > 0)
|
|
||||||
{
|
|
||||||
IEnumerable<string> common = spines[0].SkinNames;
|
|
||||||
foreach (var spine in spines.Skip(1))
|
|
||||||
common = common.Union(spine.SkinNames);
|
|
||||||
return new StandardValuesCollection(common.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return base.GetStandardValues(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SpineAnimationNameConverter : StringConverter
|
|
||||||
{
|
|
||||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
|
||||||
|
|
||||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
|
||||||
|
|
||||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
|
||||||
{
|
|
||||||
if (context?.Instance is SpineObject obj)
|
|
||||||
{
|
|
||||||
return new StandardValuesCollection(obj.AnimationNames);
|
|
||||||
}
|
|
||||||
else if (context?.Instance is SpineObject[] spines)
|
|
||||||
{
|
|
||||||
if (spines.Length > 0)
|
|
||||||
{
|
|
||||||
IEnumerable<string> common = spines[0].AnimationNames;
|
|
||||||
foreach (var spine in spines.Skip(1))
|
|
||||||
common = common.Union(spine.AnimationNames);
|
|
||||||
return new StandardValuesCollection(common.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return base.GetStandardValues(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 皮肤位包装类转换器, 实现字符串和包装类的互相转换, 并且提供标准值列表对属性进行设置, 同时还提供在面板上显示包装类属性的能力
|
|
||||||
/// </summary>
|
|
||||||
public class SkinWrapperConverter : StringConverter
|
|
||||||
{
|
|
||||||
// NOTE: 可以不用实现 ConvertTo/ConvertFrom, 因为属性实现了与字符串之间的互转
|
|
||||||
// ToString 实现了 ConvertTo
|
|
||||||
// SetValue 实现了从字符串设置属性
|
|
||||||
|
|
||||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
|
||||||
|
|
||||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
|
||||||
|
|
||||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
|
||||||
{
|
|
||||||
if (context?.Instance is SpineSkinWrapper manager)
|
|
||||||
{
|
|
||||||
return new StandardValuesCollection(manager.Spine.SkinNames);
|
|
||||||
}
|
|
||||||
else if (context?.Instance is object[] instances && instances.All(x => x is SpineSkinWrapper))
|
|
||||||
{
|
|
||||||
// XXX: 这里不知道为啥总是会得到 object[] 类型而不是具体的 SpineSkinWrapper[] 类型
|
|
||||||
var managers = instances.Cast<SpineSkinWrapper>().ToArray();
|
|
||||||
if (managers.Length > 0)
|
|
||||||
{
|
|
||||||
IEnumerable<string> common = managers[0].Spine.SkinNames;
|
|
||||||
foreach (var t in managers.Skip(1))
|
|
||||||
common = common.Union(t.Spine.SkinNames);
|
|
||||||
return new StandardValuesCollection(common.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return base.GetStandardValues(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 轨道索引包装类转换器, 实现字符串和包装类的互相转换, 并且提供标准值列表对属性进行设置, 同时还提供在面板上显示包装类属性的能力
|
|
||||||
/// </summary>
|
|
||||||
public class TrackWrapperConverter : ExpandableObjectConverter
|
|
||||||
{
|
|
||||||
// NOTE: 可以不用实现 ConvertTo/ConvertFrom, 因为属性实现了与字符串之间的互转
|
|
||||||
// ToString 实现了 ConvertTo
|
|
||||||
// SetValue 实现了从字符串设置属性
|
|
||||||
|
|
||||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
|
||||||
|
|
||||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
|
||||||
|
|
||||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
|
||||||
{
|
|
||||||
if (context?.Instance is SpineAnimationWrapper tracks)
|
|
||||||
{
|
|
||||||
return new StandardValuesCollection(tracks.Spine.AnimationNames);
|
|
||||||
}
|
|
||||||
else if (context?.Instance is object[] instances && instances.All(x => x is SpineAnimationWrapper))
|
|
||||||
{
|
|
||||||
// XXX: 这里不知道为啥总是会得到 object[] 类型而不是具体的类型
|
|
||||||
var animTracks = instances.Cast<SpineAnimationWrapper>().ToArray();
|
|
||||||
if (animTracks.Length > 0)
|
|
||||||
{
|
|
||||||
IEnumerable<string> common = animTracks[0].Spine.AnimationNames;
|
|
||||||
foreach (var t in animTracks.Skip(1))
|
|
||||||
common = common.Union(t.Spine.AnimationNames);
|
|
||||||
return new StandardValuesCollection(common.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return base.GetStandardValues(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SFMLColorConverter : ExpandableObjectConverter
|
public class SFMLColorConverter : ExpandableObjectConverter
|
||||||
{
|
{
|
||||||
private class SFMLColorPropertyDescriptor : SimplePropertyDescriptor
|
private class SFMLColorPropertyDescriptor : SimplePropertyDescriptor
|
||||||
|
|||||||
@@ -36,33 +36,33 @@ namespace SpineViewer.Utils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// skel 文件路径编辑器
|
///// skel 文件路径编辑器
|
||||||
/// </summary>
|
///// </summary>
|
||||||
public class SkelFileNameEditor : FileNameEditor
|
//public class SkelFileNameEditor : FileNameEditor
|
||||||
{
|
//{
|
||||||
protected override void InitializeDialog(OpenFileDialog openFileDialog)
|
// protected override void InitializeDialog(OpenFileDialog openFileDialog)
|
||||||
{
|
// {
|
||||||
base.InitializeDialog(openFileDialog);
|
// base.InitializeDialog(openFileDialog);
|
||||||
openFileDialog.Title = "选择 skel 文件";
|
// openFileDialog.Title = "选择 skel 文件";
|
||||||
openFileDialog.AddExtension = false;
|
// openFileDialog.AddExtension = false;
|
||||||
openFileDialog.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*";
|
// openFileDialog.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*";
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// atlas 文件路径编辑器
|
///// atlas 文件路径编辑器
|
||||||
/// </summary>
|
///// </summary>
|
||||||
public class AtlasFileNameEditor : FileNameEditor
|
//public class AtlasFileNameEditor : FileNameEditor
|
||||||
{
|
//{
|
||||||
protected override void InitializeDialog(OpenFileDialog openFileDialog)
|
// protected override void InitializeDialog(OpenFileDialog openFileDialog)
|
||||||
{
|
// {
|
||||||
base.InitializeDialog(openFileDialog);
|
// base.InitializeDialog(openFileDialog);
|
||||||
openFileDialog.Title = "选择 atlas 文件";
|
// openFileDialog.Title = "选择 atlas 文件";
|
||||||
openFileDialog.AddExtension = false;
|
// openFileDialog.AddExtension = false;
|
||||||
openFileDialog.Filter = "atlas 文件 (*.atlas)|*.atlas|所有文件 (*.*)|*.*";
|
// openFileDialog.Filter = "atlas 文件 (*.atlas)|*.atlas|所有文件 (*.*)|*.*";
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
class SFMLColorEditor : UITypeEditor
|
class SFMLColorEditor : UITypeEditor
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user