Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8de00cad76 | ||
|
|
e4c58f2f4e | ||
|
|
063dba30b6 | ||
|
|
01fa9287a1 | ||
|
|
008067fccb | ||
|
|
091301e945 | ||
|
|
145f4f3265 | ||
|
|
36d4e8c948 | ||
|
|
63de847a57 | ||
|
|
b3e1b7c902 | ||
|
|
2dbc235631 | ||
|
|
4d68b48367 | ||
|
|
65e63e2b2d | ||
|
|
58071e1de1 | ||
|
|
5009ef479f | ||
|
|
e5e9357649 | ||
|
|
a577474772 | ||
|
|
e960a09153 | ||
|
|
13d50f59c3 | ||
|
|
ed4c8475e9 | ||
|
|
2338bf4e15 | ||
|
|
267aa7ee63 | ||
|
|
3df7dbc769 | ||
|
|
5f12ab7e85 | ||
|
|
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 |
2
.github/workflows/dotnet-desktop.yml
vendored
2
.github/workflows/dotnet-desktop.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Build and Release
|
name: Build & Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
13
CHANGELOG.md
Normal file
13
CHANGELOG.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v0.10.1
|
||||||
|
|
||||||
|
- <20><><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>Ԥ<EFBFBD><D4A4>ͼ
|
||||||
|
- <20><><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>Ԥ<EFBFBD><D4A4>ͼ<EFBFBD><CDBC><EFBFBD><EFBFBD>
|
||||||
|
|
||||||
|
## 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>
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# SpineViewer
|
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
||||||
|
|
||||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
A simple and user-friendly Spine file viewer and exporter.
|
A simple and user-friendly Spine file viewer and exporter.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
29
README.md
29
README.md
@@ -1,4 +1,4 @@
|
|||||||
# SpineViewer
|
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
||||||
|
|
||||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
一个简单好用的 Spine 文件查看&导出程序.
|
一个简单好用的 Spine 文件查看&导出程序.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -18,21 +18,26 @@
|
|||||||
|
|
||||||
也可以下载带有 `SelfContained` 后缀的压缩包, 可以独立运行.
|
也可以下载带有 `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: | :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帧序列导出
|
- 支持动画PNG帧序列导出
|
||||||
- 支持缩放旋转等导出画面设置
|
- 支持缩放旋转等导出画面设置
|
||||||
|
- 支持对独立的骨骼文件进行格式转换
|
||||||
- Coming soon...
|
- Coming soon...
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
|
CHANGELOG.md = CHANGELOG.md
|
||||||
README.en.md = README.en.md
|
README.en.md = README.en.md
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
|
|||||||
65
SpineViewer/Controls/SpineListView.Designer.cs
generated
65
SpineViewer/Controls/SpineListView.Designer.cs
generated
@@ -41,6 +41,13 @@
|
|||||||
toolStripSeparator2 = new ToolStripSeparator();
|
toolStripSeparator2 = new ToolStripSeparator();
|
||||||
toolStripMenuItem_BatchAdd = new ToolStripMenuItem();
|
toolStripMenuItem_BatchAdd = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_RemoveAll = new ToolStripMenuItem();
|
toolStripMenuItem_RemoveAll = new ToolStripMenuItem();
|
||||||
|
toolStripSeparator3 = new ToolStripSeparator();
|
||||||
|
toolStripMenuItem_ChangeView = new ToolStripMenuItem();
|
||||||
|
toolStripMenuItem_LargeIconView = new ToolStripMenuItem();
|
||||||
|
toolStripMenuItem_SmallIconView = new ToolStripMenuItem();
|
||||||
|
toolStripMenuItem_DetailsView = new ToolStripMenuItem();
|
||||||
|
imageList_LargeIcon = new ImageList(components);
|
||||||
|
imageList_SmallIcon = new ImageList(components);
|
||||||
contextMenuStrip.SuspendLayout();
|
contextMenuStrip.SuspendLayout();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
@@ -52,10 +59,12 @@
|
|||||||
listView.Dock = DockStyle.Fill;
|
listView.Dock = DockStyle.Fill;
|
||||||
listView.FullRowSelect = true;
|
listView.FullRowSelect = true;
|
||||||
listView.GridLines = true;
|
listView.GridLines = true;
|
||||||
|
listView.LargeImageList = imageList_LargeIcon;
|
||||||
listView.Location = new Point(0, 0);
|
listView.Location = new Point(0, 0);
|
||||||
listView.Name = "listView";
|
listView.Name = "listView";
|
||||||
listView.ShowItemToolTips = true;
|
listView.ShowItemToolTips = true;
|
||||||
listView.Size = new Size(336, 445);
|
listView.Size = new Size(336, 445);
|
||||||
|
listView.SmallImageList = imageList_SmallIcon;
|
||||||
listView.TabIndex = 1;
|
listView.TabIndex = 1;
|
||||||
listView.UseCompatibleStateImageBehavior = false;
|
listView.UseCompatibleStateImageBehavior = false;
|
||||||
listView.View = View.Details;
|
listView.View = View.Details;
|
||||||
@@ -73,9 +82,9 @@
|
|||||||
// contextMenuStrip
|
// contextMenuStrip
|
||||||
//
|
//
|
||||||
contextMenuStrip.ImageScalingSize = new Size(24, 24);
|
contextMenuStrip.ImageScalingSize = new Size(24, 24);
|
||||||
contextMenuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_Add, toolStripMenuItem_Insert, toolStripMenuItem_Remove, toolStripSeparator1, toolStripMenuItem_MoveUp, toolStripMenuItem_MoveDown, toolStripSeparator2, toolStripMenuItem_BatchAdd, toolStripMenuItem_RemoveAll });
|
contextMenuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_Add, toolStripMenuItem_Insert, toolStripMenuItem_Remove, toolStripSeparator1, toolStripMenuItem_MoveUp, toolStripMenuItem_MoveDown, toolStripSeparator2, toolStripMenuItem_BatchAdd, toolStripMenuItem_RemoveAll, toolStripSeparator3, toolStripMenuItem_ChangeView });
|
||||||
contextMenuStrip.Name = "contextMenuStrip";
|
contextMenuStrip.Name = "contextMenuStrip";
|
||||||
contextMenuStrip.Size = new Size(188, 226);
|
contextMenuStrip.Size = new Size(188, 262);
|
||||||
contextMenuStrip.Opening += contextMenuStrip_Opening;
|
contextMenuStrip.Opening += contextMenuStrip_Opening;
|
||||||
//
|
//
|
||||||
// toolStripMenuItem_Add
|
// toolStripMenuItem_Add
|
||||||
@@ -140,6 +149,51 @@
|
|||||||
toolStripMenuItem_RemoveAll.Text = "移除全部(&X)";
|
toolStripMenuItem_RemoveAll.Text = "移除全部(&X)";
|
||||||
toolStripMenuItem_RemoveAll.Click += toolStripMenuItem_RemoveAll_Click;
|
toolStripMenuItem_RemoveAll.Click += toolStripMenuItem_RemoveAll_Click;
|
||||||
//
|
//
|
||||||
|
// toolStripSeparator3
|
||||||
|
//
|
||||||
|
toolStripSeparator3.Name = "toolStripSeparator3";
|
||||||
|
toolStripSeparator3.Size = new Size(184, 6);
|
||||||
|
//
|
||||||
|
// toolStripMenuItem_ChangeView
|
||||||
|
//
|
||||||
|
toolStripMenuItem_ChangeView.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_LargeIconView, toolStripMenuItem_SmallIconView, toolStripMenuItem_DetailsView });
|
||||||
|
toolStripMenuItem_ChangeView.Name = "toolStripMenuItem_ChangeView";
|
||||||
|
toolStripMenuItem_ChangeView.Size = new Size(187, 30);
|
||||||
|
toolStripMenuItem_ChangeView.Text = "切换视图";
|
||||||
|
//
|
||||||
|
// toolStripMenuItem_LargeIconView
|
||||||
|
//
|
||||||
|
toolStripMenuItem_LargeIconView.Name = "toolStripMenuItem_LargeIconView";
|
||||||
|
toolStripMenuItem_LargeIconView.Size = new Size(164, 34);
|
||||||
|
toolStripMenuItem_LargeIconView.Text = "大图标";
|
||||||
|
toolStripMenuItem_LargeIconView.Click += toolStripMenuItem_LargeIconView_Click;
|
||||||
|
//
|
||||||
|
// toolStripMenuItem_SmallIconView
|
||||||
|
//
|
||||||
|
toolStripMenuItem_SmallIconView.Name = "toolStripMenuItem_SmallIconView";
|
||||||
|
toolStripMenuItem_SmallIconView.Size = new Size(164, 34);
|
||||||
|
toolStripMenuItem_SmallIconView.Text = "小图标";
|
||||||
|
toolStripMenuItem_SmallIconView.Click += toolStripMenuItem_SmallIconView_Click;
|
||||||
|
//
|
||||||
|
// toolStripMenuItem_DetailsView
|
||||||
|
//
|
||||||
|
toolStripMenuItem_DetailsView.Name = "toolStripMenuItem_DetailsView";
|
||||||
|
toolStripMenuItem_DetailsView.Size = new Size(164, 34);
|
||||||
|
toolStripMenuItem_DetailsView.Text = "列表";
|
||||||
|
toolStripMenuItem_DetailsView.Click += toolStripMenuItem_DetailsView_Click;
|
||||||
|
//
|
||||||
|
// imageList_LargeIcon
|
||||||
|
//
|
||||||
|
imageList_LargeIcon.ColorDepth = ColorDepth.Depth32Bit;
|
||||||
|
imageList_LargeIcon.ImageSize = new Size(96, 96);
|
||||||
|
imageList_LargeIcon.TransparentColor = Color.Transparent;
|
||||||
|
//
|
||||||
|
// imageList_SmallIcon
|
||||||
|
//
|
||||||
|
imageList_SmallIcon.ColorDepth = ColorDepth.Depth32Bit;
|
||||||
|
imageList_SmallIcon.ImageSize = new Size(48, 48);
|
||||||
|
imageList_SmallIcon.TransparentColor = Color.Transparent;
|
||||||
|
//
|
||||||
// SpineListView
|
// SpineListView
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
@@ -164,5 +218,12 @@
|
|||||||
private ToolStripMenuItem toolStripMenuItem_MoveDown;
|
private ToolStripMenuItem toolStripMenuItem_MoveDown;
|
||||||
private ToolStripSeparator toolStripSeparator2;
|
private ToolStripSeparator toolStripSeparator2;
|
||||||
private ColumnHeader columnHeader_Name;
|
private ColumnHeader columnHeader_Name;
|
||||||
|
private ImageList imageList_SmallIcon;
|
||||||
|
private ImageList imageList_LargeIcon;
|
||||||
|
private ToolStripSeparator toolStripSeparator3;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_ChangeView;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_LargeIconView;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_SmallIconView;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_DetailsView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,39 +36,9 @@ namespace SpineViewer.Controls
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 弹出添加对话框在指定位置之前插入一项
|
/// listView.SelectedIndices
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void Insert(int index = -1)
|
public ListView.SelectedIndexCollection SelectedIndices { get => listView.SelectedIndices; }
|
||||||
{
|
|
||||||
var dialog = new Dialogs.OpenSpineDialog();
|
|
||||||
if (dialog.ShowDialog() != DialogResult.OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var spine = Spine.Spine.New(dialog.Version, dialog.SkelPath, dialog.AtlasPath);
|
|
||||||
|
|
||||||
// 如果索引无效则在末尾添加
|
|
||||||
if (index < 0 || index > listView.Items.Count)
|
|
||||||
index = listView.Items.Count;
|
|
||||||
|
|
||||||
// 锁定外部的读操作
|
|
||||||
lock (Spines) { spines.Insert(index, spine); }
|
|
||||||
listView.Items.Insert(index, new ListViewItem(spine.Name) { ToolTipText = spine.SkelPath });
|
|
||||||
|
|
||||||
// 选中新增项
|
|
||||||
listView.SelectedIndices.Clear();
|
|
||||||
listView.SelectedIndices.Add(index);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Program.Logger.Error(ex.ToString());
|
|
||||||
Program.Logger.Error("Failed to load {} {}", dialog.SkelPath, dialog.AtlasPath);
|
|
||||||
MessageBox.Show(ex.ToString(), "骨骼加载失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 弹出添加对话框
|
/// 弹出添加对话框
|
||||||
@@ -93,55 +63,25 @@ namespace SpineViewer.Controls
|
|||||||
progressDialog.ShowDialog();
|
progressDialog.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BatchAdd_Work(object? sender, DoWorkEventArgs e)
|
public void ExportPreviews()
|
||||||
{
|
{
|
||||||
var worker = sender as BackgroundWorker;
|
lock (Spines)
|
||||||
var arguments = e.Argument as Dialogs.BatchOpenSpineDialog;
|
|
||||||
var skelPaths = arguments.SkelPaths;
|
|
||||||
var version = arguments.Version;
|
|
||||||
|
|
||||||
int totalCount = skelPaths.Length;
|
|
||||||
int success = 0;
|
|
||||||
int error = 0;
|
|
||||||
|
|
||||||
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
|
||||||
for (int i = 0; i < totalCount; i++)
|
|
||||||
{
|
{
|
||||||
if (worker.CancellationPending)
|
if (spines.Count <= 0)
|
||||||
{
|
{
|
||||||
e.Cancel = true;
|
MessageBox.Show("请至少打开一个骨骼文件", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var skelPath = skelPaths[i];
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var spine = Spine.Spine.New(version, skelPath);
|
|
||||||
lock (Spines) { spines.Add(spine); }
|
|
||||||
listView.Invoke(() => listView.Items.Add(new ListViewItem(spine.Name) { ToolTipText = spine.SkelPath }));
|
|
||||||
success++;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Program.Logger.Error(ex.ToString());
|
|
||||||
Program.Logger.Error("Failed to load {}", skelPath);
|
|
||||||
error++;
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error > 0)
|
var saveDialog = new Dialogs.ExportPreviewDialog();
|
||||||
{
|
if (saveDialog.ShowDialog() != DialogResult.OK)
|
||||||
Program.Logger.Warn("Batch load {} successfully, {} failed", success, error);
|
return;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Program.Logger.Info("{} skel loaded successfully", success);
|
|
||||||
}
|
|
||||||
|
|
||||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
var progressDialog = new Dialogs.ProgressDialog();
|
||||||
|
progressDialog.DoWork += ExportPreview_Work;
|
||||||
|
progressDialog.RunWorkerAsync(saveDialog);
|
||||||
|
progressDialog.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void listView_SelectedIndexChanged(object sender, EventArgs e)
|
private void listView_SelectedIndexChanged(object sender, EventArgs e)
|
||||||
@@ -156,6 +96,10 @@ namespace SpineViewer.Controls
|
|||||||
PropertyGrid.SelectedObject = spines[listView.SelectedIndices[0]];
|
PropertyGrid.SelectedObject = spines[listView.SelectedIndices[0]];
|
||||||
else
|
else
|
||||||
PropertyGrid.SelectedObjects = listView.SelectedIndices.Cast<int>().Select(index => spines[index]).ToArray();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,6 +190,11 @@ namespace SpineViewer.Controls
|
|||||||
toolStripMenuItem_MoveUp.Enabled = selectedCount == 1 && listView.SelectedIndices[0] != 0;
|
toolStripMenuItem_MoveUp.Enabled = selectedCount == 1 && listView.SelectedIndices[0] != 0;
|
||||||
toolStripMenuItem_MoveDown.Enabled = selectedCount == 1 && listView.SelectedIndices[0] != itemsCount - 1;
|
toolStripMenuItem_MoveDown.Enabled = selectedCount == 1 && listView.SelectedIndices[0] != itemsCount - 1;
|
||||||
toolStripMenuItem_RemoveAll.Enabled = itemsCount > 0;
|
toolStripMenuItem_RemoveAll.Enabled = itemsCount > 0;
|
||||||
|
|
||||||
|
// 视图选项
|
||||||
|
toolStripMenuItem_LargeIconView.Checked = listView.View == View.LargeIcon;
|
||||||
|
toolStripMenuItem_SmallIconView.Checked = listView.View == View.SmallIcon;
|
||||||
|
toolStripMenuItem_DetailsView.Checked = listView.View == View.Details;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toolStripMenuItem_Add_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_Add_Click(object sender, EventArgs e)
|
||||||
@@ -279,8 +228,11 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
lock (Spines)
|
lock (Spines)
|
||||||
{
|
{
|
||||||
spines[i].Dispose();
|
var spine = spines[i];
|
||||||
spines.RemoveAt(i);
|
spines.RemoveAt(i);
|
||||||
|
listView.SmallImageList.Images.RemoveByKey(spine.ID);
|
||||||
|
listView.LargeImageList.Images.RemoveByKey(spine.ID);
|
||||||
|
spine.Dispose();
|
||||||
}
|
}
|
||||||
listView.Items.RemoveAt(i);
|
listView.Items.RemoveAt(i);
|
||||||
}
|
}
|
||||||
@@ -324,15 +276,182 @@ namespace SpineViewer.Controls
|
|||||||
if (MessageBox.Show($"确认移除所有 {listView.Items.Count} 项吗?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
|
if (MessageBox.Show($"确认移除所有 {listView.Items.Count} 项吗?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
lock (Spines)
|
lock (Spines)
|
||||||
{
|
{
|
||||||
foreach (var spine in spines)
|
foreach (var spine in spines)
|
||||||
spine.Dispose();
|
spine.Dispose();
|
||||||
spines.Clear();
|
spines.Clear();
|
||||||
|
listView.SmallImageList.Images.Clear();
|
||||||
|
listView.LargeImageList.Images.Clear();
|
||||||
}
|
}
|
||||||
listView.Items.Clear();
|
listView.Items.Clear();
|
||||||
if (PropertyGrid is not null)
|
if (PropertyGrid is not null)
|
||||||
PropertyGrid.SelectedObject = null;
|
PropertyGrid.SelectedObject = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_LargeIconView_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
listView.View = View.LargeIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_SmallIconView_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
listView.View = View.SmallIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_DetailsView_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
listView.View = View.Details;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 弹出添加对话框在指定位置之前插入一项
|
||||||
|
/// </summary>
|
||||||
|
private void Insert(int index = -1)
|
||||||
|
{
|
||||||
|
var dialog = new Dialogs.OpenSpineDialog();
|
||||||
|
if (dialog.ShowDialog() != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var spine = Spine.Spine.New(dialog.Version, dialog.SkelPath, dialog.AtlasPath);
|
||||||
|
|
||||||
|
// 如果索引无效则在末尾添加
|
||||||
|
if (index < 0 || index > listView.Items.Count)
|
||||||
|
index = listView.Items.Count;
|
||||||
|
|
||||||
|
// 锁定外部的读操作
|
||||||
|
lock (Spines)
|
||||||
|
{
|
||||||
|
spines.Insert(index, spine);
|
||||||
|
listView.SmallImageList.Images.Add(spine.ID, spine.Preview);
|
||||||
|
listView.LargeImageList.Images.Add(spine.ID, spine.Preview);
|
||||||
|
}
|
||||||
|
listView.Items.Insert(index, new ListViewItem(spine.Name, spine.ID) { ToolTipText = spine.SkelPath });
|
||||||
|
|
||||||
|
// 选中新增项
|
||||||
|
listView.SelectedIndices.Clear();
|
||||||
|
listView.SelectedIndices.Add(index);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Logger.Error(ex.ToString());
|
||||||
|
Program.Logger.Error("Failed to load {} {}", dialog.SkelPath, dialog.AtlasPath);
|
||||||
|
MessageBox.Show(ex.ToString(), "骨骼加载失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BatchAdd_Work(object? sender, DoWorkEventArgs e)
|
||||||
|
{
|
||||||
|
var worker = sender as BackgroundWorker;
|
||||||
|
var arguments = e.Argument as Dialogs.BatchOpenSpineDialog;
|
||||||
|
var skelPaths = arguments.SkelPaths;
|
||||||
|
var version = arguments.Version;
|
||||||
|
|
||||||
|
int totalCount = skelPaths.Length;
|
||||||
|
int success = 0;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
||||||
|
for (int i = 0; i < totalCount; i++)
|
||||||
|
{
|
||||||
|
if (worker.CancellationPending)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var skelPath = skelPaths[i];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var spine = Spine.Spine.New(version, skelPath);
|
||||||
|
var preview = spine.Preview;
|
||||||
|
lock (Spines) { spines.Add(spine); }
|
||||||
|
listView.Invoke(() =>
|
||||||
|
{
|
||||||
|
listView.SmallImageList.Images.Add(spine.ID, preview);
|
||||||
|
listView.LargeImageList.Images.Add(spine.ID, preview);
|
||||||
|
listView.Items.Add(new ListViewItem(spine.Name, spine.ID) { ToolTipText = spine.SkelPath });
|
||||||
|
});
|
||||||
|
success++;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Logger.Error(ex.ToString());
|
||||||
|
Program.Logger.Error("Failed to load {}", skelPath);
|
||||||
|
error++;
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error > 0)
|
||||||
|
{
|
||||||
|
Program.Logger.Warn("Batch load {} successfully, {} failed", success, error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Program.Logger.Info("{} skel loaded successfully", success);
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExportPreview_Work(object? sender, DoWorkEventArgs e)
|
||||||
|
{
|
||||||
|
var worker = sender as BackgroundWorker;
|
||||||
|
var arguments = e.Argument as Dialogs.ExportPreviewDialog;
|
||||||
|
var outputDir = arguments.OutputDir;
|
||||||
|
var width = arguments.PreviewWidth;
|
||||||
|
var height = arguments.PreviewHeight;
|
||||||
|
|
||||||
|
int success = 0;
|
||||||
|
int error = 0;
|
||||||
|
lock (Spines)
|
||||||
|
{
|
||||||
|
int totalCount = spines.Count;
|
||||||
|
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
||||||
|
for (int i = 0; i < totalCount; i++)
|
||||||
|
{
|
||||||
|
if (worker.CancellationPending)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var spine = spines[i];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var preview = spine.GetPreview(width, height);
|
||||||
|
var savePath = Path.Combine(outputDir, $"{spine.Name}.png");
|
||||||
|
preview.SaveToFile(savePath);
|
||||||
|
success++;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Logger.Error(ex.ToString());
|
||||||
|
Program.Logger.Error("Failed to save preview {}", spine.SkelPath);
|
||||||
|
error++;
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error > 0)
|
||||||
|
{
|
||||||
|
Program.Logger.Warn("Preview save {} successfully, {} failed", success, error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Program.Logger.Info("{} preview saved successfully", success);
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,4 +120,10 @@
|
|||||||
<metadata name="contextMenuStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
<metadata name="contextMenuStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
<value>17, 17</value>
|
<value>17, 17</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
<metadata name="imageList_LargeIcon.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
|
<value>552, 29</value>
|
||||||
|
</metadata>
|
||||||
|
<metadata name="imageList_SmallIcon.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
|
<value>267, 34</value>
|
||||||
|
</metadata>
|
||||||
</root>
|
</root>
|
||||||
@@ -24,11 +24,11 @@ namespace SpineViewer.Controls
|
|||||||
|
|
||||||
public PreviewerProperty(SpinePreviewer previewer) { this.previewer = previewer; }
|
public PreviewerProperty(SpinePreviewer previewer) { this.previewer = previewer; }
|
||||||
|
|
||||||
[TypeConverter(typeof(SizeTypeConverter))]
|
[TypeConverter(typeof(SizeConverter))]
|
||||||
[Category("导出"), DisplayName("分辨率")]
|
[Category("导出"), DisplayName("分辨率")]
|
||||||
public Size Resolution { get => previewer.Resolution; set => previewer.Resolution = value; }
|
public Size Resolution { get => previewer.Resolution; set => previewer.Resolution = value; }
|
||||||
|
|
||||||
[TypeConverter(typeof(PointFTypeConverter))]
|
[TypeConverter(typeof(PointFConverter))]
|
||||||
[Category("导出"), DisplayName("画面中心点")]
|
[Category("导出"), DisplayName("画面中心点")]
|
||||||
public PointF Center { get => previewer.Center; set => previewer.Center = value; }
|
public PointF Center { get => previewer.Center; set => previewer.Center = value; }
|
||||||
|
|
||||||
@@ -47,9 +47,6 @@ namespace SpineViewer.Controls
|
|||||||
[Category("预览"), DisplayName("显示坐标轴")]
|
[Category("预览"), DisplayName("显示坐标轴")]
|
||||||
public bool ShowAxis { get => previewer.ShowAxis; set => previewer.ShowAxis = value; }
|
public bool ShowAxis { get => previewer.ShowAxis; set => previewer.ShowAxis = value; }
|
||||||
|
|
||||||
[Category("预览"), DisplayName("显示包围盒")]
|
|
||||||
public bool ShowBounds { get => previewer.ShowBounds; set => previewer.ShowBounds = value; }
|
|
||||||
|
|
||||||
[Category("预览"), DisplayName("最大帧率")]
|
[Category("预览"), DisplayName("最大帧率")]
|
||||||
public uint MaxFps { get => previewer.MaxFps; set => previewer.MaxFps = value; }
|
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.Graphics.RenderWindow RenderWindow;
|
||||||
private readonly SFML.System.Clock Clock = new();
|
private readonly SFML.System.Clock Clock = new();
|
||||||
private SFML.System.Vector2f? draggingSrc = null;
|
private SFML.System.Vector2f? draggingSrc = null;
|
||||||
private Spine.Spine? draggingSpine = null;
|
|
||||||
|
|
||||||
private Task? task = null;
|
private Task? task = null;
|
||||||
private CancellationTokenSource? cancelToken = null;
|
private CancellationTokenSource? cancelToken = null;
|
||||||
@@ -236,13 +232,6 @@ namespace SpineViewer.Controls
|
|||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public bool ShowAxis { get; set; } = true;
|
public bool ShowAxis { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示包围盒
|
|
||||||
/// </summary>
|
|
||||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
||||||
[Browsable(false)]
|
|
||||||
public bool ShowBounds { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 最大帧率
|
/// 最大帧率
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -328,7 +317,6 @@ namespace SpineViewer.Controls
|
|||||||
// 右键优先级高, 进入画面拖动模式, 需要重新记录源点
|
// 右键优先级高, 进入画面拖动模式, 需要重新记录源点
|
||||||
if ((e.Button & MouseButtons.Right) != 0)
|
if ((e.Button & MouseButtons.Right) != 0)
|
||||||
{
|
{
|
||||||
draggingSpine = null;
|
|
||||||
draggingSrc = RenderWindow.MapPixelToCoords(new(e.X, e.Y));
|
draggingSrc = RenderWindow.MapPixelToCoords(new(e.X, e.Y));
|
||||||
Cursor = Cursors.Hand;
|
Cursor = Cursors.Hand;
|
||||||
}
|
}
|
||||||
@@ -342,12 +330,41 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
lock (SpineListView.Spines)
|
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))
|
||||||
break;
|
{
|
||||||
|
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)
|
else if ((e.Button & MouseButtons.Left) != 0)
|
||||||
{
|
{
|
||||||
if (draggingSpine is not null)
|
if (SpineListView is not null)
|
||||||
draggingSpine.Position += delta;
|
{
|
||||||
|
lock (SpineListView.Spines)
|
||||||
|
{
|
||||||
|
foreach (var spine in SpineListView.Spines)
|
||||||
|
{
|
||||||
|
if (spine.IsSelected)
|
||||||
|
spine.Position += delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
draggingSrc = dst;
|
draggingSrc = dst;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,7 +408,6 @@ namespace SpineViewer.Controls
|
|||||||
// 右键高优先级, 结束画面拖动模式
|
// 右键高优先级, 结束画面拖动模式
|
||||||
if ((e.Button & MouseButtons.Right) != 0)
|
if ((e.Button & MouseButtons.Right) != 0)
|
||||||
{
|
{
|
||||||
draggingSpine = null;
|
|
||||||
SpineListView?.PropertyGrid?.Refresh();
|
SpineListView?.PropertyGrid?.Refresh();
|
||||||
|
|
||||||
draggingSrc = null;
|
draggingSrc = null;
|
||||||
@@ -393,7 +418,6 @@ namespace SpineViewer.Controls
|
|||||||
else if ((e.Button & MouseButtons.Left) != 0 && (MouseButtons & MouseButtons.Right) == 0)
|
else if ((e.Button & MouseButtons.Left) != 0 && (MouseButtons & MouseButtons.Right) == 0)
|
||||||
{
|
{
|
||||||
draggingSrc = null;
|
draggingSrc = null;
|
||||||
draggingSpine = null;
|
|
||||||
SpineListView?.PropertyGrid?.Refresh();
|
SpineListView?.PropertyGrid?.Refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,7 +463,7 @@ namespace SpineViewer.Controls
|
|||||||
spine.Update(delta);
|
spine.Update(delta);
|
||||||
RenderWindow.Draw(spine);
|
RenderWindow.Draw(spine);
|
||||||
|
|
||||||
if (ShowBounds)
|
if (spine.IsSelected)
|
||||||
{
|
{
|
||||||
var bounds = spine.Bounds;
|
var bounds = spine.Bounds;
|
||||||
BoundsRect[0] = BoundsRect[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
BoundsRect[0] = BoundsRect[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
|||||||
@@ -194,8 +194,9 @@
|
|||||||
//
|
//
|
||||||
openFileDialog_Skel.AddExtension = false;
|
openFileDialog_Skel.AddExtension = false;
|
||||||
openFileDialog_Skel.AddToRecent = 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.Multiselect = true;
|
||||||
|
openFileDialog_Skel.Title = "批量选择skel文件";
|
||||||
//
|
//
|
||||||
// BatchOpenSpineDialog
|
// BatchOpenSpineDialog
|
||||||
//
|
//
|
||||||
@@ -213,6 +214,7 @@
|
|||||||
ShowInTaskbar = false;
|
ShowInTaskbar = false;
|
||||||
StartPosition = FormStartPosition.CenterScreen;
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
Text = "批量打开骨骼";
|
Text = "批量打开骨骼";
|
||||||
|
Load += BatchOpenSpineDialog_Load;
|
||||||
panel.ResumeLayout(false);
|
panel.ResumeLayout(false);
|
||||||
tableLayoutPanel1.ResumeLayout(false);
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
tableLayoutPanel1.PerformLayout();
|
tableLayoutPanel1.PerformLayout();
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ namespace SpineViewer.Dialogs
|
|||||||
comboBox_Version.SelectedValue = Spine.Version.V38;
|
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)
|
private void button_SelectSkel_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (openFileDialog_Skel.ShowDialog() == DialogResult.OK)
|
if (openFileDialog_Skel.ShowDialog() == DialogResult.OK)
|
||||||
@@ -38,6 +43,8 @@ namespace SpineViewer.Dialogs
|
|||||||
|
|
||||||
private void button_Ok_Click(object sender, EventArgs e)
|
private void button_Ok_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
var version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||||
|
|
||||||
if (listBox_FilePath.Items.Count <= 0)
|
if (listBox_FilePath.Items.Count <= 0)
|
||||||
{
|
{
|
||||||
MessageBox.Show("未选择任何文件", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
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();
|
SkelPaths = listBox_FilePath.Items.Cast<string>().ToArray();
|
||||||
Version = (Spine.Version)comboBox_Version.SelectedValue;
|
Version = version;
|
||||||
|
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
}
|
}
|
||||||
|
|||||||
358
SpineViewer/Dialogs/ConvertFileFormatDialog.Designer.cs
generated
Normal file
358
SpineViewer/Dialogs/ConvertFileFormatDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
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_SourceVersion = 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_SourceVersion, 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_SourceVersion.Anchor = AnchorStyles.Left;
|
||||||
|
comboBox_SourceVersion.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||||
|
comboBox_SourceVersion.FormattingEnabled = true;
|
||||||
|
comboBox_SourceVersion.Location = new Point(146, 303);
|
||||||
|
comboBox_SourceVersion.Name = "comboBox_Version";
|
||||||
|
comboBox_SourceVersion.Size = new Size(182, 32);
|
||||||
|
comboBox_SourceVersion.Sorted = true;
|
||||||
|
comboBox_SourceVersion.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 = "骨骼文件格式转换";
|
||||||
|
Load += ConvertFileFormatDialog_Load;
|
||||||
|
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_SourceVersion;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
121
SpineViewer/Dialogs/ConvertFileFormatDialog.cs
Normal file
121
SpineViewer/Dialogs/ConvertFileFormatDialog.cs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
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 SourceVersion { get; private set; }
|
||||||
|
public Spine.Version TargetVersion { get; private set; }
|
||||||
|
public bool JsonSource { get; private set; }
|
||||||
|
public bool JsonTarget { get; private set; }
|
||||||
|
|
||||||
|
public ConvertFileFormatDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
comboBox_SourceVersion.DataSource = VersionHelper.Versions.ToList();
|
||||||
|
comboBox_SourceVersion.DisplayMember = "Value";
|
||||||
|
comboBox_SourceVersion.ValueMember = "Key";
|
||||||
|
comboBox_SourceVersion.SelectedValue = Spine.Version.V38;
|
||||||
|
//comboBox_TargetVersion.DataSource = VersionHelper.Versions.ToList();
|
||||||
|
//comboBox_TargetVersion.DisplayMember = "Value";
|
||||||
|
//comboBox_TargetVersion.ValueMember = "Key";
|
||||||
|
//comboBox_TargetVersion.SelectedValue = Spine.Version.V38;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConvertFileFormatDialog_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)
|
||||||
|
{
|
||||||
|
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 sourceVersion = (Spine.Version)comboBox_SourceVersion.SelectedValue;
|
||||||
|
var targetVersion = (Spine.Version)comboBox_SourceVersion.SelectedValue; // TODO: 增加目标版本
|
||||||
|
var jsonSource = radioButton_JsonSource.Checked;
|
||||||
|
var jsonTarget = radioButton_JsonTarget.Checked;
|
||||||
|
|
||||||
|
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 (!SkeletonConverter.ImplementedVersions.Contains(sourceVersion))
|
||||||
|
{
|
||||||
|
MessageBox.Show($"{sourceVersion.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SkeletonConverter.ImplementedVersions.Contains(targetVersion))
|
||||||
|
{
|
||||||
|
MessageBox.Show($"{targetVersion.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonSource == jsonTarget && sourceVersion == targetVersion)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"不需要转换相同的格式和版本", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkelPaths = listBox_FilePath.Items.Cast<string>().ToArray();
|
||||||
|
SourceVersion = sourceVersion;
|
||||||
|
TargetVersion = targetVersion;
|
||||||
|
JsonSource = jsonSource;
|
||||||
|
JsonTarget = jsonTarget;
|
||||||
|
|
||||||
|
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
1
SpineViewer/Dialogs/ExportPngDialog.Designer.cs
generated
1
SpineViewer/Dialogs/ExportPngDialog.Designer.cs
generated
@@ -239,6 +239,7 @@
|
|||||||
ShowInTaskbar = false;
|
ShowInTaskbar = false;
|
||||||
StartPosition = FormStartPosition.CenterScreen;
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
Text = "导出PNG序列";
|
Text = "导出PNG序列";
|
||||||
|
Load += ExportPngDialog_Load;
|
||||||
panel1.ResumeLayout(false);
|
panel1.ResumeLayout(false);
|
||||||
panel1.PerformLayout();
|
panel1.PerformLayout();
|
||||||
tableLayoutPanel1.ResumeLayout(false);
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ namespace SpineViewer.Dialogs
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ExportPngDialog_Load(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
button_SelectOutputDir_Click(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
private void button_SelectOutputDir_Click(object sender, EventArgs e)
|
private void button_SelectOutputDir_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
folderBrowserDialog.InitialDirectory = textBox_OutputDir.Text;
|
folderBrowserDialog.InitialDirectory = textBox_OutputDir.Text;
|
||||||
|
|||||||
270
SpineViewer/Dialogs/ExportPreviewDialog.Designer.cs
generated
Normal file
270
SpineViewer/Dialogs/ExportPreviewDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
namespace SpineViewer.Dialogs
|
||||||
|
{
|
||||||
|
partial class ExportPreviewDialog
|
||||||
|
{
|
||||||
|
/// <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(ExportPreviewDialog));
|
||||||
|
panel1 = new Panel();
|
||||||
|
tableLayoutPanel1 = new TableLayoutPanel();
|
||||||
|
label4 = new Label();
|
||||||
|
label1 = new Label();
|
||||||
|
label2 = new Label();
|
||||||
|
label3 = new Label();
|
||||||
|
textBox_OutputDir = new TextBox();
|
||||||
|
button_SelectOutputDir = new Button();
|
||||||
|
tableLayoutPanel2 = new TableLayoutPanel();
|
||||||
|
button_Ok = new Button();
|
||||||
|
button_Cancel = new Button();
|
||||||
|
numericUpDown_Width = new NumericUpDown();
|
||||||
|
numericUpDown_Height = new NumericUpDown();
|
||||||
|
folderBrowserDialog = new FolderBrowserDialog();
|
||||||
|
panel1.SuspendLayout();
|
||||||
|
tableLayoutPanel1.SuspendLayout();
|
||||||
|
tableLayoutPanel2.SuspendLayout();
|
||||||
|
((System.ComponentModel.ISupportInitialize)numericUpDown_Width).BeginInit();
|
||||||
|
((System.ComponentModel.ISupportInitialize)numericUpDown_Height).BeginInit();
|
||||||
|
SuspendLayout();
|
||||||
|
//
|
||||||
|
// panel1
|
||||||
|
//
|
||||||
|
panel1.Controls.Add(tableLayoutPanel1);
|
||||||
|
panel1.Dock = DockStyle.Fill;
|
||||||
|
panel1.Location = new Point(0, 0);
|
||||||
|
panel1.Name = "panel1";
|
||||||
|
panel1.Padding = new Padding(50, 15, 50, 10);
|
||||||
|
panel1.Size = new Size(919, 276);
|
||||||
|
panel1.TabIndex = 2;
|
||||||
|
//
|
||||||
|
// tableLayoutPanel1
|
||||||
|
//
|
||||||
|
tableLayoutPanel1.AutoSize = true;
|
||||||
|
tableLayoutPanel1.ColumnCount = 4;
|
||||||
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
||||||
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||||
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||||
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
||||||
|
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
||||||
|
tableLayoutPanel1.Controls.Add(label1, 0, 1);
|
||||||
|
tableLayoutPanel1.Controls.Add(label2, 0, 2);
|
||||||
|
tableLayoutPanel1.Controls.Add(label3, 0, 3);
|
||||||
|
tableLayoutPanel1.Controls.Add(textBox_OutputDir, 1, 1);
|
||||||
|
tableLayoutPanel1.Controls.Add(button_SelectOutputDir, 3, 1);
|
||||||
|
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 4);
|
||||||
|
tableLayoutPanel1.Controls.Add(numericUpDown_Width, 1, 2);
|
||||||
|
tableLayoutPanel1.Controls.Add(numericUpDown_Height, 1, 3);
|
||||||
|
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
|
tableLayoutPanel1.Location = new Point(50, 15);
|
||||||
|
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||||
|
tableLayoutPanel1.RowCount = 5;
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel1.Size = new Size(819, 251);
|
||||||
|
tableLayoutPanel1.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// label4
|
||||||
|
//
|
||||||
|
label4.AutoSize = true;
|
||||||
|
tableLayoutPanel1.SetColumnSpan(label4, 4);
|
||||||
|
label4.Dock = DockStyle.Fill;
|
||||||
|
label4.Location = new Point(15, 15);
|
||||||
|
label4.Margin = new Padding(15);
|
||||||
|
label4.Name = "label4";
|
||||||
|
label4.Size = new Size(789, 24);
|
||||||
|
label4.TabIndex = 11;
|
||||||
|
label4.Text = "说明:导出的文件名与骨骼文件名相同";
|
||||||
|
label4.TextAlign = ContentAlignment.MiddleCenter;
|
||||||
|
//
|
||||||
|
// label1
|
||||||
|
//
|
||||||
|
label1.Anchor = AnchorStyles.Right;
|
||||||
|
label1.AutoSize = true;
|
||||||
|
label1.Location = new Point(3, 62);
|
||||||
|
label1.Name = "label1";
|
||||||
|
label1.Size = new Size(104, 24);
|
||||||
|
label1.TabIndex = 0;
|
||||||
|
label1.Text = "输出文件夹:";
|
||||||
|
//
|
||||||
|
// label2
|
||||||
|
//
|
||||||
|
label2.Anchor = AnchorStyles.Right;
|
||||||
|
label2.AutoSize = true;
|
||||||
|
label2.Location = new Point(75, 100);
|
||||||
|
label2.Name = "label2";
|
||||||
|
label2.Size = new Size(32, 24);
|
||||||
|
label2.TabIndex = 1;
|
||||||
|
label2.Text = "宽:";
|
||||||
|
//
|
||||||
|
// label3
|
||||||
|
//
|
||||||
|
label3.Anchor = AnchorStyles.Right;
|
||||||
|
label3.AutoSize = true;
|
||||||
|
label3.Location = new Point(75, 136);
|
||||||
|
label3.Name = "label3";
|
||||||
|
label3.Size = new Size(32, 24);
|
||||||
|
label3.TabIndex = 2;
|
||||||
|
label3.Text = "高:";
|
||||||
|
//
|
||||||
|
// textBox_OutputDir
|
||||||
|
//
|
||||||
|
tableLayoutPanel1.SetColumnSpan(textBox_OutputDir, 2);
|
||||||
|
textBox_OutputDir.Dock = DockStyle.Fill;
|
||||||
|
textBox_OutputDir.Location = new Point(113, 57);
|
||||||
|
textBox_OutputDir.Name = "textBox_OutputDir";
|
||||||
|
textBox_OutputDir.Size = new Size(664, 30);
|
||||||
|
textBox_OutputDir.TabIndex = 3;
|
||||||
|
//
|
||||||
|
// button_SelectOutputDir
|
||||||
|
//
|
||||||
|
button_SelectOutputDir.AutoSize = true;
|
||||||
|
button_SelectOutputDir.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||||
|
button_SelectOutputDir.Location = new Point(783, 57);
|
||||||
|
button_SelectOutputDir.Name = "button_SelectOutputDir";
|
||||||
|
button_SelectOutputDir.Size = new Size(32, 34);
|
||||||
|
button_SelectOutputDir.TabIndex = 5;
|
||||||
|
button_SelectOutputDir.Text = "...";
|
||||||
|
button_SelectOutputDir.UseVisualStyleBackColor = true;
|
||||||
|
button_SelectOutputDir.Click += button_SelectOutputDir_Click;
|
||||||
|
//
|
||||||
|
// 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, 208);
|
||||||
|
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||||
|
tableLayoutPanel2.RowCount = 1;
|
||||||
|
tableLayoutPanel2.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel2.Size = new Size(813, 40);
|
||||||
|
tableLayoutPanel2.TabIndex = 10;
|
||||||
|
//
|
||||||
|
// button_Ok
|
||||||
|
//
|
||||||
|
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||||
|
button_Ok.Location = new Point(264, 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(436, 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;
|
||||||
|
//
|
||||||
|
// numericUpDown_Width
|
||||||
|
//
|
||||||
|
numericUpDown_Width.Anchor = AnchorStyles.Left;
|
||||||
|
numericUpDown_Width.Location = new Point(113, 97);
|
||||||
|
numericUpDown_Width.Maximum = new decimal(new int[] { 4096, 0, 0, 0 });
|
||||||
|
numericUpDown_Width.Minimum = new decimal(new int[] { 32, 0, 0, 0 });
|
||||||
|
numericUpDown_Width.Name = "numericUpDown_Width";
|
||||||
|
numericUpDown_Width.Size = new Size(180, 30);
|
||||||
|
numericUpDown_Width.TabIndex = 12;
|
||||||
|
numericUpDown_Width.TextAlign = HorizontalAlignment.Right;
|
||||||
|
numericUpDown_Width.Value = new decimal(new int[] { 256, 0, 0, 0 });
|
||||||
|
//
|
||||||
|
// numericUpDown_Height
|
||||||
|
//
|
||||||
|
numericUpDown_Height.Anchor = AnchorStyles.Left;
|
||||||
|
numericUpDown_Height.Location = new Point(113, 133);
|
||||||
|
numericUpDown_Height.Maximum = new decimal(new int[] { 4096, 0, 0, 0 });
|
||||||
|
numericUpDown_Height.Minimum = new decimal(new int[] { 32, 0, 0, 0 });
|
||||||
|
numericUpDown_Height.Name = "numericUpDown_Height";
|
||||||
|
numericUpDown_Height.Size = new Size(180, 30);
|
||||||
|
numericUpDown_Height.TabIndex = 13;
|
||||||
|
numericUpDown_Height.TextAlign = HorizontalAlignment.Right;
|
||||||
|
numericUpDown_Height.Value = new decimal(new int[] { 256, 0, 0, 0 });
|
||||||
|
//
|
||||||
|
// folderBrowserDialog
|
||||||
|
//
|
||||||
|
folderBrowserDialog.AddToRecent = false;
|
||||||
|
//
|
||||||
|
// ExportPreviewDialog
|
||||||
|
//
|
||||||
|
AcceptButton = button_Ok;
|
||||||
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
|
CancelButton = button_Cancel;
|
||||||
|
ClientSize = new Size(919, 276);
|
||||||
|
Controls.Add(panel1);
|
||||||
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
|
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||||
|
MaximizeBox = false;
|
||||||
|
MinimizeBox = false;
|
||||||
|
Name = "ExportPreviewDialog";
|
||||||
|
ShowInTaskbar = false;
|
||||||
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
|
Text = "导出预览图";
|
||||||
|
Load += ExportPreviewDialog_Load;
|
||||||
|
panel1.ResumeLayout(false);
|
||||||
|
panel1.PerformLayout();
|
||||||
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
|
tableLayoutPanel1.PerformLayout();
|
||||||
|
tableLayoutPanel2.ResumeLayout(false);
|
||||||
|
((System.ComponentModel.ISupportInitialize)numericUpDown_Width).EndInit();
|
||||||
|
((System.ComponentModel.ISupportInitialize)numericUpDown_Height).EndInit();
|
||||||
|
ResumeLayout(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private Panel panel1;
|
||||||
|
private TableLayoutPanel tableLayoutPanel1;
|
||||||
|
private Label label4;
|
||||||
|
private Label label1;
|
||||||
|
private Label label2;
|
||||||
|
private Label label3;
|
||||||
|
private TextBox textBox_OutputDir;
|
||||||
|
private Button button_SelectOutputDir;
|
||||||
|
private TableLayoutPanel tableLayoutPanel2;
|
||||||
|
private Button button_Ok;
|
||||||
|
private Button button_Cancel;
|
||||||
|
private NumericUpDown numericUpDown_Width;
|
||||||
|
private NumericUpDown numericUpDown_Height;
|
||||||
|
private FolderBrowserDialog folderBrowserDialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
80
SpineViewer/Dialogs/ExportPreviewDialog.cs
Normal file
80
SpineViewer/Dialogs/ExportPreviewDialog.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
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 ExportPreviewDialog: Form
|
||||||
|
{
|
||||||
|
public string OutputDir { get; private set; }
|
||||||
|
public uint PreviewWidth { get; private set; }
|
||||||
|
public uint PreviewHeight { get; private set; }
|
||||||
|
|
||||||
|
public ExportPreviewDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExportPreviewDialog_Load(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
button_SelectOutputDir_Click(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_SelectOutputDir_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
folderBrowserDialog.InitialDirectory = textBox_OutputDir.Text;
|
||||||
|
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
|
||||||
|
{
|
||||||
|
textBox_OutputDir.Text = Path.GetFullPath(folderBrowserDialog.SelectedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_Ok_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var outputDir = textBox_OutputDir.Text;
|
||||||
|
if (File.Exists(outputDir))
|
||||||
|
{
|
||||||
|
MessageBox.Show("输出文件夹无效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(outputDir))
|
||||||
|
{
|
||||||
|
if (MessageBox.Show($"文件夹 {outputDir} 不存在,是否创建?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(outputDir);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Logger.Error(ex.ToString());
|
||||||
|
MessageBox.Show(ex.ToString(), "文件夹创建失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputDir = Path.GetFullPath(outputDir);
|
||||||
|
PreviewWidth = (uint)numericUpDown_Width.Value;
|
||||||
|
PreviewHeight = (uint)numericUpDown_Height.Value;
|
||||||
|
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_Cancel_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = DialogResult.Cancel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3270
SpineViewer/Dialogs/ExportPreviewDialog.resx
Normal file
3270
SpineViewer/Dialogs/ExportPreviewDialog.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.AddExtension = false;
|
||||||
openFileDialog_Skel.AddToRecent = 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
|
||||||
//
|
//
|
||||||
openFileDialog_Atlas.AddExtension = false;
|
openFileDialog_Atlas.AddExtension = false;
|
||||||
openFileDialog_Atlas.AddToRecent = false;
|
openFileDialog_Atlas.AddToRecent = false;
|
||||||
openFileDialog_Atlas.Filter = "atlas 文件 (*.atlas)|*.atlas|所有文件 (*.*)|*.*";
|
openFileDialog_Atlas.Filter = "atlas 文件 (*.atlas)|*.atlas|所有文件 (*.*)|*.*";
|
||||||
|
openFileDialog_Atlas.Title = "选择atlas文件";
|
||||||
//
|
//
|
||||||
// OpenSpineDialog
|
// OpenSpineDialog
|
||||||
//
|
//
|
||||||
@@ -256,6 +258,7 @@
|
|||||||
ShowInTaskbar = false;
|
ShowInTaskbar = false;
|
||||||
StartPosition = FormStartPosition.CenterScreen;
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
Text = "打开骨骼";
|
Text = "打开骨骼";
|
||||||
|
Load += OpenSpineDialog_Load;
|
||||||
panel1.ResumeLayout(false);
|
panel1.ResumeLayout(false);
|
||||||
panel1.PerformLayout();
|
panel1.PerformLayout();
|
||||||
tableLayoutPanel1.ResumeLayout(false);
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace SpineViewer.Dialogs
|
namespace SpineViewer.Dialogs
|
||||||
{
|
{
|
||||||
public partial class OpenSpineDialog : Form
|
public partial class OpenSpineDialog : Form
|
||||||
@@ -26,6 +25,11 @@ namespace SpineViewer.Dialogs
|
|||||||
comboBox_Version.SelectedValue = Spine.Version.V38;
|
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)
|
private void button_SelectSkel_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
openFileDialog_Skel.InitialDirectory = Path.GetDirectoryName(textBox_SkelPath.Text);
|
openFileDialog_Skel.InitialDirectory = Path.GetDirectoryName(textBox_SkelPath.Text);
|
||||||
@@ -48,6 +52,7 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
var skelPath = textBox_SkelPath.Text;
|
var skelPath = textBox_SkelPath.Text;
|
||||||
var atlasPath = textBox_AtlasPath.Text;
|
var atlasPath = textBox_AtlasPath.Text;
|
||||||
|
var version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||||
|
|
||||||
if (!File.Exists(skelPath))
|
if (!File.Exists(skelPath))
|
||||||
{
|
{
|
||||||
@@ -73,9 +78,15 @@ namespace SpineViewer.Dialogs
|
|||||||
atlasPath = Path.GetFullPath(atlasPath);
|
atlasPath = Path.GetFullPath(atlasPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Spine.Spine.ImplementedVersions.Contains(version))
|
||||||
|
{
|
||||||
|
MessageBox.Show($"{version.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SkelPath = skelPath;
|
SkelPath = skelPath;
|
||||||
AtlasPath = atlasPath;
|
AtlasPath = atlasPath;
|
||||||
Version = (Spine.Version)comboBox_Version.SelectedValue;
|
Version = version;
|
||||||
|
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,10 +118,10 @@
|
|||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<metadata name="openFileDialog_Skel.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
<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>
|
||||||
<metadata name="openFileDialog_Atlas.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
<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>
|
</metadata>
|
||||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
<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">
|
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
|||||||
65
SpineViewer/MainForm.Designer.cs
generated
65
SpineViewer/MainForm.Designer.cs
generated
@@ -36,10 +36,15 @@
|
|||||||
toolStripMenuItem_BatchOpen = new ToolStripMenuItem();
|
toolStripMenuItem_BatchOpen = new ToolStripMenuItem();
|
||||||
toolStripSeparator1 = new ToolStripSeparator();
|
toolStripSeparator1 = new ToolStripSeparator();
|
||||||
toolStripMenuItem_Export = new ToolStripMenuItem();
|
toolStripMenuItem_Export = new ToolStripMenuItem();
|
||||||
|
toolStripMenuItem_ExportPreview = new ToolStripMenuItem();
|
||||||
toolStripSeparator2 = new ToolStripSeparator();
|
toolStripSeparator2 = new ToolStripSeparator();
|
||||||
toolStripMenuItem_Exit = new ToolStripMenuItem();
|
toolStripMenuItem_Exit = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_Function = new ToolStripMenuItem();
|
toolStripMenuItem_Function = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_ResetAnimation = 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_Help = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_Diagnostics = new ToolStripMenuItem();
|
toolStripMenuItem_Diagnostics = new ToolStripMenuItem();
|
||||||
toolStripSeparator3 = new ToolStripSeparator();
|
toolStripSeparator3 = new ToolStripSeparator();
|
||||||
@@ -87,7 +92,7 @@
|
|||||||
//
|
//
|
||||||
menuStrip.BackColor = SystemColors.Control;
|
menuStrip.BackColor = SystemColors.Control;
|
||||||
menuStrip.ImageScalingSize = new Size(24, 24);
|
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.Location = new Point(0, 0);
|
||||||
menuStrip.Name = "menuStrip";
|
menuStrip.Name = "menuStrip";
|
||||||
menuStrip.Size = new Size(1741, 32);
|
menuStrip.Size = new Size(1741, 32);
|
||||||
@@ -96,7 +101,7 @@
|
|||||||
//
|
//
|
||||||
// toolStripMenuItem_File
|
// toolStripMenuItem_File
|
||||||
//
|
//
|
||||||
toolStripMenuItem_File.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Open, toolStripMenuItem_BatchOpen, toolStripSeparator1, toolStripMenuItem_Export, toolStripSeparator2, toolStripMenuItem_Exit });
|
toolStripMenuItem_File.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Open, toolStripMenuItem_BatchOpen, toolStripSeparator1, toolStripMenuItem_Export, toolStripMenuItem_ExportPreview, toolStripSeparator2, toolStripMenuItem_Exit });
|
||||||
toolStripMenuItem_File.Name = "toolStripMenuItem_File";
|
toolStripMenuItem_File.Name = "toolStripMenuItem_File";
|
||||||
toolStripMenuItem_File.Size = new Size(84, 28);
|
toolStripMenuItem_File.Size = new Size(84, 28);
|
||||||
toolStripMenuItem_File.Text = "文件(&F)";
|
toolStripMenuItem_File.Text = "文件(&F)";
|
||||||
@@ -129,6 +134,13 @@
|
|||||||
toolStripMenuItem_Export.Text = "导出(&E)...";
|
toolStripMenuItem_Export.Text = "导出(&E)...";
|
||||||
toolStripMenuItem_Export.Click += toolStripMenuItem_Export_Click;
|
toolStripMenuItem_Export.Click += toolStripMenuItem_Export_Click;
|
||||||
//
|
//
|
||||||
|
// toolStripMenuItem_ExportPreview
|
||||||
|
//
|
||||||
|
toolStripMenuItem_ExportPreview.Name = "toolStripMenuItem_ExportPreview";
|
||||||
|
toolStripMenuItem_ExportPreview.Size = new Size(254, 34);
|
||||||
|
toolStripMenuItem_ExportPreview.Text = "导出预览图(&P)...";
|
||||||
|
toolStripMenuItem_ExportPreview.Click += toolStripMenuItem_ExportPreview_Click;
|
||||||
|
//
|
||||||
// toolStripSeparator2
|
// toolStripSeparator2
|
||||||
//
|
//
|
||||||
toolStripSeparator2.Name = "toolStripSeparator2";
|
toolStripSeparator2.Name = "toolStripSeparator2";
|
||||||
@@ -156,6 +168,34 @@
|
|||||||
toolStripMenuItem_ResetAnimation.Text = "重置动画时间(&R)";
|
toolStripMenuItem_ResetAnimation.Text = "重置动画时间(&R)";
|
||||||
toolStripMenuItem_ResetAnimation.Click += toolStripMenuItem_ResetAnimation_Click;
|
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(254, 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_ManageResource.Click += toolStripMenuItem_ManageResource_Click;
|
||||||
|
//
|
||||||
// toolStripMenuItem_Help
|
// toolStripMenuItem_Help
|
||||||
//
|
//
|
||||||
toolStripMenuItem_Help.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Diagnostics, toolStripSeparator3, toolStripMenuItem_About });
|
toolStripMenuItem_Help.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Diagnostics, toolStripSeparator3, toolStripMenuItem_About });
|
||||||
@@ -261,7 +301,7 @@
|
|||||||
splitContainer_Information.Panel2.Controls.Add(splitContainer_Config);
|
splitContainer_Information.Panel2.Controls.Add(splitContainer_Config);
|
||||||
splitContainer_Information.Panel2.Cursor = Cursors.Default;
|
splitContainer_Information.Panel2.Cursor = Cursors.Default;
|
||||||
splitContainer_Information.Size = new Size(744, 848);
|
splitContainer_Information.Size = new Size(744, 848);
|
||||||
splitContainer_Information.SplitterDistance = 327;
|
splitContainer_Information.SplitterDistance = 398;
|
||||||
splitContainer_Information.TabIndex = 1;
|
splitContainer_Information.TabIndex = 1;
|
||||||
splitContainer_Information.TabStop = false;
|
splitContainer_Information.TabStop = false;
|
||||||
splitContainer_Information.SplitterMoved += splitContainer_SplitterMoved;
|
splitContainer_Information.SplitterMoved += splitContainer_SplitterMoved;
|
||||||
@@ -273,7 +313,7 @@
|
|||||||
groupBox_SkelList.Dock = DockStyle.Fill;
|
groupBox_SkelList.Dock = DockStyle.Fill;
|
||||||
groupBox_SkelList.Location = new Point(0, 0);
|
groupBox_SkelList.Location = new Point(0, 0);
|
||||||
groupBox_SkelList.Name = "groupBox_SkelList";
|
groupBox_SkelList.Name = "groupBox_SkelList";
|
||||||
groupBox_SkelList.Size = new Size(327, 848);
|
groupBox_SkelList.Size = new Size(398, 848);
|
||||||
groupBox_SkelList.TabIndex = 0;
|
groupBox_SkelList.TabIndex = 0;
|
||||||
groupBox_SkelList.TabStop = false;
|
groupBox_SkelList.TabStop = false;
|
||||||
groupBox_SkelList.Text = "模型列表";
|
groupBox_SkelList.Text = "模型列表";
|
||||||
@@ -284,7 +324,7 @@
|
|||||||
spineListView.Location = new Point(3, 26);
|
spineListView.Location = new Point(3, 26);
|
||||||
spineListView.Name = "spineListView";
|
spineListView.Name = "spineListView";
|
||||||
spineListView.PropertyGrid = propertyGrid_Spine;
|
spineListView.PropertyGrid = propertyGrid_Spine;
|
||||||
spineListView.Size = new Size(321, 819);
|
spineListView.Size = new Size(392, 819);
|
||||||
spineListView.TabIndex = 0;
|
spineListView.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// propertyGrid_Spine
|
// propertyGrid_Spine
|
||||||
@@ -293,7 +333,7 @@
|
|||||||
propertyGrid_Spine.HelpVisible = false;
|
propertyGrid_Spine.HelpVisible = false;
|
||||||
propertyGrid_Spine.Location = new Point(3, 26);
|
propertyGrid_Spine.Location = new Point(3, 26);
|
||||||
propertyGrid_Spine.Name = "propertyGrid_Spine";
|
propertyGrid_Spine.Name = "propertyGrid_Spine";
|
||||||
propertyGrid_Spine.Size = new Size(407, 470);
|
propertyGrid_Spine.Size = new Size(336, 470);
|
||||||
propertyGrid_Spine.TabIndex = 0;
|
propertyGrid_Spine.TabIndex = 0;
|
||||||
propertyGrid_Spine.ToolbarVisible = false;
|
propertyGrid_Spine.ToolbarVisible = false;
|
||||||
propertyGrid_Spine.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
propertyGrid_Spine.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
||||||
@@ -315,7 +355,7 @@
|
|||||||
//
|
//
|
||||||
splitContainer_Config.Panel2.Controls.Add(groupBox_PreviewConfig);
|
splitContainer_Config.Panel2.Controls.Add(groupBox_PreviewConfig);
|
||||||
splitContainer_Config.Panel2.Cursor = Cursors.Default;
|
splitContainer_Config.Panel2.Cursor = Cursors.Default;
|
||||||
splitContainer_Config.Size = new Size(413, 848);
|
splitContainer_Config.Size = new Size(342, 848);
|
||||||
splitContainer_Config.SplitterDistance = 499;
|
splitContainer_Config.SplitterDistance = 499;
|
||||||
splitContainer_Config.TabIndex = 0;
|
splitContainer_Config.TabIndex = 0;
|
||||||
splitContainer_Config.TabStop = false;
|
splitContainer_Config.TabStop = false;
|
||||||
@@ -328,7 +368,7 @@
|
|||||||
groupBox_SkelConfig.Dock = DockStyle.Fill;
|
groupBox_SkelConfig.Dock = DockStyle.Fill;
|
||||||
groupBox_SkelConfig.Location = new Point(0, 0);
|
groupBox_SkelConfig.Location = new Point(0, 0);
|
||||||
groupBox_SkelConfig.Name = "groupBox_SkelConfig";
|
groupBox_SkelConfig.Name = "groupBox_SkelConfig";
|
||||||
groupBox_SkelConfig.Size = new Size(413, 499);
|
groupBox_SkelConfig.Size = new Size(342, 499);
|
||||||
groupBox_SkelConfig.TabIndex = 0;
|
groupBox_SkelConfig.TabIndex = 0;
|
||||||
groupBox_SkelConfig.TabStop = false;
|
groupBox_SkelConfig.TabStop = false;
|
||||||
groupBox_SkelConfig.Text = "模型参数";
|
groupBox_SkelConfig.Text = "模型参数";
|
||||||
@@ -339,7 +379,7 @@
|
|||||||
groupBox_PreviewConfig.Dock = DockStyle.Fill;
|
groupBox_PreviewConfig.Dock = DockStyle.Fill;
|
||||||
groupBox_PreviewConfig.Location = new Point(0, 0);
|
groupBox_PreviewConfig.Location = new Point(0, 0);
|
||||||
groupBox_PreviewConfig.Name = "groupBox_PreviewConfig";
|
groupBox_PreviewConfig.Name = "groupBox_PreviewConfig";
|
||||||
groupBox_PreviewConfig.Size = new Size(413, 345);
|
groupBox_PreviewConfig.Size = new Size(342, 345);
|
||||||
groupBox_PreviewConfig.TabIndex = 1;
|
groupBox_PreviewConfig.TabIndex = 1;
|
||||||
groupBox_PreviewConfig.TabStop = false;
|
groupBox_PreviewConfig.TabStop = false;
|
||||||
groupBox_PreviewConfig.Text = "画面参数";
|
groupBox_PreviewConfig.Text = "画面参数";
|
||||||
@@ -350,7 +390,7 @@
|
|||||||
propertyGrid_Previewer.HelpVisible = false;
|
propertyGrid_Previewer.HelpVisible = false;
|
||||||
propertyGrid_Previewer.Location = new Point(3, 26);
|
propertyGrid_Previewer.Location = new Point(3, 26);
|
||||||
propertyGrid_Previewer.Name = "propertyGrid_Previewer";
|
propertyGrid_Previewer.Name = "propertyGrid_Previewer";
|
||||||
propertyGrid_Previewer.Size = new Size(407, 316);
|
propertyGrid_Previewer.Size = new Size(336, 316);
|
||||||
propertyGrid_Previewer.TabIndex = 1;
|
propertyGrid_Previewer.TabIndex = 1;
|
||||||
propertyGrid_Previewer.ToolbarVisible = false;
|
propertyGrid_Previewer.ToolbarVisible = false;
|
||||||
propertyGrid_Previewer.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
propertyGrid_Previewer.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
||||||
@@ -465,5 +505,10 @@
|
|||||||
private ToolStripMenuItem toolStripMenuItem_ResetAnimation;
|
private ToolStripMenuItem toolStripMenuItem_ResetAnimation;
|
||||||
private ToolStripMenuItem toolStripMenuItem_Diagnostics;
|
private ToolStripMenuItem toolStripMenuItem_Diagnostics;
|
||||||
private ToolStripSeparator toolStripSeparator3;
|
private ToolStripSeparator toolStripSeparator3;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_Download;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_ManageResource;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_Tool;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_ConvertFileFormat;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_ExportPreview;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using SpineViewer.Spine;
|
using SpineViewer.Spine;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
namespace SpineViewer
|
namespace SpineViewer
|
||||||
{
|
{
|
||||||
@@ -14,11 +16,11 @@ namespace SpineViewer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־<EFBFBD><EFBFBD>
|
/// 初始化窗口日志器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeLogConfiguration()
|
private void InitializeLogConfiguration()
|
||||||
{
|
{
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־
|
// 窗口日志
|
||||||
var rtbTarget = new NLog.Windows.Forms.RichTextBoxTarget
|
var rtbTarget = new NLog.Windows.Forms.RichTextBoxTarget
|
||||||
{
|
{
|
||||||
Name = "rtbTarget",
|
Name = "rtbTarget",
|
||||||
@@ -41,68 +43,6 @@ namespace SpineViewer
|
|||||||
LogManager.ReconfigExistingLoggers();
|
LogManager.ReconfigExistingLoggers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExportPng_Work(object? sender, DoWorkEventArgs e)
|
|
||||||
{
|
|
||||||
var worker = sender as BackgroundWorker;
|
|
||||||
var arguments = e.Argument as Dialogs.ExportPngDialog;
|
|
||||||
var outputDir = arguments.OutputDir;
|
|
||||||
var duration = arguments.Duration;
|
|
||||||
var fps = arguments.Fps;
|
|
||||||
var timestamp = DateTime.Now.ToString("yyMMddHHmmss");
|
|
||||||
|
|
||||||
var resolution = spinePreviewer.Resolution;
|
|
||||||
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); // <20><>֡<EFBFBD><D6A1>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>
|
|
||||||
|
|
||||||
spinePreviewer.StopPreview();
|
|
||||||
|
|
||||||
lock (spineListView.Spines)
|
|
||||||
{
|
|
||||||
var spinesReverse = spineListView.Spines.Reverse();
|
|
||||||
|
|
||||||
// <20><><EFBFBD>ö<EFBFBD><C3B6><EFBFBD>ʱ<EFBFBD><CAB1>
|
|
||||||
foreach (var spine in spinesReverse)
|
|
||||||
spine.CurrentAnimation = spine.CurrentAnimation;
|
|
||||||
|
|
||||||
Program.Logger.Info(
|
|
||||||
"Begin exporting png frames to output dir {}, duration: {}, fps: {}, totally {} spines",
|
|
||||||
[outputDir, duration, fps, spinesReverse.Count()]
|
|
||||||
);
|
|
||||||
|
|
||||||
// <20><>֡<EFBFBD><D6A1><EFBFBD><EFBFBD>
|
|
||||||
var success = 0;
|
|
||||||
worker.ReportProgress(0, $"<22>Ѵ<EFBFBD><D1B4><EFBFBD> 0/{frameCount}");
|
|
||||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
|
||||||
{
|
|
||||||
if (worker.CancellationPending)
|
|
||||||
break;
|
|
||||||
|
|
||||||
tex.Clear(SFML.Graphics.Color.Transparent);
|
|
||||||
|
|
||||||
foreach (var spine in spinesReverse)
|
|
||||||
{
|
|
||||||
tex.Draw(spine);
|
|
||||||
spine.Update(delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
tex.Display();
|
|
||||||
using (var img = tex.Texture.CopyToImage())
|
|
||||||
{
|
|
||||||
img.SaveToFile(Path.Combine(outputDir, $"{timestamp}_{fps}_{frameIndex:d6}.png"));
|
|
||||||
}
|
|
||||||
|
|
||||||
success++;
|
|
||||||
worker.ReportProgress((int)((frameIndex + 1) * 100.0) / frameCount, $"<22>Ѵ<EFBFBD><D1B4><EFBFBD> {frameIndex + 1}/{frameCount}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Program.Logger.Info("Exporting done: {}/{}", success, frameCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
spinePreviewer.StartPreview();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MainForm_Load(object sender, EventArgs e)
|
private void MainForm_Load(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
spinePreviewer.StartPreview();
|
spinePreviewer.StartPreview();
|
||||||
@@ -129,7 +69,7 @@ namespace SpineViewer
|
|||||||
{
|
{
|
||||||
if (spineListView.Spines.Count <= 0)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,6 +84,11 @@ namespace SpineViewer
|
|||||||
progressDialog.ShowDialog();
|
progressDialog.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_ExportPreview_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
spineListView.ExportPreviews();
|
||||||
|
}
|
||||||
|
|
||||||
private void toolStripMenuItem_Exit_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_Exit_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Close();
|
Close();
|
||||||
@@ -158,6 +103,23 @@ 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 toolStripMenuItem_ManageResource_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void toolStripMenuItem_About_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_About_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
(new Dialogs.AboutDialog()).ShowDialog();
|
(new Dialogs.AboutDialog()).ShowDialog();
|
||||||
@@ -175,5 +137,132 @@ namespace SpineViewer
|
|||||||
private void propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e) { (sender as PropertyGrid)?.Refresh(); }
|
private void propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e) { (sender as PropertyGrid)?.Refresh(); }
|
||||||
|
|
||||||
private void spinePreviewer_MouseUp(object sender, MouseEventArgs e) { propertyGrid_Spine.Refresh(); }
|
private void spinePreviewer_MouseUp(object sender, MouseEventArgs e) { propertyGrid_Spine.Refresh(); }
|
||||||
|
|
||||||
|
private void ExportPng_Work(object? sender, DoWorkEventArgs e)
|
||||||
|
{
|
||||||
|
var worker = sender as BackgroundWorker;
|
||||||
|
var arguments = e.Argument as Dialogs.ExportPngDialog;
|
||||||
|
var outputDir = arguments.OutputDir;
|
||||||
|
var duration = arguments.Duration;
|
||||||
|
var fps = arguments.Fps;
|
||||||
|
var timestamp = DateTime.Now.ToString("yyMMddHHmmss");
|
||||||
|
|
||||||
|
var resolution = spinePreviewer.Resolution;
|
||||||
|
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); // 零帧开始导出
|
||||||
|
|
||||||
|
spinePreviewer.StopPreview();
|
||||||
|
|
||||||
|
lock (spineListView.Spines)
|
||||||
|
{
|
||||||
|
var spinesReverse = spineListView.Spines.Reverse();
|
||||||
|
|
||||||
|
// 重置动画时间
|
||||||
|
foreach (var spine in spinesReverse)
|
||||||
|
spine.CurrentAnimation = spine.CurrentAnimation;
|
||||||
|
|
||||||
|
Program.Logger.Info(
|
||||||
|
"Begin exporting png frames to output dir {}, duration: {}, fps: {}, totally {} spines",
|
||||||
|
[outputDir, duration, fps, spinesReverse.Count()]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 逐帧导出
|
||||||
|
var success = 0;
|
||||||
|
worker.ReportProgress(0, $"已处理 0/{frameCount}");
|
||||||
|
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
||||||
|
{
|
||||||
|
if (worker.CancellationPending)
|
||||||
|
break;
|
||||||
|
|
||||||
|
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
|
||||||
|
foreach (var spine in spinesReverse)
|
||||||
|
{
|
||||||
|
tex.Draw(spine);
|
||||||
|
spine.Update(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
tex.Display();
|
||||||
|
using (var img = tex.Texture.CopyToImage())
|
||||||
|
{
|
||||||
|
img.SaveToFile(Path.Combine(outputDir, $"{timestamp}_{fps}_{frameIndex:d6}.png"));
|
||||||
|
}
|
||||||
|
|
||||||
|
success++;
|
||||||
|
worker.ReportProgress((int)((frameIndex + 1) * 100.0) / frameCount, $"已处理 {frameIndex + 1}/{frameCount}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.Logger.Info("Exporting done: {}/{}", success, frameCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
spinePreviewer.StartPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 srcVersion = arguments.SourceVersion;
|
||||||
|
var tgtVersion = arguments.TargetVersion;
|
||||||
|
var jsonSource = arguments.JsonSource;
|
||||||
|
var jsonTarget = arguments.JsonTarget;
|
||||||
|
var newSuffix = jsonTarget ? ".json" : ".skel";
|
||||||
|
|
||||||
|
if (jsonTarget == jsonSource)
|
||||||
|
{
|
||||||
|
if (tgtVersion == srcVersion)
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
newSuffix += $".{tgtVersion.ToString().ToLower()}"; // TODO: 仅转换版本的情况下考虑文件覆盖问题
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalCount = skelPaths.Length;
|
||||||
|
int success = 0;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
SkeletonConverter srcCvter = SkeletonConverter.New(srcVersion);
|
||||||
|
SkeletonConverter tgtCvter = tgtVersion == srcVersion ? srcCvter : SkeletonConverter.New(tgtVersion);
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
var root = jsonSource ? srcCvter.ReadJson(skelPath) : srcCvter.ReadBinary(skelPath);
|
||||||
|
if (tgtVersion != srcVersion) root = srcCvter.ToVersion(root, tgtVersion);
|
||||||
|
if (jsonTarget) tgtCvter.WriteJson(root, newPath); else tgtCvter.WriteBinary(root, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace SpineViewer
|
namespace SpineViewer
|
||||||
@@ -28,18 +28,18 @@ namespace SpineViewer
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Fatal(ex.ToString());
|
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>
|
/// <summary>
|
||||||
/// <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>־<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
/// 初始化日志配置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void InitializeLogConfiguration()
|
private static void InitializeLogConfiguration()
|
||||||
{
|
{
|
||||||
var config = new NLog.Config.LoggingConfiguration();
|
var config = new NLog.Config.LoggingConfiguration();
|
||||||
|
|
||||||
// <EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>־
|
// 文件日志
|
||||||
var fileTarget = new NLog.Targets.FileTarget("fileTarget")
|
var fileTarget = new NLog.Targets.FileTarget("fileTarget")
|
||||||
{
|
{
|
||||||
Encoding = System.Text.Encoding.UTF8,
|
Encoding = System.Text.Encoding.UTF8,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace SpineViewer.Spine
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// SFML 混合模式
|
/// SFML 混合模式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class BlendMode
|
public static class BlendModeSFML
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Alpha Blend
|
/// Alpha Blend
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -8,11 +8,13 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SpineRuntime21;
|
using SpineRuntime21;
|
||||||
|
|
||||||
namespace SpineViewer.Spine.Implementations
|
namespace SpineViewer.Spine.Implementations.Spine
|
||||||
{
|
{
|
||||||
[SpineImplementation(Version.V21)]
|
[SpineImplementation(Version.V21)]
|
||||||
internal class Spine21 : Spine
|
internal class Spine21 : SpineViewer.Spine.Spine
|
||||||
{
|
{
|
||||||
|
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||||
|
|
||||||
private class TextureLoader : SpineRuntime21.TextureLoader
|
private class TextureLoader : SpineRuntime21.TextureLoader
|
||||||
{
|
{
|
||||||
public void Load(AtlasPage page, string path)
|
public void Load(AtlasPage page, string path)
|
||||||
@@ -88,6 +90,8 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
atlas.Dispose();
|
atlas.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string FileVersion { get => skeletonData.Version; }
|
||||||
|
|
||||||
public override float Scale
|
public override float Scale
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -170,8 +174,15 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
|
|
||||||
public override string CurrentAnimation
|
public override string CurrentAnimation
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
set
|
||||||
|
{
|
||||||
|
if (value == EMPTY_ANIMATION)
|
||||||
|
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||||
|
else if (animationNames.Contains(value))
|
||||||
|
animationState.SetAnimation(0, value, true);
|
||||||
|
Update(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override RectangleF Bounds
|
public override RectangleF Bounds
|
||||||
@@ -234,14 +245,14 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
skeleton.UpdateWorldTransform();
|
skeleton.UpdateWorldTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
//private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime21.BlendMode spineBlendMode)
|
//private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||||
//{
|
//{
|
||||||
// return spineBlendMode switch
|
// return spineBlendMode switch
|
||||||
// {
|
// {
|
||||||
// SpineRuntime21.BlendMode.Normal => BlendMode.Normal,
|
// BlendMode.Normal => BlendMode.Normal,
|
||||||
// SpineRuntime21.BlendMode.Additive => BlendMode.Additive,
|
// BlendMode.Additive => BlendMode.Additive,
|
||||||
// SpineRuntime21.BlendMode.Multiply => BlendMode.Multiply,
|
// BlendMode.Multiply => BlendMode.Multiply,
|
||||||
// SpineRuntime21.BlendMode.Screen => BlendMode.Screen,
|
// BlendMode.Screen => BlendMode.Screen,
|
||||||
// _ => throw new NotImplementedException($"{spineBlendMode}"),
|
// _ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
// };
|
// };
|
||||||
//}
|
//}
|
||||||
@@ -311,14 +322,14 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 似乎 2.1.x 也没有 BlendMode
|
// 似乎 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;
|
states.Texture ??= texture;
|
||||||
if (states.BlendMode != blendMode || states.Texture != texture)
|
if (states.BlendMode != blendMode || states.Texture != texture)
|
||||||
{
|
{
|
||||||
if (vertexArray.VertexCount > 0)
|
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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -363,7 +374,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
//clipping.ClipEnd(slot);
|
//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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -8,11 +8,13 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SpineRuntime36;
|
using SpineRuntime36;
|
||||||
|
|
||||||
namespace SpineViewer.Spine.Implementations
|
namespace SpineViewer.Spine.Implementations.Spine
|
||||||
{
|
{
|
||||||
[SpineImplementation(Version.V36)]
|
[SpineImplementation(Version.V36)]
|
||||||
internal class Spine36 : Spine
|
internal class Spine36 : SpineViewer.Spine.Spine
|
||||||
{
|
{
|
||||||
|
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||||
|
|
||||||
private class TextureLoader : SpineRuntime36.TextureLoader
|
private class TextureLoader : SpineRuntime36.TextureLoader
|
||||||
{
|
{
|
||||||
public void Load(AtlasPage page, string path)
|
public void Load(AtlasPage page, string path)
|
||||||
@@ -87,6 +89,8 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
atlas.Dispose();
|
atlas.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string FileVersion { get => skeletonData.Version; }
|
||||||
|
|
||||||
public override float Scale
|
public override float Scale
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -168,8 +172,15 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
|
|
||||||
public override string CurrentAnimation
|
public override string CurrentAnimation
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
set
|
||||||
|
{
|
||||||
|
if (value == EMPTY_ANIMATION)
|
||||||
|
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||||
|
else if (animationNames.Contains(value))
|
||||||
|
animationState.SetAnimation(0, value, true);
|
||||||
|
Update(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override RectangleF Bounds
|
public override RectangleF Bounds
|
||||||
@@ -192,14 +203,14 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
skeleton.UpdateWorldTransform();
|
skeleton.UpdateWorldTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime36.BlendMode spineBlendMode)
|
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
SpineRuntime36.BlendMode.Normal => BlendMode.Normal,
|
BlendMode.Normal => BlendModeSFML.Normal,
|
||||||
SpineRuntime36.BlendMode.Additive => BlendMode.Additive,
|
BlendMode.Additive => BlendModeSFML.Additive,
|
||||||
SpineRuntime36.BlendMode.Multiply => BlendMode.Multiply,
|
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||||
SpineRuntime36.BlendMode.Screen => BlendMode.Screen,
|
BlendMode.Screen => BlendModeSFML.Screen,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -274,7 +285,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
{
|
{
|
||||||
if (vertexArray.VertexCount > 0)
|
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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -319,7 +330,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
clipping.ClipEnd(slot);
|
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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -5,11 +5,13 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SpineRuntime37;
|
using SpineRuntime37;
|
||||||
|
|
||||||
namespace SpineViewer.Spine.Implementations
|
namespace SpineViewer.Spine.Implementations.Spine
|
||||||
{
|
{
|
||||||
[SpineImplementation(Version.V37)]
|
[SpineImplementation(Version.V37)]
|
||||||
internal class Spine37 : Spine
|
internal class Spine37 : SpineViewer.Spine.Spine
|
||||||
{
|
{
|
||||||
|
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||||
|
|
||||||
private class TextureLoader : SpineRuntime37.TextureLoader
|
private class TextureLoader : SpineRuntime37.TextureLoader
|
||||||
{
|
{
|
||||||
public void Load(AtlasPage page, string path)
|
public void Load(AtlasPage page, string path)
|
||||||
@@ -86,6 +88,8 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
atlas.Dispose();
|
atlas.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string FileVersion { get => skeletonData.Version; }
|
||||||
|
|
||||||
public override float Scale
|
public override float Scale
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -175,8 +179,15 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
|
|
||||||
public override string CurrentAnimation
|
public override string CurrentAnimation
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
set
|
||||||
|
{
|
||||||
|
if (value == EMPTY_ANIMATION)
|
||||||
|
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||||
|
else if (animationNames.Contains(value))
|
||||||
|
animationState.SetAnimation(0, value, true);
|
||||||
|
Update(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override RectangleF Bounds
|
public override RectangleF Bounds
|
||||||
@@ -199,14 +210,14 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
skeleton.UpdateWorldTransform();
|
skeleton.UpdateWorldTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime37.BlendMode spineBlendMode)
|
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
SpineRuntime37.BlendMode.Normal => BlendMode.Normal,
|
BlendMode.Normal => BlendModeSFML.Normal,
|
||||||
SpineRuntime37.BlendMode.Additive => BlendMode.Additive,
|
BlendMode.Additive => BlendModeSFML.Additive,
|
||||||
SpineRuntime37.BlendMode.Multiply => BlendMode.Multiply,
|
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||||
SpineRuntime37.BlendMode.Screen => BlendMode.Screen,
|
BlendMode.Screen => BlendModeSFML.Screen,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -282,7 +293,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
// 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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -327,7 +338,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
clipping.ClipEnd(slot);
|
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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -7,13 +7,14 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SpineRuntime38;
|
using SpineRuntime38;
|
||||||
using SpineRuntime38.Attachments;
|
using SpineRuntime38.Attachments;
|
||||||
using SpineViewer.Spine;
|
|
||||||
|
|
||||||
namespace SpineViewer.Spine.Implementations
|
namespace SpineViewer.Spine.Implementations.Spine
|
||||||
{
|
{
|
||||||
[SpineImplementation(Version.V38)]
|
[SpineImplementation(Version.V38)]
|
||||||
internal class Spine38 : Spine
|
internal class Spine38 : SpineViewer.Spine.Spine
|
||||||
{
|
{
|
||||||
|
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||||
|
|
||||||
private class TextureLoader : SpineRuntime38.TextureLoader
|
private class TextureLoader : SpineRuntime38.TextureLoader
|
||||||
{
|
{
|
||||||
public void Load(AtlasPage page, string path)
|
public void Load(AtlasPage page, string path)
|
||||||
@@ -90,6 +91,8 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
atlas.Dispose();
|
atlas.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string FileVersion { get => skeletonData.Version; }
|
||||||
|
|
||||||
public override float Scale
|
public override float Scale
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -179,8 +182,15 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
|
|
||||||
public override string CurrentAnimation
|
public override string CurrentAnimation
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
set
|
||||||
|
{
|
||||||
|
if (value == EMPTY_ANIMATION)
|
||||||
|
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||||
|
else if (animationNames.Contains(value))
|
||||||
|
animationState.SetAnimation(0, value, true);
|
||||||
|
Update(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override RectangleF Bounds
|
public override RectangleF Bounds
|
||||||
@@ -203,14 +213,14 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
skeleton.UpdateWorldTransform();
|
skeleton.UpdateWorldTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime38.BlendMode spineBlendMode)
|
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
SpineRuntime38.BlendMode.Normal => BlendMode.Normal,
|
BlendMode.Normal => BlendModeSFML.Normal,
|
||||||
SpineRuntime38.BlendMode.Additive => BlendMode.Additive,
|
BlendMode.Additive => BlendModeSFML.Additive,
|
||||||
SpineRuntime38.BlendMode.Multiply => BlendMode.Multiply,
|
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||||
SpineRuntime38.BlendMode.Screen => BlendMode.Screen,
|
BlendMode.Screen => BlendModeSFML.Screen,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -286,7 +296,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
// 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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -331,7 +341,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
clipping.ClipEnd(slot);
|
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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -7,11 +7,13 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SpineRuntime40;
|
using SpineRuntime40;
|
||||||
|
|
||||||
namespace SpineViewer.Spine.Implementations
|
namespace SpineViewer.Spine.Implementations.Spine
|
||||||
{
|
{
|
||||||
[SpineImplementation(Version.V40)]
|
[SpineImplementation(Version.V40)]
|
||||||
internal class Spine40 : Spine
|
internal class Spine40 : SpineViewer.Spine.Spine
|
||||||
{
|
{
|
||||||
|
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||||
|
|
||||||
private class TextureLoader : SpineRuntime40.TextureLoader
|
private class TextureLoader : SpineRuntime40.TextureLoader
|
||||||
{
|
{
|
||||||
public void Load(AtlasPage page, string path)
|
public void Load(AtlasPage page, string path)
|
||||||
@@ -88,6 +90,8 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
atlas.Dispose();
|
atlas.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string FileVersion { get => skeletonData.Version; }
|
||||||
|
|
||||||
public override float Scale
|
public override float Scale
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -177,8 +181,15 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
|
|
||||||
public override string CurrentAnimation
|
public override string CurrentAnimation
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
set
|
||||||
|
{
|
||||||
|
if (value == EMPTY_ANIMATION)
|
||||||
|
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||||
|
else if (animationNames.Contains(value))
|
||||||
|
animationState.SetAnimation(0, value, true);
|
||||||
|
Update(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override RectangleF Bounds
|
public override RectangleF Bounds
|
||||||
@@ -201,14 +212,14 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
skeleton.UpdateWorldTransform();
|
skeleton.UpdateWorldTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime40.BlendMode spineBlendMode)
|
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
SpineRuntime40.BlendMode.Normal => BlendMode.Normal,
|
BlendMode.Normal => BlendModeSFML.Normal,
|
||||||
SpineRuntime40.BlendMode.Additive => BlendMode.Additive,
|
BlendMode.Additive => BlendModeSFML.Additive,
|
||||||
SpineRuntime40.BlendMode.Multiply => BlendMode.Multiply,
|
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||||
SpineRuntime40.BlendMode.Screen => BlendMode.Screen,
|
BlendMode.Screen => BlendModeSFML.Screen,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -284,7 +295,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
// 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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -329,7 +340,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
clipping.ClipEnd(slot);
|
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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -7,11 +7,13 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SpineRuntime41;
|
using SpineRuntime41;
|
||||||
|
|
||||||
namespace SpineViewer.Spine.Implementations
|
namespace SpineViewer.Spine.Implementations.Spine
|
||||||
{
|
{
|
||||||
[SpineImplementation(Version.V41)]
|
[SpineImplementation(Version.V41)]
|
||||||
internal class Spine41 : Spine
|
internal class Spine41 : SpineViewer.Spine.Spine
|
||||||
{
|
{
|
||||||
|
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||||
|
|
||||||
private class TextureLoader : SpineRuntime41.TextureLoader
|
private class TextureLoader : SpineRuntime41.TextureLoader
|
||||||
{
|
{
|
||||||
public void Load(AtlasPage page, string path)
|
public void Load(AtlasPage page, string path)
|
||||||
@@ -88,6 +90,8 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
atlas.Dispose();
|
atlas.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string FileVersion { get => skeletonData.Version; }
|
||||||
|
|
||||||
public override float Scale
|
public override float Scale
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -177,8 +181,15 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
|
|
||||||
public override string CurrentAnimation
|
public override string CurrentAnimation
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
set
|
||||||
|
{
|
||||||
|
if (value == EMPTY_ANIMATION)
|
||||||
|
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||||
|
else if (animationNames.Contains(value))
|
||||||
|
animationState.SetAnimation(0, value, true);
|
||||||
|
Update(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override RectangleF Bounds
|
public override RectangleF Bounds
|
||||||
@@ -201,14 +212,14 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
skeleton.UpdateWorldTransform();
|
skeleton.UpdateWorldTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime41.BlendMode spineBlendMode)
|
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
SpineRuntime41.BlendMode.Normal => BlendMode.Normal,
|
BlendMode.Normal => BlendModeSFML.Normal,
|
||||||
SpineRuntime41.BlendMode.Additive => BlendMode.Additive,
|
BlendMode.Additive => BlendModeSFML.Additive,
|
||||||
SpineRuntime41.BlendMode.Multiply => BlendMode.Multiply,
|
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||||
SpineRuntime41.BlendMode.Screen => BlendMode.Screen,
|
BlendMode.Screen => BlendModeSFML.Screen,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -284,7 +295,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
// 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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -329,7 +340,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
clipping.ClipEnd(slot);
|
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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -7,11 +7,13 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SpineRuntime42;
|
using SpineRuntime42;
|
||||||
|
|
||||||
namespace SpineViewer.Spine.Implementations
|
namespace SpineViewer.Spine.Implementations.Spine
|
||||||
{
|
{
|
||||||
[SpineImplementation(Version.V42)]
|
[SpineImplementation(Version.V42)]
|
||||||
internal class Spine42 : Spine
|
internal class Spine42 : SpineViewer.Spine.Spine
|
||||||
{
|
{
|
||||||
|
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||||
|
|
||||||
private class TextureLoader : SpineRuntime42.TextureLoader
|
private class TextureLoader : SpineRuntime42.TextureLoader
|
||||||
{
|
{
|
||||||
public void Load(AtlasPage page, string path)
|
public void Load(AtlasPage page, string path)
|
||||||
@@ -88,6 +90,8 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
atlas.Dispose();
|
atlas.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string FileVersion { get => skeletonData.Version; }
|
||||||
|
|
||||||
public override float Scale
|
public override float Scale
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -177,8 +181,15 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
|
|
||||||
public override string CurrentAnimation
|
public override string CurrentAnimation
|
||||||
{
|
{
|
||||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
set
|
||||||
|
{
|
||||||
|
if (value == EMPTY_ANIMATION)
|
||||||
|
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||||
|
else if (animationNames.Contains(value))
|
||||||
|
animationState.SetAnimation(0, value, true);
|
||||||
|
Update(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override RectangleF Bounds
|
public override RectangleF Bounds
|
||||||
@@ -201,14 +212,14 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
|
skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime42.BlendMode spineBlendMode)
|
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||||
{
|
{
|
||||||
return spineBlendMode switch
|
return spineBlendMode switch
|
||||||
{
|
{
|
||||||
SpineRuntime42.BlendMode.Normal => BlendMode.Normal,
|
BlendMode.Normal => BlendModeSFML.Normal,
|
||||||
SpineRuntime42.BlendMode.Additive => BlendMode.Additive,
|
BlendMode.Additive => BlendModeSFML.Additive,
|
||||||
SpineRuntime42.BlendMode.Multiply => BlendMode.Multiply,
|
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||||
SpineRuntime42.BlendMode.Screen => BlendMode.Screen,
|
BlendMode.Screen => BlendModeSFML.Screen,
|
||||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -284,7 +295,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
if (vertexArray.VertexCount > 0)
|
if (vertexArray.VertexCount > 0)
|
||||||
{
|
{
|
||||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
// 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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
@@ -329,7 +340,7 @@ namespace SpineViewer.Spine.Implementations
|
|||||||
clipping.ClipEnd(slot);
|
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;
|
states.Shader = FragmentShader;
|
||||||
else
|
else
|
||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
333
SpineViewer/Spine/SkeletonConverter.cs
Normal file
333
SpineViewer/Spine/SkeletonConverter.cs
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
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>
|
||||||
|
public abstract JsonObject ReadBinary(string binPath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 Json 对象写入二进制骨骼文件
|
||||||
|
/// </summary>
|
||||||
|
public abstract void WriteBinary(JsonObject root, string binPath, bool nonessential = false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取 Json 对象
|
||||||
|
/// </summary>
|
||||||
|
public 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>
|
||||||
|
public void WriteJson(JsonObject root, string jsonPath)
|
||||||
|
{
|
||||||
|
using var output = File.Create(jsonPath);
|
||||||
|
using var writer = new Utf8JsonWriter(output, jsonWriterOptions);
|
||||||
|
root.WriteTo(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 转换到目标版本
|
||||||
|
/// </summary>
|
||||||
|
public abstract JsonObject ToVersion(JsonObject root, Version version);
|
||||||
|
|
||||||
|
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> StringTable = 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(string val)
|
||||||
|
{
|
||||||
|
if (val is null)
|
||||||
|
{
|
||||||
|
WriteVarInt(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int index = StringTable.IndexOf(val);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
StringTable.Add(val);
|
||||||
|
index = StringTable.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
|
namespace SpineViewer.Spine
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Spine 实现类标记
|
/// Spine 实现类标记
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -38,10 +36,26 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Spine : SFML.Graphics.Drawable, IDisposable
|
public abstract class Spine : SFML.Graphics.Drawable, IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 空动画标记
|
||||||
|
/// </summary>
|
||||||
|
public const string EMPTY_ANIMATION = "<Empty>";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预览图大小
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Size PREVIEW_SIZE = new(256, 256);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 缩放最小值
|
||||||
|
/// </summary>
|
||||||
|
public const float SCALE_MIN = 0.001f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 实现类缓存
|
/// 实现类缓存
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Dictionary<Version, Type> ImplementationTypes = [];
|
private static readonly Dictionary<Version, Type> ImplementationTypes = [];
|
||||||
|
public static readonly Dictionary<Version, Type>.KeyCollection ImplementedVersions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用于解决 PMA 和渐变动画问题的片段着色器
|
/// 用于解决 PMA 和渐变动画问题的片段着色器
|
||||||
@@ -70,10 +84,13 @@ namespace SpineViewer.Spine
|
|||||||
var attr = type.GetCustomAttribute<SpineImplementationAttribute>();
|
var attr = type.GetCustomAttribute<SpineImplementationAttribute>();
|
||||||
if (attr is not null)
|
if (attr is not null)
|
||||||
{
|
{
|
||||||
|
if (ImplementationTypes.ContainsKey(attr.Version))
|
||||||
|
throw new InvalidOperationException($"Multiple implementations found: {attr.Version}");
|
||||||
ImplementationTypes[attr.Version] = type;
|
ImplementationTypes[attr.Version] = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Program.Logger.Debug("Find Spine implementations: [{}]", string.Join(", ", ImplementationTypes.Keys));
|
Program.Logger.Debug("Find Spine implementations: [{}]", string.Join(", ", ImplementationTypes.Keys));
|
||||||
|
ImplementedVersions = ImplementationTypes.Keys;
|
||||||
|
|
||||||
// 加载 FragmentShader
|
// 加载 FragmentShader
|
||||||
try
|
try
|
||||||
@@ -101,6 +118,11 @@ namespace SpineViewer.Spine
|
|||||||
return (Spine)Activator.CreateInstance(spineType, skelPath, atlasPath);
|
return (Spine)Activator.CreateInstance(spineType, skelPath, atlasPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 标识符
|
||||||
|
/// </summary>
|
||||||
|
public readonly string ID = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造函数
|
/// 构造函数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -125,19 +147,13 @@ namespace SpineViewer.Spine
|
|||||||
|
|
||||||
~Spine() { Dispose(false); }
|
~Spine() { Dispose(false); }
|
||||||
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
|
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
|
||||||
protected virtual void Dispose(bool disposing) { }
|
protected virtual void Dispose(bool disposing) { preview?.Dispose(); }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 缩放最小值
|
|
||||||
/// </summary>
|
|
||||||
[Browsable(false)]
|
|
||||||
public const float SCALE_MIN = 0.001f;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取所属版本
|
/// 获取所属版本
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TypeConverter(typeof(VersionTypeConverter))]
|
[TypeConverter(typeof(VersionConverter))]
|
||||||
[Category("基本信息"), DisplayName("版本")]
|
[Category("基本信息"), DisplayName("运行时版本")]
|
||||||
public Version Version { get; }
|
public Version Version { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -155,6 +171,12 @@ namespace SpineViewer.Spine
|
|||||||
[Category("基本信息"), DisplayName("名称")]
|
[Category("基本信息"), DisplayName("名称")]
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取所属文件版本
|
||||||
|
/// </summary>
|
||||||
|
[Category("基本信息"), DisplayName("文件版本")]
|
||||||
|
public abstract string FileVersion { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 缩放比例
|
/// 缩放比例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -164,7 +186,7 @@ namespace SpineViewer.Spine
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 位置
|
/// 位置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TypeConverter(typeof(PointFTypeConverter))]
|
[TypeConverter(typeof(PointFConverter))]
|
||||||
[Category("变换"), DisplayName("位置")]
|
[Category("变换"), DisplayName("位置")]
|
||||||
public abstract PointF Position { get; set; }
|
public abstract PointF Position { get; set; }
|
||||||
|
|
||||||
@@ -191,7 +213,7 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public ReadOnlyCollection<string> AnimationNames { get => animationNames.AsReadOnly(); }
|
public ReadOnlyCollection<string> AnimationNames { get => animationNames.AsReadOnly(); }
|
||||||
protected List<string> animationNames = [];
|
protected List<string> animationNames = [EMPTY_ANIMATION];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 默认动画名称
|
/// 默认动画名称
|
||||||
@@ -202,7 +224,7 @@ namespace SpineViewer.Spine
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前动画名称
|
/// 当前动画名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TypeConverter(typeof(AnimationTypeConverter))]
|
[TypeConverter(typeof(AnimationConverter))]
|
||||||
[Category("动画"), DisplayName("当前动画")]
|
[Category("动画"), DisplayName("当前动画")]
|
||||||
public abstract string CurrentAnimation { get; set; }
|
public abstract string CurrentAnimation { get; set; }
|
||||||
|
|
||||||
@@ -218,6 +240,61 @@ namespace SpineViewer.Spine
|
|||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public abstract RectangleF Bounds { get; }
|
public abstract RectangleF Bounds { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 骨骼预览图
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
public Image Preview
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (preview is null)
|
||||||
|
{
|
||||||
|
using var img = GetPreview((uint)PREVIEW_SIZE.Width, (uint)PREVIEW_SIZE.Height);
|
||||||
|
img.SaveToMemory(out var imgBuffer, "bmp");
|
||||||
|
using var stream = new MemoryStream(imgBuffer);
|
||||||
|
preview = new Bitmap(stream);
|
||||||
|
}
|
||||||
|
return preview;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Image preview = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定尺寸的预览图
|
||||||
|
/// </summary>
|
||||||
|
public SFML.Graphics.Image GetPreview(uint width, uint height)
|
||||||
|
{
|
||||||
|
var curAnimation = CurrentAnimation;
|
||||||
|
CurrentAnimation = EMPTY_ANIMATION;
|
||||||
|
var bounds = Bounds;
|
||||||
|
|
||||||
|
float viewX = width;
|
||||||
|
float viewY = height;
|
||||||
|
float sizeX = bounds.Width;
|
||||||
|
float sizeY = bounds.Height;
|
||||||
|
|
||||||
|
var scale = 1f;
|
||||||
|
if ((sizeY / sizeX) < (viewY / viewX))
|
||||||
|
scale = sizeX / viewX;// 相同的 X, 视窗 Y 更大
|
||||||
|
else
|
||||||
|
scale = sizeY / viewY;// 相同的 Y, 视窗 X 更大
|
||||||
|
|
||||||
|
viewX *= scale;
|
||||||
|
viewY *= scale;
|
||||||
|
|
||||||
|
using var tex = new SFML.Graphics.RenderTexture(width, height);
|
||||||
|
var view = tex.GetView();
|
||||||
|
view.Center = new(bounds.X + viewX / 2, bounds.Y + viewY / 2);
|
||||||
|
view.Size = new(viewX, -viewY);
|
||||||
|
tex.SetView(view);
|
||||||
|
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
tex.Draw(this);
|
||||||
|
tex.Display();
|
||||||
|
CurrentAnimation = curAnimation;
|
||||||
|
return tex.Texture.CopyToImage();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取动画时长, 如果动画不存在则返回 0
|
/// 获取动画时长, 如果动画不存在则返回 0
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -243,5 +320,11 @@ namespace SpineViewer.Spine
|
|||||||
/// SFML.Graphics.Drawable 接口实现
|
/// SFML.Graphics.Drawable 接口实现
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
|
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
|
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)
|
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)
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
|
||||||
{
|
{
|
||||||
@@ -42,11 +42,7 @@ namespace SpineViewer.Spine
|
|||||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||||
{
|
{
|
||||||
if (context?.Instance is Spine obj)
|
if (context?.Instance is Spine obj)
|
||||||
{
|
|
||||||
// 返回 AnimationNames 作为下拉选项
|
|
||||||
return new StandardValuesCollection(obj.AnimationNames);
|
return new StandardValuesCollection(obj.AnimationNames);
|
||||||
}
|
|
||||||
|
|
||||||
return base.GetStandardValues(context);
|
return base.GetStandardValues(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum Version
|
public enum Version
|
||||||
{
|
{
|
||||||
[Description("v2.1.x")] V21 = 0x0201,
|
[Description("2.1.x")] V21 = 0x0201,
|
||||||
[Description("v3.6.x")] V36 = 0x0306,
|
[Description("3.6.x")] V36 = 0x0306,
|
||||||
[Description("v3.7.x")] V37 = 0x0307,
|
[Description("3.7.x")] V37 = 0x0307,
|
||||||
[Description("v3.8.x")] V38 = 0x0308,
|
[Description("3.8.x")] V38 = 0x0308,
|
||||||
[Description("v4.0.x")] V40 = 0x0400,
|
[Description("4.0.x")] V40 = 0x0400,
|
||||||
[Description("v4.1.x")] V41 = 0x0401,
|
[Description("4.1.x")] V41 = 0x0401,
|
||||||
[Description("v4.2.x")] V42 = 0x0402,
|
[Description("4.2.x")] V42 = 0x0402,
|
||||||
[Description("v4.3.x")] V43 = 0x0403,
|
[Description("4.3.x")] V43 = 0x0403,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||||
<Version>0.9.6</Version>
|
<Version>0.10.1</Version>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace SpineViewer
|
namespace SpineViewer
|
||||||
{
|
{
|
||||||
public class PointFTypeConverter : ExpandableObjectConverter
|
public class PointFConverter : ExpandableObjectConverter
|
||||||
{
|
{
|
||||||
public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType)
|
public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType)
|
||||||
{
|
{
|
||||||
@@ -52,48 +52,4 @@ namespace SpineViewer
|
|||||||
|
|
||||||
public override bool GetPropertiesSupported(ITypeDescriptorContext? context) => true;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
img/preview.jpg
Normal file
BIN
img/preview.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 302 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 234 KiB |
Reference in New Issue
Block a user