Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
750a8b8aff | ||
|
|
cd86155878 | ||
|
|
16739c39d6 | ||
|
|
c7971a9829 | ||
|
|
44c4fc4b21 | ||
|
|
6f1c8e3320 | ||
|
|
8f818416ba | ||
|
|
de6858ca48 | ||
|
|
3fd3d2a378 | ||
|
|
706c9125e6 | ||
|
|
5f026b000c | ||
|
|
0b0d036f08 | ||
|
|
6b9017d535 | ||
|
|
5eb47e33ac | ||
|
|
4d31335da0 | ||
|
|
0b5e76a448 |
@@ -10,6 +10,12 @@
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
:sparkles: `v0.12.5` 新特性: 支持自定义槽位附件 :sparkles:
|
||||
|
||||
---
|
||||
|
||||
## 安装
|
||||
|
||||
前往 [Release](https://github.com/ww-rm/SpineViewer/releases) 界面下载压缩包.
|
||||
|
||||
@@ -39,8 +39,9 @@
|
||||
tabPage_Skin = new TabPage();
|
||||
propertyGrid_Skin = new PropertyGrid();
|
||||
contextMenuStrip_Skin = new ContextMenuStrip(components);
|
||||
toolStripMenuItem_AddSkin = new ToolStripMenuItem();
|
||||
toolStripMenuItem_RemoveSkin = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ReloadSkins = new ToolStripMenuItem();
|
||||
tabPage_Slot = new TabPage();
|
||||
propertyGrid_Slot = new PropertyGrid();
|
||||
tabPage_Animation = new TabPage();
|
||||
propertyGrid_Animation = new PropertyGrid();
|
||||
contextMenuStrip_Animation = new ContextMenuStrip(components);
|
||||
@@ -54,6 +55,7 @@
|
||||
tabPage_Transform.SuspendLayout();
|
||||
tabPage_Skin.SuspendLayout();
|
||||
contextMenuStrip_Skin.SuspendLayout();
|
||||
tabPage_Slot.SuspendLayout();
|
||||
tabPage_Animation.SuspendLayout();
|
||||
contextMenuStrip_Animation.SuspendLayout();
|
||||
tabPage_Debug.SuspendLayout();
|
||||
@@ -66,6 +68,7 @@
|
||||
tabControl.Controls.Add(tabPage_Render);
|
||||
tabControl.Controls.Add(tabPage_Transform);
|
||||
tabControl.Controls.Add(tabPage_Skin);
|
||||
tabControl.Controls.Add(tabPage_Slot);
|
||||
tabControl.Controls.Add(tabPage_Animation);
|
||||
tabControl.Controls.Add(tabPage_Debug);
|
||||
tabControl.Dock = DockStyle.Fill;
|
||||
@@ -108,7 +111,7 @@
|
||||
tabPage_Render.Location = new Point(4, 4);
|
||||
tabPage_Render.Margin = new Padding(0);
|
||||
tabPage_Render.Name = "tabPage_Render";
|
||||
tabPage_Render.Size = new Size(364, 380);
|
||||
tabPage_Render.Size = new Size(364, 370);
|
||||
tabPage_Render.TabIndex = 1;
|
||||
tabPage_Render.Text = "渲染";
|
||||
//
|
||||
@@ -119,7 +122,7 @@
|
||||
propertyGrid_Render.Location = new Point(0, 0);
|
||||
propertyGrid_Render.Name = "propertyGrid_Render";
|
||||
propertyGrid_Render.PropertySort = PropertySort.Alphabetical;
|
||||
propertyGrid_Render.Size = new Size(364, 380);
|
||||
propertyGrid_Render.Size = new Size(364, 370);
|
||||
propertyGrid_Render.TabIndex = 1;
|
||||
propertyGrid_Render.ToolbarVisible = false;
|
||||
//
|
||||
@@ -130,7 +133,7 @@
|
||||
tabPage_Transform.Location = new Point(4, 4);
|
||||
tabPage_Transform.Margin = new Padding(0);
|
||||
tabPage_Transform.Name = "tabPage_Transform";
|
||||
tabPage_Transform.Size = new Size(364, 380);
|
||||
tabPage_Transform.Size = new Size(364, 370);
|
||||
tabPage_Transform.TabIndex = 2;
|
||||
tabPage_Transform.Text = "变换";
|
||||
//
|
||||
@@ -141,7 +144,7 @@
|
||||
propertyGrid_Transform.Location = new Point(0, 0);
|
||||
propertyGrid_Transform.Name = "propertyGrid_Transform";
|
||||
propertyGrid_Transform.PropertySort = PropertySort.Alphabetical;
|
||||
propertyGrid_Transform.Size = new Size(364, 380);
|
||||
propertyGrid_Transform.Size = new Size(364, 370);
|
||||
propertyGrid_Transform.TabIndex = 1;
|
||||
propertyGrid_Transform.ToolbarVisible = false;
|
||||
//
|
||||
@@ -152,7 +155,7 @@
|
||||
tabPage_Skin.Location = new Point(4, 4);
|
||||
tabPage_Skin.Margin = new Padding(0);
|
||||
tabPage_Skin.Name = "tabPage_Skin";
|
||||
tabPage_Skin.Size = new Size(364, 380);
|
||||
tabPage_Skin.Size = new Size(364, 370);
|
||||
tabPage_Skin.TabIndex = 3;
|
||||
tabPage_Skin.Text = "皮肤";
|
||||
//
|
||||
@@ -164,31 +167,45 @@
|
||||
propertyGrid_Skin.Location = new Point(0, 0);
|
||||
propertyGrid_Skin.Name = "propertyGrid_Skin";
|
||||
propertyGrid_Skin.PropertySort = PropertySort.NoSort;
|
||||
propertyGrid_Skin.Size = new Size(364, 380);
|
||||
propertyGrid_Skin.Size = new Size(364, 370);
|
||||
propertyGrid_Skin.TabIndex = 1;
|
||||
propertyGrid_Skin.ToolbarVisible = false;
|
||||
//
|
||||
// contextMenuStrip_Skin
|
||||
//
|
||||
contextMenuStrip_Skin.ImageScalingSize = new Size(24, 24);
|
||||
contextMenuStrip_Skin.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_AddSkin, toolStripMenuItem_RemoveSkin });
|
||||
contextMenuStrip_Skin.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_ReloadSkins });
|
||||
contextMenuStrip_Skin.Name = "contextMenuStrip1";
|
||||
contextMenuStrip_Skin.Size = new Size(117, 64);
|
||||
contextMenuStrip_Skin.Opening += contextMenuStrip_Skin_Opening;
|
||||
contextMenuStrip_Skin.Size = new Size(225, 34);
|
||||
//
|
||||
// toolStripMenuItem_AddSkin
|
||||
// toolStripMenuItem_ReloadSkins
|
||||
//
|
||||
toolStripMenuItem_AddSkin.Name = "toolStripMenuItem_AddSkin";
|
||||
toolStripMenuItem_AddSkin.Size = new Size(116, 30);
|
||||
toolStripMenuItem_AddSkin.Text = "添加";
|
||||
toolStripMenuItem_AddSkin.Click += toolStripMenuItem_AddSkin_Click;
|
||||
toolStripMenuItem_ReloadSkins.Name = "toolStripMenuItem_ReloadSkins";
|
||||
toolStripMenuItem_ReloadSkins.Size = new Size(224, 30);
|
||||
toolStripMenuItem_ReloadSkins.Text = "重新加载所选皮肤";
|
||||
toolStripMenuItem_ReloadSkins.Click += toolStripMenuItem_ReloadSkins_Click;
|
||||
//
|
||||
// toolStripMenuItem_RemoveSkin
|
||||
// tabPage_Slot
|
||||
//
|
||||
toolStripMenuItem_RemoveSkin.Name = "toolStripMenuItem_RemoveSkin";
|
||||
toolStripMenuItem_RemoveSkin.Size = new Size(116, 30);
|
||||
toolStripMenuItem_RemoveSkin.Text = "移除";
|
||||
toolStripMenuItem_RemoveSkin.Click += toolStripMenuItem_RemoveSkin_Click;
|
||||
tabPage_Slot.BackColor = SystemColors.Control;
|
||||
tabPage_Slot.Controls.Add(propertyGrid_Slot);
|
||||
tabPage_Slot.Location = new Point(4, 4);
|
||||
tabPage_Slot.Margin = new Padding(0);
|
||||
tabPage_Slot.Name = "tabPage_Slot";
|
||||
tabPage_Slot.Size = new Size(364, 370);
|
||||
tabPage_Slot.TabIndex = 6;
|
||||
tabPage_Slot.Text = "插槽";
|
||||
//
|
||||
// propertyGrid_Slot
|
||||
//
|
||||
propertyGrid_Slot.Dock = DockStyle.Fill;
|
||||
propertyGrid_Slot.HelpVisible = false;
|
||||
propertyGrid_Slot.Location = new Point(0, 0);
|
||||
propertyGrid_Slot.Name = "propertyGrid_Slot";
|
||||
propertyGrid_Slot.PropertySort = PropertySort.Alphabetical;
|
||||
propertyGrid_Slot.Size = new Size(364, 370);
|
||||
propertyGrid_Slot.TabIndex = 2;
|
||||
propertyGrid_Slot.ToolbarVisible = false;
|
||||
//
|
||||
// tabPage_Animation
|
||||
//
|
||||
@@ -197,7 +214,7 @@
|
||||
tabPage_Animation.Location = new Point(4, 4);
|
||||
tabPage_Animation.Margin = new Padding(0);
|
||||
tabPage_Animation.Name = "tabPage_Animation";
|
||||
tabPage_Animation.Size = new Size(364, 380);
|
||||
tabPage_Animation.Size = new Size(364, 370);
|
||||
tabPage_Animation.TabIndex = 4;
|
||||
tabPage_Animation.Text = "动画";
|
||||
//
|
||||
@@ -209,7 +226,7 @@
|
||||
propertyGrid_Animation.Location = new Point(0, 0);
|
||||
propertyGrid_Animation.Name = "propertyGrid_Animation";
|
||||
propertyGrid_Animation.PropertySort = PropertySort.NoSort;
|
||||
propertyGrid_Animation.Size = new Size(364, 380);
|
||||
propertyGrid_Animation.Size = new Size(364, 370);
|
||||
propertyGrid_Animation.TabIndex = 1;
|
||||
propertyGrid_Animation.ToolbarVisible = false;
|
||||
//
|
||||
@@ -241,7 +258,7 @@
|
||||
tabPage_Debug.Controls.Add(propertyGrid_Debug);
|
||||
tabPage_Debug.Location = new Point(4, 4);
|
||||
tabPage_Debug.Name = "tabPage_Debug";
|
||||
tabPage_Debug.Size = new Size(364, 380);
|
||||
tabPage_Debug.Size = new Size(364, 370);
|
||||
tabPage_Debug.TabIndex = 5;
|
||||
tabPage_Debug.Text = "调试";
|
||||
//
|
||||
@@ -252,16 +269,16 @@
|
||||
propertyGrid_Debug.Location = new Point(0, 0);
|
||||
propertyGrid_Debug.Name = "propertyGrid_Debug";
|
||||
propertyGrid_Debug.PropertySort = PropertySort.NoSort;
|
||||
propertyGrid_Debug.Size = new Size(364, 380);
|
||||
propertyGrid_Debug.Size = new Size(364, 370);
|
||||
propertyGrid_Debug.TabIndex = 2;
|
||||
propertyGrid_Debug.ToolbarVisible = false;
|
||||
//
|
||||
// SpinePropertyGrid
|
||||
// SpineViewPropertyGrid
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
Controls.Add(tabControl);
|
||||
Name = "SpinePropertyGrid";
|
||||
Name = "SpineViewPropertyGrid";
|
||||
Size = new Size(372, 448);
|
||||
tabControl.ResumeLayout(false);
|
||||
tabPage_BaseInfo.ResumeLayout(false);
|
||||
@@ -269,6 +286,7 @@
|
||||
tabPage_Transform.ResumeLayout(false);
|
||||
tabPage_Skin.ResumeLayout(false);
|
||||
contextMenuStrip_Skin.ResumeLayout(false);
|
||||
tabPage_Slot.ResumeLayout(false);
|
||||
tabPage_Animation.ResumeLayout(false);
|
||||
contextMenuStrip_Animation.ResumeLayout(false);
|
||||
tabPage_Debug.ResumeLayout(false);
|
||||
@@ -290,11 +308,12 @@
|
||||
private PropertyGrid propertyGrid_Animation;
|
||||
private ContextMenuStrip contextMenuStrip_Skin;
|
||||
private ContextMenuStrip contextMenuStrip_Animation;
|
||||
private ToolStripMenuItem toolStripMenuItem_AddSkin;
|
||||
private ToolStripMenuItem toolStripMenuItem_RemoveSkin;
|
||||
private ToolStripMenuItem toolStripMenuItem_ReloadSkins;
|
||||
private ToolStripMenuItem toolStripMenuItem_AddAnimation;
|
||||
private ToolStripMenuItem toolStripMenuItem_RemoveAnimation;
|
||||
private TabPage tabPage_Debug;
|
||||
private PropertyGrid propertyGrid_Debug;
|
||||
private TabPage tabPage_Slot;
|
||||
private PropertyGrid propertyGrid_Slot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace SpineViewer.Controls
|
||||
propertyGrid_Render.SelectedObject = null;
|
||||
propertyGrid_Transform.SelectedObject = null;
|
||||
propertyGrid_Skin.SelectedObject = null;
|
||||
propertyGrid_Slot.SelectedObject = null;
|
||||
propertyGrid_Animation.SelectedObject = null;
|
||||
propertyGrid_Debug.SelectedObject = null;
|
||||
}
|
||||
@@ -44,6 +45,7 @@ namespace SpineViewer.Controls
|
||||
propertyGrid_Render.SelectedObjects = value.Select(e => e.Render).ToArray();
|
||||
propertyGrid_Transform.SelectedObjects = value.Select(e => e.Transform).ToArray();
|
||||
propertyGrid_Skin.SelectedObjects = value.Select(e => e.Skin).ToArray();
|
||||
propertyGrid_Slot.SelectedObjects = value.Select(e => e.Slot).ToArray();
|
||||
propertyGrid_Animation.SelectedObjects = value.Select(e => e.Animation).ToArray();
|
||||
propertyGrid_Debug.SelectedObjects = value.Select(e => e.Debug).ToArray();
|
||||
}
|
||||
@@ -51,20 +53,6 @@ namespace SpineViewer.Controls
|
||||
}
|
||||
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 SkinNameProperty;
|
||||
}
|
||||
else
|
||||
{
|
||||
toolStripMenuItem_AddSkin.Enabled = false;
|
||||
toolStripMenuItem_RemoveSkin.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void contextMenuStrip_Animation_Opening(object sender, CancelEventArgs e)
|
||||
{
|
||||
if (selectedSpines?.Length == 1)
|
||||
@@ -79,31 +67,10 @@ namespace SpineViewer.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_AddSkin_Click(object sender, EventArgs e)
|
||||
private void toolStripMenuItem_ReloadSkins_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (selectedSpines?.Length != 1) return;
|
||||
|
||||
var spine = selectedSpines[0].Skin.Spine;
|
||||
|
||||
if (spine.SkinNames.Count <= 0)
|
||||
{
|
||||
MessagePopup.Info("没有可用的皮肤");
|
||||
return;
|
||||
}
|
||||
|
||||
spine.LoadSkin(spine.SkinNames[0]);
|
||||
propertyGrid_Skin.Refresh();
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_RemoveSkin_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (selectedSpines?.Length != 1) return;
|
||||
|
||||
if (propertyGrid_Skin.SelectedGridItem.Value is SkinNameProperty wrapper)
|
||||
{
|
||||
selectedSpines[0].Skin.Spine.UnloadSkin(wrapper.Index);
|
||||
propertyGrid_Skin.Refresh();
|
||||
}
|
||||
foreach (var sp in selectedSpines)
|
||||
sp.Spine.ReloadSkins();
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_AddAnimation_Click(object sender, EventArgs e)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -61,6 +63,11 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
// 2.1.x 不支持剪裁
|
||||
//private SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject21(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
atlas = new Atlas(AtlasPath, textureLoader);
|
||||
@@ -87,11 +94,19 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var (k, att) in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots[k.Key].Name;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
@@ -137,15 +152,15 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
|
||||
// reload skel-dependent data
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
|
||||
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
|
||||
skeleton = new Skeleton(skeletonData);
|
||||
animationState = new AnimationState(animationStateData);
|
||||
|
||||
// 恢复状态
|
||||
position = pos;
|
||||
flipX = fX;
|
||||
flipY = fY;
|
||||
foreach (var s in loadedSkins) addSkin(s);
|
||||
reloadSkins();
|
||||
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
|
||||
}
|
||||
}
|
||||
@@ -172,6 +187,16 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
set => skeleton.FlipY = value;
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (skeletonData.FindSkin(name) is Skin sk)
|
||||
@@ -183,7 +208,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.Skin.Attachments.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
@@ -197,7 +222,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -221,7 +246,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.FlipY = skeleton.FlipY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var name in loadedSkins)
|
||||
foreach (var (name, _) in skinLoadStatus.Where(e => e.Value))
|
||||
{
|
||||
foreach (var (k, v) in skeletonData.FindSkin(name).Attachments)
|
||||
tmpSkeleton.Skin.AddAttachment(k.Key, k.Value, v);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -60,6 +62,11 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject36(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
atlas = new Atlas(AtlasPath, textureLoader);
|
||||
@@ -86,11 +93,19 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var (k, att) in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[k.slotIndex].Name;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
@@ -136,15 +151,15 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
|
||||
// reload skel-dependent data
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
|
||||
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
|
||||
skeleton = new Skeleton(skeletonData);
|
||||
animationState = new AnimationState(animationStateData);
|
||||
|
||||
// 恢复状态
|
||||
position = pos;
|
||||
flipX = fX;
|
||||
flipY = fY;
|
||||
foreach (var s in loadedSkins) addSkin(s);
|
||||
reloadSkins();
|
||||
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
|
||||
}
|
||||
}
|
||||
@@ -171,6 +186,16 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
set => skeleton.FlipY = value;
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (skeletonData.FindSkin(name) is Skin sk)
|
||||
@@ -182,7 +207,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.Skin.Attachments.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
@@ -196,7 +221,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -221,7 +246,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.FlipY = skeleton.FlipY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var name in loadedSkins)
|
||||
foreach (var (name, _) in skinLoadStatus.Where(e => e.Value))
|
||||
{
|
||||
foreach (var (k, v) in skeletonData.FindSkin(name).Attachments)
|
||||
tmpSkeleton.Skin.AddAttachment(k.slotIndex, k.name, v);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -57,6 +59,11 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject37(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
atlas = new Atlas(AtlasPath, textureLoader);
|
||||
@@ -83,11 +90,19 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var (k, att) in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[k.slotIndex].Name;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
@@ -142,6 +157,16 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (skeletonData.FindSkin(name) is Skin sk)
|
||||
@@ -153,7 +178,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.Skin.Attachments.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
@@ -167,7 +192,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -192,7 +217,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var name in loadedSkins)
|
||||
foreach (var (name, _) in skinLoadStatus.Where(e => e.Value))
|
||||
{
|
||||
foreach (var (k, v) in skeletonData.FindSkin(name).Attachments)
|
||||
tmpSkeleton.Skin.AddAttachment(k.slotIndex, k.name, v);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -64,6 +66,11 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject38(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
atlas = new Atlas(AtlasPath, textureLoader);
|
||||
@@ -90,11 +97,19 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var (k, att) in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[k.SlotIndex].Name;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
@@ -149,6 +164,16 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (skeletonData.FindSkin(name) is Skin sk)
|
||||
@@ -158,7 +183,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.Skin.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
@@ -172,7 +197,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -197,7 +222,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var sk in loadedSkins) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var (sk, _) in skinLoadStatus.Where(e => e.Value)) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var tr in animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null))
|
||||
{
|
||||
var ani = animationState.GetCurrent(tr).Animation;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -59,6 +61,11 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject40(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
atlas = new Atlas(AtlasPath, textureLoader);
|
||||
@@ -85,11 +92,20 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var e in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[e.SlotIndex].Name;
|
||||
var att = e.Attachment;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
@@ -144,6 +160,16 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (skeletonData.FindSkin(name) is Skin sk)
|
||||
@@ -153,7 +179,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.Skin.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
@@ -167,7 +193,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -192,7 +218,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var sk in loadedSkins) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var (sk, _) in skinLoadStatus.Where(e => e.Value)) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var tr in animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null))
|
||||
{
|
||||
var ani = animationState.GetCurrent(tr).Animation;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -59,6 +61,11 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject41(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
atlas = new Atlas(AtlasPath, textureLoader);
|
||||
@@ -85,11 +92,20 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var e in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[e.SlotIndex].Name;
|
||||
var att = e.Attachment;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
@@ -144,6 +160,16 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (skeletonData.FindSkin(name) is Skin sk)
|
||||
@@ -153,7 +179,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.Skin.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
@@ -167,7 +193,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -192,7 +218,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var sk in loadedSkins) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var (sk, _) in skinLoadStatus.Where(e => e.Value)) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var tr in animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null))
|
||||
{
|
||||
var ani = animationState.GetCurrent(tr).Animation;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -12,7 +14,7 @@ using SpineViewer.Utils;
|
||||
namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
{
|
||||
[SpineImplementation(SpineVersion.V42)]
|
||||
internal class Spineobject42 : Spine.SpineObject
|
||||
internal class SpineObject42 : Spine.SpineObject
|
||||
{
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
@@ -59,7 +61,12 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
public Spineobject42(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject42(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
atlas = new Atlas(AtlasPath, textureLoader);
|
||||
try
|
||||
@@ -85,11 +92,20 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var e in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[e.SlotIndex].Name;
|
||||
var att = e.Attachment;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
@@ -144,6 +160,16 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (skeletonData.FindSkin(name) is Skin sk)
|
||||
@@ -153,7 +179,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.Skin.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
@@ -167,7 +193,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -192,7 +218,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var sk in loadedSkins) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var (sk, _) in skinLoadStatus.Where(e => e.Value)) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var tr in animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null))
|
||||
{
|
||||
var ani = animationState.GetCurrent(tr).Animation;
|
||||
@@ -59,7 +59,23 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
/// <summary>
|
||||
/// 画面分辨率
|
||||
/// </summary>
|
||||
public Size Resolution { get; set; } = new(100, 100);
|
||||
public Size Resolution
|
||||
{
|
||||
get => resolution;
|
||||
set
|
||||
{
|
||||
if (value.Width <= 0) value.Width = 100;
|
||||
if (value.Height <= 0) value.Height = 100;
|
||||
resolution = value;
|
||||
exportResolution = new(value.Width + Margin.Horizontal, value.Height + Margin.Vertical);
|
||||
}
|
||||
}
|
||||
private Size resolution = new(100, 100);
|
||||
|
||||
/// <summary>
|
||||
/// 包含边缘的分辨率
|
||||
/// </summary>
|
||||
private Size exportResolution = new(100, 100);
|
||||
|
||||
/// <summary>
|
||||
/// 预览画面的视区
|
||||
@@ -86,7 +102,7 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
bcPma.R = (byte)(bcPma.R * a);
|
||||
bcPma.G = (byte)(bcPma.G * a);
|
||||
bcPma.B = (byte)(bcPma.B * a);
|
||||
BackgroundColorPma = bcPma;
|
||||
backgroundColorPma = bcPma;
|
||||
}
|
||||
}
|
||||
private SFML.Graphics.Color backgroundColor = SFML.Graphics.Color.Transparent;
|
||||
@@ -94,7 +110,7 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
/// <summary>
|
||||
/// 预乘后的背景颜色
|
||||
/// </summary>
|
||||
public SFML.Graphics.Color BackgroundColorPma { get; private set; } = SFML.Graphics.Color.Transparent;
|
||||
private SFML.Graphics.Color backgroundColorPma = SFML.Graphics.Color.Transparent;
|
||||
|
||||
/// <summary>
|
||||
/// 四周边缘距离, 单位为像素
|
||||
@@ -109,6 +125,7 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
if (value.Top < 0) value.Top = 0;
|
||||
if (value.Bottom < 0) value.Bottom = 0;
|
||||
margin = value;
|
||||
exportResolution = new(Resolution.Width + value.Horizontal, Resolution.Height + value.Vertical);
|
||||
}
|
||||
}
|
||||
private Padding margin = new(0);
|
||||
@@ -145,6 +162,10 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
/// </summary>
|
||||
private SFML.Graphics.RenderTexture GetRenderTexture(SpineObject[]? spinesToRender = null)
|
||||
{
|
||||
uint width;
|
||||
uint height;
|
||||
SFML.Graphics.View view;
|
||||
|
||||
if (spinesToRender is null)
|
||||
{
|
||||
if (exportViewCache is null)
|
||||
@@ -162,9 +183,9 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
exportViewCache.SetViewport(Resolution, Margin, Padding);
|
||||
}
|
||||
}
|
||||
var tex = new SFML.Graphics.RenderTexture((uint)(Resolution.Width + Margin.Horizontal), (uint)(Resolution.Height + Margin.Vertical));
|
||||
tex.SetView(exportViewCache);
|
||||
return tex;
|
||||
width = (uint)exportResolution.Width;
|
||||
height = (uint)exportResolution.Height;
|
||||
view = exportViewCache;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -180,7 +201,7 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
var spineResolution = new Size((int)Math.Ceiling(spineBounds.Width), (int)Math.Ceiling(spineBounds.Height));
|
||||
var canvasBounds = spineBounds.GetCanvasBounds(spineResolution, Margin); // 忽略填充
|
||||
|
||||
spineResolutionCache[cacheKey] = spineResolution;
|
||||
spineResolutionCache[cacheKey] = new(spineResolution.Width + Margin.Horizontal, spineResolution.Height + Margin.Vertical);
|
||||
spineViewCache[cacheKey] = spineView = new SFML.Graphics.View(
|
||||
new(canvasBounds.X + canvasBounds.Width / 2, canvasBounds.Y + canvasBounds.Height / 2),
|
||||
new(canvasBounds.Width, -canvasBounds.Height)
|
||||
@@ -188,14 +209,14 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
|
||||
logger.Info("Auto resolusion: ({}, {})", spineResolution.Width, spineResolution.Height);
|
||||
}
|
||||
|
||||
var tex = new SFML.Graphics.RenderTexture(
|
||||
(uint)(spineResolutionCache[cacheKey].Width + Margin.Horizontal),
|
||||
(uint)(spineResolutionCache[cacheKey].Height + Margin.Vertical)
|
||||
);
|
||||
tex.SetView(spineViewCache[cacheKey]);
|
||||
return tex;
|
||||
width = (uint)spineResolutionCache[cacheKey].Width;
|
||||
height = (uint)spineResolutionCache[cacheKey].Height;
|
||||
view = spineViewCache[cacheKey];
|
||||
}
|
||||
|
||||
var tex = new SFML.Graphics.RenderTexture(width, height);
|
||||
tex.SetView(view);
|
||||
return tex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -212,7 +233,7 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
using var texPma = GetRenderTexture(AutoResolution ? spinesToRender : null);
|
||||
|
||||
// 先将预乘结果准确绘制出来, 注意背景色也应当是预乘的
|
||||
texPma.Clear(BackgroundColorPma);
|
||||
texPma.Clear(backgroundColorPma);
|
||||
foreach (var spine in spinesToRender) texPma.Draw(spine);
|
||||
texPma.Display();
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ using NLog;
|
||||
using System.Xml.Linq;
|
||||
using SpineViewer.Extensions;
|
||||
using SpineViewer.Utils;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Frozen;
|
||||
using System.Linq;
|
||||
|
||||
namespace SpineViewer.Spine
|
||||
{
|
||||
@@ -14,6 +17,11 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
public abstract class SpineObject : ImplementationResolver<SpineObject, SpineImplementationAttribute, SpineVersion>, SFML.Graphics.Drawable, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 空附件标记
|
||||
/// </summary>
|
||||
protected const string EMPTY_ATTACHMENT = "<Empty>";
|
||||
|
||||
/// <summary>
|
||||
/// 空动画标记
|
||||
/// </summary>
|
||||
@@ -65,9 +73,6 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
private SpineObject PostInit()
|
||||
{
|
||||
SkinNames = skinNames.AsReadOnly();
|
||||
AnimationNames = animationNames.AsReadOnly();
|
||||
|
||||
// 必须 Update 一次否则包围盒还没有值
|
||||
update(0);
|
||||
|
||||
@@ -88,13 +93,11 @@ namespace SpineViewer.Spine
|
||||
tex.Display();
|
||||
Preview = tex.Texture.CopyToBitmap();
|
||||
|
||||
// 默认初始化10个空位
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
setAnimation(i, AnimationNames.First());
|
||||
loadedSkins.Add(SkinNames.First());
|
||||
}
|
||||
reloadSkins();
|
||||
// 初始化皮肤加载情况
|
||||
foreach (var n in SkinNames) skinLoadStatus[n] = false;
|
||||
|
||||
// 默认初始化10个动画空位
|
||||
for (int i = 0; i < 10; i++) setAnimation(i, AnimationNames.First());
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -201,18 +204,6 @@ namespace SpineViewer.Spine
|
||||
}
|
||||
protected abstract bool flipY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 包含的所有皮肤名称
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<string> SkinNames { get; private set; }
|
||||
protected readonly List<string> skinNames = [];
|
||||
|
||||
/// <summary>
|
||||
/// 包含的所有动画名称
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<string> AnimationNames { get; private set; }
|
||||
protected readonly List<string> animationNames = [EMPTY_ANIMATION];
|
||||
|
||||
/// <summary>
|
||||
/// 是否被选中
|
||||
/// </summary>
|
||||
@@ -334,70 +325,67 @@ namespace SpineViewer.Spine
|
||||
protected bool debugClippings = false;
|
||||
|
||||
/// <summary>
|
||||
/// 获取已加载的皮肤列表快照, 允许出现重复值
|
||||
/// 所有插槽下可用的附件名
|
||||
/// </summary>
|
||||
public string[] GetLoadedSkins() { lock (_lock) return loadedSkins.ToArray(); }
|
||||
protected readonly List<string> loadedSkins = [];
|
||||
public FrozenDictionary<string, ImmutableArray<string>> SlotAttachmentNames { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 加载指定皮肤, 添加至列表末尾, 如果不存在则忽略, 允许加载重复的值
|
||||
/// 包含的所有皮肤名称 (不含 default 默认皮肤)
|
||||
/// </summary>
|
||||
public void LoadSkin(string name)
|
||||
public ImmutableArray<string> SkinNames { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 包含的所有动画名称
|
||||
/// </summary>
|
||||
public ImmutableArray<string> AnimationNames { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取某个插槽当前加载的附件
|
||||
/// </summary>
|
||||
public string GetSlotAttachment(string slot) { lock (_lock) return getSlotAttachment(slot); }
|
||||
protected abstract string getSlotAttachment(string slot);
|
||||
|
||||
/// <summary>
|
||||
/// 设置某个插槽当前加载的附件
|
||||
/// </summary>
|
||||
public void SetSlotAttachment(string slot, string name) { lock (_lock) setSlotAttachment(slot, name); }
|
||||
protected abstract void setSlotAttachment(string slot, string name);
|
||||
|
||||
/// <summary>
|
||||
/// 皮肤的加载情况记录表
|
||||
/// </summary>
|
||||
protected readonly Dictionary<string, bool> skinLoadStatus = [];
|
||||
|
||||
/// <summary>
|
||||
/// 查询皮肤加载状态, 皮肤不存在时返回 false
|
||||
/// </summary>
|
||||
public bool GetSkinStatus(string name) { lock (_lock) return skinLoadStatus.TryGetValue(name, out var status) && status; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置皮肤加载状态, 忽略不存在的皮肤
|
||||
/// </summary>
|
||||
public void SetSkinStatus(string name, bool status)
|
||||
{
|
||||
if (!skinNames.Contains(name)) return;
|
||||
if (!skinLoadStatus.ContainsKey(name)) return;
|
||||
lock (_lock)
|
||||
{
|
||||
loadedSkins.Add(name);
|
||||
skinLoadStatus[name] = status;
|
||||
reloadSkins();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卸载列表指定位置皮肤, 如果超出范围则忽略
|
||||
/// </summary>
|
||||
public void UnloadSkin(int idx)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (idx < 0 || idx >= loadedSkins.Count) return;
|
||||
loadedSkins.RemoveAt(idx);
|
||||
reloadSkins();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 替换皮肤列表指定位置皮肤, 超出范围或者皮肤不存在则忽略
|
||||
/// </summary>
|
||||
public void ReplaceSkin(int idx, string name)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (idx < 0 || idx >= loadedSkins.Count || !skinNames.Contains(name)) return;
|
||||
loadedSkins[idx] = name;
|
||||
reloadSkins();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新加载现有皮肤列表, 用于刷新等操作
|
||||
/// 刷新已加载皮肤
|
||||
/// </summary>
|
||||
public void ReloadSkins() { lock (_lock) reloadSkins(); }
|
||||
private void reloadSkins()
|
||||
protected void reloadSkins()
|
||||
{
|
||||
clearSkin();
|
||||
foreach (var s in loadedSkins.Distinct()) addSkin(s);
|
||||
clearSkins();
|
||||
foreach (var (name, _) in skinLoadStatus.Where(e => e.Value)) addSkin(name);
|
||||
update(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载皮肤, 如果不存在则忽略
|
||||
/// </summary>
|
||||
protected abstract void addSkin(string name);
|
||||
|
||||
/// <summary>
|
||||
/// 清空加载的所有皮肤
|
||||
/// </summary>
|
||||
protected abstract void clearSkin();
|
||||
protected abstract void clearSkins();
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有非 null 的轨道索引快照
|
||||
@@ -576,4 +564,4 @@ namespace SpineViewer.Spine
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +85,9 @@ namespace SpineViewer.Spine.SpineView
|
||||
var props = new PropertyDescriptorCollection(TypeDescriptor.GetProperties(this, attributes, true).Cast<PropertyDescriptor>().ToArray());
|
||||
foreach (var i in Spine.GetTrackIndices())
|
||||
{
|
||||
if (!pdCache.ContainsKey(i))
|
||||
pdCache[i] = new TrackWrapperPropertyDescriptor(i, [new DisplayNameAttribute($"轨道 {i}")]);
|
||||
props.Add(pdCache[i]);
|
||||
if (!pdCache.TryGetValue(i, out var pd))
|
||||
pdCache[i] = pd = new TrackWrapperPropertyDescriptor(i, [new DisplayNameAttribute($"轨道 {i}")]);
|
||||
props.Add(pd);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
@@ -186,17 +186,12 @@ namespace SpineViewer.Spine.SpineView
|
||||
{
|
||||
return new StandardValuesCollection(tracks.Spine.AnimationNames);
|
||||
}
|
||||
else if (context?.Instance is object[] instances && instances.All(x => x is SpineAnimationProperty))
|
||||
else if (context?.Instance is object[] instances)
|
||||
{
|
||||
// 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());
|
||||
}
|
||||
IEnumerable<string> common = [];
|
||||
foreach (SpineAnimationProperty prop in instances.Where(inst => inst is SpineAnimationProperty))
|
||||
common = common.Union(prop.Spine.AnimationNames);
|
||||
return new StandardValuesCollection(common.ToArray());
|
||||
}
|
||||
return base.GetStandardValues(context);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ namespace SpineViewer.Spine.SpineView
|
||||
[DisplayName("皮肤")]
|
||||
public SpineSkinProperty Skin { get; } = new(spine);
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
[DisplayName("插槽")]
|
||||
public SpineSlotProperty Slot { get; } = new(spine);
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
[DisplayName("动画")]
|
||||
public SpineAnimationProperty Animation { get; } = new(spine);
|
||||
|
||||
@@ -10,7 +10,7 @@ using System.Threading.Tasks;
|
||||
namespace SpineViewer.Spine.SpineView
|
||||
{
|
||||
/// <summary>
|
||||
/// 皮肤列表动态类型包装类, 用于提供对 Spine 皮肤列表的管理能力
|
||||
/// 皮肤动态类型包装类, 用于提供对 Spine 皮肤的管理能力
|
||||
/// </summary>
|
||||
/// <param name="spine">关联的 Spine 对象</param>
|
||||
public class SpineSkinProperty(SpineObject spine) : ICustomTypeDescriptor
|
||||
@@ -18,34 +18,10 @@ namespace SpineViewer.Spine.SpineView
|
||||
[Browsable(false)]
|
||||
public SpineObject Spine { get; } = spine;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SpineSkinProperty"/> 属性缓存
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, SkinNameProperty> skinNameProperties = [];
|
||||
|
||||
/// <summary>
|
||||
/// <c>this.Skin{i}</c>
|
||||
/// </summary>
|
||||
public SkinNameProperty GetSkinName(int i)
|
||||
{
|
||||
if (!skinNameProperties.ContainsKey(i))
|
||||
skinNameProperties[i] = new SkinNameProperty(Spine, i);
|
||||
return skinNameProperties[i];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>this.Skin{i} = <paramref name="value"/></c>
|
||||
/// </summary>
|
||||
public void SetSkinName(int i, string value)
|
||||
{
|
||||
Spine.ReplaceSkin(i, value);
|
||||
TypeDescriptor.Refresh(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在属性面板悬停可以显示已加载的皮肤列表
|
||||
/// </summary>
|
||||
public override string ToString() => $"[{string.Join(", ", Spine.GetLoadedSkins())}]";
|
||||
public override string ToString() => $"[{string.Join(", ", Spine.SkinNames.Where(Spine.GetSkinStatus))}]";
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
@@ -59,7 +35,7 @@ namespace SpineViewer.Spine.SpineView
|
||||
|
||||
// XXX: 必须实现 ICustomTypeDescriptor 接口, 不能继承 CustomTypeDescriptor, 似乎继承下来的东西会有问题, 导致某些调用不正确
|
||||
|
||||
private static readonly Dictionary<int, SkinNamePropertyDescriptor> pdCache = [];
|
||||
private static readonly Dictionary<string, SkinPropertyDescriptor> pdCache = [];
|
||||
|
||||
public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
|
||||
public string? GetClassName() => TypeDescriptor.GetClassName(this, true);
|
||||
@@ -75,112 +51,44 @@ namespace SpineViewer.Spine.SpineView
|
||||
public PropertyDescriptorCollection GetProperties(Attribute[]? attributes)
|
||||
{
|
||||
var props = new PropertyDescriptorCollection(TypeDescriptor.GetProperties(this, attributes, true).Cast<PropertyDescriptor>().ToArray());
|
||||
for (var i = 0; i < Spine.GetLoadedSkins().Length; i++)
|
||||
foreach (var name in Spine.SkinNames)
|
||||
{
|
||||
if (!pdCache.ContainsKey(i))
|
||||
pdCache[i] = new SkinNamePropertyDescriptor(i, [new DisplayNameAttribute($"皮肤 {i}")]);
|
||||
props.Add(pdCache[i]);
|
||||
if (!pdCache.TryGetValue(name, out var pd))
|
||||
pdCache[name] = pd = new SkinPropertyDescriptor(name, [new DisplayNameAttribute(name)]);
|
||||
props.Add(pd);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 皮肤属性描述符, 实现对属性的读取和赋值
|
||||
/// 皮肤属性描述符, 实现对皮肤的加载和卸载, <c><see cref="SpineSkinProperty"/>.{name}</c>
|
||||
/// </summary>
|
||||
private class SkinNamePropertyDescriptor(int i, Attribute[]? attributes) : PropertyDescriptor($"Skin{i}", attributes)
|
||||
private class SkinPropertyDescriptor(string name, Attribute[]? attributes) : PropertyDescriptor(name, 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 Type PropertyType => typeof(bool);
|
||||
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 prop.Spine.GetSkinStatus(Name);
|
||||
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);
|
||||
if (value is bool s)
|
||||
prop.Spine.SetSkinStatus(Name, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
170
SpineViewer/Spine/SpineView/SpineSlotProperty.cs
Normal file
170
SpineViewer/Spine/SpineView/SpineSlotProperty.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using SpineViewer.Spine;
|
||||
using SpineViewer.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SpineViewer.Spine.SpineView
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于在 PropertyGrid 上显示插槽附件加载情况包装类
|
||||
/// </summary>
|
||||
public class SpineSlotProperty(SpineObject spine) : ICustomTypeDescriptor
|
||||
{
|
||||
[Browsable(false)]
|
||||
public SpineObject Spine { get; } = spine;
|
||||
|
||||
/// <summary>
|
||||
/// 显示所有插槽集合
|
||||
/// </summary>
|
||||
public override string ToString() => $"[{string.Join(", ", Spine.SlotAttachmentNames.Keys)}]";
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is SpineAnimationProperty prop) return ToString() == prop.ToString();
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(SpineAnimationProperty).FullName.GetHashCode(), ToString().GetHashCode());
|
||||
|
||||
#region ICustomTypeDescriptor 接口实现
|
||||
|
||||
// XXX: 必须实现 ICustomTypeDescriptor 接口, 不能继承 CustomTypeDescriptor, 似乎继承下来的东西会有问题, 导致某些调用不正确
|
||||
|
||||
/// <summary>
|
||||
/// 属性描述符缓存
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, SlotPropertyDescriptor> pdCache = [];
|
||||
|
||||
public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
|
||||
public string? GetClassName() => TypeDescriptor.GetClassName(this, true);
|
||||
public string? GetComponentName() => TypeDescriptor.GetComponentName(this, true);
|
||||
public TypeConverter? GetConverter() => TypeDescriptor.GetConverter(this, true);
|
||||
public EventDescriptor? GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true);
|
||||
public PropertyDescriptor? GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(this, true);
|
||||
public object? GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(this, editorBaseType, true);
|
||||
public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(this, true);
|
||||
public EventDescriptorCollection GetEvents(Attribute[]? attributes) => TypeDescriptor.GetEvents(this, attributes, true);
|
||||
public object? GetPropertyOwner(PropertyDescriptor? pd) => this;
|
||||
public PropertyDescriptorCollection GetProperties() => GetProperties(null);
|
||||
public PropertyDescriptorCollection GetProperties(Attribute[]? attributes)
|
||||
{
|
||||
var props = new PropertyDescriptorCollection(TypeDescriptor.GetProperties(this, attributes, true).Cast<PropertyDescriptor>().ToArray());
|
||||
foreach (var slotName in Spine.SlotAttachmentNames.Keys)
|
||||
{
|
||||
if (!pdCache.TryGetValue(slotName, out var pd))
|
||||
pdCache[slotName] = pd = new SlotPropertyDescriptor(slotName, [new DisplayNameAttribute($"{slotName}")]);
|
||||
props.Add(pd);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插槽属性描述符, 实现对属性的读取和赋值
|
||||
/// </summary>
|
||||
internal class SlotPropertyDescriptor(string name, Attribute[]? attributes) : PropertyDescriptor(name, attributes)
|
||||
{
|
||||
public override Type ComponentType => typeof(SpineSlotProperty);
|
||||
public override bool IsReadOnly => false;
|
||||
public override Type PropertyType => typeof(SlotProperty);
|
||||
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 SpineSlotProperty slots)
|
||||
return slots.Spine.GetSlotAttachment(Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 允许通过字符串赋值修改该轨道的动画, 这里决定了当其他地方的调用 (比如 Converter) 通过 value 来设置属性值的时候应该怎么处理
|
||||
/// </summary>
|
||||
public override void SetValue(object? component, object? value)
|
||||
{
|
||||
if (component is SpineSlotProperty slots)
|
||||
{
|
||||
if (value is string s)
|
||||
slots.Spine.SetSlotAttachment(Name, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对 <c><see cref="SpineSlotProperty"/>.{name}</c> 属性的包装类
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(SlotPropertyConverter))]
|
||||
public class SlotProperty(SpineObject spine, string name)
|
||||
{
|
||||
private readonly SpineObject spine = spine;
|
||||
|
||||
[Browsable(false)]
|
||||
public string Name { get; } = name;
|
||||
|
||||
/// <summary>
|
||||
/// 实现了默认的转为字符串的方式
|
||||
/// </summary>
|
||||
public override string ToString() => spine.GetSlotAttachment(Name);
|
||||
|
||||
/// <summary>
|
||||
/// 影响了属性面板的判断, 当动画名称相同的时候认为两个对象是相同的, 这样属性面板可以在多选的时候正确显示相同取值的内容
|
||||
/// </summary>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is SlotProperty) return ToString() == obj.ToString();
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 哈希码需要和 Equals 行为类似
|
||||
/// </summary>
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(SlotProperty).FullName.GetHashCode(), ToString().GetHashCode());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轨道索引包装类转换器, 实现字符串和包装类的互相转换, 并且提供标准值列表对属性进行设置, 同时还提供在面板上显示包装类属性的能力
|
||||
/// </summary>
|
||||
public class SlotPropertyConverter : 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?.PropertyDescriptor is PropertyDescriptor pd)
|
||||
{
|
||||
if (context?.Instance is SpineSlotProperty slots)
|
||||
{
|
||||
if (slots.Spine.SlotAttachmentNames.TryGetValue(pd.Name, out var names))
|
||||
return new StandardValuesCollection(names);
|
||||
}
|
||||
else if (context?.Instance is object[] instances)
|
||||
{
|
||||
IEnumerable<string> common = [];
|
||||
foreach (SpineSlotProperty prop in instances.Where(inst => inst is SpineSlotProperty))
|
||||
{
|
||||
if (prop.Spine.SlotAttachmentNames.TryGetValue(pd.Name, out var names))
|
||||
common = common.Union(names);
|
||||
}
|
||||
return new StandardValuesCollection(common.ToArray());
|
||||
}
|
||||
}
|
||||
return base.GetStandardValues(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.12.4</Version>
|
||||
<Version>0.12.5</Version>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||
|
||||
@@ -111,6 +111,8 @@ namespace SpineViewer.Utils
|
||||
public override void SetValue(object? component, object? value) => component?.GetType().GetField(Name)?.SetValue(component, value);
|
||||
}
|
||||
|
||||
private static PropertyDescriptorCollection pdCollection = null;
|
||||
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
|
||||
@@ -156,18 +158,16 @@ namespace SpineViewer.Utils
|
||||
|
||||
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext? context, object value, Attribute[]? attributes)
|
||||
{
|
||||
// 自定义属性集合
|
||||
var properties = new List<PropertyDescriptor>
|
||||
{
|
||||
// 定义 R, G, B, A 四个字段的描述器
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "R", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "G", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "B", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "A", typeof(byte))
|
||||
};
|
||||
|
||||
// 返回自定义属性集合
|
||||
return new PropertyDescriptorCollection(properties.ToArray());
|
||||
pdCollection ??= new(
|
||||
[
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "R", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "G", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "B", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "A", typeof(byte))
|
||||
],
|
||||
true
|
||||
);
|
||||
return pdCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
img/preview.webp
BIN
img/preview.webp
Binary file not shown.
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 134 KiB |
Reference in New Issue
Block a user