Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac0adc5f95 | ||
|
|
208b702065 | ||
|
|
7e61fbfbac | ||
|
|
0591549727 | ||
|
|
a0833580f8 | ||
|
|
c622b60215 | ||
|
|
c228cf9072 | ||
|
|
4c68dd4904 | ||
|
|
32fde582fc | ||
|
|
2bf2509df7 | ||
|
|
07042189c8 | ||
|
|
d251c94638 | ||
|
|
b4119087fb | ||
|
|
e3959e80fb | ||
|
|
0495a2344c | ||
|
|
c781ec5a4f | ||
|
|
a58566735f | ||
|
|
b37e5c25c3 | ||
|
|
63a937a45b | ||
|
|
c920471c0c | ||
|
|
c4863ee09b | ||
|
|
c0b85c454e | ||
|
|
763a49a4d3 | ||
|
|
0e1540873c | ||
|
|
39dcc636ca | ||
|
|
342778c56e | ||
|
|
fd524891aa | ||
|
|
48cb60020c | ||
|
|
d502c592f7 | ||
|
|
e4377436a7 | ||
|
|
eb44c1271e |
8
CHANGELOG.md
Normal file
8
CHANGELOG.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.10.0
|
||||
|
||||
- <20><><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><CBBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><D0B1><EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɾ<EFBFBD><C9BE><EFBFBD><EFBFBD>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE>Χ<EFBFBD><CEA7>ѡ<EFBFBD><D1A1>
|
||||
- <20><><EFBFBD><EFBFBD><EFBFBD>˹<EFBFBD><CBB9><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>ʽת<CABD><D7AA><EFBFBD><EFBFBD><EFBFBD>ܣ<EFBFBD>Ŀǰ<C4BF><C7B0>֧<EFBFBD>ֲ<EFBFBD><D6B2>ְ汾<D6B0>IJ<EFBFBD><C4B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- <20>Ż<EFBFBD><C5BB>˲<EFBFBD><CBB2><EFBFBD>ʹ<EFBFBD><CAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
|
||||
24
README.md
24
README.md
@@ -18,17 +18,21 @@
|
||||
|
||||
也可以下载带有 `SelfContained` 后缀的压缩包, 可以独立运行.
|
||||
|
||||
## 功能
|
||||
## 功能支持
|
||||
|
||||
| 版本 | 查看&导出 | 转换 |
|
||||
| :---: | :---: | :---: |
|
||||
| `2.1.x` | :white_check_mark: | |
|
||||
| `3.1.x` | | |
|
||||
| `3.4.x` | | |
|
||||
| `3.5.x` | | |
|
||||
| `3.6.x` | :white_check_mark: | |
|
||||
| `3.7.x` | :white_check_mark: | |
|
||||
| `3.8.x` | :white_check_mark: | |
|
||||
| `4.1.x` | :white_check_mark: | |
|
||||
| `4.2.x` | :white_check_mark: | |
|
||||
| `4.3.x` | | |
|
||||
|
||||
- 支持不同版本 Spine 查看
|
||||
- [x] `v2.1.x`
|
||||
- [x] `v3.6.x`
|
||||
- [x] `v3.7.x`
|
||||
- [x] `v3.8.x`
|
||||
- [x] `v4.0.x`
|
||||
- [x] `v4.1.x`
|
||||
- [x] `v4.2.x`
|
||||
- [ ] `v4.3.x`
|
||||
- 支持多骨骼文件动画预览
|
||||
- 支持每个骨骼独立参数设置
|
||||
- 支持动画PNG帧序列导出
|
||||
|
||||
@@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.gitignore = .gitignore
|
||||
CHANGELOG.md = CHANGELOG.md
|
||||
README.en.md = README.en.md
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
|
||||
@@ -35,6 +35,11 @@ namespace SpineViewer.Controls
|
||||
Spines = spines.AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// listView.SelectedIndices
|
||||
/// </summary>
|
||||
public ListView.SelectedIndexCollection SelectedIndices { get => listView.SelectedIndices; }
|
||||
|
||||
/// <summary>
|
||||
/// 弹出添加对话框在指定位置之前插入一项
|
||||
/// </summary>
|
||||
@@ -156,6 +161,10 @@ namespace SpineViewer.Controls
|
||||
PropertyGrid.SelectedObject = spines[listView.SelectedIndices[0]];
|
||||
else
|
||||
PropertyGrid.SelectedObjects = listView.SelectedIndices.Cast<int>().Select(index => spines[index]).ToArray();
|
||||
|
||||
// 标记选中的 Spine
|
||||
for (int i = 0; i < spines.Count; i++)
|
||||
spines[i].IsSelected = listView.SelectedIndices.Contains(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ namespace SpineViewer.Controls
|
||||
|
||||
public PreviewerProperty(SpinePreviewer previewer) { this.previewer = previewer; }
|
||||
|
||||
[TypeConverter(typeof(SizeTypeConverter))]
|
||||
[TypeConverter(typeof(SizeConverter))]
|
||||
[Category("导出"), DisplayName("分辨率")]
|
||||
public Size Resolution { get => previewer.Resolution; set => previewer.Resolution = value; }
|
||||
|
||||
[TypeConverter(typeof(PointFTypeConverter))]
|
||||
[TypeConverter(typeof(PointFConverter))]
|
||||
[Category("导出"), DisplayName("画面中心点")]
|
||||
public PointF Center { get => previewer.Center; set => previewer.Center = value; }
|
||||
|
||||
@@ -47,9 +47,6 @@ namespace SpineViewer.Controls
|
||||
[Category("预览"), DisplayName("显示坐标轴")]
|
||||
public bool ShowAxis { get => previewer.ShowAxis; set => previewer.ShowAxis = value; }
|
||||
|
||||
[Category("预览"), DisplayName("显示包围盒")]
|
||||
public bool ShowBounds { get => previewer.ShowBounds; set => previewer.ShowBounds = value; }
|
||||
|
||||
[Category("预览"), DisplayName("最大帧率")]
|
||||
public uint MaxFps { get => previewer.MaxFps; set => previewer.MaxFps = value; }
|
||||
}
|
||||
@@ -84,7 +81,6 @@ namespace SpineViewer.Controls
|
||||
private readonly SFML.Graphics.RenderWindow RenderWindow;
|
||||
private readonly SFML.System.Clock Clock = new();
|
||||
private SFML.System.Vector2f? draggingSrc = null;
|
||||
private Spine.Spine? draggingSpine = null;
|
||||
|
||||
private Task? task = null;
|
||||
private CancellationTokenSource? cancelToken = null;
|
||||
@@ -236,13 +232,6 @@ namespace SpineViewer.Controls
|
||||
[Browsable(false)]
|
||||
public bool ShowAxis { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 显示包围盒
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[Browsable(false)]
|
||||
public bool ShowBounds { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 最大帧率
|
||||
/// </summary>
|
||||
@@ -328,7 +317,6 @@ namespace SpineViewer.Controls
|
||||
// 右键优先级高, 进入画面拖动模式, 需要重新记录源点
|
||||
if ((e.Button & MouseButtons.Right) != 0)
|
||||
{
|
||||
draggingSpine = null;
|
||||
draggingSrc = RenderWindow.MapPixelToCoords(new(e.X, e.Y));
|
||||
Cursor = Cursors.Hand;
|
||||
}
|
||||
@@ -342,14 +330,43 @@ namespace SpineViewer.Controls
|
||||
{
|
||||
lock (SpineListView.Spines)
|
||||
{
|
||||
foreach (var spine in SpineListView.Spines)
|
||||
var spines = SpineListView.Spines;
|
||||
|
||||
// 没有按下 Ctrl 键就只选中点击的那个, 所以先清空选中列表
|
||||
if ((ModifierKeys & Keys.Control) == 0)
|
||||
{
|
||||
if (spine.Bounds.Contains(src))
|
||||
bool hit = false;
|
||||
for (int i = 0; i < spines.Count; i++)
|
||||
{
|
||||
draggingSpine = spine;
|
||||
if (spines[i].Bounds.Contains(src))
|
||||
{
|
||||
hit = true;
|
||||
|
||||
// 如果点到了没被选中的东西, 则清空原先选中的, 改为只选中这一次点的
|
||||
if (!SpineListView.SelectedIndices.Contains(i))
|
||||
{
|
||||
SpineListView.SelectedIndices.Clear();
|
||||
SpineListView.SelectedIndices.Add(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果点了空白的地方, 就清空选中列表
|
||||
if (!hit)
|
||||
SpineListView.SelectedIndices.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < spines.Count; i++)
|
||||
{
|
||||
if (spines[i].Bounds.Contains(src))
|
||||
{
|
||||
SpineListView.SelectedIndices.Add(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,8 +388,17 @@ namespace SpineViewer.Controls
|
||||
}
|
||||
else if ((e.Button & MouseButtons.Left) != 0)
|
||||
{
|
||||
if (draggingSpine is not null)
|
||||
draggingSpine.Position += delta;
|
||||
if (SpineListView is not null)
|
||||
{
|
||||
lock (SpineListView.Spines)
|
||||
{
|
||||
foreach (var spine in SpineListView.Spines)
|
||||
{
|
||||
if (spine.IsSelected)
|
||||
spine.Position += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
draggingSrc = dst;
|
||||
}
|
||||
}
|
||||
@@ -382,7 +408,6 @@ namespace SpineViewer.Controls
|
||||
// 右键高优先级, 结束画面拖动模式
|
||||
if ((e.Button & MouseButtons.Right) != 0)
|
||||
{
|
||||
draggingSpine = null;
|
||||
SpineListView?.PropertyGrid?.Refresh();
|
||||
|
||||
draggingSrc = null;
|
||||
@@ -393,7 +418,6 @@ namespace SpineViewer.Controls
|
||||
else if ((e.Button & MouseButtons.Left) != 0 && (MouseButtons & MouseButtons.Right) == 0)
|
||||
{
|
||||
draggingSrc = null;
|
||||
draggingSpine = null;
|
||||
SpineListView?.PropertyGrid?.Refresh();
|
||||
}
|
||||
}
|
||||
@@ -439,7 +463,7 @@ namespace SpineViewer.Controls
|
||||
spine.Update(delta);
|
||||
RenderWindow.Draw(spine);
|
||||
|
||||
if (ShowBounds)
|
||||
if (spine.IsSelected)
|
||||
{
|
||||
var bounds = spine.Bounds;
|
||||
BoundsRect[0] = BoundsRect[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||
|
||||
@@ -194,8 +194,9 @@
|
||||
//
|
||||
openFileDialog_Skel.AddExtension = false;
|
||||
openFileDialog_Skel.AddToRecent = false;
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Multiselect = true;
|
||||
openFileDialog_Skel.Title = "批量选择skel文件";
|
||||
//
|
||||
// BatchOpenSpineDialog
|
||||
//
|
||||
@@ -213,6 +214,7 @@
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Text = "批量打开骨骼";
|
||||
Load += BatchOpenSpineDialog_Load;
|
||||
panel.ResumeLayout(false);
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
tableLayoutPanel1.PerformLayout();
|
||||
|
||||
@@ -25,6 +25,11 @@ namespace SpineViewer.Dialogs
|
||||
comboBox_Version.SelectedValue = Spine.Version.V38;
|
||||
}
|
||||
|
||||
private void BatchOpenSpineDialog_Load(object sender, EventArgs e)
|
||||
{
|
||||
button_SelectSkel_Click(sender, e);
|
||||
}
|
||||
|
||||
private void button_SelectSkel_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (openFileDialog_Skel.ShowDialog() == DialogResult.OK)
|
||||
@@ -38,6 +43,8 @@ namespace SpineViewer.Dialogs
|
||||
|
||||
private void button_Ok_Click(object sender, EventArgs e)
|
||||
{
|
||||
var version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||
|
||||
if (listBox_FilePath.Items.Count <= 0)
|
||||
{
|
||||
MessageBox.Show("未选择任何文件", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
@@ -53,8 +60,14 @@ namespace SpineViewer.Dialogs
|
||||
}
|
||||
}
|
||||
|
||||
if (!Spine.Spine.ImplementedVersions.Contains(version))
|
||||
{
|
||||
MessageBox.Show($"{version.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
SkelPaths = listBox_FilePath.Items.Cast<string>().ToArray();
|
||||
Version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||
Version = version;
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
357
SpineViewer/Dialogs/ConvertFileFormatDialog.Designer.cs
generated
Normal file
357
SpineViewer/Dialogs/ConvertFileFormatDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,357 @@
|
||||
namespace SpineViewer.Dialogs
|
||||
{
|
||||
partial class ConvertFileFormatDialog
|
||||
{
|
||||
/// <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()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConvertFileFormatDialog));
|
||||
panel = new Panel();
|
||||
tableLayoutPanel1 = new TableLayoutPanel();
|
||||
flowLayoutPanel_TargetFormat = new FlowLayoutPanel();
|
||||
radioButton_BinaryTarget = new RadioButton();
|
||||
radioButton_JsonTarget = new RadioButton();
|
||||
label1 = new Label();
|
||||
label4 = new Label();
|
||||
label3 = new Label();
|
||||
comboBox_Version = new ComboBox();
|
||||
tableLayoutPanel2 = new TableLayoutPanel();
|
||||
button_Ok = new Button();
|
||||
button_Cancel = new Button();
|
||||
listBox_FilePath = new ListBox();
|
||||
button_SelectSkel = new Button();
|
||||
label_Tip = new Label();
|
||||
label2 = new Label();
|
||||
flowLayoutPanel_SourceFormat = new FlowLayoutPanel();
|
||||
radioButton_BinarySource = new RadioButton();
|
||||
radioButton_JsonSource = new RadioButton();
|
||||
openFileDialog_Skel = new OpenFileDialog();
|
||||
panel.SuspendLayout();
|
||||
tableLayoutPanel1.SuspendLayout();
|
||||
flowLayoutPanel_TargetFormat.SuspendLayout();
|
||||
tableLayoutPanel2.SuspendLayout();
|
||||
flowLayoutPanel_SourceFormat.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// panel
|
||||
//
|
||||
panel.Controls.Add(tableLayoutPanel1);
|
||||
panel.Dock = DockStyle.Fill;
|
||||
panel.Location = new Point(0, 0);
|
||||
panel.Name = "panel";
|
||||
panel.Padding = new Padding(50, 15, 50, 10);
|
||||
panel.Size = new Size(1039, 530);
|
||||
panel.TabIndex = 2;
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
tableLayoutPanel1.ColumnCount = 2;
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanel1.Controls.Add(flowLayoutPanel_TargetFormat, 1, 5);
|
||||
tableLayoutPanel1.Controls.Add(label1, 0, 4);
|
||||
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
||||
tableLayoutPanel1.Controls.Add(label3, 0, 3);
|
||||
tableLayoutPanel1.Controls.Add(comboBox_Version, 1, 3);
|
||||
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 6);
|
||||
tableLayoutPanel1.Controls.Add(listBox_FilePath, 0, 2);
|
||||
tableLayoutPanel1.Controls.Add(button_SelectSkel, 0, 1);
|
||||
tableLayoutPanel1.Controls.Add(label_Tip, 1, 1);
|
||||
tableLayoutPanel1.Controls.Add(label2, 0, 5);
|
||||
tableLayoutPanel1.Controls.Add(flowLayoutPanel_SourceFormat, 1, 4);
|
||||
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||
tableLayoutPanel1.Location = new Point(50, 15);
|
||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
tableLayoutPanel1.RowCount = 7;
|
||||
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.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.Size = new Size(939, 505);
|
||||
tableLayoutPanel1.TabIndex = 1;
|
||||
//
|
||||
// flowLayoutPanel_TargetFormat
|
||||
//
|
||||
flowLayoutPanel_TargetFormat.AutoSize = true;
|
||||
flowLayoutPanel_TargetFormat.Controls.Add(radioButton_BinaryTarget);
|
||||
flowLayoutPanel_TargetFormat.Controls.Add(radioButton_JsonTarget);
|
||||
flowLayoutPanel_TargetFormat.Dock = DockStyle.Fill;
|
||||
flowLayoutPanel_TargetFormat.Location = new Point(146, 381);
|
||||
flowLayoutPanel_TargetFormat.Name = "flowLayoutPanel_TargetFormat";
|
||||
flowLayoutPanel_TargetFormat.Size = new Size(790, 34);
|
||||
flowLayoutPanel_TargetFormat.TabIndex = 19;
|
||||
//
|
||||
// radioButton_BinaryTarget
|
||||
//
|
||||
radioButton_BinaryTarget.AutoSize = true;
|
||||
radioButton_BinaryTarget.Location = new Point(3, 3);
|
||||
radioButton_BinaryTarget.Name = "radioButton_BinaryTarget";
|
||||
radioButton_BinaryTarget.Size = new Size(151, 28);
|
||||
radioButton_BinaryTarget.TabIndex = 17;
|
||||
radioButton_BinaryTarget.Text = "二进制 (*.skel)";
|
||||
radioButton_BinaryTarget.UseVisualStyleBackColor = true;
|
||||
radioButton_BinaryTarget.CheckedChanged += radioButton_Target_CheckedChanged;
|
||||
//
|
||||
// radioButton_JsonTarget
|
||||
//
|
||||
radioButton_JsonTarget.AutoSize = true;
|
||||
radioButton_JsonTarget.Checked = true;
|
||||
radioButton_JsonTarget.Location = new Point(160, 3);
|
||||
radioButton_JsonTarget.Name = "radioButton_JsonTarget";
|
||||
radioButton_JsonTarget.Size = new Size(135, 28);
|
||||
radioButton_JsonTarget.TabIndex = 18;
|
||||
radioButton_JsonTarget.TabStop = true;
|
||||
radioButton_JsonTarget.Text = "文本 (*.json)";
|
||||
radioButton_JsonTarget.UseVisualStyleBackColor = true;
|
||||
radioButton_JsonTarget.CheckedChanged += radioButton_Target_CheckedChanged;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
label1.Anchor = AnchorStyles.Right;
|
||||
label1.AutoSize = true;
|
||||
label1.Location = new Point(72, 346);
|
||||
label1.Name = "label1";
|
||||
label1.Size = new Size(68, 24);
|
||||
label1.TabIndex = 15;
|
||||
label1.Text = "源格式:";
|
||||
//
|
||||
// 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(909, 24);
|
||||
label4.TabIndex = 14;
|
||||
label4.Text = "说明:将在每个文件同级目录下生成目标格式后缀的文件,会覆盖已存在文件";
|
||||
label4.TextAlign = ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// label3
|
||||
//
|
||||
label3.Anchor = AnchorStyles.Right;
|
||||
label3.AutoSize = true;
|
||||
label3.Location = new Point(72, 307);
|
||||
label3.Name = "label3";
|
||||
label3.Size = new Size(68, 24);
|
||||
label3.TabIndex = 12;
|
||||
label3.Text = "源版本:";
|
||||
//
|
||||
// comboBox_Version
|
||||
//
|
||||
comboBox_Version.Anchor = AnchorStyles.Left;
|
||||
comboBox_Version.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
comboBox_Version.FormattingEnabled = true;
|
||||
comboBox_Version.Location = new Point(146, 303);
|
||||
comboBox_Version.Name = "comboBox_Version";
|
||||
comboBox_Version.Size = new Size(182, 32);
|
||||
comboBox_Version.Sorted = true;
|
||||
comboBox_Version.TabIndex = 13;
|
||||
//
|
||||
// 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, 462);
|
||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
tableLayoutPanel2.RowCount = 1;
|
||||
tableLayoutPanel2.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel2.Size = new Size(933, 40);
|
||||
tableLayoutPanel2.TabIndex = 11;
|
||||
//
|
||||
// button_Ok
|
||||
//
|
||||
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
button_Ok.Location = new Point(324, 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(496, 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;
|
||||
//
|
||||
// listBox_FilePath
|
||||
//
|
||||
tableLayoutPanel1.SetColumnSpan(listBox_FilePath, 2);
|
||||
listBox_FilePath.Dock = DockStyle.Fill;
|
||||
listBox_FilePath.FormattingEnabled = true;
|
||||
listBox_FilePath.HorizontalScrollbar = true;
|
||||
listBox_FilePath.ItemHeight = 24;
|
||||
listBox_FilePath.Location = new Point(3, 97);
|
||||
listBox_FilePath.Name = "listBox_FilePath";
|
||||
listBox_FilePath.Size = new Size(933, 200);
|
||||
listBox_FilePath.TabIndex = 2;
|
||||
//
|
||||
// button_SelectSkel
|
||||
//
|
||||
button_SelectSkel.Anchor = AnchorStyles.None;
|
||||
button_SelectSkel.Location = new Point(3, 57);
|
||||
button_SelectSkel.Name = "button_SelectSkel";
|
||||
button_SelectSkel.Size = new Size(137, 34);
|
||||
button_SelectSkel.TabIndex = 1;
|
||||
button_SelectSkel.Text = "选择文件...";
|
||||
button_SelectSkel.UseVisualStyleBackColor = true;
|
||||
button_SelectSkel.Click += button_SelectSkel_Click;
|
||||
//
|
||||
// label_Tip
|
||||
//
|
||||
label_Tip.AutoSize = true;
|
||||
label_Tip.Dock = DockStyle.Fill;
|
||||
label_Tip.Location = new Point(146, 54);
|
||||
label_Tip.Name = "label_Tip";
|
||||
label_Tip.Size = new Size(790, 40);
|
||||
label_Tip.TabIndex = 0;
|
||||
label_Tip.Text = "已选择 0 个文件";
|
||||
label_Tip.TextAlign = ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// label2
|
||||
//
|
||||
label2.Anchor = AnchorStyles.Right;
|
||||
label2.AutoSize = true;
|
||||
label2.Location = new Point(54, 386);
|
||||
label2.Name = "label2";
|
||||
label2.Size = new Size(86, 24);
|
||||
label2.TabIndex = 16;
|
||||
label2.Text = "目标格式:";
|
||||
//
|
||||
// flowLayoutPanel_SourceFormat
|
||||
//
|
||||
flowLayoutPanel_SourceFormat.AutoSize = true;
|
||||
flowLayoutPanel_SourceFormat.Controls.Add(radioButton_BinarySource);
|
||||
flowLayoutPanel_SourceFormat.Controls.Add(radioButton_JsonSource);
|
||||
flowLayoutPanel_SourceFormat.Dock = DockStyle.Fill;
|
||||
flowLayoutPanel_SourceFormat.Location = new Point(146, 341);
|
||||
flowLayoutPanel_SourceFormat.Name = "flowLayoutPanel_SourceFormat";
|
||||
flowLayoutPanel_SourceFormat.Size = new Size(790, 34);
|
||||
flowLayoutPanel_SourceFormat.TabIndex = 18;
|
||||
//
|
||||
// radioButton_BinarySource
|
||||
//
|
||||
radioButton_BinarySource.AutoSize = true;
|
||||
radioButton_BinarySource.Checked = true;
|
||||
radioButton_BinarySource.Location = new Point(3, 3);
|
||||
radioButton_BinarySource.Name = "radioButton_BinarySource";
|
||||
radioButton_BinarySource.Size = new Size(151, 28);
|
||||
radioButton_BinarySource.TabIndex = 17;
|
||||
radioButton_BinarySource.TabStop = true;
|
||||
radioButton_BinarySource.Text = "二进制 (*.skel)";
|
||||
radioButton_BinarySource.UseVisualStyleBackColor = true;
|
||||
radioButton_BinarySource.CheckedChanged += radioButton_Source_CheckedChanged;
|
||||
//
|
||||
// radioButton_JsonSource
|
||||
//
|
||||
radioButton_JsonSource.AutoSize = true;
|
||||
radioButton_JsonSource.Location = new Point(160, 3);
|
||||
radioButton_JsonSource.Name = "radioButton_JsonSource";
|
||||
radioButton_JsonSource.Size = new Size(135, 28);
|
||||
radioButton_JsonSource.TabIndex = 18;
|
||||
radioButton_JsonSource.Text = "文本 (*.json)";
|
||||
radioButton_JsonSource.UseVisualStyleBackColor = true;
|
||||
radioButton_JsonSource.CheckedChanged += radioButton_Source_CheckedChanged;
|
||||
//
|
||||
// openFileDialog_Skel
|
||||
//
|
||||
openFileDialog_Skel.AddExtension = false;
|
||||
openFileDialog_Skel.AddToRecent = false;
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Multiselect = true;
|
||||
openFileDialog_Skel.Title = "批量选择skel文件";
|
||||
//
|
||||
// ConvertFileFormatDialog
|
||||
//
|
||||
AcceptButton = button_Ok;
|
||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
CancelButton = button_Cancel;
|
||||
ClientSize = new Size(1039, 530);
|
||||
Controls.Add(panel);
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
Name = "ConvertFileFormatDialog";
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Text = "骨骼文件格式转换";
|
||||
panel.ResumeLayout(false);
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
tableLayoutPanel1.PerformLayout();
|
||||
flowLayoutPanel_TargetFormat.ResumeLayout(false);
|
||||
flowLayoutPanel_TargetFormat.PerformLayout();
|
||||
tableLayoutPanel2.ResumeLayout(false);
|
||||
flowLayoutPanel_SourceFormat.ResumeLayout(false);
|
||||
flowLayoutPanel_SourceFormat.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Panel panel;
|
||||
private TableLayoutPanel tableLayoutPanel1;
|
||||
private Label label4;
|
||||
private Label label3;
|
||||
private ComboBox comboBox_Version;
|
||||
private TableLayoutPanel tableLayoutPanel2;
|
||||
private Button button_Ok;
|
||||
private Button button_Cancel;
|
||||
private ListBox listBox_FilePath;
|
||||
private Button button_SelectSkel;
|
||||
private Label label_Tip;
|
||||
private OpenFileDialog openFileDialog_Skel;
|
||||
private Label label1;
|
||||
private Label label2;
|
||||
private RadioButton radioButton_BinarySource;
|
||||
private FlowLayoutPanel flowLayoutPanel_SourceFormat;
|
||||
private RadioButton radioButton_JsonSource;
|
||||
private FlowLayoutPanel flowLayoutPanel_TargetFormat;
|
||||
private RadioButton radioButton_BinaryTarget;
|
||||
private RadioButton radioButton_JsonTarget;
|
||||
}
|
||||
}
|
||||
94
SpineViewer/Dialogs/ConvertFileFormatDialog.cs
Normal file
94
SpineViewer/Dialogs/ConvertFileFormatDialog.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using SpineViewer.Spine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace SpineViewer.Dialogs
|
||||
{
|
||||
public partial class ConvertFileFormatDialog : Form
|
||||
{
|
||||
public string[] SkelPaths { get; private set; }
|
||||
public Spine.Version Version { get; private set; }
|
||||
public bool ConvertToJson { get; private set; }
|
||||
|
||||
public ConvertFileFormatDialog()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (openFileDialog_Skel.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
listBox_FilePath.Items.Clear();
|
||||
foreach (var p in openFileDialog_Skel.FileNames)
|
||||
listBox_FilePath.Items.Add(Path.GetFullPath(p));
|
||||
label_Tip.Text = $"已选择 {listBox_FilePath.Items.Count} 个文件";
|
||||
}
|
||||
}
|
||||
|
||||
private void button_Ok_Click(object sender, EventArgs e)
|
||||
{
|
||||
var version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||
|
||||
if (listBox_FilePath.Items.Count <= 0)
|
||||
{
|
||||
MessageBox.Show("未选择任何文件", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string p in listBox_FilePath.Items)
|
||||
{
|
||||
if (!File.Exists(p))
|
||||
{
|
||||
MessageBox.Show($"{p}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Spine.Spine.ImplementedVersions.Contains(version) ||
|
||||
!SkeletonConverter.ImplementedVersions.Contains(version))
|
||||
{
|
||||
MessageBox.Show($"{version.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
SkelPaths = listBox_FilePath.Items.Cast<string>().ToArray();
|
||||
Version = version;
|
||||
ConvertToJson = radioButton_BinarySource.Checked;
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
private void button_Cancel_Click(object sender, EventArgs e)
|
||||
{
|
||||
DialogResult = DialogResult.Cancel;
|
||||
}
|
||||
|
||||
private void radioButton_Source_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (radioButton_BinarySource.Checked)
|
||||
radioButton_JsonTarget.Checked = true;
|
||||
else
|
||||
radioButton_BinaryTarget.Checked = true;
|
||||
}
|
||||
|
||||
private void radioButton_Target_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (radioButton_BinaryTarget.Checked)
|
||||
radioButton_JsonSource.Checked = true;
|
||||
else
|
||||
radioButton_BinarySource.Checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
3270
SpineViewer/Dialogs/ConvertFileFormatDialog.resx
Normal file
3270
SpineViewer/Dialogs/ConvertFileFormatDialog.resx
Normal file
File diff suppressed because it is too large
Load Diff
5
SpineViewer/Dialogs/OpenSpineDialog.Designer.cs
generated
5
SpineViewer/Dialogs/OpenSpineDialog.Designer.cs
generated
@@ -232,13 +232,15 @@
|
||||
//
|
||||
openFileDialog_Skel.AddExtension = false;
|
||||
openFileDialog_Skel.AddToRecent = false;
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Title = "选择skel文件";
|
||||
//
|
||||
// openFileDialog_Atlas
|
||||
//
|
||||
openFileDialog_Atlas.AddExtension = false;
|
||||
openFileDialog_Atlas.AddToRecent = false;
|
||||
openFileDialog_Atlas.Filter = "atlas 文件 (*.atlas)|*.atlas|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Atlas.Title = "选择atlas文件";
|
||||
//
|
||||
// OpenSpineDialog
|
||||
//
|
||||
@@ -256,6 +258,7 @@
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Text = "打开骨骼";
|
||||
Load += OpenSpineDialog_Load;
|
||||
panel1.ResumeLayout(false);
|
||||
panel1.PerformLayout();
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace SpineViewer.Dialogs
|
||||
{
|
||||
public partial class OpenSpineDialog : Form
|
||||
@@ -26,6 +25,11 @@ namespace SpineViewer.Dialogs
|
||||
comboBox_Version.SelectedValue = Spine.Version.V38;
|
||||
}
|
||||
|
||||
private void OpenSpineDialog_Load(object sender, EventArgs e)
|
||||
{
|
||||
button_SelectSkel_Click(sender, e);
|
||||
}
|
||||
|
||||
private void button_SelectSkel_Click(object sender, EventArgs e)
|
||||
{
|
||||
openFileDialog_Skel.InitialDirectory = Path.GetDirectoryName(textBox_SkelPath.Text);
|
||||
@@ -48,6 +52,7 @@ namespace SpineViewer.Dialogs
|
||||
{
|
||||
var skelPath = textBox_SkelPath.Text;
|
||||
var atlasPath = textBox_AtlasPath.Text;
|
||||
var version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||
|
||||
if (!File.Exists(skelPath))
|
||||
{
|
||||
@@ -73,9 +78,15 @@ namespace SpineViewer.Dialogs
|
||||
atlasPath = Path.GetFullPath(atlasPath);
|
||||
}
|
||||
|
||||
if (!Spine.Spine.ImplementedVersions.Contains(version))
|
||||
{
|
||||
MessageBox.Show($"{version.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
SkelPath = skelPath;
|
||||
AtlasPath = atlasPath;
|
||||
Version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||
Version = version;
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
@@ -118,10 +118,10 @@
|
||||
<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>75, 24</value>
|
||||
<value>58, 25</value>
|
||||
</metadata>
|
||||
<metadata name="openFileDialog_Atlas.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>317, 22</value>
|
||||
<value>349, 29</value>
|
||||
</metadata>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
|
||||
37
SpineViewer/MainForm.Designer.cs
generated
37
SpineViewer/MainForm.Designer.cs
generated
@@ -40,6 +40,10 @@
|
||||
toolStripMenuItem_Exit = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Function = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ResetAnimation = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Tool = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ConvertFileFormat = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Download = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ManageResource = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Help = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Diagnostics = new ToolStripMenuItem();
|
||||
toolStripSeparator3 = new ToolStripSeparator();
|
||||
@@ -87,7 +91,7 @@
|
||||
//
|
||||
menuStrip.BackColor = SystemColors.Control;
|
||||
menuStrip.ImageScalingSize = new Size(24, 24);
|
||||
menuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_File, toolStripMenuItem_Function, toolStripMenuItem_Help });
|
||||
menuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_File, toolStripMenuItem_Function, toolStripMenuItem_Tool, toolStripMenuItem_Download, toolStripMenuItem_Help });
|
||||
menuStrip.Location = new Point(0, 0);
|
||||
menuStrip.Name = "menuStrip";
|
||||
menuStrip.Size = new Size(1741, 32);
|
||||
@@ -156,6 +160,33 @@
|
||||
toolStripMenuItem_ResetAnimation.Text = "重置动画时间(&R)";
|
||||
toolStripMenuItem_ResetAnimation.Click += toolStripMenuItem_ResetAnimation_Click;
|
||||
//
|
||||
// toolStripMenuItem_Tool
|
||||
//
|
||||
toolStripMenuItem_Tool.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ConvertFileFormat });
|
||||
toolStripMenuItem_Tool.Name = "toolStripMenuItem_Tool";
|
||||
toolStripMenuItem_Tool.Size = new Size(84, 28);
|
||||
toolStripMenuItem_Tool.Text = "工具(&T)";
|
||||
//
|
||||
// toolStripMenuItem_ConvertFileFormat
|
||||
//
|
||||
toolStripMenuItem_ConvertFileFormat.Name = "toolStripMenuItem_ConvertFileFormat";
|
||||
toolStripMenuItem_ConvertFileFormat.Size = new Size(270, 34);
|
||||
toolStripMenuItem_ConvertFileFormat.Text = "转换文件格式(&C)...";
|
||||
toolStripMenuItem_ConvertFileFormat.Click += toolStripMenuItem_ConvertFileFormat_Click;
|
||||
//
|
||||
// toolStripMenuItem_Download
|
||||
//
|
||||
toolStripMenuItem_Download.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ManageResource });
|
||||
toolStripMenuItem_Download.Name = "toolStripMenuItem_Download";
|
||||
toolStripMenuItem_Download.Size = new Size(88, 28);
|
||||
toolStripMenuItem_Download.Text = "下载(&D)";
|
||||
//
|
||||
// toolStripMenuItem_ManageResource
|
||||
//
|
||||
toolStripMenuItem_ManageResource.Name = "toolStripMenuItem_ManageResource";
|
||||
toolStripMenuItem_ManageResource.Size = new Size(260, 34);
|
||||
toolStripMenuItem_ManageResource.Text = "管理下载资源(&M)...";
|
||||
//
|
||||
// toolStripMenuItem_Help
|
||||
//
|
||||
toolStripMenuItem_Help.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Diagnostics, toolStripSeparator3, toolStripMenuItem_About });
|
||||
@@ -465,5 +496,9 @@
|
||||
private ToolStripMenuItem toolStripMenuItem_ResetAnimation;
|
||||
private ToolStripMenuItem toolStripMenuItem_Diagnostics;
|
||||
private ToolStripSeparator toolStripSeparator3;
|
||||
private ToolStripMenuItem toolStripMenuItem_Download;
|
||||
private ToolStripMenuItem toolStripMenuItem_ManageResource;
|
||||
private ToolStripMenuItem toolStripMenuItem_Tool;
|
||||
private ToolStripMenuItem toolStripMenuItem_ConvertFileFormat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using NLog;
|
||||
using NLog;
|
||||
using SpineViewer.Spine;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
@@ -14,11 +14,11 @@ namespace SpineViewer
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־<EFBFBD><EFBFBD>
|
||||
/// 初始化窗口日志器
|
||||
/// </summary>
|
||||
private void InitializeLogConfiguration()
|
||||
{
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־
|
||||
// 窗口日志
|
||||
var rtbTarget = new NLog.Windows.Forms.RichTextBoxTarget
|
||||
{
|
||||
Name = "rtbTarget",
|
||||
@@ -54,7 +54,7 @@ namespace SpineViewer
|
||||
var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
|
||||
tex.SetView(spinePreviewer.View);
|
||||
var delta = 1f / fps;
|
||||
var frameCount = 1 + (int)(duration / delta); // <EFBFBD><EFBFBD>֡<EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
var frameCount = 1 + (int)(duration / delta); // 零帧开始导出
|
||||
|
||||
spinePreviewer.StopPreview();
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace SpineViewer
|
||||
{
|
||||
var spinesReverse = spineListView.Spines.Reverse();
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD>ö<EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD>
|
||||
// 重置动画时间
|
||||
foreach (var spine in spinesReverse)
|
||||
spine.CurrentAnimation = spine.CurrentAnimation;
|
||||
|
||||
@@ -71,9 +71,9 @@ namespace SpineViewer
|
||||
[outputDir, duration, fps, spinesReverse.Count()]
|
||||
);
|
||||
|
||||
// <EFBFBD><EFBFBD>֡<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
// 逐帧导出
|
||||
var success = 0;
|
||||
worker.ReportProgress(0, $"<EFBFBD>Ѵ<EFBFBD><EFBFBD><EFBFBD> 0/{frameCount}");
|
||||
worker.ReportProgress(0, $"已处理 0/{frameCount}");
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
@@ -94,7 +94,7 @@ namespace SpineViewer
|
||||
}
|
||||
|
||||
success++;
|
||||
worker.ReportProgress((int)((frameIndex + 1) * 100.0) / frameCount, $"<EFBFBD>Ѵ<EFBFBD><EFBFBD><EFBFBD> {frameIndex + 1}/{frameCount}");
|
||||
worker.ReportProgress((int)((frameIndex + 1) * 100.0) / frameCount, $"已处理 {frameIndex + 1}/{frameCount}");
|
||||
}
|
||||
|
||||
Program.Logger.Info("Exporting done: {}/{}", success, frameCount);
|
||||
@@ -129,7 +129,7 @@ namespace SpineViewer
|
||||
{
|
||||
if (spineListView.Spines.Count <= 0)
|
||||
{
|
||||
MessageBox.Show("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ٴ<EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>", "<EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD>Ϣ", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
MessageBox.Show("请至少打开一个骨骼文件", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -158,6 +158,73 @@ namespace SpineViewer
|
||||
}
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_ConvertFileFormat_Click(object sender, EventArgs e)
|
||||
{
|
||||
var openDialog = new Dialogs.ConvertFileFormatDialog();
|
||||
if (openDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var progressDialog = new Dialogs.ProgressDialog();
|
||||
progressDialog.DoWork += ConvertFileFormat_Work;
|
||||
progressDialog.RunWorkerAsync(openDialog);
|
||||
progressDialog.ShowDialog();
|
||||
}
|
||||
|
||||
private void ConvertFileFormat_Work(object? sender, DoWorkEventArgs e)
|
||||
{
|
||||
var worker = sender as BackgroundWorker;
|
||||
var arguments = e.Argument as Dialogs.ConvertFileFormatDialog;
|
||||
var skelPaths = arguments.SkelPaths;
|
||||
var version = arguments.Version;
|
||||
var convertToJson = arguments.ConvertToJson;
|
||||
var newSuffix = convertToJson ? ".json" : ".skel";
|
||||
|
||||
int totalCount = skelPaths.Length;
|
||||
int success = 0;
|
||||
int error = 0;
|
||||
|
||||
SkeletonConverter cvter = SkeletonConverter.New(version);
|
||||
|
||||
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
||||
for (int i = 0; i < totalCount; i++)
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
{
|
||||
e.Cancel = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var skelPath = skelPaths[i];
|
||||
var newPath = Path.ChangeExtension(skelPath, newSuffix);
|
||||
|
||||
try
|
||||
{
|
||||
if (convertToJson)
|
||||
cvter.BinaryToJson(skelPath, newPath);
|
||||
else
|
||||
cvter.JsonToBinary(skelPath, newPath);
|
||||
success++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Logger.Error(ex.ToString());
|
||||
Program.Logger.Error("Failed to convert {}", skelPath);
|
||||
error++;
|
||||
}
|
||||
|
||||
worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
|
||||
}
|
||||
|
||||
if (error > 0)
|
||||
{
|
||||
Program.Logger.Warn("Batch convert {} successfully, {} failed", success, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.Logger.Info("{} skel converted successfully", success);
|
||||
}
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_About_Click(object sender, EventArgs e)
|
||||
{
|
||||
(new Dialogs.AboutDialog()).ShowDialog();
|
||||
@@ -175,5 +242,6 @@ namespace SpineViewer
|
||||
private void propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e) { (sender as PropertyGrid)?.Refresh(); }
|
||||
|
||||
private void spinePreviewer_MouseUp(object sender, MouseEventArgs e) { propertyGrid_Spine.Refresh(); }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using NLog;
|
||||
using NLog;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace SpineViewer
|
||||
@@ -28,18 +28,18 @@ namespace SpineViewer
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Fatal(ex.ToString());
|
||||
MessageBox.Show(ex.ToString(), "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѱ<EFBFBD><EFBFBD><EFBFBD>", MessageBoxButtons.OK, MessageBoxIcon.Stop);
|
||||
MessageBox.Show(ex.ToString(), "程序已崩溃", MessageBoxButtons.OK, MessageBoxIcon.Stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>־<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 初始化日志配置
|
||||
/// </summary>
|
||||
private static void InitializeLogConfiguration()
|
||||
{
|
||||
var config = new NLog.Config.LoggingConfiguration();
|
||||
|
||||
// <EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>־
|
||||
// 文件日志
|
||||
var fileTarget = new NLog.Targets.FileTarget("fileTarget")
|
||||
{
|
||||
Encoding = System.Text.Encoding.UTF8,
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace SpineViewer.Spine
|
||||
/// <summary>
|
||||
/// SFML 混合模式
|
||||
/// </summary>
|
||||
public static class BlendMode
|
||||
public static class BlendModeSFML
|
||||
{
|
||||
/// <summary>
|
||||
/// Alpha Blend
|
||||
@@ -0,0 +1,824 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime38;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using SpineRuntime38.Attachments;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
{
|
||||
[SkeletonConverterImplementation(Version.V38)]
|
||||
class SkeletonConverter38 : SpineViewer.Spine.SkeletonConverter
|
||||
{
|
||||
private SkeletonReader reader = null;
|
||||
private JsonObject root = null;
|
||||
|
||||
private bool nonessential = false;
|
||||
private List<JsonObject> idx2Event = [];
|
||||
|
||||
protected override JsonObject ReadBinary(string binPath)
|
||||
{
|
||||
using var input = File.OpenRead(binPath);
|
||||
reader = new(input);
|
||||
|
||||
var result = root = [];
|
||||
root["skeleton"] = ReadSkeleton();
|
||||
ReadStrings();
|
||||
root["bones"] = ReadBones();
|
||||
root["slots"] = ReadSlots();
|
||||
root["ik"] = ReadIK();
|
||||
root["transform"] = ReadTransform();
|
||||
root["path"] = ReadPath();
|
||||
root["skins"] = ReadSkins();
|
||||
root["events"] = ReadEvents();
|
||||
root["animations"] = ReadAnimations();
|
||||
|
||||
reader = null;
|
||||
nonessential = false;
|
||||
root = null;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private JsonObject ReadSkeleton()
|
||||
{
|
||||
JsonObject skeleton = [];
|
||||
skeleton["hash"] = reader.ReadString();
|
||||
skeleton["spine"] = reader.ReadString();
|
||||
skeleton["x"] = reader.ReadFloat();
|
||||
skeleton["y"] = reader.ReadFloat();
|
||||
skeleton["width"] = reader.ReadFloat();
|
||||
skeleton["height"] = reader.ReadFloat();
|
||||
nonessential = reader.ReadBoolean();
|
||||
if (nonessential)
|
||||
{
|
||||
skeleton["fps"] = reader.ReadFloat();
|
||||
skeleton["images"] = reader.ReadString();
|
||||
skeleton["audio"] = reader.ReadString();
|
||||
}
|
||||
return skeleton;
|
||||
}
|
||||
|
||||
private void ReadStrings()
|
||||
{
|
||||
for (int n = reader.ReadVarInt(); n > 0; n--)
|
||||
reader.StringTable.Add(reader.ReadString());
|
||||
}
|
||||
|
||||
private JsonArray ReadBones()
|
||||
{
|
||||
JsonArray bones = [];
|
||||
for (int i = 0, n = reader.ReadVarInt(); i < n; i++)
|
||||
{
|
||||
JsonObject data = [];
|
||||
data["name"] = reader.ReadString();
|
||||
if (i > 0) data["parent"] = bones[reader.ReadVarInt()]["name"].GetValue<string>();
|
||||
data["rotation"] = reader.ReadFloat();
|
||||
data["x"] = reader.ReadFloat();
|
||||
data["y"] = reader.ReadFloat();
|
||||
data["scaleX"] = reader.ReadFloat();
|
||||
data["scaleY"] = reader.ReadFloat();
|
||||
data["shearX"] = reader.ReadFloat();
|
||||
data["shearY"] = reader.ReadFloat();
|
||||
data["length"] = reader.ReadFloat();
|
||||
data["transform"] = SkeletonBinary.TransformModeValues[reader.ReadVarInt()].ToString();
|
||||
data["skin"] = reader.ReadBoolean();
|
||||
if (nonessential) reader.ReadInt();
|
||||
bones.Add(data);
|
||||
}
|
||||
return bones;
|
||||
}
|
||||
|
||||
private JsonArray ReadSlots()
|
||||
{
|
||||
JsonArray bones = root["bones"].AsArray();
|
||||
JsonArray slots = [];
|
||||
for (int n = reader.ReadVarInt(); n > 0; n--)
|
||||
{
|
||||
JsonObject data = [];
|
||||
data["name"] = reader.ReadString();
|
||||
data["bone"] = bones[reader.ReadVarInt()]["name"].GetValue<string>();
|
||||
data["color"] = reader.ReadInt().ToString("x8"); // 0xrrggbbaa -> rrggbbaa
|
||||
int dark = reader.ReadInt();
|
||||
if (dark != -1) data["dark"] = dark.ToString("x6"); // 0x00rrggbb -> rrggbb
|
||||
data["attachment"] = reader.ReadStringRef();
|
||||
data["blend"] = ((BlendMode)reader.ReadVarInt()).ToString();
|
||||
slots.Add(data);
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
private JsonArray ReadIK()
|
||||
{
|
||||
JsonArray bones = root["bones"].AsArray();
|
||||
JsonArray ik = [];
|
||||
for (int n = reader.ReadVarInt(); n > 0; n--)
|
||||
{
|
||||
JsonObject data = [];
|
||||
data["name"] = reader.ReadString();
|
||||
data["order"] = reader.ReadVarInt();
|
||||
data["skin"] = reader.ReadBoolean();
|
||||
data["bones"] = ReadNames(bones);
|
||||
data["target"] = bones[reader.ReadVarInt()]["name"].GetValue<string>();
|
||||
data["mix"] = reader.ReadFloat();
|
||||
data["softness"] = reader.ReadFloat();
|
||||
data["bendPositive"] = reader.ReadSByte() > 0;
|
||||
data["compress"] = reader.ReadBoolean();
|
||||
data["stretch"] = reader.ReadBoolean();
|
||||
data["uniform"] = reader.ReadBoolean();
|
||||
ik.Add(data);
|
||||
}
|
||||
return ik;
|
||||
}
|
||||
|
||||
private JsonArray ReadTransform()
|
||||
{
|
||||
JsonArray bones = root["bones"].AsArray();
|
||||
JsonArray transform = [];
|
||||
for (int n = reader.ReadVarInt(); n > 0; n--)
|
||||
{
|
||||
JsonObject data = [];
|
||||
data["name"] = reader.ReadString();
|
||||
data["order"] = reader.ReadVarInt();
|
||||
data["skin"] = reader.ReadBoolean();
|
||||
data["bones"] = ReadNames(bones);
|
||||
data["target"] = bones[reader.ReadVarInt()]["name"].GetValue<string>();
|
||||
data["local"] = reader.ReadBoolean();
|
||||
data["relative"] = reader.ReadBoolean();
|
||||
data["rotation"] = reader.ReadFloat();
|
||||
data["x"] = reader.ReadFloat();
|
||||
data["y"] = reader.ReadFloat();
|
||||
data["scaleX"] = reader.ReadFloat();
|
||||
data["scaleY"] = reader.ReadFloat();
|
||||
data["shearY"] = reader.ReadFloat();
|
||||
data["rotateMix"] = reader.ReadFloat();
|
||||
data["translateMix"] = reader.ReadFloat();
|
||||
data["scaleMix"] = reader.ReadFloat();
|
||||
data["shearMix"] = reader.ReadFloat();
|
||||
transform.Add(data);
|
||||
}
|
||||
return transform;
|
||||
}
|
||||
|
||||
private JsonArray ReadPath()
|
||||
{
|
||||
JsonArray bones = root["bones"].AsArray();
|
||||
JsonArray path = [];
|
||||
for (int n = reader.ReadVarInt(); n > 0; n--)
|
||||
{
|
||||
JsonObject data = [];
|
||||
data["name"] = reader.ReadString();
|
||||
data["order"] = reader.ReadVarInt();
|
||||
data["skin"] = reader.ReadBoolean();
|
||||
data["bones"] = ReadNames(bones);
|
||||
data["target"] = bones[reader.ReadVarInt()]["name"].GetValue<string>();
|
||||
data["positionMode"] = ((PositionMode)reader.ReadVarInt()).ToString();
|
||||
data["spacingMode"] = ((SpacingMode)reader.ReadVarInt()).ToString();
|
||||
data["rotateMode"] = ((RotateMode)reader.ReadVarInt()).ToString();
|
||||
data["rotation"] = reader.ReadFloat();
|
||||
data["position"] = reader.ReadFloat();
|
||||
data["spacing"] = reader.ReadFloat();
|
||||
data["rotateMix"] = reader.ReadFloat();
|
||||
data["translateMix"] = reader.ReadFloat();
|
||||
path.Add(data);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private JsonArray ReadSkins()
|
||||
{
|
||||
JsonArray skins = [];
|
||||
|
||||
// default skin
|
||||
if (ReadSkin(true) is JsonObject data)
|
||||
skins.Add(data);
|
||||
|
||||
// other skins
|
||||
for (int n = reader.ReadVarInt(); n > 0; n--)
|
||||
skins.Add(ReadSkin());
|
||||
|
||||
return skins;
|
||||
}
|
||||
|
||||
private JsonObject? ReadSkin(bool isDefault = false)
|
||||
{
|
||||
JsonObject skin = [];
|
||||
int slotCount;
|
||||
if (isDefault)
|
||||
{
|
||||
// 这里固定有一个给 default 的 count 值, 算是占位符, 如果是 0 则表示没有 default 的 skin
|
||||
skin["name"] = "default";
|
||||
slotCount = reader.ReadVarInt();
|
||||
if (slotCount <= 0) return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
skin["name"] = reader.ReadStringRef();
|
||||
skin["bones"] = ReadNames(root["bones"].AsArray());
|
||||
skin["ik"] = ReadNames(root["ik"].AsArray());
|
||||
skin["transform"] = ReadNames(root["transform"].AsArray()); ;
|
||||
skin["path"] = ReadNames(root["path"].AsArray()); ;
|
||||
slotCount = reader.ReadVarInt();
|
||||
}
|
||||
|
||||
JsonArray slots = root["slots"].AsArray();
|
||||
JsonObject skinAttachments = [];
|
||||
while (slotCount-- > 0)
|
||||
{
|
||||
JsonObject slotAttachments = [];
|
||||
skinAttachments[slots[reader.ReadVarInt()]["name"].GetValue<string>()] = slotAttachments;
|
||||
for (int attachmentCount = reader.ReadVarInt(); attachmentCount > 0; attachmentCount--)
|
||||
{
|
||||
var attachmentKey = reader.ReadStringRef();
|
||||
slotAttachments[attachmentKey] = ReadAttachment(attachmentKey);
|
||||
}
|
||||
}
|
||||
skin["attachments"] = skinAttachments;
|
||||
|
||||
return skin;
|
||||
}
|
||||
|
||||
private JsonObject ReadAttachment(string keyName)
|
||||
{
|
||||
JsonArray slots = root["slots"].AsArray();
|
||||
JsonObject attachment = [];
|
||||
int vertexCount;
|
||||
string path;
|
||||
|
||||
string name = reader.ReadStringRef() ?? keyName;
|
||||
var type = (AttachmentType)reader.ReadByte();
|
||||
attachment["name"] = name;
|
||||
attachment["type"] = type.ToString();
|
||||
switch (type)
|
||||
{
|
||||
case AttachmentType.Region:
|
||||
path = reader.ReadStringRef();
|
||||
if (path is not null) attachment["path"] = path;
|
||||
attachment["rotation"] = reader.ReadFloat();
|
||||
attachment["x"] = reader.ReadFloat();
|
||||
attachment["y"] = reader.ReadFloat();
|
||||
attachment["scaleX"] = reader.ReadFloat();
|
||||
attachment["scaleY"] = reader.ReadFloat();
|
||||
attachment["width"] = reader.ReadFloat();
|
||||
attachment["height"] = reader.ReadFloat();
|
||||
attachment["color"] = reader.ReadInt().ToString("x8");
|
||||
break;
|
||||
case AttachmentType.Boundingbox:
|
||||
vertexCount = reader.ReadVarInt();
|
||||
attachment["vertexCount"] = vertexCount;
|
||||
attachment["vertices"] = ReadVertices(vertexCount);
|
||||
if (nonessential) reader.ReadInt();
|
||||
break;
|
||||
case AttachmentType.Mesh:
|
||||
path = reader.ReadStringRef();
|
||||
if (path is not null) attachment["path"] = path;
|
||||
attachment["color"] = reader.ReadInt().ToString("x8");
|
||||
vertexCount = reader.ReadVarInt();
|
||||
attachment["uvs"] = ReadFloatArray(vertexCount << 1); // vertexCount = uvs.Length
|
||||
attachment["triangles"] = ReadShortArray();
|
||||
attachment["vertices"] = ReadVertices(vertexCount);
|
||||
attachment["hull"] = reader.ReadVarInt();
|
||||
if (nonessential)
|
||||
{
|
||||
attachment["edges"] = ReadShortArray();
|
||||
attachment["width"] = reader.ReadFloat();
|
||||
attachment["height"] = reader.ReadFloat();
|
||||
}
|
||||
break;
|
||||
case AttachmentType.Linkedmesh:
|
||||
path = reader.ReadStringRef();
|
||||
if (path is not null) attachment["path"] = path;
|
||||
attachment["color"] = reader.ReadInt().ToString("x8");
|
||||
attachment["skin"] = reader.ReadStringRef();
|
||||
attachment["parent"] = reader.ReadStringRef();
|
||||
attachment["deform"] = reader.ReadBoolean();
|
||||
if (nonessential)
|
||||
{
|
||||
attachment["width"] = reader.ReadFloat();
|
||||
attachment["height"] = reader.ReadFloat();
|
||||
}
|
||||
// 补充缺失的必需 key
|
||||
attachment["uvs"] = new JsonArray();
|
||||
attachment["triangles"] = new JsonArray();
|
||||
attachment["vertices"] = new JsonArray();
|
||||
break;
|
||||
case AttachmentType.Path:
|
||||
attachment["closed"] = reader.ReadBoolean();
|
||||
attachment["constantSpeed"] = reader.ReadBoolean();
|
||||
vertexCount = reader.ReadVarInt();
|
||||
attachment["vertexCount"] = vertexCount;
|
||||
attachment["vertices"] = ReadVertices(vertexCount);
|
||||
attachment["lengths"] = ReadFloatArray(vertexCount / 3);
|
||||
if (nonessential) reader.ReadInt();
|
||||
break;
|
||||
case AttachmentType.Point:
|
||||
attachment["rotation"] = reader.ReadFloat();
|
||||
attachment["x"] = reader.ReadFloat();
|
||||
attachment["y"] = reader.ReadFloat();
|
||||
if (nonessential) reader.ReadInt();
|
||||
break;
|
||||
case AttachmentType.Clipping:
|
||||
attachment["end"] = slots[reader.ReadVarInt()]["name"].GetValue<string>();
|
||||
vertexCount = reader.ReadVarInt();
|
||||
attachment["vertexCount"] = vertexCount;
|
||||
attachment["vertices"] = ReadVertices(vertexCount);
|
||||
if (nonessential) reader.ReadInt();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid attachment type: {type}");
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
|
||||
private JsonObject ReadEvents()
|
||||
{
|
||||
idx2Event.Clear();
|
||||
JsonObject events = [];
|
||||
for (int n = reader.ReadVarInt(); n > 0; n--)
|
||||
{
|
||||
JsonObject data = [];
|
||||
var name = reader.ReadStringRef();
|
||||
events[name] = data;
|
||||
data["name"] = name; // 额外增加的, 方便后面查找
|
||||
data["int"] = reader.ReadVarInt(false);
|
||||
data["float"] = reader.ReadFloat();
|
||||
data["string"] = reader.ReadString();
|
||||
if (reader.ReadString() is string audio)
|
||||
{
|
||||
data["audio"] = audio;
|
||||
data["volume"] = reader.ReadFloat();
|
||||
data["balance"] = reader.ReadFloat();
|
||||
}
|
||||
idx2Event.Add(data);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private JsonObject ReadAnimations()
|
||||
{
|
||||
JsonObject animations = [];
|
||||
for (int n = reader.ReadVarInt(); n > 0; n--)
|
||||
{
|
||||
JsonObject data = [];
|
||||
animations[reader.ReadString()] = data;
|
||||
if (ReadSlotTimelines() is JsonObject slots) data["slots"] = slots;
|
||||
if (ReadBoneTimelines() is JsonObject bones) data["bones"] = bones;
|
||||
if (ReadIKTimelines() is JsonObject ik) data["ik"] = ik;
|
||||
if (ReadTransformTimelines() is JsonObject transform) data["transform"] = transform;
|
||||
if (ReadPathTimelines() is JsonObject path) data["path"] = path;
|
||||
if (ReadDeformTimelines() is JsonObject deform) data["deform"] = deform;
|
||||
if (ReadDrawOrderTimelines() is JsonArray draworder) data["drawOrder"] = draworder;
|
||||
if (ReadEventTimelines() is JsonArray events) data["events"] = events;
|
||||
}
|
||||
return animations;
|
||||
}
|
||||
|
||||
private JsonObject? ReadSlotTimelines()
|
||||
{
|
||||
JsonArray slots = root["slots"].AsArray();
|
||||
JsonObject slotTimelines = [];
|
||||
|
||||
for (int slotCount = reader.ReadVarInt(); slotCount > 0; slotCount--)
|
||||
{
|
||||
JsonObject timeline = [];
|
||||
slotTimelines[slots[reader.ReadVarInt()]["name"].GetValue<string>()] = timeline;
|
||||
for (int timelineCount = reader.ReadVarInt(); timelineCount > 0; timelineCount--)
|
||||
{
|
||||
JsonArray frames = [];
|
||||
var type = reader.ReadByte();
|
||||
var frameCount = reader.ReadVarInt();
|
||||
switch (type)
|
||||
{
|
||||
case SkeletonBinary.SLOT_ATTACHMENT:
|
||||
timeline["attachment"] = frames;
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
frames.Add(new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["name"] = reader.ReadStringRef(),
|
||||
});
|
||||
}
|
||||
break;
|
||||
case SkeletonBinary.SLOT_COLOR:
|
||||
timeline["color"] = frames;
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["color"] = reader.ReadInt().ToString("x8"),
|
||||
};
|
||||
if (frameCount > 0) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
break;
|
||||
case SkeletonBinary.SLOT_TWO_COLOR:
|
||||
timeline["twoColor"] = frames;
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["light"] = reader.ReadInt().ToString("x8"),
|
||||
["dark"] = reader.ReadInt().ToString("x6"),
|
||||
};
|
||||
if (frameCount > 0) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid slot timeline type: {type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return slotTimelines.Count > 0 ? slotTimelines : null;
|
||||
}
|
||||
|
||||
private JsonObject? ReadBoneTimelines()
|
||||
{
|
||||
JsonArray bones = root["bones"].AsArray();
|
||||
JsonObject boneTimelines = [];
|
||||
|
||||
for (int boneCount = reader.ReadVarInt(); boneCount > 0; boneCount--)
|
||||
{
|
||||
JsonObject timeline = [];
|
||||
boneTimelines[bones[reader.ReadVarInt()]["name"].GetValue<string>()] = timeline;
|
||||
for (int timelineCount = reader.ReadVarInt(); timelineCount > 0; timelineCount--)
|
||||
{
|
||||
JsonArray frames = [];
|
||||
var type = reader.ReadByte();
|
||||
var frameCount = reader.ReadVarInt();
|
||||
switch (type)
|
||||
{
|
||||
case SkeletonBinary.BONE_ROTATE:
|
||||
timeline["rotate"] = frames;
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["angle"] = reader.ReadFloat(),
|
||||
};
|
||||
if (frameCount > 0) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
break;
|
||||
case SkeletonBinary.BONE_TRANSLATE:
|
||||
timeline["translate"] = frames;
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["x"] = reader.ReadFloat(),
|
||||
["y"] = reader.ReadFloat(),
|
||||
};
|
||||
if (frameCount > 0) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
break;
|
||||
case SkeletonBinary.BONE_SCALE:
|
||||
timeline["scale"] = frames;
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["x"] = reader.ReadFloat(),
|
||||
["y"] = reader.ReadFloat(),
|
||||
};
|
||||
if (frameCount > 0) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
break;
|
||||
case SkeletonBinary.BONE_SHEAR:
|
||||
timeline["shear"] = frames;
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["x"] = reader.ReadFloat(),
|
||||
["y"] = reader.ReadFloat(),
|
||||
};
|
||||
if (frameCount > 0) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid bone timeline type: {type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return boneTimelines.Count > 0 ? boneTimelines : null;
|
||||
}
|
||||
|
||||
private JsonObject? ReadIKTimelines()
|
||||
{
|
||||
JsonArray ik = root["ik"].AsArray();
|
||||
JsonObject ikTimelines = [];
|
||||
|
||||
for (int ikCount = reader.ReadVarInt(); ikCount > 0; ikCount--)
|
||||
{
|
||||
JsonArray frames = [];
|
||||
ikTimelines[ik[reader.ReadVarInt()]["name"].GetValue<string>()] = frames;
|
||||
for (int frameCount = reader.ReadVarInt(); frameCount > 0; frameCount--)
|
||||
{
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["mix"] = reader.ReadFloat(),
|
||||
["softness"] = reader.ReadFloat(),
|
||||
["bendPositive"] = reader.ReadSByte() > 0,
|
||||
["compress"] = reader.ReadBoolean(),
|
||||
["stretch"] = reader.ReadBoolean(),
|
||||
};
|
||||
if (frameCount > 1) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
}
|
||||
|
||||
return ikTimelines.Count > 0 ? ikTimelines : null;
|
||||
}
|
||||
|
||||
private JsonObject? ReadTransformTimelines()
|
||||
{
|
||||
JsonArray transform = root["transform"].AsArray();
|
||||
JsonObject transformTimelines = [];
|
||||
|
||||
for (int transformCount = reader.ReadVarInt(); transformCount > 0; transformCount--)
|
||||
{
|
||||
JsonArray frames = [];
|
||||
transformTimelines[transform[reader.ReadVarInt()]["name"].GetValue<string>()] = frames;
|
||||
for (int frameCount = reader.ReadVarInt(); frameCount > 0; frameCount--)
|
||||
{
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["rotateMix"] = reader.ReadFloat(),
|
||||
["translateMix"] = reader.ReadFloat(),
|
||||
["scaleMix"] = reader.ReadFloat(),
|
||||
["shearMix"] = reader.ReadFloat(),
|
||||
};
|
||||
if (frameCount > 1) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
}
|
||||
|
||||
return transformTimelines.Count > 0 ? transformTimelines : null;
|
||||
}
|
||||
|
||||
private JsonObject? ReadPathTimelines()
|
||||
{
|
||||
JsonArray path = root["path"].AsArray();
|
||||
JsonObject pathTimelines = [];
|
||||
|
||||
for (int pathCount = reader.ReadVarInt(); pathCount > 0; pathCount--)
|
||||
{
|
||||
JsonObject timeline = [];
|
||||
pathTimelines[path[reader.ReadVarInt()]["name"].GetValue<string>()] = timeline;
|
||||
for (int timelineCount = reader.ReadVarInt(); timelineCount > 0; timelineCount--)
|
||||
{
|
||||
JsonArray frames = [];
|
||||
var type = reader.ReadByte();
|
||||
var frameCount = reader.ReadVarInt();
|
||||
switch (type)
|
||||
{
|
||||
case SkeletonBinary.PATH_POSITION:
|
||||
timeline["position"] = frames;
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
frames.Add(new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["position"] = reader.ReadFloat(),
|
||||
});
|
||||
}
|
||||
break;
|
||||
case SkeletonBinary.PATH_SPACING:
|
||||
timeline["spacing"] = frames;
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["spacing"] = reader.ReadFloat(),
|
||||
};
|
||||
if (frameCount > 0) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
break;
|
||||
case SkeletonBinary.PATH_MIX:
|
||||
timeline["mix"] = frames;
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["rotateMix"] = reader.ReadFloat(),
|
||||
["translateMix"] = reader.ReadFloat(),
|
||||
};
|
||||
if (frameCount > 0) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid path timeline type: {type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pathTimelines.Count > 0 ? pathTimelines : null;
|
||||
}
|
||||
|
||||
private JsonObject? ReadDeformTimelines()
|
||||
{
|
||||
JsonArray slots = root["slots"].AsArray();
|
||||
JsonArray skins = root["skins"].AsArray();
|
||||
JsonObject deformTimelines = [];
|
||||
|
||||
//for (int skinCount = reader.ReadVarInt(); skinCount > 0; skinCount--)
|
||||
for (int i = 0, n = reader.ReadVarInt(); i < n; i++)
|
||||
{
|
||||
JsonObject skinValue = [];
|
||||
deformTimelines[skins[reader.ReadVarInt()]["name"].GetValue<string>()] = skinValue;
|
||||
//for (int slotCount = reader.ReadVarInt(); slotCount > 0; slotCount--)
|
||||
for (int ii = 0, nn = reader.ReadVarInt(); ii < nn; ii++)
|
||||
{
|
||||
JsonObject slotValue = [];
|
||||
skinValue[slots[reader.ReadVarInt()]["name"].GetValue<string>()] = slotValue;
|
||||
//for (int attachmentCount = reader.ReadVarInt(); attachmentCount > 0; attachmentCount--)
|
||||
for (int iii = 0, nnn = reader.ReadVarInt(); iii < nnn; iii++)
|
||||
{
|
||||
JsonArray frames = [];
|
||||
slotValue[reader.ReadStringRef()] = frames;
|
||||
var frameCount = reader.ReadVarInt();
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
};
|
||||
var end = reader.ReadVarInt();
|
||||
if (end > 0)
|
||||
{
|
||||
var start = reader.ReadVarInt();
|
||||
o["offset"] = start;
|
||||
o["vertices"] = ReadFloatArray(end);
|
||||
}
|
||||
if (frameCount > 0) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return deformTimelines.Count > 0 ? deformTimelines : null;
|
||||
}
|
||||
|
||||
private JsonArray? ReadDrawOrderTimelines()
|
||||
{
|
||||
JsonArray slots = root["slots"].AsArray();
|
||||
JsonArray drawOrderTimelines = [];
|
||||
|
||||
for (int drawOrderCount = reader.ReadVarInt(); drawOrderCount > 0; drawOrderCount--)
|
||||
{
|
||||
JsonObject data = new()
|
||||
{
|
||||
["time"] = reader.ReadFloat()
|
||||
};
|
||||
JsonArray offsets = [];
|
||||
data["offsets"] = offsets;
|
||||
for (int offsetCount = reader.ReadVarInt(); offsetCount > 0; offsetCount--)
|
||||
{
|
||||
offsets.Add(new JsonObject()
|
||||
{
|
||||
["slot"] = slots[reader.ReadVarInt()]["name"].GetValue<string>(),
|
||||
["offset"] = reader.ReadVarInt(),
|
||||
});
|
||||
}
|
||||
drawOrderTimelines.Add(data);
|
||||
}
|
||||
|
||||
return drawOrderTimelines.Count > 0 ? drawOrderTimelines : null;
|
||||
}
|
||||
|
||||
private JsonArray? ReadEventTimelines()
|
||||
{
|
||||
JsonArray eventTimelines = [];
|
||||
for (int eventCount = reader.ReadVarInt(); eventCount > 0; eventCount--)
|
||||
{
|
||||
JsonObject data = [];
|
||||
data["time"] = reader.ReadFloat();
|
||||
JsonObject eventData = idx2Event[reader.ReadVarInt()].AsObject();
|
||||
data["name"] = eventData["name"].GetValue<string>();
|
||||
data["int"] = reader.ReadVarInt();
|
||||
data["float"] = reader.ReadFloat();
|
||||
data["string"] = reader.ReadBoolean() ? reader.ReadString() : eventData["string"].GetValue<string>();
|
||||
if (eventData.ContainsKey("audio"))
|
||||
{
|
||||
data["volume"] = eventData["volume"].GetValue<string>();
|
||||
data["balance"] = eventData["balance"].GetValue<string>();
|
||||
}
|
||||
eventTimelines.Add(data);
|
||||
}
|
||||
|
||||
return eventTimelines.Count > 0 ? eventTimelines : null;
|
||||
}
|
||||
|
||||
private JsonArray ReadNames(JsonArray array)
|
||||
{
|
||||
JsonArray names = [];
|
||||
for (int n = reader.ReadVarInt(); n > 0; n--)
|
||||
names.Add(array[reader.ReadVarInt()]["name"].GetValue<string>());
|
||||
return names;
|
||||
}
|
||||
|
||||
private JsonArray ReadFloatArray(int n)
|
||||
{
|
||||
JsonArray array = [];
|
||||
while (n-- > 0)
|
||||
array.Add(reader.ReadFloat());
|
||||
return array;
|
||||
}
|
||||
|
||||
private JsonArray ReadShortArray()
|
||||
{
|
||||
JsonArray array = [];
|
||||
for (int n = reader.ReadVarInt(); n > 0; n--)
|
||||
array.Add((reader.ReadByte() << 8) | reader.ReadByte());
|
||||
return array;
|
||||
}
|
||||
|
||||
private JsonArray ReadVertices(int vertexCount)
|
||||
{
|
||||
JsonArray vertices = [];
|
||||
if (!reader.ReadBoolean())
|
||||
return ReadFloatArray(vertexCount << 1);
|
||||
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
int bonesCount = reader.ReadVarInt();
|
||||
vertices.Add(bonesCount);
|
||||
for (int j = 0; j < bonesCount; j++)
|
||||
{
|
||||
vertices.Add(reader.ReadVarInt());
|
||||
vertices.Add(reader.ReadFloat());
|
||||
vertices.Add(reader.ReadFloat());
|
||||
vertices.Add(reader.ReadFloat());
|
||||
}
|
||||
}
|
||||
return vertices;
|
||||
}
|
||||
|
||||
private void ReadCurve(JsonObject obj)
|
||||
{
|
||||
var type = reader.ReadByte();
|
||||
switch (type)
|
||||
{
|
||||
case SkeletonBinary.CURVE_LINEAR:
|
||||
obj["curve"] = 1 / 3f;
|
||||
obj["c2"] = 1 / 3f;
|
||||
obj["c3"] = 2 / 3f;
|
||||
obj["c4"] = 2 / 3f;
|
||||
break;
|
||||
case SkeletonBinary.CURVE_STEPPED:
|
||||
obj["curve"] = "stepped";
|
||||
break;
|
||||
case SkeletonBinary.CURVE_BEZIER:
|
||||
obj["curve"] = reader.ReadFloat();
|
||||
obj["c2"] = reader.ReadFloat();
|
||||
obj["c3"] = reader.ReadFloat();
|
||||
obj["c4"] = reader.ReadFloat();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid curve type: {type}"); ;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WriteBinary(JsonObject root, string binPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
//public void WriteFloatArray(float[] array)
|
||||
//{
|
||||
// foreach (var i in array)
|
||||
// writer.WriteFloat(i);
|
||||
//}
|
||||
|
||||
//public void WriteShortArray(int[] array)
|
||||
//{
|
||||
// foreach (var i in array)
|
||||
// {
|
||||
// writer.WriteByte((byte)(i >> 8));
|
||||
// writer.WriteByte((byte)i);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime21;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V21)]
|
||||
internal class Spine21 : Spine
|
||||
internal class Spine21 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private class TextureLoader : SpineRuntime21.TextureLoader
|
||||
{
|
||||
@@ -88,6 +88,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -234,14 +236,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
//private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime21.BlendMode spineBlendMode)
|
||||
//private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
//{
|
||||
// return spineBlendMode switch
|
||||
// {
|
||||
// SpineRuntime21.BlendMode.Normal => BlendMode.Normal,
|
||||
// SpineRuntime21.BlendMode.Additive => BlendMode.Additive,
|
||||
// SpineRuntime21.BlendMode.Multiply => BlendMode.Multiply,
|
||||
// SpineRuntime21.BlendMode.Screen => BlendMode.Screen,
|
||||
// BlendMode.Normal => BlendMode.Normal,
|
||||
// BlendMode.Additive => BlendMode.Additive,
|
||||
// BlendMode.Multiply => BlendMode.Multiply,
|
||||
// BlendMode.Screen => BlendMode.Screen,
|
||||
// _ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
// };
|
||||
//}
|
||||
@@ -311,14 +313,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
}
|
||||
|
||||
// 似乎 2.1.x 也没有 BlendMode
|
||||
SFML.Graphics.BlendMode blendMode = slot.Data.AdditiveBlending ? BlendMode.Additive : BlendMode.Normal;
|
||||
SFML.Graphics.BlendMode blendMode = slot.Data.AdditiveBlending ? BlendModeSFML.Additive : BlendModeSFML.Normal;
|
||||
|
||||
states.Texture ??= texture;
|
||||
if (states.BlendMode != blendMode || states.Texture != texture)
|
||||
{
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -363,7 +365,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
//clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -8,10 +8,10 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime36;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V36)]
|
||||
internal class Spine36 : Spine
|
||||
internal class Spine36 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private class TextureLoader : SpineRuntime36.TextureLoader
|
||||
{
|
||||
@@ -87,6 +87,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -192,14 +194,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime36.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(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,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -274,7 +276,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
{
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -319,7 +321,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -5,10 +5,10 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime37;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V37)]
|
||||
internal class Spine37 : Spine
|
||||
internal class Spine37 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private class TextureLoader : SpineRuntime37.TextureLoader
|
||||
{
|
||||
@@ -86,6 +86,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -199,14 +201,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime37.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
SpineRuntime37.BlendMode.Normal => BlendMode.Normal,
|
||||
SpineRuntime37.BlendMode.Additive => BlendMode.Additive,
|
||||
SpineRuntime37.BlendMode.Multiply => BlendMode.Multiply,
|
||||
SpineRuntime37.BlendMode.Screen => BlendMode.Screen,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -282,7 +284,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -327,7 +329,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -7,12 +7,11 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime38;
|
||||
using SpineRuntime38.Attachments;
|
||||
using SpineViewer.Spine;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V38)]
|
||||
internal class Spine38 : Spine
|
||||
internal class Spine38 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private class TextureLoader : SpineRuntime38.TextureLoader
|
||||
{
|
||||
@@ -90,6 +89,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -203,14 +204,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime38.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(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,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -286,7 +287,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -331,7 +332,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -7,10 +7,10 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime40;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V40)]
|
||||
internal class Spine40 : Spine
|
||||
internal class Spine40 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private class TextureLoader : SpineRuntime40.TextureLoader
|
||||
{
|
||||
@@ -88,6 +88,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -201,14 +203,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime40.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
SpineRuntime40.BlendMode.Normal => BlendMode.Normal,
|
||||
SpineRuntime40.BlendMode.Additive => BlendMode.Additive,
|
||||
SpineRuntime40.BlendMode.Multiply => BlendMode.Multiply,
|
||||
SpineRuntime40.BlendMode.Screen => BlendMode.Screen,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -284,7 +286,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -329,7 +331,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -7,10 +7,10 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime41;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V41)]
|
||||
internal class Spine41 : Spine
|
||||
internal class Spine41 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private class TextureLoader : SpineRuntime41.TextureLoader
|
||||
{
|
||||
@@ -88,6 +88,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -201,14 +203,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime41.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
SpineRuntime41.BlendMode.Normal => BlendMode.Normal,
|
||||
SpineRuntime41.BlendMode.Additive => BlendMode.Additive,
|
||||
SpineRuntime41.BlendMode.Multiply => BlendMode.Multiply,
|
||||
SpineRuntime41.BlendMode.Screen => BlendMode.Screen,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -284,7 +286,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -329,7 +331,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -7,10 +7,10 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime42;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V42)]
|
||||
internal class Spine42 : Spine
|
||||
internal class Spine42 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private class TextureLoader : SpineRuntime42.TextureLoader
|
||||
{
|
||||
@@ -88,6 +88,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -201,14 +203,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime42.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
SpineRuntime42.BlendMode.Normal => BlendMode.Normal,
|
||||
SpineRuntime42.BlendMode.Additive => BlendMode.Additive,
|
||||
SpineRuntime42.BlendMode.Multiply => BlendMode.Multiply,
|
||||
SpineRuntime42.BlendMode.Screen => BlendMode.Screen,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -284,7 +286,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -329,7 +331,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
344
SpineViewer/Spine/SkeletonConverter.cs
Normal file
344
SpineViewer/Spine/SkeletonConverter.cs
Normal file
@@ -0,0 +1,344 @@
|
||||
using Microsoft.VisualBasic;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace SpineViewer.Spine
|
||||
{
|
||||
/// <summary>
|
||||
/// SkeletonConverter 实现类标记
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class SkeletonConverterImplementationAttribute : Attribute
|
||||
{
|
||||
public Version Version { get; }
|
||||
|
||||
public SkeletonConverterImplementationAttribute(Version version)
|
||||
{
|
||||
Version = version;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SkeletonConverter 基类, 使用静态方法 New 来创建具体版本对象
|
||||
/// </summary>
|
||||
public abstract class SkeletonConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 实现类缓存
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Version, Type> ImplementationTypes = [];
|
||||
public static readonly Dictionary<Version, Type>.KeyCollection ImplementedVersions;
|
||||
|
||||
/// <summary>
|
||||
/// 静态构造函数
|
||||
/// </summary>
|
||||
static SkeletonConverter()
|
||||
{
|
||||
// 遍历并缓存标记了 SkeletonConverterImplementationAttribute 的类型
|
||||
var impTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => typeof(SkeletonConverter).IsAssignableFrom(t) && !t.IsAbstract);
|
||||
foreach (var type in impTypes)
|
||||
{
|
||||
var attr = type.GetCustomAttribute<SkeletonConverterImplementationAttribute>();
|
||||
if (attr is not null)
|
||||
{
|
||||
if (ImplementationTypes.ContainsKey(attr.Version))
|
||||
throw new InvalidOperationException($"Multiple implementations found: {attr.Version}");
|
||||
ImplementationTypes[attr.Version] = type;
|
||||
}
|
||||
}
|
||||
Program.Logger.Debug("Find SkeletonConverter implementations: [{}]", string.Join(", ", ImplementationTypes.Keys));
|
||||
ImplementedVersions = ImplementationTypes.Keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建特定版本的 SkeletonConverter
|
||||
/// </summary>
|
||||
public static SkeletonConverter New(Version version)
|
||||
{
|
||||
if (!ImplementationTypes.TryGetValue(version, out var cvterType))
|
||||
{
|
||||
throw new NotImplementedException($"Not implemented version: {version}");
|
||||
}
|
||||
return (SkeletonConverter)Activator.CreateInstance(cvterType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Json 格式控制
|
||||
/// </summary>
|
||||
private static readonly JsonWriterOptions jsonWriterOptions = new()
|
||||
{
|
||||
Indented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 读取二进制骨骼文件并构造 Json 对象
|
||||
/// </summary>
|
||||
protected abstract JsonObject ReadBinary(string binPath);
|
||||
|
||||
/// <summary>
|
||||
/// 将 Json 对象写入二进制骨骼文件
|
||||
/// </summary>
|
||||
protected abstract void WriteBinary(JsonObject root, string binPath);
|
||||
|
||||
/// <summary>
|
||||
/// 读取 Json 对象
|
||||
/// </summary>
|
||||
private JsonObject ReadJson(string jsonPath)
|
||||
{
|
||||
using var input = File.OpenRead(jsonPath);
|
||||
if (JsonNode.Parse(input) is JsonObject root)
|
||||
return root;
|
||||
else
|
||||
throw new InvalidOperationException($"{jsonPath} is not a valid json object");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入 Json 对象
|
||||
/// </summary>
|
||||
private void WriteJson(JsonObject root, string jsonPath)
|
||||
{
|
||||
using var output = File.Create(jsonPath);
|
||||
using var writer = new Utf8JsonWriter(output, jsonWriterOptions);
|
||||
root.WriteTo(writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 二进制转 Json 格式
|
||||
/// </summary>
|
||||
public void BinaryToJson(string binPath, string jsonPath)
|
||||
{
|
||||
WriteJson(ReadBinary(binPath), jsonPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Json 转二进制格式
|
||||
/// </summary>
|
||||
public void JsonToBinary(string jsonPath, string binPath)
|
||||
{
|
||||
WriteBinary(ReadJson(jsonPath), binPath);
|
||||
}
|
||||
|
||||
protected class SkeletonReader
|
||||
{
|
||||
protected byte[] buffer = new byte[32];
|
||||
protected byte[] bytesBigEndian = new byte[8];
|
||||
public readonly List<string> StringTable = new(32);
|
||||
protected Stream input;
|
||||
|
||||
public SkeletonReader(Stream input) { this.input = input; }
|
||||
public int Read()
|
||||
{
|
||||
int val = input.ReadByte();
|
||||
if (val == -1) throw new EndOfStreamException();
|
||||
return val;
|
||||
}
|
||||
public byte ReadByte() => (byte)Read();
|
||||
public byte ReadUByte() => (byte)Read();
|
||||
public sbyte ReadSByte() => (sbyte)ReadByte();
|
||||
public bool ReadBoolean() => Read() != 0;
|
||||
public float ReadFloat()
|
||||
{
|
||||
if (input.Read(bytesBigEndian, 0, 4) < 4) throw new EndOfStreamException();
|
||||
buffer[3] = bytesBigEndian[0];
|
||||
buffer[2] = bytesBigEndian[1];
|
||||
buffer[1] = bytesBigEndian[2];
|
||||
buffer[0] = bytesBigEndian[3];
|
||||
return BitConverter.ToSingle(buffer, 0);
|
||||
}
|
||||
public int ReadInt()
|
||||
{
|
||||
if (input.Read(bytesBigEndian, 0, 4) < 4) throw new EndOfStreamException();
|
||||
return (bytesBigEndian[0] << 24)
|
||||
| (bytesBigEndian[1] << 16)
|
||||
| (bytesBigEndian[2] << 8)
|
||||
| bytesBigEndian[3];
|
||||
}
|
||||
public long ReadLong()
|
||||
{
|
||||
if (input.Read(bytesBigEndian, 0, 8) < 8) throw new EndOfStreamException();
|
||||
return ((long)(bytesBigEndian[0]) << 56)
|
||||
| ((long)(bytesBigEndian[1]) << 48)
|
||||
| ((long)(bytesBigEndian[2]) << 40)
|
||||
| ((long)(bytesBigEndian[3]) << 32)
|
||||
| ((long)(bytesBigEndian[4]) << 24)
|
||||
| ((long)(bytesBigEndian[5]) << 16)
|
||||
| ((long)(bytesBigEndian[6]) << 8)
|
||||
| (long)(bytesBigEndian[7]);
|
||||
}
|
||||
public int ReadVarInt(bool optimizePositive = true)
|
||||
{
|
||||
byte b = ReadByte();
|
||||
int val = b & 0x7F;
|
||||
if ((b & 0x80) != 0)
|
||||
{
|
||||
b = ReadByte();
|
||||
val |= (b & 0x7F) << 7;
|
||||
if ((b & 0x80) != 0)
|
||||
{
|
||||
b = ReadByte();
|
||||
val |= (b & 0x7F) << 14;
|
||||
if ((b & 0x80) != 0)
|
||||
{
|
||||
b = ReadByte();
|
||||
val |= (b & 0x7F) << 21;
|
||||
if ((b & 0x80) != 0)
|
||||
val |= (ReadByte() & 0x7F) << 28;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最低位是符号, 根据符号得到全 1 或全 0
|
||||
// 无符号右移, 符号按原样设置在最高位, 其他位与符号异或
|
||||
return optimizePositive ? val : (val >>> 1) ^ -(val & 1);
|
||||
}
|
||||
public string ReadString()
|
||||
{
|
||||
int byteCount = ReadVarInt();
|
||||
switch (byteCount)
|
||||
{
|
||||
case 0: return null;
|
||||
case 1: return "";
|
||||
}
|
||||
byteCount--;
|
||||
if (buffer.Length < byteCount) buffer = new byte[byteCount];
|
||||
ReadFully(buffer, 0, byteCount);
|
||||
return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
|
||||
}
|
||||
public string ReadStringRef()
|
||||
{
|
||||
int index = ReadVarInt();
|
||||
return index == 0 ? null : StringTable[index - 1];
|
||||
}
|
||||
public void ReadFully(byte[] buffer, int offset, int length)
|
||||
{
|
||||
while (length > 0)
|
||||
{
|
||||
int count = input.Read(buffer, offset, length);
|
||||
if (count <= 0) throw new EndOfStreamException();
|
||||
offset += count;
|
||||
length -= count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected class SkeletonWriter
|
||||
{
|
||||
protected byte[] buffer = new byte[32];
|
||||
protected byte[] bytesBigEndian = new byte[8];
|
||||
public readonly List<string> Strings = new(32);
|
||||
protected Stream output;
|
||||
|
||||
public SkeletonWriter(Stream output) { this.output = output; }
|
||||
public void Write(int val) => output.WriteByte((byte)val);
|
||||
public void WriteByte(byte val) => output.WriteByte(val);
|
||||
public void WriteUByte(byte val) => output.WriteByte(val);
|
||||
public void WriteSByte(sbyte val) => output.WriteByte((byte)val);
|
||||
public void WriteBoolean(bool val) => output.WriteByte((byte)(val ? 1 : 0));
|
||||
public void WriteFloat(float val)
|
||||
{
|
||||
uint v = BitConverter.SingleToUInt32Bits(val);
|
||||
bytesBigEndian[0] = (byte)(v >> 24);
|
||||
bytesBigEndian[1] = (byte)(v >> 16);
|
||||
bytesBigEndian[2] = (byte)(v >> 8);
|
||||
bytesBigEndian[3] = (byte)v;
|
||||
output.Write(bytesBigEndian, 0, 4);
|
||||
}
|
||||
public void WriteInt(int val)
|
||||
{
|
||||
bytesBigEndian[0] = (byte)(val >> 24);
|
||||
bytesBigEndian[1] = (byte)(val >> 16);
|
||||
bytesBigEndian[2] = (byte)(val >> 8);
|
||||
bytesBigEndian[3] = (byte)val;
|
||||
output.Write(bytesBigEndian, 0, 4);
|
||||
}
|
||||
public void WriteLong(long val)
|
||||
{
|
||||
bytesBigEndian[0] = (byte)(val >> 56);
|
||||
bytesBigEndian[1] = (byte)(val >> 48);
|
||||
bytesBigEndian[2] = (byte)(val >> 40);
|
||||
bytesBigEndian[3] = (byte)(val >> 32);
|
||||
bytesBigEndian[4] = (byte)(val >> 24);
|
||||
bytesBigEndian[5] = (byte)(val >> 16);
|
||||
bytesBigEndian[6] = (byte)(val >> 8);
|
||||
bytesBigEndian[7] = (byte)val;
|
||||
output.Write(bytesBigEndian, 0, 8);
|
||||
}
|
||||
public void WriteVarInt(int val, bool optimizePositive = true)
|
||||
{
|
||||
// 有符号右移, 会变成全 1 或者全 0 符号
|
||||
// 其他位与符号异或, 符号按原样设置在最低位
|
||||
if (!optimizePositive) val = (val << 1) ^ (val >> 31);
|
||||
|
||||
byte b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output.WriteByte(b);
|
||||
}
|
||||
public void WriteString(string val)
|
||||
{
|
||||
if (val == null)
|
||||
{
|
||||
WriteVarInt(0);
|
||||
return;
|
||||
}
|
||||
if (val.Length == 0)
|
||||
{
|
||||
WriteVarInt(1);
|
||||
return;
|
||||
}
|
||||
int byteCount = System.Text.Encoding.UTF8.GetByteCount(val);
|
||||
WriteVarInt(byteCount + 1);
|
||||
if (buffer.Length < byteCount) buffer = new byte[byteCount];
|
||||
System.Text.Encoding.UTF8.GetBytes(val, 0, val.Length, buffer, 0);
|
||||
WriteFully(buffer, 0, byteCount);
|
||||
}
|
||||
public void WriteStringRef(List<string> strings, string val)
|
||||
{
|
||||
if (val is null)
|
||||
{
|
||||
WriteVarInt(0);
|
||||
return;
|
||||
}
|
||||
int index = strings.IndexOf(val);
|
||||
if (index < 0)
|
||||
{
|
||||
strings.Add(val);
|
||||
index = strings.Count - 1;
|
||||
}
|
||||
WriteVarInt(index + 1);
|
||||
}
|
||||
public void WriteFully(byte[] buffer, int offset, int length) => output.Write(buffer, offset, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,6 @@ using System.Globalization;
|
||||
|
||||
namespace SpineViewer.Spine
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Spine 实现类标记
|
||||
/// </summary>
|
||||
@@ -42,6 +40,7 @@ namespace SpineViewer.Spine
|
||||
/// 实现类缓存
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Version, Type> ImplementationTypes = [];
|
||||
public static readonly Dictionary<Version, Type>.KeyCollection ImplementedVersions;
|
||||
|
||||
/// <summary>
|
||||
/// 用于解决 PMA 和渐变动画问题的片段着色器
|
||||
@@ -70,10 +69,13 @@ namespace SpineViewer.Spine
|
||||
var attr = type.GetCustomAttribute<SpineImplementationAttribute>();
|
||||
if (attr is not null)
|
||||
{
|
||||
if (ImplementationTypes.ContainsKey(attr.Version))
|
||||
throw new InvalidOperationException($"Multiple implementations found: {attr.Version}");
|
||||
ImplementationTypes[attr.Version] = type;
|
||||
}
|
||||
}
|
||||
Program.Logger.Debug("Find Spine implementations: [{}]", string.Join(", ", ImplementationTypes.Keys));
|
||||
ImplementedVersions = ImplementationTypes.Keys;
|
||||
|
||||
// 加载 FragmentShader
|
||||
try
|
||||
@@ -136,8 +138,8 @@ namespace SpineViewer.Spine
|
||||
/// <summary>
|
||||
/// 获取所属版本
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(VersionTypeConverter))]
|
||||
[Category("基本信息"), DisplayName("版本")]
|
||||
[TypeConverter(typeof(VersionConverter))]
|
||||
[Category("基本信息"), DisplayName("运行时版本")]
|
||||
public Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -155,6 +157,12 @@ namespace SpineViewer.Spine
|
||||
[Category("基本信息"), DisplayName("名称")]
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取所属文件版本
|
||||
/// </summary>
|
||||
[Category("基本信息"), DisplayName("文件版本")]
|
||||
public abstract string FileVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 缩放比例
|
||||
/// </summary>
|
||||
@@ -164,7 +172,7 @@ namespace SpineViewer.Spine
|
||||
/// <summary>
|
||||
/// 位置
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(PointFTypeConverter))]
|
||||
[TypeConverter(typeof(PointFConverter))]
|
||||
[Category("变换"), DisplayName("位置")]
|
||||
public abstract PointF Position { get; set; }
|
||||
|
||||
@@ -202,7 +210,7 @@ namespace SpineViewer.Spine
|
||||
/// <summary>
|
||||
/// 当前动画名称
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(AnimationTypeConverter))]
|
||||
[TypeConverter(typeof(AnimationConverter))]
|
||||
[Category("动画"), DisplayName("当前动画")]
|
||||
public abstract string CurrentAnimation { get; set; }
|
||||
|
||||
@@ -243,5 +251,11 @@ namespace SpineViewer.Spine
|
||||
/// SFML.Graphics.Drawable 接口实现
|
||||
/// </summary>
|
||||
public abstract void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
|
||||
|
||||
/// <summary>
|
||||
/// 是否被选中
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public bool IsSelected { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace SpineViewer.Spine
|
||||
{
|
||||
public class VersionTypeConverter : EnumConverter
|
||||
public class VersionConverter : EnumConverter
|
||||
{
|
||||
public VersionTypeConverter() : base(typeof(Version)) { }
|
||||
public VersionConverter() : base(typeof(Version)) { }
|
||||
|
||||
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type? destinationType)
|
||||
{
|
||||
@@ -25,7 +25,7 @@ namespace SpineViewer.Spine
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimationTypeConverter : StringConverter
|
||||
public class AnimationConverter : StringConverter
|
||||
{
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
|
||||
{
|
||||
|
||||
@@ -40,13 +40,13 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
public enum Version
|
||||
{
|
||||
[Description("v2.1.x")] V21 = 0x0201,
|
||||
[Description("v3.6.x")] V36 = 0x0306,
|
||||
[Description("v3.7.x")] V37 = 0x0307,
|
||||
[Description("v3.8.x")] V38 = 0x0308,
|
||||
[Description("v4.0.x")] V40 = 0x0400,
|
||||
[Description("v4.1.x")] V41 = 0x0401,
|
||||
[Description("v4.2.x")] V42 = 0x0402,
|
||||
[Description("v4.3.x")] V43 = 0x0403,
|
||||
[Description("2.1.x")] V21 = 0x0201,
|
||||
[Description("3.6.x")] V36 = 0x0306,
|
||||
[Description("3.7.x")] V37 = 0x0307,
|
||||
[Description("3.8.x")] V38 = 0x0308,
|
||||
[Description("4.0.x")] V40 = 0x0400,
|
||||
[Description("4.1.x")] V41 = 0x0401,
|
||||
[Description("4.2.x")] V42 = 0x0402,
|
||||
[Description("4.3.x")] V43 = 0x0403,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.9.6</Version>
|
||||
<Version>0.10.0</Version>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||
|
||||
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace SpineViewer
|
||||
{
|
||||
public class PointFTypeConverter : ExpandableObjectConverter
|
||||
public class PointFConverter : ExpandableObjectConverter
|
||||
{
|
||||
public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType)
|
||||
{
|
||||
@@ -52,48 +52,4 @@ namespace SpineViewer
|
||||
|
||||
public override bool GetPropertiesSupported(ITypeDescriptorContext? context) => true;
|
||||
}
|
||||
|
||||
public class SizeTypeConverter : 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 Size size)
|
||||
{
|
||||
return $"{size.Width}, {size.Height}";
|
||||
}
|
||||
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 &&
|
||||
int.TryParse(parts[0], out var width) &&
|
||||
int.TryParse(parts[1], out var height))
|
||||
{
|
||||
return new Size(width, height);
|
||||
}
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
|
||||
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext? context, object value, Attribute[]? attributes)
|
||||
{
|
||||
return TypeDescriptor.GetProperties(typeof(Size), attributes);
|
||||
}
|
||||
|
||||
public override bool GetPropertiesSupported(ITypeDescriptorContext? context) => true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user