diff --git a/SpineViewer/Controls/SkelFileListBox.cs b/SpineViewer/Controls/SkelFileListBox.cs index 512f8f3..ed41523 100644 --- a/SpineViewer/Controls/SkelFileListBox.cs +++ b/SpineViewer/Controls/SkelFileListBox.cs @@ -34,14 +34,14 @@ namespace SpineViewer.Controls { 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)); } else if (Directory.Exists(path)) { 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); } } @@ -58,7 +58,7 @@ namespace SpineViewer.Controls { 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); } } diff --git a/SpineViewer/Controls/SpineListView.cs b/SpineViewer/Controls/SpineListView.cs index 430e875..98f500a 100644 --- a/SpineViewer/Controls/SpineListView.cs +++ b/SpineViewer/Controls/SpineListView.cs @@ -39,7 +39,7 @@ namespace SpineViewer.Controls /// /// 用于属性页显示模型参数的包装类 /// - private readonly Dictionary spinePropertyWrappers = []; + private readonly Dictionary spinePropertyWrappers = []; public SpineListView() { @@ -205,14 +205,14 @@ namespace SpineViewer.Controls { if (File.Exists(path)) { - if (SpineHelper.CommonSkelSuffix.Contains(Path.GetExtension(path).ToLower())) + if (SpineUtils.CommonSkelSuffix.Contains(Path.GetExtension(path).ToLower())) validPaths.Add(path); } else if (Directory.Exists(path)) { 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); } } diff --git a/SpineViewer/Controls/SpinePropertyGrid.cs b/SpineViewer/Controls/SpinePropertyGrid.cs index 1302635..ff8f78d 100644 --- a/SpineViewer/Controls/SpinePropertyGrid.cs +++ b/SpineViewer/Controls/SpinePropertyGrid.cs @@ -22,7 +22,7 @@ namespace SpineViewer.Controls /// /// 设置选中的对象列表, 可以赋值 null 来清空选中, 行为与 PropertyGrid.SelectedObjects 类似 /// - public SpineWrapper[] SelectedSpines + public SpineObjectProperty[] SelectedSpines { get => selectedSpines ?? []; 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) { if (selectedSpines?.Length == 1) { toolStripMenuItem_AddSkin.Enabled = true; - toolStripMenuItem_RemoveSkin.Enabled = propertyGrid_Skin.SelectedGridItem.Value is SkinWrapper; + toolStripMenuItem_RemoveSkin.Enabled = propertyGrid_Skin.SelectedGridItem.Value is SkinNameProperty; } else { @@ -70,7 +70,7 @@ namespace SpineViewer.Controls if (selectedSpines?.Length == 1) { toolStripMenuItem_AddAnimation.Enabled = true; - toolStripMenuItem_RemoveAnimation.Enabled = propertyGrid_Animation.SelectedGridItem.Value is TrackWrapper; + toolStripMenuItem_RemoveAnimation.Enabled = propertyGrid_Animation.SelectedGridItem.Value is TrackAnimationProperty; } else { @@ -99,7 +99,7 @@ namespace SpineViewer.Controls { 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); propertyGrid_Skin.Refresh(); @@ -119,7 +119,7 @@ namespace SpineViewer.Controls { 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); propertyGrid_Animation.Refresh(); diff --git a/SpineViewer/Dialogs/BatchOpenSpineDialog.cs b/SpineViewer/Dialogs/BatchOpenSpineDialog.cs index 0e7ee71..7b2ba0a 100644 --- a/SpineViewer/Dialogs/BatchOpenSpineDialog.cs +++ b/SpineViewer/Dialogs/BatchOpenSpineDialog.cs @@ -22,7 +22,7 @@ namespace SpineViewer.Dialogs public BatchOpenSpineDialog() { InitializeComponent(); - comboBox_Version.DataSource = SpineHelper.Names.ToList(); + comboBox_Version.DataSource = SpineUtils.Names.ToList(); comboBox_Version.DisplayMember = "Value"; comboBox_Version.ValueMember = "Key"; comboBox_Version.SelectedValue = SpineVersion.Auto; diff --git a/SpineViewer/Dialogs/ConvertFileFormatDialog.cs b/SpineViewer/Dialogs/ConvertFileFormatDialog.cs index 5124015..969c226 100644 --- a/SpineViewer/Dialogs/ConvertFileFormatDialog.cs +++ b/SpineViewer/Dialogs/ConvertFileFormatDialog.cs @@ -23,13 +23,13 @@ namespace SpineViewer.Dialogs { InitializeComponent(); - comboBox_SourceVersion.DataSource = SpineHelper.Names.ToList(); + comboBox_SourceVersion.DataSource = SpineUtils.Names.ToList(); comboBox_SourceVersion.DisplayMember = "Value"; comboBox_SourceVersion.ValueMember = "Key"; comboBox_SourceVersion.SelectedValue = SpineVersion.Auto; // 目标版本不包含自动 - var versionsWithoutAuto = SpineHelper.Names.ToDictionary(); + var versionsWithoutAuto = SpineUtils.Names.ToDictionary(); versionsWithoutAuto.Remove(SpineVersion.Auto); comboBox_TargetVersion.DataSource = versionsWithoutAuto.ToList(); comboBox_TargetVersion.DisplayMember = "Value"; diff --git a/SpineViewer/Dialogs/OpenSpineDialog.cs b/SpineViewer/Dialogs/OpenSpineDialog.cs index 2525818..b69bae1 100644 --- a/SpineViewer/Dialogs/OpenSpineDialog.cs +++ b/SpineViewer/Dialogs/OpenSpineDialog.cs @@ -21,7 +21,7 @@ namespace SpineViewer.Dialogs public OpenSpineDialog() { InitializeComponent(); - comboBox_Version.DataSource = SpineHelper.Names.ToList(); + comboBox_Version.DataSource = SpineUtils.Names.ToList(); comboBox_Version.DisplayMember = "Value"; comboBox_Version.ValueMember = "Key"; comboBox_Version.SelectedValue = SpineVersion.Auto; diff --git a/SpineViewer/Forms/SpineViewerForm.cs b/SpineViewer/Forms/SpineViewerForm.cs index 9cec6e7..c7b5a09 100644 --- a/SpineViewer/Forms/SpineViewerForm.cs +++ b/SpineViewer/Forms/SpineViewerForm.cs @@ -379,7 +379,7 @@ namespace SpineViewer { try { - srcCvter = SkeletonConverter.New(SpineHelper.GetVersion(skelPath)); + srcCvter = SkeletonConverter.New(SpineUtils.GetVersion(skelPath)); } catch (Exception ex) { diff --git a/SpineViewer/Spine/SpineObject.cs b/SpineViewer/Spine/SpineObject.cs index 59e11a6..5a033dd 100644 --- a/SpineViewer/Spine/SpineObject.cs +++ b/SpineViewer/Spine/SpineObject.cs @@ -38,7 +38,7 @@ namespace SpineViewer.Spine skelPath = Path.GetFullPath(skelPath); 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"); return New(version, [skelPath, atlasPath]).PostInit(); } diff --git a/SpineViewer/Spine/SpineHelper.cs b/SpineViewer/Spine/SpineUtils.cs similarity index 86% rename from SpineViewer/Spine/SpineHelper.cs rename to SpineViewer/Spine/SpineUtils.cs index 4b60700..9ef3ae2 100644 --- a/SpineViewer/Spine/SpineHelper.cs +++ b/SpineViewer/Spine/SpineUtils.cs @@ -12,35 +12,10 @@ using System.Threading.Tasks; namespace SpineViewer.Spine { - /// - /// 支持的 Spine 版本 - /// - public enum SpineVersion - { - [Description("")] 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, - } - - /// - /// Spine 实现类标记 - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class SpineImplementationAttribute(SpineVersion version) : Attribute, IImplementationKey - { - public SpineVersion ImplementationKey { get; private set; } = version; - } - /// /// Spine 版本静态辅助类 /// - public static class SpineHelper + public static class SpineUtils { /// /// 版本名称 @@ -53,7 +28,7 @@ namespace SpineViewer.Spine /// private static readonly Dictionary runtimes = []; - static SpineHelper() + static SpineUtils() { // 初始化缓存 foreach (var value in Enum.GetValues(typeof(SpineVersion))) diff --git a/SpineViewer/Spine/SpineVersion.cs b/SpineViewer/Spine/SpineVersion.cs new file mode 100644 index 0000000..1d37e80 --- /dev/null +++ b/SpineViewer/Spine/SpineVersion.cs @@ -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 +{ + /// + /// 支持的 Spine 版本 + /// + public enum SpineVersion + { + [Description("")] 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, + } + + /// + /// Spine 实现类标记 + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class SpineImplementationAttribute(SpineVersion version) : Attribute, IImplementationKey + { + 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); + } + } +} diff --git a/SpineViewer/Spine/SpineView/SpineAnimationWrapper.cs b/SpineViewer/Spine/SpineView/SpineAnimationProperty.cs similarity index 63% rename from SpineViewer/Spine/SpineView/SpineAnimationWrapper.cs rename to SpineViewer/Spine/SpineView/SpineAnimationProperty.cs index 66616aa..e46506f 100644 --- a/SpineViewer/Spine/SpineView/SpineAnimationWrapper.cs +++ b/SpineViewer/Spine/SpineView/SpineAnimationProperty.cs @@ -9,85 +9,11 @@ using System.Threading.Tasks; namespace SpineViewer.Spine.SpineView { - /// - /// 对轨道索引属性的包装类, 能够在面板上显示例如时长的属性, 但是处理该属性时按字符串去处理, 例如 ToString 和判断对象相等都是用动画名称实现逻辑 - /// - /// - /// - [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)); - - /// - /// 实现了默认的转为字符串的方式 - /// - public override string ToString() => spine.GetAnimation(Index); - - /// - /// 影响了属性面板的判断, 当动画名称相同的时候认为两个对象是相同的, 这样属性面板可以在多选的时候正确显示相同取值的内容 - /// - public override bool Equals(object? obj) - { - if (obj is TrackWrapper) return ToString() == obj.ToString(); - return base.Equals(obj); - } - - /// - /// 哈希码需要和 Equals 行为类似 - /// - public override int GetHashCode() => HashCode.Combine(typeof(TrackWrapper).FullName.GetHashCode(), ToString().GetHashCode()); - } - /// /// 用于在 PropertyGrid 上显示 Spine 动画列表的包装类 /// - public class SpineAnimationWrapper(SpineObject spine) : ICustomTypeDescriptor + public class SpineAnimationProperty(SpineObject spine) : ICustomTypeDescriptor { - /// - /// 轨道属性描述符, 实现对属性的读取和赋值 - /// - /// 轨道索引 - 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; - - /// - /// 得到一个轨道包装类, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性 - /// - public override object? GetValue(object? component) - { - if (component is SpineAnimationWrapper tracks) - return tracks.GetTrackWrapper(idx); - return null; - } - - /// - /// 允许通过字符串赋值修改该轨道的动画, 这里决定了当其他地方的调用 (比如 Converter) 通过 value 来设置属性值的时候应该怎么处理 - /// - public override void SetValue(object? component, object? value) - { - if (component is SpineAnimationWrapper tracks) - { - if (value is string s) - tracks.SetTrackWrapper(idx, s); - } - } - } - [Browsable(false)] 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(); /// - /// TrackWrapper 属性对象缓存 + /// 属性对象缓存 /// - private readonly Dictionary trackWrapperProperties = []; + private readonly Dictionary trackAnimationProperties = []; /// - /// 访问 TrackWrapper 属性 AnimationTracks.Track{i} + /// this.Track{i} /// - public TrackWrapper GetTrackWrapper(int i) + public TrackAnimationProperty GetTrackAnimation(int i) { - if (!trackWrapperProperties.ContainsKey(i)) - trackWrapperProperties[i] = new TrackWrapper(Spine, i); - return trackWrapperProperties[i]; + if (!trackAnimationProperties.ContainsKey(i)) + trackAnimationProperties[i] = new TrackAnimationProperty(Spine, i); + return trackAnimationProperties[i]; } /// - /// 设置 TrackWrapper 属性 AnimationTracks.Track{i} = + /// this.Track{i} = /// - public void SetTrackWrapper(int i, string value) + public void SetTrackAnimation(int i, string value) { Spine.SetAnimation(i, value); TypeDescriptor.Refresh(this); @@ -128,11 +54,11 @@ namespace SpineViewer.Spine.SpineView 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); } - 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 接口实现 @@ -166,6 +92,113 @@ namespace SpineViewer.Spine.SpineView return props; } + /// + /// 轨道属性描述符, 实现对属性的读取和赋值 + /// + /// 轨道索引 + 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; + + /// + /// 得到一个轨道包装类, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性 + /// + public override object? GetValue(object? component) + { + if (component is SpineAnimationProperty tracks) + return tracks.GetTrackAnimation(idx); + return null; + } + + /// + /// 允许通过字符串赋值修改该轨道的动画, 这里决定了当其他地方的调用 (比如 Converter) 通过 value 来设置属性值的时候应该怎么处理 + /// + public override void SetValue(object? component, object? value) + { + if (component is SpineAnimationProperty tracks) + { + if (value is string s) + tracks.SetTrackAnimation(idx, s); + } + } + } + #endregion } + + /// + /// 对 .Track{i} 属性的包装类 + /// + [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)); + + /// + /// 实现了默认的转为字符串的方式 + /// + public override string ToString() => spine.GetAnimation(Index); + + /// + /// 影响了属性面板的判断, 当动画名称相同的时候认为两个对象是相同的, 这样属性面板可以在多选的时候正确显示相同取值的内容 + /// + public override bool Equals(object? obj) + { + if (obj is TrackAnimationProperty) return ToString() == obj.ToString(); + return base.Equals(obj); + } + + /// + /// 哈希码需要和 Equals 行为类似 + /// + public override int GetHashCode() => HashCode.Combine(typeof(TrackAnimationProperty).FullName.GetHashCode(), ToString().GetHashCode()); + } + + /// + /// 轨道索引包装类转换器, 实现字符串和包装类的互相转换, 并且提供标准值列表对属性进行设置, 同时还提供在面板上显示包装类属性的能力 + /// + 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().ToArray(); + if (animTracks.Length > 0) + { + IEnumerable 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); + } + } } diff --git a/SpineViewer/Spine/SpineView/SpineBaseInfoWrapper.cs b/SpineViewer/Spine/SpineView/SpineBaseInfoProperty.cs similarity index 96% rename from SpineViewer/Spine/SpineView/SpineBaseInfoWrapper.cs rename to SpineViewer/Spine/SpineView/SpineBaseInfoProperty.cs index 8ce37ab..c2b6fab 100644 --- a/SpineViewer/Spine/SpineView/SpineBaseInfoWrapper.cs +++ b/SpineViewer/Spine/SpineView/SpineBaseInfoProperty.cs @@ -12,7 +12,7 @@ namespace SpineViewer.Spine.SpineView /// /// 用于在 PropertyGrid 上显示 Spine 基本信息的包装类 /// - public class SpineBaseInfoWrapper(SpineObject spine) + public class SpineBaseInfoProperty(SpineObject spine) { [Browsable(false)] public SpineObject Spine { get; } = spine; diff --git a/SpineViewer/Spine/SpineView/SpineDebugWrapper.cs b/SpineViewer/Spine/SpineView/SpineDebugProperty.cs similarity index 94% rename from SpineViewer/Spine/SpineView/SpineDebugWrapper.cs rename to SpineViewer/Spine/SpineView/SpineDebugProperty.cs index 848a43e..c56db9d 100644 --- a/SpineViewer/Spine/SpineView/SpineDebugWrapper.cs +++ b/SpineViewer/Spine/SpineView/SpineDebugProperty.cs @@ -11,7 +11,7 @@ namespace SpineViewer.Spine.SpineView /// /// 用于在 PropertyGrid 上显示 Spine 调试属性的包装类 /// - public class SpineDebugWrapper(SpineObject spine) + public class SpineDebugProperty(SpineObject spine) { [Browsable(false)] public SpineObject Spine { get; } = spine; diff --git a/SpineViewer/Spine/SpineView/SpineWrapper.cs b/SpineViewer/Spine/SpineView/SpineObjectProperty.cs similarity index 59% rename from SpineViewer/Spine/SpineView/SpineWrapper.cs rename to SpineViewer/Spine/SpineView/SpineObjectProperty.cs index 7128c61..9ffae04 100644 --- a/SpineViewer/Spine/SpineView/SpineWrapper.cs +++ b/SpineViewer/Spine/SpineView/SpineObjectProperty.cs @@ -9,29 +9,29 @@ using SpineViewer.Spine; namespace SpineViewer.Spine.SpineView { - public class SpineWrapper(SpineObject spine) + public class SpineObjectProperty(SpineObject spine) { [Browsable(false)] public SpineObject Spine { get; } = spine; [DisplayName("基本信息")] - public SpineBaseInfoWrapper BaseInfo { get; } = new(spine); + public SpineBaseInfoProperty BaseInfo { get; } = new(spine); [DisplayName("渲染")] - public SpineRenderWrapper Render { get; } = new(spine); + public SpineRenderProperty Render { get; } = new(spine); [DisplayName("变换")] - public SpineTransformWrapper Transform { get; } = new(spine); + public SpineTransformProperty Transform { get; } = new(spine); [TypeConverter(typeof(ExpandableObjectConverter))] [DisplayName("皮肤")] - public SpineSkinWrapper Skin { get; } = new(spine); + public SpineSkinProperty Skin { get; } = new(spine); [TypeConverter(typeof(ExpandableObjectConverter))] [DisplayName("动画")] - public SpineAnimationWrapper Animation { get; } = new(spine); + public SpineAnimationProperty Animation { get; } = new(spine); [DisplayName("调试")] - public SpineDebugWrapper Debug { get; } = new(spine); + public SpineDebugProperty Debug { get; } = new(spine); } } diff --git a/SpineViewer/Spine/SpineView/SpineRenderWrapper.cs b/SpineViewer/Spine/SpineView/SpineRenderProperty.cs similarity index 94% rename from SpineViewer/Spine/SpineView/SpineRenderWrapper.cs rename to SpineViewer/Spine/SpineView/SpineRenderProperty.cs index f378e4f..6445a74 100644 --- a/SpineViewer/Spine/SpineView/SpineRenderWrapper.cs +++ b/SpineViewer/Spine/SpineView/SpineRenderProperty.cs @@ -11,7 +11,7 @@ namespace SpineViewer.Spine.SpineView /// /// 用于在 PropertyGrid 上显示 Spine 渲染设置的包装类 /// - public class SpineRenderWrapper(SpineObject spine) + public class SpineRenderProperty(SpineObject spine) { [Browsable(false)] public SpineObject Spine { get; } = spine; diff --git a/SpineViewer/Spine/SpineView/SpineSkinWrapper.cs b/SpineViewer/Spine/SpineView/SpineSkinProperty.cs similarity index 57% rename from SpineViewer/Spine/SpineView/SpineSkinWrapper.cs rename to SpineViewer/Spine/SpineView/SpineSkinProperty.cs index 02d47a7..cc9b2b7 100644 --- a/SpineViewer/Spine/SpineView/SpineSkinWrapper.cs +++ b/SpineViewer/Spine/SpineView/SpineSkinProperty.cs @@ -9,99 +9,34 @@ using System.Threading.Tasks; namespace SpineViewer.Spine.SpineView { - /// - /// 对皮肤属性的包装类 - /// - [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()); - } - /// /// 皮肤列表动态类型包装类, 用于提供对 Spine 皮肤列表的管理能力 /// /// 关联的 Spine 对象 - public class SpineSkinWrapper(SpineObject spine) : ICustomTypeDescriptor + public class SpineSkinProperty(SpineObject spine) : ICustomTypeDescriptor { - /// - /// 皮肤属性描述符, 实现对属性的读取和赋值 - /// - 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; - - /// - /// 得到一个 SkinWrapper, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性 - /// - public override object? GetValue(object? component) - { - if (component is SpineSkinWrapper manager) - return manager.GetSkinWrapper(idx); - return null; - } - - /// - /// 允许通过字符串赋值修改该位置的皮肤 - /// - public override void SetValue(object? component, object? value) - { - if (component is SpineSkinWrapper manager) - { - if (value is string s) - manager.SetSkinWrapper(idx, s); - } - } - } - [Browsable(false)] public SpineObject Spine { get; } = spine; /// - /// SkinWrapper 属性缓存 + /// 属性缓存 /// - private readonly Dictionary skinWrapperProperties = []; + private readonly Dictionary skinNameProperties = []; /// - /// 访问 SkinWrapper 属性 SkinManager.Skin{i} + /// this.Skin{i} /// - public SkinWrapper GetSkinWrapper(int i) + public SkinNameProperty GetSkinName(int i) { - if (!skinWrapperProperties.ContainsKey(i)) - skinWrapperProperties[i] = new SkinWrapper(Spine, i); - return skinWrapperProperties[i]; + if (!skinNameProperties.ContainsKey(i)) + skinNameProperties[i] = new SkinNameProperty(Spine, i); + return skinNameProperties[i]; } /// - /// 设置 SkinWrapper 属性 SkinManager.Skin{i} = + /// this.Skin{i} = /// - public void SetSkinWrapper(int i, string value) + public void SetSkinName(int i, string value) { Spine.ReplaceSkin(i, value); TypeDescriptor.Refresh(this); @@ -114,17 +49,17 @@ namespace SpineViewer.Spine.SpineView 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); } - 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 接口实现 // XXX: 必须实现 ICustomTypeDescriptor 接口, 不能继承 CustomTypeDescriptor, 似乎继承下来的东西会有问题, 导致某些调用不正确 - private static readonly Dictionary pdCache = []; + private static readonly Dictionary pdCache = []; public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(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++) { if (!pdCache.ContainsKey(i)) - pdCache[i] = new SkinWrapperPropertyDescriptor(i, [new DisplayNameAttribute($"皮肤 {i}")]); + pdCache[i] = new SkinNamePropertyDescriptor(i, [new DisplayNameAttribute($"皮肤 {i}")]); props.Add(pdCache[i]); } return props; } + /// + /// 皮肤属性描述符, 实现对属性的读取和赋值 + /// + 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; + + /// + /// 得到一个 , 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性 + /// + public override object? GetValue(object? component) + { + if (component is SpineSkinProperty prop) + return prop.GetSkinName(idx); + return null; + } + + /// + /// 允许通过字符串赋值修改该位置的皮肤 + /// + public override void SetValue(object? component, object? value) + { + if (component is SpineSkinProperty prop) + { + if (value is string s) + prop.SetSkinName(idx, s); + } + } + } + #endregion } + + /// + /// 对 .Skin{i} 属性的包装类 + /// + [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().ToArray(); + if (managers.Length > 0) + { + IEnumerable 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); + } + } } diff --git a/SpineViewer/Spine/SpineView/SpineTransformWrapper.cs b/SpineViewer/Spine/SpineView/SpineTransformProperty.cs similarity index 95% rename from SpineViewer/Spine/SpineView/SpineTransformWrapper.cs rename to SpineViewer/Spine/SpineView/SpineTransformProperty.cs index d93898f..cba2d1c 100644 --- a/SpineViewer/Spine/SpineView/SpineTransformWrapper.cs +++ b/SpineViewer/Spine/SpineView/SpineTransformProperty.cs @@ -12,7 +12,7 @@ namespace SpineViewer.Spine.SpineView /// /// 用于在 PropertyGrid 上显示 Spine 空间变换的包装类 /// - public class SpineTransformWrapper(SpineObject spine) + public class SpineTransformProperty(SpineObject spine) { [Browsable(false)] public SpineObject Spine { get; } = spine; diff --git a/SpineViewer/Utils/TypeConverter.cs b/SpineViewer/Utils/TypeConverter.cs index 98994bb..5da003f 100644 --- a/SpineViewer/Utils/TypeConverter.cs +++ b/SpineViewer/Utils/TypeConverter.cs @@ -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 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 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); - } - } - - /// - /// 皮肤位包装类转换器, 实现字符串和包装类的互相转换, 并且提供标准值列表对属性进行设置, 同时还提供在面板上显示包装类属性的能力 - /// - 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().ToArray(); - if (managers.Length > 0) - { - IEnumerable 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); - } - } - - /// - /// 轨道索引包装类转换器, 实现字符串和包装类的互相转换, 并且提供标准值列表对属性进行设置, 同时还提供在面板上显示包装类属性的能力 - /// - 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().ToArray(); - if (animTracks.Length > 0) - { - IEnumerable 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 { private class SFMLColorPropertyDescriptor : SimplePropertyDescriptor diff --git a/SpineViewer/Utils/UITypeEditor.cs b/SpineViewer/Utils/UITypeEditor.cs index c4eb140..17c2b13 100644 --- a/SpineViewer/Utils/UITypeEditor.cs +++ b/SpineViewer/Utils/UITypeEditor.cs @@ -36,33 +36,33 @@ namespace SpineViewer.Utils } } - /// - /// skel 文件路径编辑器 - /// - public class SkelFileNameEditor : FileNameEditor - { - protected override void InitializeDialog(OpenFileDialog openFileDialog) - { - base.InitializeDialog(openFileDialog); - openFileDialog.Title = "选择 skel 文件"; - openFileDialog.AddExtension = false; - openFileDialog.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*"; - } - } + ///// + ///// skel 文件路径编辑器 + ///// + //public class SkelFileNameEditor : FileNameEditor + //{ + // protected override void InitializeDialog(OpenFileDialog openFileDialog) + // { + // base.InitializeDialog(openFileDialog); + // openFileDialog.Title = "选择 skel 文件"; + // openFileDialog.AddExtension = false; + // openFileDialog.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*"; + // } + //} - /// - /// atlas 文件路径编辑器 - /// - public class AtlasFileNameEditor : FileNameEditor - { - protected override void InitializeDialog(OpenFileDialog openFileDialog) - { - base.InitializeDialog(openFileDialog); - openFileDialog.Title = "选择 atlas 文件"; - openFileDialog.AddExtension = false; - openFileDialog.Filter = "atlas 文件 (*.atlas)|*.atlas|所有文件 (*.*)|*.*"; - } - } + ///// + ///// atlas 文件路径编辑器 + ///// + //public class AtlasFileNameEditor : FileNameEditor + //{ + // protected override void InitializeDialog(OpenFileDialog openFileDialog) + // { + // base.InitializeDialog(openFileDialog); + // openFileDialog.Title = "选择 atlas 文件"; + // openFileDialog.AddExtension = false; + // openFileDialog.Filter = "atlas 文件 (*.atlas)|*.atlas|所有文件 (*.*)|*.*"; + // } + //} class SFMLColorEditor : UITypeEditor {