首次提交

This commit is contained in:
ww-rm
2025-02-26 14:26:34 +08:00
parent eed514e798
commit dca04050b6
25 changed files with 3562 additions and 0 deletions

11
.editorconfig Normal file
View File

@@ -0,0 +1,11 @@
[*.cs]
dotnet_diagnostic.CS8600.severity = suggestion
dotnet_diagnostic.CS8601.severity = suggestion
dotnet_diagnostic.CS8602.severity = suggestion
dotnet_diagnostic.CS8603.severity = suggestion
dotnet_diagnostic.CS8604.severity = suggestion
dotnet_diagnostic.CS8605.severity = suggestion
dotnet_diagnostic.CS8618.severity = suggestion
dotnet_diagnostic.CS8625.severity = suggestion
dotnet_diagnostic.CS8714.severity = suggestion

398
.gitignore vendored Normal file
View File

@@ -0,0 +1,398 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

13
README.md Normal file
View File

@@ -0,0 +1,13 @@
# SpineViewer
[中文](README.md) | [English](README.en.md)
---
## 简介
## 安装
---
*如果你觉得这个项目不错请给个 :star:, 并分享给更多人知道! :)*

64
SpineViewer.sln Normal file
View File

@@ -0,0 +1,64 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35208.52
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpineViewer", "SpineViewer\SpineViewer.csproj", "{ECD11621-9F72-4BCB-92A4-7D8A426F84FA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SpineRuntimes", "SpineRuntimes", "{EA2E1399-02BC-43BC-AD9F-42E23E9C3DA8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpineRuntime36", "SpineRuntimes\SpineRuntime36\SpineRuntime36.csproj", "{CA964DA9-3649-44BC-84F7-B1108A652905}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpineRuntime38", "SpineRuntimes\SpineRuntime38\SpineRuntime38.csproj", "{ECF7297E-031B-4E37-8033-7C2345DB8766}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{65DD4332-305A-4AAA-AD92-A7D293296BC9}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{ECD11621-9F72-4BCB-92A4-7D8A426F84FA}.Debug|Any CPU.ActiveCfg = Debug|x64
{ECD11621-9F72-4BCB-92A4-7D8A426F84FA}.Debug|Any CPU.Build.0 = Debug|x64
{ECD11621-9F72-4BCB-92A4-7D8A426F84FA}.Debug|x64.ActiveCfg = Debug|x64
{ECD11621-9F72-4BCB-92A4-7D8A426F84FA}.Debug|x64.Build.0 = Debug|x64
{ECD11621-9F72-4BCB-92A4-7D8A426F84FA}.Release|Any CPU.ActiveCfg = Release|x64
{ECD11621-9F72-4BCB-92A4-7D8A426F84FA}.Release|Any CPU.Build.0 = Release|x64
{ECD11621-9F72-4BCB-92A4-7D8A426F84FA}.Release|x64.ActiveCfg = Release|x64
{ECD11621-9F72-4BCB-92A4-7D8A426F84FA}.Release|x64.Build.0 = Release|x64
{CA964DA9-3649-44BC-84F7-B1108A652905}.Debug|Any CPU.ActiveCfg = Debug|x64
{CA964DA9-3649-44BC-84F7-B1108A652905}.Debug|Any CPU.Build.0 = Debug|x64
{CA964DA9-3649-44BC-84F7-B1108A652905}.Debug|x64.ActiveCfg = Debug|x64
{CA964DA9-3649-44BC-84F7-B1108A652905}.Debug|x64.Build.0 = Debug|x64
{CA964DA9-3649-44BC-84F7-B1108A652905}.Release|Any CPU.ActiveCfg = Release|x64
{CA964DA9-3649-44BC-84F7-B1108A652905}.Release|Any CPU.Build.0 = Release|x64
{CA964DA9-3649-44BC-84F7-B1108A652905}.Release|x64.ActiveCfg = Release|x64
{CA964DA9-3649-44BC-84F7-B1108A652905}.Release|x64.Build.0 = Release|x64
{ECF7297E-031B-4E37-8033-7C2345DB8766}.Debug|Any CPU.ActiveCfg = Debug|x64
{ECF7297E-031B-4E37-8033-7C2345DB8766}.Debug|Any CPU.Build.0 = Debug|x64
{ECF7297E-031B-4E37-8033-7C2345DB8766}.Debug|x64.ActiveCfg = Debug|x64
{ECF7297E-031B-4E37-8033-7C2345DB8766}.Debug|x64.Build.0 = Debug|x64
{ECF7297E-031B-4E37-8033-7C2345DB8766}.Release|Any CPU.ActiveCfg = Release|x64
{ECF7297E-031B-4E37-8033-7C2345DB8766}.Release|Any CPU.Build.0 = Release|x64
{ECF7297E-031B-4E37-8033-7C2345DB8766}.Release|x64.ActiveCfg = Release|x64
{ECF7297E-031B-4E37-8033-7C2345DB8766}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{CA964DA9-3649-44BC-84F7-B1108A652905} = {EA2E1399-02BC-43BC-AD9F-42E23E9C3DA8}
{ECF7297E-031B-4E37-8033-7C2345DB8766} = {EA2E1399-02BC-43BC-AD9F-42E23E9C3DA8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {91F0EFD1-4B07-4C3C-82D8-90432349D3A5}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace SpineViewer.Properties {
using System;
/// <summary>
/// 一个强类型的资源类,用于查找本地化的字符串等。
/// </summary>
// 此类是由 StronglyTypedResourceBuilder
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// 返回此类使用的缓存的 ResourceManager 实例。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SpineViewer.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// 重写当前线程的 CurrentUICulture 属性,对
/// 使用此强类型资源类的所有资源查找执行重写。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View 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>

View File

@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>x64</Platforms>
<Version>0.1.0</Version>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NLog.Windows.Forms" Version="5.2.3" />
<PackageReference Include="SFML.Net" Version="2.6.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SpineRuntimes\SpineRuntime36\SpineRuntime36.csproj" />
<ProjectReference Include="..\SpineRuntimes\SpineRuntime38\SpineRuntime38.csproj" />
</ItemGroup>
<ItemGroup>
<RuntimeHostConfigurationOption Include="System.Runtime.Loader.UseRidGraph" Value="true" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

572
SpineViewer/src/MainForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,572 @@
namespace SpineViewer
{
partial class MainForm
{
/// <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()
{
components = new System.ComponentModel.Container();
ListViewItem listViewItem1 = new ListViewItem(new string[] { "A Loooooooooog Name1", "A Loooooooooog Version" }, -1);
menuStrip = new MenuStrip();
toolStripMenuItem_File = new ToolStripMenuItem();
toolStripMenuItem_Open = new ToolStripMenuItem();
toolStripMenuItem_BatchOpen = new ToolStripMenuItem();
toolStripSeparator1 = new ToolStripSeparator();
toolStripMenuItem_Export = new ToolStripMenuItem();
toolStripSeparator2 = new ToolStripSeparator();
toolStripMenuItem_Exit = new ToolStripMenuItem();
toolStripMenuItem_Help = new ToolStripMenuItem();
toolStripMenuItem_About = new ToolStripMenuItem();
rtbLog = new RichTextBox();
splitContainer_MainForm = new SplitContainer();
splitContainer_Functional = new SplitContainer();
splitContainer_Information = new SplitContainer();
groupBox_SkelList = new GroupBox();
tableLayoutPanel = new TableLayoutPanel();
flowLayoutPanel_Buttons = new FlowLayoutPanel();
button_Add = new Button();
button_Insert = new Button();
button_Remove = new Button();
button_MoveUp = new Button();
button_MoveDown = new Button();
listView_SkelList = new ListView();
columnHeader_Name = new ColumnHeader();
columnHeader_Version = new ColumnHeader();
splitContainer_Config = new SplitContainer();
groupBox_SkelConfig = new GroupBox();
propertyGrid_Skel = new PropertyGrid();
groupBox_PreviewConfig = new GroupBox();
groupBox_Preview = new GroupBox();
panel_PreviewContainer = new Panel();
panel_Preview = new Panel();
panel_MainForm = new Panel();
openFileDialog_Skel = new OpenFileDialog();
openFileDialog_Atlas = new OpenFileDialog();
toolTip1 = new ToolTip(components);
menuStrip.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitContainer_MainForm).BeginInit();
splitContainer_MainForm.Panel1.SuspendLayout();
splitContainer_MainForm.Panel2.SuspendLayout();
splitContainer_MainForm.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitContainer_Functional).BeginInit();
splitContainer_Functional.Panel1.SuspendLayout();
splitContainer_Functional.Panel2.SuspendLayout();
splitContainer_Functional.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitContainer_Information).BeginInit();
splitContainer_Information.Panel1.SuspendLayout();
splitContainer_Information.Panel2.SuspendLayout();
splitContainer_Information.SuspendLayout();
groupBox_SkelList.SuspendLayout();
tableLayoutPanel.SuspendLayout();
flowLayoutPanel_Buttons.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitContainer_Config).BeginInit();
splitContainer_Config.Panel1.SuspendLayout();
splitContainer_Config.Panel2.SuspendLayout();
splitContainer_Config.SuspendLayout();
groupBox_SkelConfig.SuspendLayout();
groupBox_Preview.SuspendLayout();
panel_PreviewContainer.SuspendLayout();
panel_MainForm.SuspendLayout();
SuspendLayout();
//
// menuStrip
//
menuStrip.BackColor = SystemColors.Control;
menuStrip.ImageScalingSize = new Size(24, 24);
menuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_File, toolStripMenuItem_Help });
menuStrip.Location = new Point(0, 0);
menuStrip.Name = "menuStrip";
menuStrip.Size = new Size(1519, 32);
menuStrip.TabIndex = 0;
menuStrip.Text = "菜单";
//
// toolStripMenuItem_File
//
toolStripMenuItem_File.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Open, toolStripMenuItem_BatchOpen, toolStripSeparator1, toolStripMenuItem_Export, toolStripSeparator2, toolStripMenuItem_Exit });
toolStripMenuItem_File.Name = "toolStripMenuItem_File";
toolStripMenuItem_File.Size = new Size(84, 28);
toolStripMenuItem_File.Text = "文件(&F)";
//
// toolStripMenuItem_Open
//
toolStripMenuItem_Open.Name = "toolStripMenuItem_Open";
toolStripMenuItem_Open.ShortcutKeys = Keys.Control | Keys.O;
toolStripMenuItem_Open.Size = new Size(254, 34);
toolStripMenuItem_Open.Text = "打开(&O)...";
toolStripMenuItem_Open.Click += toolStripMenuItem_Open_Click;
//
// toolStripMenuItem_BatchOpen
//
toolStripMenuItem_BatchOpen.Name = "toolStripMenuItem_BatchOpen";
toolStripMenuItem_BatchOpen.Size = new Size(254, 34);
toolStripMenuItem_BatchOpen.Text = "批量打开(&B)...";
toolStripMenuItem_BatchOpen.Click += toolStripMenuItem_BatchOpen_Click;
//
// toolStripSeparator1
//
toolStripSeparator1.Name = "toolStripSeparator1";
toolStripSeparator1.Size = new Size(251, 6);
//
// toolStripMenuItem_Export
//
toolStripMenuItem_Export.Name = "toolStripMenuItem_Export";
toolStripMenuItem_Export.ShortcutKeys = Keys.Control | Keys.S;
toolStripMenuItem_Export.Size = new Size(254, 34);
toolStripMenuItem_Export.Text = "导出(&E)...";
toolStripMenuItem_Export.Click += toolStripMenuItem_Export_Click;
//
// toolStripSeparator2
//
toolStripSeparator2.Name = "toolStripSeparator2";
toolStripSeparator2.Size = new Size(251, 6);
//
// toolStripMenuItem_Exit
//
toolStripMenuItem_Exit.Name = "toolStripMenuItem_Exit";
toolStripMenuItem_Exit.ShortcutKeys = Keys.Alt | Keys.F4;
toolStripMenuItem_Exit.Size = new Size(254, 34);
toolStripMenuItem_Exit.Text = "退出(&X)";
toolStripMenuItem_Exit.Click += toolStripMenuItem_Exit_Click;
//
// toolStripMenuItem_Help
//
toolStripMenuItem_Help.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_About });
toolStripMenuItem_Help.Name = "toolStripMenuItem_Help";
toolStripMenuItem_Help.Size = new Size(88, 28);
toolStripMenuItem_Help.Text = "帮助(&H)";
//
// toolStripMenuItem_About
//
toolStripMenuItem_About.Name = "toolStripMenuItem_About";
toolStripMenuItem_About.Size = new Size(171, 34);
toolStripMenuItem_About.Text = "关于(&A)";
//
// rtbLog
//
rtbLog.BackColor = SystemColors.Window;
rtbLog.BorderStyle = BorderStyle.None;
rtbLog.Dock = DockStyle.Fill;
rtbLog.Font = new Font("Consolas", 9F);
rtbLog.Location = new Point(0, 0);
rtbLog.Margin = new Padding(3, 2, 3, 2);
rtbLog.Name = "rtbLog";
rtbLog.ReadOnly = true;
rtbLog.Size = new Size(1499, 102);
rtbLog.TabIndex = 0;
rtbLog.Text = "";
rtbLog.WordWrap = false;
//
// splitContainer_MainForm
//
splitContainer_MainForm.Cursor = Cursors.SizeNS;
splitContainer_MainForm.Dock = DockStyle.Fill;
splitContainer_MainForm.Location = new Point(10, 5);
splitContainer_MainForm.Name = "splitContainer_MainForm";
splitContainer_MainForm.Orientation = Orientation.Horizontal;
//
// splitContainer_MainForm.Panel1
//
splitContainer_MainForm.Panel1.Controls.Add(splitContainer_Functional);
splitContainer_MainForm.Panel1.Cursor = Cursors.Default;
//
// splitContainer_MainForm.Panel2
//
splitContainer_MainForm.Panel2.Controls.Add(rtbLog);
splitContainer_MainForm.Panel2.Cursor = Cursors.Default;
splitContainer_MainForm.Size = new Size(1499, 838);
splitContainer_MainForm.SplitterDistance = 732;
splitContainer_MainForm.TabIndex = 3;
splitContainer_MainForm.TabStop = false;
splitContainer_MainForm.SplitterMoved += splitContainer_SplitterMoved;
splitContainer_MainForm.MouseUp += splitContainer_MouseUp;
//
// splitContainer_Functional
//
splitContainer_Functional.Cursor = Cursors.SizeWE;
splitContainer_Functional.Dock = DockStyle.Fill;
splitContainer_Functional.Location = new Point(0, 0);
splitContainer_Functional.Name = "splitContainer_Functional";
//
// splitContainer_Functional.Panel1
//
splitContainer_Functional.Panel1.Controls.Add(splitContainer_Information);
splitContainer_Functional.Panel1.Cursor = Cursors.Default;
//
// splitContainer_Functional.Panel2
//
splitContainer_Functional.Panel2.Controls.Add(groupBox_Preview);
splitContainer_Functional.Panel2.Cursor = Cursors.Default;
splitContainer_Functional.Size = new Size(1499, 732);
splitContainer_Functional.SplitterDistance = 698;
splitContainer_Functional.TabIndex = 2;
splitContainer_Functional.TabStop = false;
splitContainer_Functional.SplitterMoved += splitContainer_SplitterMoved;
splitContainer_Functional.MouseUp += splitContainer_MouseUp;
//
// splitContainer_Information
//
splitContainer_Information.Cursor = Cursors.SizeWE;
splitContainer_Information.Dock = DockStyle.Fill;
splitContainer_Information.Location = new Point(0, 0);
splitContainer_Information.Name = "splitContainer_Information";
//
// splitContainer_Information.Panel1
//
splitContainer_Information.Panel1.Controls.Add(groupBox_SkelList);
splitContainer_Information.Panel1.Cursor = Cursors.Default;
//
// splitContainer_Information.Panel2
//
splitContainer_Information.Panel2.Controls.Add(splitContainer_Config);
splitContainer_Information.Panel2.Cursor = Cursors.Default;
splitContainer_Information.Size = new Size(698, 732);
splitContainer_Information.SplitterDistance = 336;
splitContainer_Information.TabIndex = 1;
splitContainer_Information.TabStop = false;
splitContainer_Information.SplitterMoved += splitContainer_SplitterMoved;
splitContainer_Information.MouseUp += splitContainer_MouseUp;
//
// groupBox_SkelList
//
groupBox_SkelList.Controls.Add(tableLayoutPanel);
groupBox_SkelList.Dock = DockStyle.Fill;
groupBox_SkelList.Location = new Point(0, 0);
groupBox_SkelList.Name = "groupBox_SkelList";
groupBox_SkelList.Size = new Size(336, 732);
groupBox_SkelList.TabIndex = 0;
groupBox_SkelList.TabStop = false;
groupBox_SkelList.Text = "模型列表";
//
// tableLayoutPanel
//
tableLayoutPanel.ColumnCount = 1;
tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
tableLayoutPanel.Controls.Add(flowLayoutPanel_Buttons, 0, 0);
tableLayoutPanel.Controls.Add(listView_SkelList, 0, 1);
tableLayoutPanel.Dock = DockStyle.Fill;
tableLayoutPanel.Location = new Point(3, 26);
tableLayoutPanel.Name = "tableLayoutPanel";
tableLayoutPanel.RowCount = 2;
tableLayoutPanel.RowStyles.Add(new RowStyle());
tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
tableLayoutPanel.Size = new Size(330, 703);
tableLayoutPanel.TabIndex = 1;
//
// flowLayoutPanel_Buttons
//
flowLayoutPanel_Buttons.AutoSize = true;
flowLayoutPanel_Buttons.Controls.Add(button_Add);
flowLayoutPanel_Buttons.Controls.Add(button_Insert);
flowLayoutPanel_Buttons.Controls.Add(button_Remove);
flowLayoutPanel_Buttons.Controls.Add(button_MoveUp);
flowLayoutPanel_Buttons.Controls.Add(button_MoveDown);
flowLayoutPanel_Buttons.Dock = DockStyle.Fill;
flowLayoutPanel_Buttons.Location = new Point(3, 3);
flowLayoutPanel_Buttons.Name = "flowLayoutPanel_Buttons";
flowLayoutPanel_Buttons.Size = new Size(324, 40);
flowLayoutPanel_Buttons.TabIndex = 4;
//
// button_Add
//
button_Add.Anchor = AnchorStyles.None;
button_Add.AutoSize = true;
button_Add.Location = new Point(3, 3);
button_Add.Name = "button_Add";
button_Add.Size = new Size(56, 34);
button_Add.TabIndex = 0;
button_Add.Text = "添加";
button_Add.UseVisualStyleBackColor = true;
button_Add.Click += toolStripMenuItem_Open_Click;
//
// button_Insert
//
button_Insert.Anchor = AnchorStyles.None;
button_Insert.AutoSize = true;
button_Insert.Enabled = false;
button_Insert.Location = new Point(65, 3);
button_Insert.Name = "button_Insert";
button_Insert.Size = new Size(56, 34);
button_Insert.TabIndex = 4;
button_Insert.Text = "插入";
button_Insert.UseVisualStyleBackColor = true;
button_Insert.Click += button_Insert_Click;
//
// button_Remove
//
button_Remove.Anchor = AnchorStyles.None;
button_Remove.AutoSize = true;
button_Remove.Enabled = false;
button_Remove.Location = new Point(127, 3);
button_Remove.Name = "button_Remove";
button_Remove.Size = new Size(56, 34);
button_Remove.TabIndex = 1;
button_Remove.Text = "移除";
button_Remove.UseVisualStyleBackColor = true;
button_Remove.Click += button_Remove_Click;
//
// button_MoveUp
//
button_MoveUp.Anchor = AnchorStyles.None;
button_MoveUp.AutoSize = true;
button_MoveUp.AutoSizeMode = AutoSizeMode.GrowAndShrink;
button_MoveUp.Enabled = false;
button_MoveUp.Location = new Point(189, 3);
button_MoveUp.Name = "button_MoveUp";
button_MoveUp.Size = new Size(56, 34);
button_MoveUp.TabIndex = 2;
button_MoveUp.Text = "上移";
button_MoveUp.UseVisualStyleBackColor = true;
button_MoveUp.Click += button_MoveUp_Click;
//
// button_MoveDown
//
button_MoveDown.Anchor = AnchorStyles.None;
button_MoveDown.AutoSize = true;
button_MoveDown.AutoSizeMode = AutoSizeMode.GrowAndShrink;
button_MoveDown.Enabled = false;
button_MoveDown.Location = new Point(251, 3);
button_MoveDown.Name = "button_MoveDown";
button_MoveDown.Size = new Size(56, 34);
button_MoveDown.TabIndex = 3;
button_MoveDown.Text = "下移";
button_MoveDown.UseVisualStyleBackColor = true;
button_MoveDown.Click += button_MoveDown_Click;
//
// listView_SkelList
//
listView_SkelList.Columns.AddRange(new ColumnHeader[] { columnHeader_Name, columnHeader_Version });
listView_SkelList.Dock = DockStyle.Fill;
listView_SkelList.FullRowSelect = true;
listView_SkelList.GridLines = true;
listView_SkelList.Items.AddRange(new ListViewItem[] { listViewItem1 });
listView_SkelList.Location = new Point(3, 49);
listView_SkelList.Name = "listView_SkelList";
listView_SkelList.ShowItemToolTips = true;
listView_SkelList.Size = new Size(324, 651);
listView_SkelList.TabIndex = 1;
listView_SkelList.UseCompatibleStateImageBehavior = false;
listView_SkelList.View = View.Details;
listView_SkelList.SelectedIndexChanged += listView_SkelList_SelectedIndexChanged;
//
// columnHeader_Name
//
columnHeader_Name.Text = "名称";
columnHeader_Name.Width = 150;
//
// columnHeader_Version
//
columnHeader_Version.Text = "版本";
columnHeader_Version.Width = 150;
//
// splitContainer_Config
//
splitContainer_Config.Cursor = Cursors.SizeNS;
splitContainer_Config.Dock = DockStyle.Fill;
splitContainer_Config.Location = new Point(0, 0);
splitContainer_Config.Name = "splitContainer_Config";
splitContainer_Config.Orientation = Orientation.Horizontal;
//
// splitContainer_Config.Panel1
//
splitContainer_Config.Panel1.Controls.Add(groupBox_SkelConfig);
splitContainer_Config.Panel1.Cursor = Cursors.Default;
//
// splitContainer_Config.Panel2
//
splitContainer_Config.Panel2.Controls.Add(groupBox_PreviewConfig);
splitContainer_Config.Panel2.Cursor = Cursors.Default;
splitContainer_Config.Size = new Size(358, 732);
splitContainer_Config.SplitterDistance = 493;
splitContainer_Config.TabIndex = 0;
splitContainer_Config.TabStop = false;
splitContainer_Config.SplitterMoved += splitContainer_SplitterMoved;
splitContainer_Config.MouseUp += splitContainer_MouseUp;
//
// groupBox_SkelConfig
//
groupBox_SkelConfig.Controls.Add(propertyGrid_Skel);
groupBox_SkelConfig.Dock = DockStyle.Fill;
groupBox_SkelConfig.Location = new Point(0, 0);
groupBox_SkelConfig.Name = "groupBox_SkelConfig";
groupBox_SkelConfig.Size = new Size(358, 493);
groupBox_SkelConfig.TabIndex = 0;
groupBox_SkelConfig.TabStop = false;
groupBox_SkelConfig.Text = "模型参数";
//
// propertyGrid_Skel
//
propertyGrid_Skel.Dock = DockStyle.Fill;
propertyGrid_Skel.HelpVisible = false;
propertyGrid_Skel.Location = new Point(3, 26);
propertyGrid_Skel.Name = "propertyGrid_Skel";
propertyGrid_Skel.Size = new Size(352, 464);
propertyGrid_Skel.TabIndex = 0;
propertyGrid_Skel.ToolbarVisible = false;
//
// groupBox_PreviewConfig
//
groupBox_PreviewConfig.Dock = DockStyle.Fill;
groupBox_PreviewConfig.Location = new Point(0, 0);
groupBox_PreviewConfig.Name = "groupBox_PreviewConfig";
groupBox_PreviewConfig.Size = new Size(358, 235);
groupBox_PreviewConfig.TabIndex = 1;
groupBox_PreviewConfig.TabStop = false;
groupBox_PreviewConfig.Text = "画面参数";
//
// groupBox_Preview
//
groupBox_Preview.Controls.Add(panel_PreviewContainer);
groupBox_Preview.Dock = DockStyle.Fill;
groupBox_Preview.Location = new Point(0, 0);
groupBox_Preview.Name = "groupBox_Preview";
groupBox_Preview.Size = new Size(797, 732);
groupBox_Preview.TabIndex = 1;
groupBox_Preview.TabStop = false;
groupBox_Preview.Text = "预览画面";
//
// panel_PreviewContainer
//
panel_PreviewContainer.Controls.Add(panel_Preview);
panel_PreviewContainer.Dock = DockStyle.Fill;
panel_PreviewContainer.Location = new Point(3, 26);
panel_PreviewContainer.Margin = new Padding(0);
panel_PreviewContainer.Name = "panel_PreviewContainer";
panel_PreviewContainer.Size = new Size(791, 703);
panel_PreviewContainer.TabIndex = 1;
//
// panel_Preview
//
panel_Preview.BackColor = SystemColors.ControlDark;
panel_Preview.Location = new Point(107, 95);
panel_Preview.Name = "panel_Preview";
panel_Preview.Size = new Size(256, 256);
panel_Preview.TabIndex = 0;
//
// panel_MainForm
//
panel_MainForm.Controls.Add(splitContainer_MainForm);
panel_MainForm.Dock = DockStyle.Fill;
panel_MainForm.Location = new Point(0, 32);
panel_MainForm.Name = "panel_MainForm";
panel_MainForm.Padding = new Padding(10, 5, 10, 10);
panel_MainForm.Size = new Size(1519, 853);
panel_MainForm.TabIndex = 4;
//
// openFileDialog_Skel
//
openFileDialog_Skel.AddExtension = false;
openFileDialog_Skel.AddToRecent = false;
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json";
//
// openFileDialog_Atlas
//
openFileDialog_Atlas.AddExtension = false;
openFileDialog_Atlas.AddToRecent = false;
openFileDialog_Atlas.Filter = "atlas 文件 (*.atlas)|*.atlas";
//
// MainForm
//
AutoScaleDimensions = new SizeF(11F, 24F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1519, 885);
Controls.Add(panel_MainForm);
Controls.Add(menuStrip);
MainMenuStrip = menuStrip;
Margin = new Padding(3, 2, 3, 2);
Name = "MainForm";
StartPosition = FormStartPosition.CenterScreen;
Text = "SpineViewer";
menuStrip.ResumeLayout(false);
menuStrip.PerformLayout();
splitContainer_MainForm.Panel1.ResumeLayout(false);
splitContainer_MainForm.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)splitContainer_MainForm).EndInit();
splitContainer_MainForm.ResumeLayout(false);
splitContainer_Functional.Panel1.ResumeLayout(false);
splitContainer_Functional.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)splitContainer_Functional).EndInit();
splitContainer_Functional.ResumeLayout(false);
splitContainer_Information.Panel1.ResumeLayout(false);
splitContainer_Information.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)splitContainer_Information).EndInit();
splitContainer_Information.ResumeLayout(false);
groupBox_SkelList.ResumeLayout(false);
tableLayoutPanel.ResumeLayout(false);
tableLayoutPanel.PerformLayout();
flowLayoutPanel_Buttons.ResumeLayout(false);
flowLayoutPanel_Buttons.PerformLayout();
splitContainer_Config.Panel1.ResumeLayout(false);
splitContainer_Config.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)splitContainer_Config).EndInit();
splitContainer_Config.ResumeLayout(false);
groupBox_SkelConfig.ResumeLayout(false);
groupBox_Preview.ResumeLayout(false);
panel_PreviewContainer.ResumeLayout(false);
panel_MainForm.ResumeLayout(false);
ResumeLayout(false);
PerformLayout();
}
#endregion
private MenuStrip menuStrip;
private ToolStripMenuItem toolStripMenuItem_File;
private ToolStripMenuItem toolStripMenuItem_Open;
private ToolStripMenuItem toolStripMenuItem_Exit;
private ToolStripSeparator toolStripSeparator1;
private ToolStripMenuItem toolStripMenuItem_Export;
private ToolStripSeparator toolStripSeparator2;
private RichTextBox rtbLog;
private SplitContainer splitContainer_MainForm;
private SplitContainer splitContainer_Functional;
private SplitContainer splitContainer_Information;
private GroupBox groupBox_SkelList;
private GroupBox groupBox_SkelConfig;
private SplitContainer splitContainer_Config;
private GroupBox groupBox_PreviewConfig;
private Panel panel_MainForm;
private ToolStripMenuItem toolStripMenuItem_Help;
private ToolStripMenuItem toolStripMenuItem_About;
private ToolStripMenuItem toolStripMenuItem_BatchOpen;
private TableLayoutPanel tableLayoutPanel;
private FlowLayoutPanel flowLayoutPanel_Buttons;
private Button button_Add;
private Button button_Insert;
private Button button_Remove;
private Button button_MoveUp;
private Button button_MoveDown;
private ListView listView_SkelList;
private ColumnHeader columnHeader_Name;
private ColumnHeader columnHeader_Version;
private GroupBox groupBox_Preview;
private Panel panel_Preview;
private Panel panel_PreviewContainer;
private OpenFileDialog openFileDialog_Skel;
private OpenFileDialog openFileDialog_Atlas;
private ToolTip toolTip1;
private PropertyGrid propertyGrid_Skel;
}
}

179
SpineViewer/src/MainForm.cs Normal file
View File

@@ -0,0 +1,179 @@
using NLog;
using SpineViewer.Spine;
using System.ComponentModel;
using System.Diagnostics;
namespace SpineViewer
{
public partial class MainForm : Form
{
Manager spineManger = null;
public MainForm()
{
InitializeComponent();
InitializeLogConfiguration();
spineManger = new(listView_SkelList);
}
/// <summary>
/// <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־<EFBFBD><D6BE>
/// </summary>
private void InitializeLogConfiguration()
{
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־
var rtbTarget = new NLog.Windows.Forms.RichTextBoxTarget
{
Name = "rtbTarget",
TargetForm = this,
TargetRichTextBox = rtbLog,
AutoScroll = true,
MaxLines = 3000,
SupportLinks = true,
Layout = "[${level:format=OneLetter}]${date:format=yyyy-MM-dd HH\\:mm\\:ss} - ${message}"
};
rtbTarget.WordColoringRules.Add(new("[D]", "Gray", "Empty", FontStyle.Bold));
rtbTarget.WordColoringRules.Add(new("[I]", "Gray", "Empty", FontStyle.Bold));
rtbTarget.WordColoringRules.Add(new("[W]", "DarkOrange", "Empty", FontStyle.Bold));
rtbTarget.WordColoringRules.Add(new("[E]", "Red", "Empty", FontStyle.Bold));
rtbTarget.WordColoringRules.Add(new("[F]", "Red", "Empty", FontStyle.Bold));
LogManager.Configuration.AddTarget(rtbTarget);
LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Fatal, rtbTarget);
LogManager.ReconfigExistingLoggers();
}
#region <EFBFBD>˵<EFBFBD><EFBFBD><EFBFBD>
private void toolStripMenuItem_Open_Click(object sender, EventArgs e)
{
var dialog = new SkelSelectDialog();
if (dialog.ShowDialog() == DialogResult.OK)
{
try
{
spineManger.Add(Spine.Spine.New(dialog.Version, dialog.SkelPath, dialog.AtlasPath));
}
catch (Exception ex)
{
Program.Logger.Error(ex.ToString());
Program.Logger.Error($"Failed to load {dialog.SkelPath} {dialog.AtlasPath}");
MessageBox.Show(ex.ToString(), "<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void toolStripMenuItem_BatchOpen_Click(object sender, EventArgs e)
{
}
private void toolStripMenuItem_Export_Click(object sender, EventArgs e)
{
var a = new SizeF(10, 100);
spineManger.Spines[0].Position = spineManger.Spines[0].Position + a;
}
private void toolStripMenuItem_Exit_Click(object sender, EventArgs e)
{
Close();
}
#endregion
#region ģ<EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>
// button_Add_Click => toolStripMenuItem_Open_Click
private void button_Insert_Click(object sender, EventArgs e)
{
if (listView_SkelList.SelectedIndices.Count <= 0)
return;
var index = listView_SkelList.SelectedIndices[0];
var dialog = new SkelSelectDialog();
dialog.ShowDialog();
try
{
spineManger.Insert(index, Spine.Spine.New(dialog.Version, dialog.SkelPath, dialog.AtlasPath));
}
catch (Exception ex)
{
Program.Logger.Error(ex.ToString());
Program.Logger.Error($"Failed to load {dialog.SkelPath} {dialog.AtlasPath}");
}
}
private void button_Remove_Click(object sender, EventArgs e)
{
if (listView_SkelList.SelectedIndices.Count <= 0)
return;
spineManger.Remove(listView_SkelList.SelectedIndices.Cast<int>());
}
private void button_MoveUp_Click(object sender, EventArgs e)
{
if (listView_SkelList.SelectedIndices.Count <= 0)
return;
spineManger.MoveUp(listView_SkelList.SelectedIndices[0]);
}
private void button_MoveDown_Click(object sender, EventArgs e)
{
if (listView_SkelList.SelectedIndices.Count <= 0)
return;
spineManger.MoveDown(listView_SkelList.SelectedIndices[0]);
}
private void listView_SkelList_SelectedIndexChanged(object sender, EventArgs e)
{
if (listView_SkelList.SelectedIndices.Count <= 0)
{
button_Insert.Enabled = false;
button_Remove.Enabled = false;
button_MoveUp.Enabled = false;
button_MoveDown.Enabled = false;
propertyGrid_Skel.SelectedObject = null;
}
else if (listView_SkelList.SelectedIndices.Count <= 1)
{
button_Insert.Enabled = true;
button_Remove.Enabled = true;
button_MoveUp.Enabled = true;
button_MoveDown.Enabled = true;
propertyGrid_Skel.SelectedObject = spineManger.Spines[listView_SkelList.SelectedIndices[0]];
}
else
{
button_Insert.Enabled = false;
button_Remove.Enabled = true;
button_MoveUp.Enabled = false;
button_MoveDown.Enabled = false;
propertyGrid_Skel.SelectedObject = null;
}
}
#endregion
#region <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
#endregion
#region Ԥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
#endregion
#region <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
private void splitContainer_SplitterMoved(object sender, SplitterEventArgs e)
{
ActiveControl = null;
}
private void splitContainer_MouseUp(object sender, MouseEventArgs e)
{
ActiveControl = null;
}
#endregion
}
}

View File

@@ -0,0 +1,135 @@
<?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>
<metadata name="menuStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="openFileDialog_Skel.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>156, 25</value>
</metadata>
<metadata name="openFileDialog_Atlas.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>404, 23</value>
</metadata>
<metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>651, 23</value>
</metadata>
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>89</value>
</metadata>
</root>

View File

@@ -0,0 +1,49 @@
using NLog;
namespace SpineViewer
{
internal static class Program
{
public static readonly Logger Logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
InitializeLogConfiguration();
Logger.Info("Program Started.");
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
/// <summary>
/// <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>־<EFBFBD><D6BE><EFBFBD><EFBFBD>
/// </summary>
private static void InitializeLogConfiguration()
{
var config = new NLog.Config.LoggingConfiguration();
// <20>ļ<EFBFBD><C4BC><EFBFBD>־
var fileTarget = new NLog.Targets.FileTarget("fileTarget")
{
Encoding = System.Text.Encoding.UTF8,
FileName = "${basedir}/logs/app.log",
ArchiveFileName = "${basedir}/logs/app.{#}.log",
ArchiveNumbering = NLog.Targets.ArchiveNumberingMode.Rolling,
ArchiveAboveSize = 1048576,
MaxArchiveFiles = 5,
Layout = "${date:format=yyyy-MM-dd HH\\:mm\\:ss} - ${level:uppercase=true} - ${callsite-filename:includeSourcePath=false}:${callsite-linenumber} - ${message}"
};
config.AddTarget(fileTarget);
config.AddRule(LogLevel.Debug, LogLevel.Fatal, fileTarget);
LogManager.Configuration = config;
}
}
}

View File

@@ -0,0 +1,39 @@
namespace SpineViewer.src
{
partial class SkelBatchSelectForm
{
/// <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()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Text = "SkelBatchSelectForm";
}
#endregion
}
}

View File

@@ -0,0 +1,20 @@
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.src
{
public partial class SkelBatchSelectForm: Form
{
public SkelBatchSelectForm()
{
InitializeComponent();
}
}
}

View 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>

View File

@@ -0,0 +1,285 @@
namespace SpineViewer
{
partial class SkelSelectDialog
{
/// <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()
{
panel1 = new Panel();
tableLayoutPanel1 = new TableLayoutPanel();
label4 = new Label();
label1 = new Label();
label2 = new Label();
label3 = new Label();
textBox_SkelPath = new TextBox();
button_SelectSkel = new Button();
button_SelectAtlas = new Button();
comboBox_Version = new ComboBox();
textBox_AtlasPath = new TextBox();
tableLayoutPanel2 = new TableLayoutPanel();
button_Ok = new Button();
button_Cancel = new Button();
openFileDialog_Skel = new OpenFileDialog();
openFileDialog_Atlas = new OpenFileDialog();
panel1.SuspendLayout();
tableLayoutPanel1.SuspendLayout();
tableLayoutPanel2.SuspendLayout();
SuspendLayout();
//
// panel1
//
panel1.Controls.Add(tableLayoutPanel1);
panel1.Dock = DockStyle.Fill;
panel1.Location = new Point(0, 0);
panel1.Name = "panel1";
panel1.Padding = new Padding(50, 15, 50, 10);
panel1.Size = new Size(907, 286);
panel1.TabIndex = 0;
//
// tableLayoutPanel1
//
tableLayoutPanel1.AutoSize = true;
tableLayoutPanel1.ColumnCount = 4;
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
tableLayoutPanel1.Controls.Add(label4, 0, 0);
tableLayoutPanel1.Controls.Add(label1, 0, 1);
tableLayoutPanel1.Controls.Add(label2, 0, 2);
tableLayoutPanel1.Controls.Add(label3, 0, 3);
tableLayoutPanel1.Controls.Add(textBox_SkelPath, 1, 1);
tableLayoutPanel1.Controls.Add(button_SelectSkel, 3, 1);
tableLayoutPanel1.Controls.Add(button_SelectAtlas, 3, 2);
tableLayoutPanel1.Controls.Add(comboBox_Version, 1, 3);
tableLayoutPanel1.Controls.Add(textBox_AtlasPath, 1, 2);
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 4);
tableLayoutPanel1.Dock = DockStyle.Fill;
tableLayoutPanel1.Location = new Point(50, 15);
tableLayoutPanel1.Name = "tableLayoutPanel1";
tableLayoutPanel1.RowCount = 5;
tableLayoutPanel1.RowStyles.Add(new RowStyle());
tableLayoutPanel1.RowStyles.Add(new RowStyle());
tableLayoutPanel1.RowStyles.Add(new RowStyle());
tableLayoutPanel1.RowStyles.Add(new RowStyle());
tableLayoutPanel1.RowStyles.Add(new RowStyle());
tableLayoutPanel1.Size = new Size(807, 261);
tableLayoutPanel1.TabIndex = 0;
//
// label4
//
label4.AutoSize = true;
tableLayoutPanel1.SetColumnSpan(label4, 4);
label4.Dock = DockStyle.Fill;
label4.Location = new Point(15, 15);
label4.Margin = new Padding(15);
label4.Name = "label4";
label4.Size = new Size(777, 24);
label4.TabIndex = 11;
label4.Text = "说明如果没有选择atlas则会自动读取与skel同目录下同名的atlas文件";
label4.TextAlign = ContentAlignment.MiddleCenter;
//
// label1
//
label1.Anchor = AnchorStyles.Right;
label1.AutoSize = true;
label1.Location = new Point(10, 62);
label1.Name = "label1";
label1.Size = new Size(119, 24);
label1.TabIndex = 0;
label1.Text = "skel文件路径:";
//
// label2
//
label2.Anchor = AnchorStyles.Right;
label2.AutoSize = true;
label2.Location = new Point(3, 102);
label2.Name = "label2";
label2.Size = new Size(126, 24);
label2.TabIndex = 1;
label2.Text = "atlas文件路径:";
//
// label3
//
label3.Anchor = AnchorStyles.Right;
label3.AutoSize = true;
label3.Location = new Point(79, 141);
label3.Name = "label3";
label3.Size = new Size(50, 24);
label3.TabIndex = 2;
label3.Text = "版本:";
//
// textBox_SkelPath
//
tableLayoutPanel1.SetColumnSpan(textBox_SkelPath, 2);
textBox_SkelPath.Dock = DockStyle.Fill;
textBox_SkelPath.Location = new Point(135, 57);
textBox_SkelPath.Name = "textBox_SkelPath";
textBox_SkelPath.Size = new Size(630, 30);
textBox_SkelPath.TabIndex = 3;
//
// button_SelectSkel
//
button_SelectSkel.AutoSize = true;
button_SelectSkel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
button_SelectSkel.Location = new Point(771, 57);
button_SelectSkel.Name = "button_SelectSkel";
button_SelectSkel.Size = new Size(32, 34);
button_SelectSkel.TabIndex = 5;
button_SelectSkel.Text = "...";
button_SelectSkel.UseVisualStyleBackColor = true;
button_SelectSkel.Click += button_SelectSkel_Click;
//
// button_SelectAtlas
//
button_SelectAtlas.AutoSize = true;
button_SelectAtlas.AutoSizeMode = AutoSizeMode.GrowAndShrink;
button_SelectAtlas.Location = new Point(771, 97);
button_SelectAtlas.Name = "button_SelectAtlas";
button_SelectAtlas.Size = new Size(32, 34);
button_SelectAtlas.TabIndex = 6;
button_SelectAtlas.Text = "...";
button_SelectAtlas.UseVisualStyleBackColor = true;
button_SelectAtlas.Click += button_SelectAtlas_Click;
//
// comboBox_Version
//
comboBox_Version.Anchor = AnchorStyles.Left;
comboBox_Version.DropDownStyle = ComboBoxStyle.DropDownList;
comboBox_Version.FormattingEnabled = true;
comboBox_Version.Location = new Point(135, 137);
comboBox_Version.Name = "comboBox_Version";
comboBox_Version.Size = new Size(182, 32);
comboBox_Version.Sorted = true;
comboBox_Version.TabIndex = 9;
//
// textBox_AtlasPath
//
tableLayoutPanel1.SetColumnSpan(textBox_AtlasPath, 2);
textBox_AtlasPath.Dock = DockStyle.Fill;
textBox_AtlasPath.Location = new Point(135, 97);
textBox_AtlasPath.Name = "textBox_AtlasPath";
textBox_AtlasPath.Size = new Size(630, 30);
textBox_AtlasPath.TabIndex = 4;
//
// tableLayoutPanel2
//
tableLayoutPanel2.AutoSize = true;
tableLayoutPanel2.AutoSizeMode = AutoSizeMode.GrowAndShrink;
tableLayoutPanel2.ColumnCount = 2;
tableLayoutPanel1.SetColumnSpan(tableLayoutPanel2, 4);
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel2.Controls.Add(button_Ok, 0, 0);
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
tableLayoutPanel2.Dock = DockStyle.Bottom;
tableLayoutPanel2.Location = new Point(3, 218);
tableLayoutPanel2.Name = "tableLayoutPanel2";
tableLayoutPanel2.RowCount = 1;
tableLayoutPanel2.RowStyles.Add(new RowStyle());
tableLayoutPanel2.Size = new Size(801, 40);
tableLayoutPanel2.TabIndex = 10;
//
// button_Ok
//
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
button_Ok.Location = new Point(258, 3);
button_Ok.Margin = new Padding(3, 3, 30, 3);
button_Ok.Name = "button_Ok";
button_Ok.Size = new Size(112, 34);
button_Ok.TabIndex = 7;
button_Ok.Text = "确认";
button_Ok.UseVisualStyleBackColor = true;
button_Ok.Click += button_Ok_Click;
//
// button_Cancel
//
button_Cancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
button_Cancel.Location = new Point(430, 3);
button_Cancel.Margin = new Padding(30, 3, 3, 3);
button_Cancel.Name = "button_Cancel";
button_Cancel.Size = new Size(112, 34);
button_Cancel.TabIndex = 8;
button_Cancel.Text = "取消";
button_Cancel.UseVisualStyleBackColor = true;
button_Cancel.Click += button_Cancel_Click;
//
// openFileDialog_Skel
//
openFileDialog_Skel.AddExtension = false;
openFileDialog_Skel.AddToRecent = false;
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json";
//
// openFileDialog_Atlas
//
openFileDialog_Atlas.AddExtension = false;
openFileDialog_Atlas.AddToRecent = false;
openFileDialog_Atlas.Filter = "atlas 文件 (*.atlas)|*.atlas";
//
// SkelSelectDialog
//
AcceptButton = button_Ok;
AutoScaleDimensions = new SizeF(11F, 24F);
AutoScaleMode = AutoScaleMode.Font;
CancelButton = button_Cancel;
ClientSize = new Size(907, 286);
Controls.Add(panel1);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
Name = "SkelSelectDialog";
ShowIcon = false;
ShowInTaskbar = false;
StartPosition = FormStartPosition.CenterScreen;
Text = "打开骨骼";
panel1.ResumeLayout(false);
panel1.PerformLayout();
tableLayoutPanel1.ResumeLayout(false);
tableLayoutPanel1.PerformLayout();
tableLayoutPanel2.ResumeLayout(false);
ResumeLayout(false);
}
#endregion
private Panel panel1;
private TableLayoutPanel tableLayoutPanel1;
private Label label1;
private Label label2;
private Label label3;
private TextBox textBox_SkelPath;
private Button button_SelectSkel;
private Button button_SelectAtlas;
private Button button_Ok;
private Button button_Cancel;
private ComboBox comboBox_Version;
private TextBox textBox_AtlasPath;
private TableLayoutPanel tableLayoutPanel2;
private OpenFileDialog openFileDialog_Skel;
private OpenFileDialog openFileDialog_Atlas;
private Label label4;
}
}

View File

@@ -0,0 +1,80 @@
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
{
public partial class SkelSelectDialog : Form
{
public string SkelPath { get; private set; }
public string? AtlasPath { get; private set; }
public Spine.Version Version { get; private set; }
public SkelSelectDialog()
{
InitializeComponent();
comboBox_Version.DataSource = VersionHelper.Versions.ToList();
comboBox_Version.DisplayMember = "Value";
comboBox_Version.ValueMember = "Key";
comboBox_Version.SelectedValue = Spine.Version.V38;
}
private void button_SelectSkel_Click(object sender, EventArgs e)
{
openFileDialog_Skel.InitialDirectory = Path.GetDirectoryName(textBox_SkelPath.Text);
if (openFileDialog_Skel.ShowDialog() == DialogResult.OK)
{
textBox_SkelPath.Text = Path.GetFullPath(openFileDialog_Skel.FileName);
}
}
private void button_SelectAtlas_Click(object sender, EventArgs e)
{
openFileDialog_Atlas.InitialDirectory = Path.GetDirectoryName(textBox_AtlasPath.Text);
if (openFileDialog_Atlas.ShowDialog() == DialogResult.OK)
{
textBox_AtlasPath.Text = Path.GetFullPath(openFileDialog_Atlas.FileName);
}
}
private void button_Ok_Click(object sender, EventArgs e)
{
var skelPath = textBox_SkelPath.Text;
var atlasPath = textBox_AtlasPath.Text;
if (!File.Exists(skelPath))
{
MessageBox.Show($"{skelPath}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (string.IsNullOrEmpty(atlasPath))
{
atlasPath = null;
}
else if (!File.Exists(atlasPath))
{
MessageBox.Show($"{atlasPath}", "atlas文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
SkelPath = skelPath;
AtlasPath = atlasPath;
Version = (Spine.Version)comboBox_Version.SelectedValue;
DialogResult = DialogResult.OK;
}
private void button_Cancel_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
}
}
}

View File

@@ -0,0 +1,126 @@
<?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>
<metadata name="openFileDialog_Skel.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>156, 25</value>
</metadata>
<metadata name="openFileDialog_Atlas.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>404, 23</value>
</metadata>
</root>

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpineViewer.Spine
{
/// <summary>
/// SFML 混合模式
/// </summary>
public static class BlendMode
{
/// <summary>
/// Alpha Blend
/// <code>
/// res.c = src.c * src.a + dst.c * (1 - src.a)
/// res.a = src.a * 1 + dst.a * (1 - src.a)
/// </code>
/// </summary>
public static SFML.Graphics.BlendMode Normal = SFML.Graphics.BlendMode.Alpha;
/// <summary>
/// Additive Blend
/// <code>
/// res.c = src.c * src.a + dst.c * 1
/// res.a = src.a * 1 + dst.a * 1
/// </code>
/// </summary>
public static SFML.Graphics.BlendMode Additive = SFML.Graphics.BlendMode.Add;
/// <summary>
/// Multiply Blend (PremultipliedAlpha Only)
/// <code>
/// res.c = src.c * dst.c + dst.c * (1 - src.a)
/// res.a = src.a * 1 + dst.a * (1 - src.a)
/// </code>
/// </summary>
public static SFML.Graphics.BlendMode Multiply = new(
SFML.Graphics.BlendMode.Factor.DstColor,
SFML.Graphics.BlendMode.Factor.OneMinusSrcAlpha,
SFML.Graphics.BlendMode.Equation.Add,
SFML.Graphics.BlendMode.Factor.One,
SFML.Graphics.BlendMode.Factor.OneMinusSrcAlpha,
SFML.Graphics.BlendMode.Equation.Add
);
/// <summary>
/// Screen Blend (PremultipliedAlpha Only)
/// <code>
/// res.c = src.c * 1 + dst.c * (1 - src.c) = 1 - [(1 - src.c)(1 - dst.c)]
/// res.a = src.a * 1 + dst.a * (1 - src.a)
/// </code>
/// </summary>
public static SFML.Graphics.BlendMode Screen = new(
SFML.Graphics.BlendMode.Factor.One,
SFML.Graphics.BlendMode.Factor.OneMinusSrcColor,
SFML.Graphics.BlendMode.Equation.Add,
SFML.Graphics.BlendMode.Factor.One,
SFML.Graphics.BlendMode.Factor.OneMinusSrcAlpha,
SFML.Graphics.BlendMode.Equation.Add
);
}
}

View File

@@ -0,0 +1,323 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SpineRuntime36;
namespace SpineViewer.Spine.Implementations
{
[SpineImplementation(Version.V36)]
internal class Spine36 : Spine
{
private class TextureLoader : SpineRuntime36.TextureLoader
{
public void Load(AtlasPage page, string path)
{
var texture = new SFML.Graphics.Texture(path);
if (page.magFilter == TextureFilter.Linear)
texture.Smooth = true;
if (page.uWrap == TextureWrap.Repeat && page.vWrap == TextureWrap.Repeat)
texture.Repeated = true;
page.rendererObject = texture;
page.width = (int)texture.Size.X;
page.height = (int)texture.Size.Y;
}
public void Unload(object texture)
{
((SFML.Graphics.Texture)texture).Dispose();
}
}
private static TextureLoader textureLoader = new();
private Atlas atlas;
private SkeletonBinary? skeletonBinary;
private SkeletonJson? skeletonJson;
private SkeletonData skeletonData;
private AnimationStateData animationStateData;
private Skeleton skeleton;
private AnimationState animationState;
private SkeletonClipping clipping = new();
public Spine36(string skelPath, string? atlasPath = null) : base(skelPath, atlasPath)
{
atlas = new Atlas(AtlasPath, textureLoader);
if (Path.GetExtension(SkelPath) == ".skel")
{
skeletonJson = null;
skeletonBinary = new SkeletonBinary(atlas);
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
}
else if (Path.GetExtension(SkelPath) == ".json")
{
skeletonBinary = null;
skeletonJson = new SkeletonJson(atlas);
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
}
else
{
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
}
animationStateData = new AnimationStateData(skeletonData);
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name);
CurrentAnimation = DefaultAnimationName;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
atlas.Dispose();
}
public override float Scale
{
get
{
if (skeletonBinary is not null)
return skeletonBinary.Scale;
else if (skeletonJson is not null)
return skeletonJson.Scale;
else
return 1f;
}
set
{
// 保存状态
var position = Position;
var flipX = FlipX;
var flipY = FlipY;
var savedTrack0 = animationState.GetCurrent(0);
var val = Math.Max(value, SCALE_MIN);
if (skeletonBinary is not null)
{
skeletonBinary.Scale = val;
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
}
else if (skeletonJson is not null)
{
skeletonJson.Scale = val;
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
}
// reload skel-dependent data
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
// 恢复状态
Position = position;
FlipX = flipX;
FlipY = flipY;
// 恢复原本 Track0 上所有动画
if (savedTrack0 is not null)
{
var entry = animationState.SetAnimation(0, savedTrack0.Animation.Name, true);
entry.TrackTime = savedTrack0.TrackTime;
var savedEntry = savedTrack0.Next;
while (savedEntry is not null)
{
entry = animationState.AddAnimation(0, savedEntry.Animation.Name, true, 0);
entry.TrackTime = savedEntry.TrackTime;
savedEntry = savedEntry.Next;
}
}
}
}
public override PointF Position
{
get => new(skeleton.X, skeleton.Y);
set
{
skeleton.X = value.X;
skeleton.Y = value.Y;
}
}
public override bool FlipX
{
get => skeleton.FlipX;
set => skeleton.FlipX = value;
}
public override bool FlipY
{
get => skeleton.FlipY;
set => skeleton.FlipY = value;
}
public override string CurrentAnimation
{
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
set { if (animationNames.Contains(value)) animationState.SetAnimation(0, value, true); }
}
public override RectangleF Bounds
{
get
{
float[] _ = [];
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
return new RectangleF(x, y, w, h);
}
}
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
public override void Update(float delta)
{
skeleton.Update(delta);
animationState.Update(delta);
animationState.Apply(skeleton);
skeleton.UpdateWorldTransform();
}
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime36.BlendMode spineBlendMode)
{
return spineBlendMode switch
{
SpineRuntime36.BlendMode.Normal => BlendMode.Normal,
SpineRuntime36.BlendMode.Additive => BlendMode.Additive,
SpineRuntime36.BlendMode.Multiply => BlendMode.Multiply,
SpineRuntime36.BlendMode.Screen => BlendMode.Screen,
_ => throw new NotImplementedException($"{spineBlendMode}"),
};
}
public override void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states)
{
vertexArray.Clear();
states.Texture = null;
// 要用 DrawOrder 而不是 Slots
foreach (var slot in skeleton.DrawOrder)
{
var attachment = slot.Attachment;
SFML.Graphics.Texture texture;
float[] worldVertices = worldVerticesBuffer; // 顶点世界坐标, 连续的 [x0, y0, x1, y1, ...] 坐标值
int worldVerticesCount; // 等于顶点数组的长度除以 2
int[] worldTriangleIndices; // 三角形索引, 从顶点坐标数组取的时候要乘以 2, 最大值是 worldVerticesCount - 1
int worldTriangleIndicesLength; // 三角形索引数组长度
float[] uvs; // 纹理坐标
float tintR = skeleton.R * slot.R;
float tintG = skeleton.G * slot.G;
float tintB = skeleton.B * slot.B;
float tintA = skeleton.A * slot.A;
if (attachment is RegionAttachment regionAttachment)
{
texture = (SFML.Graphics.Texture)((AtlasRegion)regionAttachment.RendererObject).page.rendererObject;
regionAttachment.ComputeWorldVertices(slot.Bone, worldVertices, 0);
worldVerticesCount = 4;
worldTriangleIndices = [0, 1, 2, 2, 3, 0];
worldTriangleIndicesLength = 6;
uvs = regionAttachment.UVs;
tintR *= regionAttachment.R;
tintG *= regionAttachment.G;
tintB *= regionAttachment.B;
tintA *= regionAttachment.A;
}
else if (attachment is MeshAttachment meshAttachment)
{
texture = (SFML.Graphics.Texture)((AtlasRegion)meshAttachment.RendererObject).page.rendererObject;
if (meshAttachment.WorldVerticesLength > worldVertices.Length)
worldVertices = worldVerticesBuffer = new float[meshAttachment.WorldVerticesLength * 2];
meshAttachment.ComputeWorldVertices(slot, worldVertices);
worldVerticesCount = meshAttachment.WorldVerticesLength / 2;
worldTriangleIndices = meshAttachment.Triangles;
worldTriangleIndicesLength = meshAttachment.Triangles.Length;
uvs = meshAttachment.UVs;
tintR *= meshAttachment.R;
tintG *= meshAttachment.G;
tintB *= meshAttachment.B;
tintA *= meshAttachment.A;
}
else if (attachment is ClippingAttachment clippingAttachment)
{
clipping.ClipStart(slot, clippingAttachment);
continue;
}
else
{
clipping.ClipEnd(slot);
continue;
}
SFML.Graphics.BlendMode blendMode = GetSFMLBlendMode(slot.Data.BlendMode);
states.Texture ??= texture;
if (states.BlendMode != blendMode || states.Texture != texture)
{
if (vertexArray.VertexCount > 0)
{
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
vertexArray.Clear();
}
states.BlendMode = blendMode;
states.Texture = texture;
}
if (clipping.IsClipping)
{
// 这里必须单独记录 Count, 和 Items 的 Length 是不一致的
clipping.ClipTriangles(worldVertices, worldVerticesCount * 2, worldTriangleIndices, worldTriangleIndicesLength, uvs);
worldVertices = clipping.ClippedVertices.Items;
worldVerticesCount = clipping.ClippedVertices.Count / 2;
worldTriangleIndices = clipping.ClippedTriangles.Items;
worldTriangleIndicesLength = clipping.ClippedTriangles.Count;
uvs = clipping.ClippedUVs.Items;
}
var textureSizeX = texture.Size.X;
var textureSizeY = texture.Size.Y;
SFML.Graphics.Vertex vertex = new();
vertex.Color.R = (byte)(tintR * 255);
vertex.Color.G = (byte)(tintG * 255);
vertex.Color.B = (byte)(tintB * 255);
vertex.Color.A = (byte)(tintA * 255);
// 必须用 worldTriangleIndicesLength 不能直接 foreach
for (int i = 0; i < worldTriangleIndicesLength; i++)
{
var index = worldTriangleIndices[i] * 2;
vertex.Position.X = worldVertices[index];
vertex.Position.Y = worldVertices[index + 1];
vertex.TexCoords.X = uvs[index] * textureSizeX;
vertex.TexCoords.Y = uvs[index + 1] * textureSizeY;
vertexArray.Append(vertex);
}
clipping.ClipEnd(slot);
}
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
clipping.ClipEnd();
}
}
}

View File

@@ -0,0 +1,335 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SpineRuntime38;
using SpineViewer.Spine;
namespace SpineViewer.Spine.Implementations
{
[SpineImplementation(Version.V38)]
internal class Spine38 : Spine
{
private class TextureLoader : SpineRuntime38.TextureLoader
{
public void Load(AtlasPage page, string path)
{
var texture = new SFML.Graphics.Texture(path);
if (page.magFilter == TextureFilter.Linear)
texture.Smooth = true;
if (page.uWrap == TextureWrap.Repeat && page.vWrap == TextureWrap.Repeat)
texture.Repeated = true;
page.rendererObject = texture;
page.width = (int)texture.Size.X;
page.height = (int)texture.Size.Y;
}
public void Unload(object texture)
{
((SFML.Graphics.Texture)texture).Dispose();
}
}
private static TextureLoader textureLoader = new();
private Atlas atlas;
private SkeletonBinary? skeletonBinary;
private SkeletonJson? skeletonJson;
private SkeletonData skeletonData;
private AnimationStateData animationStateData;
private Skeleton skeleton;
private AnimationState animationState;
private SkeletonClipping clipping = new();
public Spine38(string skelPath, string? atlasPath = null) : base(skelPath, atlasPath)
{
atlas = new Atlas(AtlasPath, textureLoader);
if (Path.GetExtension(SkelPath) == ".skel")
{
skeletonJson = null;
skeletonBinary = new SkeletonBinary(atlas);
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
}
else if (Path.GetExtension(SkelPath) == ".json")
{
skeletonBinary = null;
skeletonJson = new SkeletonJson(atlas);
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
}
else
{
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
}
animationStateData = new AnimationStateData(skeletonData);
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name);
CurrentAnimation = DefaultAnimationName;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
atlas.Dispose();
}
public override float Scale
{
get
{
if (skeletonBinary is not null)
return skeletonBinary.Scale;
else if (skeletonJson is not null)
return skeletonJson.Scale;
else
return 1f;
}
set
{
// 保存状态
var position = Position;
var flipX = FlipX;
var flipY = FlipY;
var savedTrack0 = animationState.GetCurrent(0);
var val = Math.Max(value, SCALE_MIN);
if (skeletonBinary is not null)
{
skeletonBinary.Scale = val;
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
}
else if (skeletonJson is not null)
{
skeletonJson.Scale = val;
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
}
// reload skel-dependent data
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
// 恢复状态
Position = position;
FlipX = flipX;
FlipY = flipY;
// 恢复原本 Track0 上所有动画
if (savedTrack0 is not null)
{
var entry = animationState.SetAnimation(0, savedTrack0.Animation.Name, true);
entry.TrackTime = savedTrack0.TrackTime;
var savedEntry = savedTrack0.Next;
while (savedEntry is not null)
{
entry = animationState.AddAnimation(0, savedEntry.Animation.Name, true, 0);
entry.TrackTime = savedEntry.TrackTime;
savedEntry = savedEntry.Next;
}
}
}
}
public override PointF Position
{
get => new(skeleton.X, skeleton.Y);
set
{
skeleton.X = value.X;
skeleton.Y = value.Y;
}
}
public override bool FlipX
{
get => skeleton.ScaleX < 0;
set
{
if (skeleton.ScaleX > 0 && value || skeleton.ScaleX < 0 && !value)
skeleton.ScaleX *= -1;
}
}
public override bool FlipY
{
get => skeleton.ScaleY < 0;
set
{
if (skeleton.ScaleY > 0 && value || skeleton.ScaleY < 0 && !value)
skeleton.ScaleY *= -1;
}
}
public override string CurrentAnimation
{
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
set { if (animationNames.Contains(value)) animationState.SetAnimation(0, value, true); }
}
public override RectangleF Bounds
{
get
{
float[] _ = [];
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
return new RectangleF(x, y, w, h);
}
}
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
public override void Update(float delta)
{
skeleton.Update(delta);
animationState.Update(delta);
animationState.Apply(skeleton);
skeleton.UpdateWorldTransform();
}
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime38.BlendMode spineBlendMode)
{
return spineBlendMode switch
{
SpineRuntime38.BlendMode.Normal => BlendMode.Normal,
SpineRuntime38.BlendMode.Additive => BlendMode.Additive,
SpineRuntime38.BlendMode.Multiply => BlendMode.Multiply,
SpineRuntime38.BlendMode.Screen => BlendMode.Screen,
_ => throw new NotImplementedException($"{spineBlendMode}"),
};
}
public override void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states)
{
vertexArray.Clear();
states.Texture = null;
// 要用 DrawOrder 而不是 Slots
foreach (var slot in skeleton.DrawOrder)
{
var attachment = slot.Attachment;
SFML.Graphics.Texture texture;
float[] worldVertices = worldVerticesBuffer; // 顶点世界坐标, 连续的 [x0, y0, x1, y1, ...] 坐标值
int worldVerticesCount; // 等于顶点数组的长度除以 2
int[] worldTriangleIndices; // 三角形索引, 从顶点坐标数组取的时候要乘以 2, 最大值是 worldVerticesCount - 1
int worldTriangleIndicesLength; // 三角形索引数组长度
float[] uvs; // 纹理坐标
float tintR = skeleton.R * slot.R;
float tintG = skeleton.G * slot.G;
float tintB = skeleton.B * slot.B;
float tintA = skeleton.A * slot.A;
if (attachment is RegionAttachment regionAttachment)
{
texture = (SFML.Graphics.Texture)((AtlasRegion)regionAttachment.RendererObject).page.rendererObject;
regionAttachment.ComputeWorldVertices(slot.Bone, worldVertices, 0);
worldVerticesCount = 4;
worldTriangleIndices = [0, 1, 2, 2, 3, 0];
worldTriangleIndicesLength = 6;
uvs = regionAttachment.UVs;
tintR *= regionAttachment.R;
tintG *= regionAttachment.G;
tintB *= regionAttachment.B;
tintA *= regionAttachment.A;
}
else if (attachment is MeshAttachment meshAttachment)
{
texture = (SFML.Graphics.Texture)((AtlasRegion)meshAttachment.RendererObject).page.rendererObject;
if (meshAttachment.WorldVerticesLength > worldVertices.Length)
worldVertices = worldVerticesBuffer = new float[meshAttachment.WorldVerticesLength * 2];
meshAttachment.ComputeWorldVertices(slot, worldVertices);
worldVerticesCount = meshAttachment.WorldVerticesLength / 2;
worldTriangleIndices = meshAttachment.Triangles;
worldTriangleIndicesLength = meshAttachment.Triangles.Length;
uvs = meshAttachment.UVs;
tintR *= meshAttachment.R;
tintG *= meshAttachment.G;
tintB *= meshAttachment.B;
tintA *= meshAttachment.A;
}
else if (attachment is ClippingAttachment clippingAttachment)
{
clipping.ClipStart(slot, clippingAttachment);
continue;
}
else
{
clipping.ClipEnd(slot);
continue;
}
SFML.Graphics.BlendMode blendMode = GetSFMLBlendMode(slot.Data.BlendMode);
states.Texture ??= texture;
if (states.BlendMode != blendMode || states.Texture != texture)
{
if (vertexArray.VertexCount > 0)
{
// XXX: 实测不用设置 sampler2D 的值也正确
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
vertexArray.Clear();
}
states.BlendMode = blendMode;
states.Texture = texture;
}
if (clipping.IsClipping)
{
// 这里必须单独记录 Count, 和 Items 的 Length 是不一致的
clipping.ClipTriangles(worldVertices, worldVerticesCount * 2, worldTriangleIndices, worldTriangleIndicesLength, uvs);
worldVertices = clipping.ClippedVertices.Items;
worldVerticesCount = clipping.ClippedVertices.Count / 2;
worldTriangleIndices = clipping.ClippedTriangles.Items;
worldTriangleIndicesLength = clipping.ClippedTriangles.Count;
uvs = clipping.ClippedUVs.Items;
}
var textureSizeX = texture.Size.X;
var textureSizeY = texture.Size.Y;
SFML.Graphics.Vertex vertex = new();
vertex.Color.R = (byte)(tintR * 255);
vertex.Color.G = (byte)(tintG * 255);
vertex.Color.B = (byte)(tintB * 255);
vertex.Color.A = (byte)(tintA * 255);
// 必须用 worldTriangleIndicesLength 不能直接 foreach
for (int i = 0; i < worldTriangleIndicesLength; i++)
{
var index = worldTriangleIndices[i] * 2;
vertex.Position.X = worldVertices[index];
vertex.Position.Y = worldVertices[index + 1];
vertex.TexCoords.X = uvs[index] * textureSizeX;
vertex.TexCoords.Y = uvs[index + 1] * textureSizeY;
vertexArray.Append(vertex);
}
clipping.ClipEnd(slot);
}
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
clipping.ClipEnd();
}
}
}

View File

@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpineViewer.Spine
{
/// <summary>
/// 骨骼列表管理器
/// </summary>
class Manager
{
/// <summary>
/// 骨骼列表
/// </summary>
public ReadOnlyCollection<Spine> Spines { get => spines.AsReadOnly(); }
private readonly List<Spine> spines = [];
private readonly ListView listView;
/// <summary>
/// 骨骼管理器
/// </summary>
/// <param name="listView">用于显示骨骼信息的列表</param>
public Manager(ListView listView)
{
listView.BeginUpdate();
listView.Columns.Clear();
listView.Items.Clear();
listView.Columns.AddRange([
new() { Text = "名称", Width = 150},
new() { Text = "版本", Width = 150 }
]);
listView.EndUpdate();
this.listView = listView;
}
/// <summary>
/// 在末尾添加一个
/// </summary>
public void Add(Spine spine)
{
spines.Add(spine);
listView.Items.Add(new ListViewItem([
Path.GetFileNameWithoutExtension(spine.SkelPath),
spine.Version.String()
], -1)
{ ToolTipText = spine.SkelPath });
}
/// <summary>
/// 在指定下标之前添加一个
/// </summary>
public void Insert(int index, Spine spine)
{
spines.Insert(index, spine);
listView.Items.Insert(index, new ListViewItem([
Path.GetFileNameWithoutExtension(spine.SkelPath),
spine.Version.String()
], -1)
{ ToolTipText = spine.SkelPath });
}
/// <summary>
/// 批量移除
/// </summary>
public void Remove(IEnumerable<int> indices)
{
foreach (var i in indices.OrderByDescending(x => x))
{
spines.RemoveAt(i);
listView.Items.RemoveAt(i);
}
}
/// <summary>
/// 指定下标元素前移一位
/// </summary>
public void MoveUp(int index)
{
if (index > 0)
{
(spines[index - 1], spines[index]) = (spines[index], spines[index - 1]);
var item = listView.Items[index];
listView.Items.RemoveAt(index);
listView.Items.Insert(index - 1, item);
}
}
/// <summary>
/// 指定下标元素后移一位
/// </summary>
public void MoveDown(int index)
{
if (index < spines.Count - 1)
{
(spines[index], spines[index + 1]) = (spines[index + 1], spines[index]);
var item = listView.Items[index + 1];
listView.Items.RemoveAt(index + 1);
listView.Items.Insert(index, item);
}
}
/// <summary>
/// 全部移除
/// </summary>
public void Clear()
{
spines.Clear();
listView.Clear();
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpineViewer.Spine
{
/// <summary>
/// 骨骼预览器
/// </summary>
class Previewer
{
private readonly Panel panel;
public Previewer(Panel panel)
{
this.panel = panel;
}
}
}

View File

@@ -0,0 +1,240 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Numerics;
using System.Collections;
using System.Collections.ObjectModel;
using SFML.System;
using SFML.Window;
using System.ComponentModel;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace SpineViewer.Spine
{
/// <summary>
/// Spine 实现类标记
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class SpineImplementationAttribute : Attribute
{
public Version Version { get; }
public SpineImplementationAttribute(Version version)
{
Version = version;
}
}
/// <summary>
/// Spine 基类, 使用静态方法 New 来创建具体版本对象
/// </summary>
public abstract class Spine : SFML.Graphics.Drawable, IDisposable
{
/// <summary>
/// 实现类缓存
/// </summary>
private static readonly Dictionary<Version, Type> ImplementationTypes = [];
/// <summary>
/// 用于解决 PMA 和渐变动画问题的片段着色器
/// </summary>
private const string FRAGMENT_SHADER = (
"uniform sampler2D t;" +
"void main() { vec4 p = texture2D(t, gl_TexCoord[0].xy);" +
"if (p.a > 0) p.rgb /= max(max(max(p.r, p.g), p.b), p.a);" +
"gl_FragColor = gl_Color * p; }"
);
/// <summary>
/// 用于解决 PMA 和渐变动画问题的片段着色器
/// </summary>
protected static readonly SFML.Graphics.Shader? FragmentShader = null;
/// <summary>
/// 静态构造函数
/// </summary>
static Spine()
{
// 遍历并缓存标记了 SpineImplementationAttribute 的类型
var impTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => typeof(Spine).IsAssignableFrom(t) && !t.IsAbstract);
foreach (var type in impTypes)
{
var attr = type.GetCustomAttribute<SpineImplementationAttribute>();
if (attr is not null)
{
ImplementationTypes[attr.Version] = type;
}
}
Program.Logger.Debug($"Find Spine implementations: [{string.Join(", ", ImplementationTypes.Keys)}]");
// 加载 FragmentShader
try
{
FragmentShader = SFML.Graphics.Shader.FromString(null, null, FRAGMENT_SHADER);
}
catch (Exception ex)
{
Program.Logger.Error(ex.ToString());
Program.Logger.Error("Failed to load fragment shader");
FragmentShader = null;
}
}
/// <summary>
/// 创建特定版本的 Spine
/// </summary>
public static Spine New(Version version, string skelPath, string? atlasPath = null)
{
if (!ImplementationTypes.TryGetValue(version, out var spineType))
{
throw new NotImplementedException($"Not implemented version: {version}");
}
return (Spine)Activator.CreateInstance(spineType, skelPath, atlasPath);
}
/// <summary>
/// 构造函数
/// </summary>
public Spine(string skelPath, string? atlasPath = null)
{
// 获取子类类型
var type = GetType();
var attr = type.GetCustomAttribute<SpineImplementationAttribute>();
if (attr is null)
{
throw new InvalidOperationException($"Class {type.Name} has no SpineImplementationAttribute.");
}
atlasPath ??= Path.ChangeExtension(skelPath, ".atlas");
// 设置 Version
Version = attr.Version;
SkelPath = Path.GetFullPath(skelPath);
AtlasPath = Path.GetFullPath(atlasPath);
Name = Path.GetFileNameWithoutExtension(skelPath);
}
~Spine() { Dispose(false); }
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
protected virtual void Dispose(bool disposing) { }
/// <summary>
/// 缩放最小值
/// </summary>
[Browsable(false)]
public const float SCALE_MIN = 0.001f;
/// <summary>
/// 获取所属版本
/// </summary>
[TypeConverter(typeof(VersionTypeConverter))]
[Browsable(true), Category("基本信息"), DisplayName("运行时版本")]
public Version Version { get; }
/// <summary>
/// skel 文件完整路径
/// </summary>
[Browsable(true), Category("基本信息"), DisplayName("skel文件路径")]
public string SkelPath { get; }
/// <summary>
/// atlas 文件完整路径
/// </summary>
[Browsable(true), Category("基本信息"), DisplayName("atlas文件路径")]
public string AtlasPath { get; }
[Browsable(true), Category("基本信息"), DisplayName("名称")]
public string Name { get; }
/// <summary>
/// 缩放比例
/// </summary>
[Browsable(true), Category("空间变换"), DisplayName("缩放比例")]
public abstract float Scale { get; set; }
/// <summary>
/// 位置
/// </summary>
[TypeConverter(typeof(PointFTypeConverter))]
[Browsable(true), Category("空间变换"), DisplayName("位置")]
public abstract PointF Position { get; set; }
/// <summary>
/// 水平翻转
/// </summary>
[Browsable(true), Category("空间变换"), DisplayName("水平翻转")]
public abstract bool FlipX { get; set; }
/// <summary>
/// 垂直翻转
/// </summary>
[Browsable(true), Category("空间变换"), DisplayName("垂直翻转")]
public abstract bool FlipY { get; set; }
/// <summary>
/// 是否使用预乘Alpha
/// </summary>
[Browsable(true), Category("其他"), DisplayName("预乘Alpha通道")]
public bool UsePremultipliedAlpha { get; set; }
/// <summary>
/// 包含的所有动画名称
/// </summary>
[Browsable(false)]
public ReadOnlyCollection<string> AnimationNames { get => animationNames.AsReadOnly(); }
protected List<string> animationNames = [];
/// <summary>
/// 默认动画名称
/// </summary>
[Browsable(false)]
public string DefaultAnimationName { get => animationNames.Last(); }
/// <summary>
/// 当前动画名称
/// </summary>
[TypeConverter(typeof(AnimationTypeConverter))]
[Browsable(true), Category("其他"), DisplayName("当前播放动画"), PropertyTab()]
public abstract string CurrentAnimation { get; set; }
/// <summary>
/// 骨骼包围盒
/// </summary>
[Browsable(false)]
public abstract RectangleF Bounds { get; }
/// <summary>
/// 获取动画时长, 如果动画不存在则返回 0
/// </summary>
public abstract float GetAnimationDuration(string name);
/// <summary>
/// 更新内部状态
/// </summary>
/// <param name="delta">时间间隔</param>
public abstract void Update(float delta);
/// <summary>
/// 顶点坐标缓冲区
/// </summary>
protected float[] worldVerticesBuffer = new float[1024];
/// <summary>
/// 顶点缓冲区
/// </summary>
protected SFML.Graphics.VertexArray vertexArray = new(SFML.Graphics.PrimitiveType.Triangles);
/// <summary>
/// SFML.Graphics.Drawable 接口实现
/// </summary>
public abstract void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpineViewer.Spine
{
public class VersionTypeConverter : EnumConverter
{
public VersionTypeConverter() : base(typeof(Version)) { }
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type? destinationType)
{
if (destinationType == typeof(string) && value is Version version)
{
// 调用自定义的 String() 方法
return version.String();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
public class PointFTypeConverter : ExpandableObjectConverter
{
public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType)
{
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
}
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (destinationType == typeof(string) && value is PointF point)
{
return $"{point.X}, {point.Y}";
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is string str)
{
var parts = str.Split(',');
if (parts.Length == 2 &&
float.TryParse(parts[0], out var x) &&
float.TryParse(parts[1], out var y))
{
return new PointF(x, y);
}
}
return base.ConvertFrom(context, culture, value);
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext? context, object value, Attribute[]? attributes)
{
return TypeDescriptor.GetProperties(typeof(PointF), attributes);
}
public override bool GetPropertiesSupported(ITypeDescriptorContext? context) => true;
}
public class AnimationTypeConverter : StringConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
{
// 支持标准值列表
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context)
{
// 排他模式,只有下拉列表中的值可选
return true;
}
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
{
if (context?.Instance is Spine obj)
{
// 返回 AnimationNames 作为下拉选项
return new StandardValuesCollection(obj.AnimationNames);
}
return base.GetStandardValues(context);
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace SpineViewer.Spine
{
public static class VersionHelper
{
/// <summary>
/// 描述缓存
/// </summary>
public static readonly Dictionary<Version, string> Versions = [];
static VersionHelper()
{
// 初始化缓存
foreach (var value in Enum.GetValues(typeof(Version)))
{
var field = typeof(Version).GetField(value.ToString());
var attribute = field?.GetCustomAttribute<DescriptionAttribute>();
Versions[(Version)value] = attribute?.Description ?? value.ToString();
}
}
/// <summary>
/// 版本号字符串
/// </summary>
public static string String(this Version version)
{
return Versions.TryGetValue(version, out var description) ? description : version.ToString();
}
}
/// <summary>
/// 支持的 Spine 版本
/// </summary>
public enum Version
{
[Description("v3.6.x")] V36 = 0x0306,
[Description("v3.7.x")] V37 = 0x0307,
[Description("v3.8.x")] V38 = 0x0308,
[Description("v3.9.x")] V39 = 0x0309,
[Description("v4.0.x")] V40 = 0x0400,
[Description("v4.1.x")] V41 = 0x0401,
[Description("v4.2.x")] V42 = 0x0402
}
}