增加3.8及以上版本多皮肤支持
This commit is contained in:
@@ -37,7 +37,7 @@ namespace SpineViewer.Dialogs
|
|||||||
spine.ClearTrack(tr.Index);
|
spine.ClearTrack(tr.Index);
|
||||||
}
|
}
|
||||||
propertyGrid_AnimationTracks.Refresh();
|
propertyGrid_AnimationTracks.Refresh();
|
||||||
propertyGrid_AnimationTracks.SelectedGridItem = propertyGrid_AnimationTracks.SelectedGridItem.Parent.GridItems.Cast<GridItem>().Last();
|
propertyGrid_AnimationTracks.SelectedGridItem = propertyGrid_AnimationTracks.SelectedGridItem?.Parent?.GridItems?.Cast<GridItem>().Last();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
139
SpineViewer/Dialogs/SkinManagerEditorDialog.Designer.cs
generated
Normal file
139
SpineViewer/Dialogs/SkinManagerEditorDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
namespace SpineViewer.Dialogs
|
||||||
|
{
|
||||||
|
partial class SkinManagerEditorDialog
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Required designer variable.
|
||||||
|
/// </summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resources being used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && (components != null))
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required method for Designer support - do not modify
|
||||||
|
/// the contents of this method with the code editor.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
panel = new Panel();
|
||||||
|
tableLayoutPanel1 = new TableLayoutPanel();
|
||||||
|
flowLayoutPanel1 = new FlowLayoutPanel();
|
||||||
|
button_Add = new Button();
|
||||||
|
button_Delete = new Button();
|
||||||
|
propertyGrid_SkinManager = new PropertyGrid();
|
||||||
|
panel.SuspendLayout();
|
||||||
|
tableLayoutPanel1.SuspendLayout();
|
||||||
|
flowLayoutPanel1.SuspendLayout();
|
||||||
|
SuspendLayout();
|
||||||
|
//
|
||||||
|
// panel
|
||||||
|
//
|
||||||
|
panel.Controls.Add(tableLayoutPanel1);
|
||||||
|
panel.Dock = DockStyle.Fill;
|
||||||
|
panel.Location = new Point(0, 0);
|
||||||
|
panel.Name = "panel";
|
||||||
|
panel.Padding = new Padding(50, 15, 50, 10);
|
||||||
|
panel.Size = new Size(666, 483);
|
||||||
|
panel.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// tableLayoutPanel1
|
||||||
|
//
|
||||||
|
tableLayoutPanel1.ColumnCount = 1;
|
||||||
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel1.Controls.Add(flowLayoutPanel1, 0, 1);
|
||||||
|
tableLayoutPanel1.Controls.Add(propertyGrid_SkinManager, 0, 0);
|
||||||
|
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
|
tableLayoutPanel1.Location = new Point(50, 15);
|
||||||
|
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||||
|
tableLayoutPanel1.RowCount = 2;
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel1.Size = new Size(566, 458);
|
||||||
|
tableLayoutPanel1.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// flowLayoutPanel1
|
||||||
|
//
|
||||||
|
flowLayoutPanel1.AutoSize = true;
|
||||||
|
flowLayoutPanel1.Controls.Add(button_Add);
|
||||||
|
flowLayoutPanel1.Controls.Add(button_Delete);
|
||||||
|
flowLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
|
flowLayoutPanel1.Location = new Point(3, 415);
|
||||||
|
flowLayoutPanel1.Name = "flowLayoutPanel1";
|
||||||
|
flowLayoutPanel1.Size = new Size(560, 40);
|
||||||
|
flowLayoutPanel1.TabIndex = 2;
|
||||||
|
//
|
||||||
|
// button_Add
|
||||||
|
//
|
||||||
|
button_Add.Location = new Point(3, 3);
|
||||||
|
button_Add.Name = "button_Add";
|
||||||
|
button_Add.Size = new Size(112, 34);
|
||||||
|
button_Add.TabIndex = 0;
|
||||||
|
button_Add.Text = "添加";
|
||||||
|
button_Add.UseVisualStyleBackColor = true;
|
||||||
|
button_Add.Click += button_Add_Click;
|
||||||
|
//
|
||||||
|
// button_Delete
|
||||||
|
//
|
||||||
|
button_Delete.Location = new Point(121, 3);
|
||||||
|
button_Delete.Name = "button_Delete";
|
||||||
|
button_Delete.Size = new Size(112, 34);
|
||||||
|
button_Delete.TabIndex = 1;
|
||||||
|
button_Delete.Text = "删除";
|
||||||
|
button_Delete.UseVisualStyleBackColor = true;
|
||||||
|
button_Delete.Click += button_Delete_Click;
|
||||||
|
//
|
||||||
|
// propertyGrid_SkinManager
|
||||||
|
//
|
||||||
|
propertyGrid_SkinManager.Dock = DockStyle.Fill;
|
||||||
|
propertyGrid_SkinManager.HelpVisible = false;
|
||||||
|
propertyGrid_SkinManager.Location = new Point(3, 3);
|
||||||
|
propertyGrid_SkinManager.Name = "propertyGrid_SkinManager";
|
||||||
|
propertyGrid_SkinManager.PropertySort = PropertySort.NoSort;
|
||||||
|
propertyGrid_SkinManager.Size = new Size(560, 406);
|
||||||
|
propertyGrid_SkinManager.TabIndex = 1;
|
||||||
|
propertyGrid_SkinManager.ToolbarVisible = false;
|
||||||
|
//
|
||||||
|
// SkinManagerEditorDialog
|
||||||
|
//
|
||||||
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
|
ClientSize = new Size(666, 483);
|
||||||
|
Controls.Add(panel);
|
||||||
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
|
MaximizeBox = false;
|
||||||
|
MinimizeBox = false;
|
||||||
|
Name = "SkinManagerEditorDialog";
|
||||||
|
ShowIcon = false;
|
||||||
|
ShowInTaskbar = false;
|
||||||
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
|
Text = "皮肤列表实时编辑器";
|
||||||
|
panel.ResumeLayout(false);
|
||||||
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
|
tableLayoutPanel1.PerformLayout();
|
||||||
|
flowLayoutPanel1.ResumeLayout(false);
|
||||||
|
ResumeLayout(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private Panel panel;
|
||||||
|
private TableLayoutPanel tableLayoutPanel1;
|
||||||
|
private FlowLayoutPanel flowLayoutPanel1;
|
||||||
|
private Button button_Add;
|
||||||
|
private Button button_Delete;
|
||||||
|
private PropertyGrid propertyGrid_SkinManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
SpineViewer/Dialogs/SkinManagerEditorDialog.cs
Normal file
45
SpineViewer/Dialogs/SkinManagerEditorDialog.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using SpineViewer.Spine;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Data;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace SpineViewer.Dialogs
|
||||||
|
{
|
||||||
|
public partial class SkinManagerEditorDialog : Form
|
||||||
|
{
|
||||||
|
private readonly Spine.Spine spine;
|
||||||
|
public SkinManagerEditorDialog(Spine.Spine spine)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.spine = spine;
|
||||||
|
propertyGrid_SkinManager.SelectedObject = spine.SkinManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_Add_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (spine.SkinNames.Count <= 0)
|
||||||
|
{
|
||||||
|
MessageBox.Info($"{spine.Name} 没有可用的皮肤");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
spine.LoadSkin(spine.SkinNames[0]);
|
||||||
|
propertyGrid_SkinManager.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_Delete_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (propertyGrid_SkinManager.SelectedGridItem?.Value is SkinWrapper sk)
|
||||||
|
spine.UnloadSkin(sk.Index);
|
||||||
|
propertyGrid_SkinManager.Refresh();
|
||||||
|
|
||||||
|
if (propertyGrid_SkinManager.SelectedGridItem?.Parent?.GridItems?.Cast<GridItem>().Last() is GridItem gt)
|
||||||
|
propertyGrid_SkinManager.SelectedGridItem = gt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
SpineViewer/Dialogs/SkinManagerEditorDialog.resx
Normal file
120
SpineViewer/Dialogs/SkinManagerEditorDialog.resx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
@@ -33,14 +33,14 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
if (obj is TrackWrapper tr) return ToString() == obj.ToString();
|
if (obj is TrackWrapper) return ToString() == obj.ToString();
|
||||||
return base.Equals(obj);
|
return base.Equals(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 哈希码需要和 Equals 行为类似
|
/// 哈希码需要和 Equals 行为类似
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override int GetHashCode() => ToString().GetHashCode();
|
public override int GetHashCode() => (typeof(TrackWrapper).FullName + ToString()).GetHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -119,6 +119,6 @@ namespace SpineViewer.Spine
|
|||||||
return base.Equals(obj);
|
return base.Equals(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode() => ToString().GetHashCode();
|
public override int GetHashCode() => (typeof(AnimationTracksType).FullName + ToString()).GetHashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
var fX = flipX;
|
var fX = flipX;
|
||||||
var fY = flipY;
|
var fY = flipY;
|
||||||
var animations = animationState.Tracks.Where(te => te is not null).Select(te => te.Animation.Name).ToArray();
|
var animations = animationState.Tracks.Where(te => te is not null).Select(te => te.Animation.Name).ToArray();
|
||||||
var sk = skin;
|
|
||||||
|
|
||||||
var val = Math.Max(value, SCALE_MIN);
|
var val = Math.Max(value, SCALE_MIN);
|
||||||
if (skeletonBinary is not null)
|
if (skeletonBinary is not null)
|
||||||
@@ -133,8 +132,8 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
position = pos;
|
position = pos;
|
||||||
flipX = fX;
|
flipX = fX;
|
||||||
flipY = fY;
|
flipY = fY;
|
||||||
|
foreach (var s in loadedSkins) addSkin(s);
|
||||||
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
|
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
|
||||||
skin = sk;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,15 +159,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
set => skeleton.FlipY = value;
|
set => skeleton.FlipY = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (!skinNames.Contains(name)) return;
|
||||||
set
|
skeleton.SetSkin(name); // XXX: 3.7 及以下不支持 AddSkin
|
||||||
{
|
skeleton.SetSlotsToSetupPose();
|
||||||
if (!skinNames.Contains(value)) return;
|
}
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
protected override void clearSkin()
|
||||||
}
|
{
|
||||||
|
skeleton.SetSkin(skeletonData.DefaultSkin);
|
||||||
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks[i] is not null).ToArray();
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks[i] is not null).ToArray();
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
var fX = flipX;
|
var fX = flipX;
|
||||||
var fY = flipY;
|
var fY = flipY;
|
||||||
var animations = animationState.Tracks.Where(te => te is not null).Select(te => te.Animation.Name).ToArray();
|
var animations = animationState.Tracks.Where(te => te is not null).Select(te => te.Animation.Name).ToArray();
|
||||||
var sk = skin;
|
|
||||||
|
|
||||||
var val = Math.Max(value, SCALE_MIN);
|
var val = Math.Max(value, SCALE_MIN);
|
||||||
if (skeletonBinary is not null)
|
if (skeletonBinary is not null)
|
||||||
@@ -132,8 +131,8 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
position = pos;
|
position = pos;
|
||||||
flipX = fX;
|
flipX = fX;
|
||||||
flipY = fY;
|
flipY = fY;
|
||||||
|
foreach (var s in loadedSkins) addSkin(s);
|
||||||
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
|
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
|
||||||
skin = sk;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,15 +158,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
set => skeleton.FlipY = value;
|
set => skeleton.FlipY = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (!skinNames.Contains(name)) return;
|
||||||
set
|
skeleton.SetSkin(name); // XXX: 3.7 及以下不支持 AddSkin
|
||||||
{
|
skeleton.SetSlotsToSetupPose();
|
||||||
if (!skinNames.Contains(value)) return;
|
}
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
protected override void clearSkin()
|
||||||
}
|
{
|
||||||
|
skeleton.SetSkin(skeletonData.DefaultSkin);
|
||||||
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|||||||
@@ -129,15 +129,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (!skinNames.Contains(name)) return;
|
||||||
set
|
skeleton.SetSkin(name); // XXX: 3.7 及以下不支持 AddSkin
|
||||||
{
|
skeleton.SetSlotsToSetupPose();
|
||||||
if (!skinNames.Contains(value)) return;
|
}
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
protected override void clearSkin()
|
||||||
}
|
{
|
||||||
|
skeleton.SetSkin(skeletonData.DefaultSkin);
|
||||||
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
foreach (var anime in skeletonData.Animations)
|
foreach (var anime in skeletonData.Animations)
|
||||||
animationNames.Add(anime.Name);
|
animationNames.Add(anime.Name);
|
||||||
|
|
||||||
skeleton = new Skeleton(skeletonData);
|
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||||
animationStateData = new AnimationStateData(skeletonData);
|
animationStateData = new AnimationStateData(skeletonData);
|
||||||
animationState = new AnimationState(animationStateData);
|
animationState = new AnimationState(animationStateData);
|
||||||
}
|
}
|
||||||
@@ -135,15 +135,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (!skinNames.Contains(name)) return;
|
||||||
set
|
skeleton.Skin.AddSkin(skeletonData.FindSkin(name));
|
||||||
{
|
skeleton.SetSlotsToSetupPose();
|
||||||
if (!skinNames.Contains(value)) return;
|
}
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
protected override void clearSkin()
|
||||||
}
|
{
|
||||||
|
skeleton.Skin.Clear();
|
||||||
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
foreach (var anime in skeletonData.Animations)
|
foreach (var anime in skeletonData.Animations)
|
||||||
animationNames.Add(anime.Name);
|
animationNames.Add(anime.Name);
|
||||||
|
|
||||||
skeleton = new Skeleton(skeletonData);
|
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||||
animationStateData = new AnimationStateData(skeletonData);
|
animationStateData = new AnimationStateData(skeletonData);
|
||||||
animationState = new AnimationState(animationStateData);
|
animationState = new AnimationState(animationStateData);
|
||||||
}
|
}
|
||||||
@@ -131,15 +131,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (!skinNames.Contains(name)) return;
|
||||||
set
|
skeleton.Skin.AddSkin(skeletonData.FindSkin(name));
|
||||||
{
|
skeleton.SetSlotsToSetupPose();
|
||||||
if (!skinNames.Contains(value)) return;
|
}
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
protected override void clearSkin()
|
||||||
}
|
{
|
||||||
|
skeleton.Skin.Clear();
|
||||||
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
foreach (var anime in skeletonData.Animations)
|
foreach (var anime in skeletonData.Animations)
|
||||||
animationNames.Add(anime.Name);
|
animationNames.Add(anime.Name);
|
||||||
|
|
||||||
skeleton = new Skeleton(skeletonData);
|
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||||
animationStateData = new AnimationStateData(skeletonData);
|
animationStateData = new AnimationStateData(skeletonData);
|
||||||
animationState = new AnimationState(animationStateData);
|
animationState = new AnimationState(animationStateData);
|
||||||
}
|
}
|
||||||
@@ -131,15 +131,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (!skinNames.Contains(name)) return;
|
||||||
set
|
skeleton.Skin.AddSkin(skeletonData.FindSkin(name));
|
||||||
{
|
skeleton.SetSlotsToSetupPose();
|
||||||
if (!skinNames.Contains(value)) return;
|
}
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
protected override void clearSkin()
|
||||||
}
|
{
|
||||||
|
skeleton.Skin.Clear();
|
||||||
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
foreach (var anime in skeletonData.Animations)
|
foreach (var anime in skeletonData.Animations)
|
||||||
animationNames.Add(anime.Name);
|
animationNames.Add(anime.Name);
|
||||||
|
|
||||||
skeleton = new Skeleton(skeletonData);
|
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||||
animationStateData = new AnimationStateData(skeletonData);
|
animationStateData = new AnimationStateData(skeletonData);
|
||||||
animationState = new AnimationState(animationStateData);
|
animationState = new AnimationState(animationStateData);
|
||||||
}
|
}
|
||||||
@@ -131,15 +131,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string skin
|
protected override void addSkin(string name)
|
||||||
{
|
{
|
||||||
get => skeleton.Skin?.Name ?? "default";
|
if (!skinNames.Contains(name)) return;
|
||||||
set
|
skeleton.Skin.AddSkin(skeletonData.FindSkin(name));
|
||||||
{
|
skeleton.SetSlotsToSetupPose();
|
||||||
if (!skinNames.Contains(value)) return;
|
}
|
||||||
skeleton.SetSkin(value);
|
|
||||||
skeleton.SetSlotsToSetupPose();
|
protected override void clearSkin()
|
||||||
}
|
{
|
||||||
|
skeleton.Skin.Clear();
|
||||||
|
skeleton.SetSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
protected override int[] getTrackIndices() => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
|
||||||
|
|||||||
115
SpineViewer/Spine/SkinManager.cs
Normal file
115
SpineViewer/Spine/SkinManager.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpineViewer.Spine
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 对皮肤的包装类
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(SkinWrapperConverter))]
|
||||||
|
public class SkinWrapper(Spine spine, int i)
|
||||||
|
{
|
||||||
|
private readonly Spine 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() => (typeof(SkinWrapper).FullName + ToString()).GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 皮肤属性描述符, 实现对属性的读取和赋值
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spine">关联的 Spine 对象</param>
|
||||||
|
public class SkinWrapperPropertyDescriptor(Spine spine, int i) : PropertyDescriptor($"Skin{i}", [new DisplayNameAttribute($"皮肤 {i}")])
|
||||||
|
{
|
||||||
|
private readonly Spine spine = spine;
|
||||||
|
private readonly int idx = i;
|
||||||
|
|
||||||
|
public override Type ComponentType => typeof(SkinManager);
|
||||||
|
public override bool IsReadOnly => false;
|
||||||
|
public override Type PropertyType => typeof(SkinWrapper);
|
||||||
|
public override bool CanResetValue(object component) => false;
|
||||||
|
public override void ResetValue(object component) { }
|
||||||
|
public override bool ShouldSerializeValue(object component) => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 得到一个 SkinWrapper, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性
|
||||||
|
/// </summary>
|
||||||
|
public override object? GetValue(object? component) => new SkinWrapper(spine, idx);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 允许通过字符串赋值修改该位置的皮肤
|
||||||
|
/// </summary>
|
||||||
|
public override void SetValue(object? component, object? value)
|
||||||
|
{
|
||||||
|
if (value is string s) spine.ReplaceSkin(idx, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SkinManager 动态类型包装类, 用于提供对 Spine 皮肤列表的管理能力
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spine">关联的 Spine 对象</param>
|
||||||
|
public class SkinManager(Spine spine) : ICustomTypeDescriptor
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, SkinWrapperPropertyDescriptor> pdCache = [];
|
||||||
|
public Spine Spine { get; } = spine;
|
||||||
|
|
||||||
|
// XXX: 必须实现 ICustomTypeDescriptor 接口, 不能继承 CustomTypeDescriptor, 似乎继承下来的东西会有问题, 导致某些调用不正确
|
||||||
|
|
||||||
|
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 List<SkinWrapperPropertyDescriptor>();
|
||||||
|
for (var i = 0; i < Spine.GetLoadedSkins().Length; i++)
|
||||||
|
{
|
||||||
|
if (!pdCache.ContainsKey(i))
|
||||||
|
pdCache[i] = new SkinWrapperPropertyDescriptor(Spine, i);
|
||||||
|
props.Add(pdCache[i]);
|
||||||
|
}
|
||||||
|
return new PropertyDescriptorCollection(props.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在属性面板悬停可以显示已加载的皮肤列表
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString() => $"[{string.Join(", ", Spine.GetLoadedSkins())}]";
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is SkinManager manager) return ToString() == manager.ToString();
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode() => (typeof(SkinManager).FullName + ToString()).GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using System.Collections.ObjectModel;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Drawing.Design;
|
using System.Drawing.Design;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
namespace SpineViewer.Spine
|
namespace SpineViewer.Spine
|
||||||
{
|
{
|
||||||
@@ -11,6 +12,9 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Spine : ImplementationResolver<Spine, SpineImplementationAttribute, SpineVersion>, SFML.Graphics.Drawable, IDisposable
|
public abstract class Spine : ImplementationResolver<Spine, SpineImplementationAttribute, SpineVersion>, SFML.Graphics.Drawable, IDisposable
|
||||||
{
|
{
|
||||||
|
private readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
|
private bool skinLoggerWarned = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 空动画标记
|
/// 空动画标记
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -64,6 +68,7 @@ namespace SpineViewer.Spine
|
|||||||
private Spine PostInit()
|
private Spine PostInit()
|
||||||
{
|
{
|
||||||
SkinNames = skinNames.AsReadOnly();
|
SkinNames = skinNames.AsReadOnly();
|
||||||
|
SkinManager = new(this);
|
||||||
AnimationNames = animationNames.AsReadOnly();
|
AnimationNames = animationNames.AsReadOnly();
|
||||||
AnimationTracks = new(this);
|
AnimationTracks = new(this);
|
||||||
|
|
||||||
@@ -83,7 +88,7 @@ namespace SpineViewer.Spine
|
|||||||
Preview = tex.Texture.CopyToBitmap();
|
Preview = tex.Texture.CopyToBitmap();
|
||||||
|
|
||||||
// 取最后一个作为初始, 尽可能去显示非默认的内容
|
// 取最后一个作为初始, 尽可能去显示非默认的内容
|
||||||
skin = SkinNames.Last();
|
//skin = SkinNames.Last();
|
||||||
setAnimation(0, AnimationNames.Last());
|
setAnimation(0, AnimationNames.Last());
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
@@ -212,16 +217,12 @@ namespace SpineViewer.Spine
|
|||||||
#region 属性 | [3] 动画
|
#region 属性 | [3] 动画
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 使用的皮肤名称, 如果设置的皮肤不存在则忽略
|
/// 已加载皮肤列表
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TypeConverter(typeof(SkinConverter))]
|
[Editor(typeof(SkinManagerEditor), typeof(UITypeEditor))]
|
||||||
[Category("[3] 动画"), DisplayName("皮肤")]
|
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||||
public string Skin
|
[Category("[3] 动画"), DisplayName("已加载皮肤列表")]
|
||||||
{
|
public SkinManager SkinManager { get; private set; }
|
||||||
get { lock (_lock) return skin; }
|
|
||||||
set { lock (_lock) { skin = value; update(0); } }
|
|
||||||
}
|
|
||||||
protected abstract string skin { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 默认轨道动画名称, 如果设置的动画不存在则忽略
|
/// 默认轨道动画名称, 如果设置的动画不存在则忽略
|
||||||
@@ -253,17 +254,95 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public ReadOnlyCollection<string> SkinNames { get; private set; }
|
public ReadOnlyCollection<string> SkinNames { get; private set; }
|
||||||
protected List<string> skinNames = [];
|
protected readonly List<string> skinNames = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取已加载的皮肤列表快照, 允许出现重复值
|
||||||
|
/// </summary>
|
||||||
|
public string[] GetLoadedSkins() { lock (_lock) return loadedSkins.ToArray(); }
|
||||||
|
protected readonly List<string> loadedSkins = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加载指定皮肤, 添加至列表末尾, 如果不存在则忽略, 允许加载重复的值
|
||||||
|
/// </summary>
|
||||||
|
public void LoadSkin(string name)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (skinNames.Contains(name))
|
||||||
|
{
|
||||||
|
loadedSkins.Add(name);
|
||||||
|
reloadSkins();
|
||||||
|
|
||||||
|
if (!skinLoggerWarned && Version <= SpineVersion.V37 && loadedSkins.Count > 1)
|
||||||
|
{
|
||||||
|
logger.Warn($"Multiplt skins not supported in SpineVersion {Version.GetName()}");
|
||||||
|
skinLoggerWarned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 卸载列表指定位置皮肤, 如果超出范围则忽略
|
||||||
|
/// </summary>
|
||||||
|
public void UnloadSkin(int idx)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (idx >= 0 && idx < loadedSkins.Count)
|
||||||
|
{
|
||||||
|
loadedSkins.RemoveAt(idx);
|
||||||
|
reloadSkins();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 替换皮肤列表指定位置皮肤, 超出范围或者皮肤不存在则忽略
|
||||||
|
/// </summary>
|
||||||
|
public void ReplaceSkin(int idx, string name)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (idx >= 0 && idx < loadedSkins.Count && skinNames.Contains(name))
|
||||||
|
{
|
||||||
|
loadedSkins[idx] = name;
|
||||||
|
reloadSkins();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重新加载现有皮肤列表, 用于刷新等操作
|
||||||
|
/// </summary>
|
||||||
|
public void ReloadSkins() { lock (_lock) reloadSkins(); }
|
||||||
|
private void reloadSkins()
|
||||||
|
{
|
||||||
|
clearSkin();
|
||||||
|
foreach (var s in loadedSkins) addSkin(s);
|
||||||
|
update(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加载皮肤, 之后需要使用 <see cref="setSlotsToSetupPose"/> 来复位
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void addSkin(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清空加载的所有皮肤
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void clearSkin();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包含的所有动画名称
|
/// 包含的所有动画名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public ReadOnlyCollection<string> AnimationNames { get; private set; }
|
public ReadOnlyCollection<string> AnimationNames { get; private set; }
|
||||||
protected List<string> animationNames = [EMPTY_ANIMATION];
|
protected readonly List<string> animationNames = [EMPTY_ANIMATION];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取所有非 null 的轨道索引
|
/// 获取所有非 null 的轨道索引快照
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int[] GetTrackIndices() { lock (_lock) return getTrackIndices(); }
|
public int[] GetTrackIndices() { lock (_lock) return getTrackIndices(); }
|
||||||
protected abstract int[] getTrackIndices();
|
protected abstract int[] getTrackIndices();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
@@ -107,4 +108,36 @@ namespace SpineViewer.Spine
|
|||||||
return base.GetStandardValues(context);
|
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 SkinManager manager)
|
||||||
|
{
|
||||||
|
return new StandardValuesCollection(manager.Spine.SkinNames);
|
||||||
|
}
|
||||||
|
else if (context.Instance is object[] instances && instances.All(x => x is SkinManager))
|
||||||
|
{
|
||||||
|
// XXX: 这里不知道为啥总是会得到 object[] 类型而不是具体的 SkinManager[] 类型
|
||||||
|
var managers = instances.Cast<SkinManager>().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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,4 +61,28 @@ namespace SpineViewer.Spine
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 多轨道动画编辑器
|
||||||
|
/// </summary>
|
||||||
|
public class SkinManagerEditor : UITypeEditor
|
||||||
|
{
|
||||||
|
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext? context) => UITypeEditorEditStyle.Modal;
|
||||||
|
|
||||||
|
public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider provider, object? value)
|
||||||
|
{
|
||||||
|
if (provider == null || context == null || context.Instance is not Spine)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
IWindowsFormsEditorService editorService = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
|
||||||
|
if (editorService == null)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
using (var dialog = new SkinManagerEditorDialog((Spine)context.Instance))
|
||||||
|
editorService.ShowDialog(dialog);
|
||||||
|
|
||||||
|
TypeDescriptor.Refresh(context.Instance);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user