Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ccb110e36 | ||
|
|
2c238dca9b | ||
|
|
3e0aa53fca | ||
|
|
12b4e44296 | ||
|
|
9a2cf4aefe | ||
|
|
0e2a116e0a | ||
|
|
7bf30eb54a | ||
|
|
8dda8c8ff3 | ||
|
|
988fdb22be | ||
|
|
1dd2c8fb4d | ||
|
|
2b39384b28 | ||
|
|
28d1275023 | ||
|
|
979181fc3b | ||
|
|
b374b88ad5 | ||
|
|
6643c19a20 | ||
|
|
7460874c81 | ||
|
|
13dd7511f6 | ||
|
|
f153d251c8 | ||
|
|
3442ace981 | ||
|
|
547cebf5a9 | ||
|
|
7a24d22bc6 | ||
|
|
8f5728afe4 | ||
|
|
41b5ac2c61 | ||
|
|
694ca3bf25 | ||
|
|
674d314b55 | ||
|
|
08a35cc5d1 | ||
|
|
176e5db4d9 | ||
|
|
2535a9ebf9 | ||
|
|
8ff99ee925 | ||
|
|
abc8218487 | ||
|
|
e4765750c3 | ||
|
|
02cddf556b | ||
|
|
e1e6d3c72d | ||
|
|
b401a16002 | ||
|
|
523b0ce295 | ||
|
|
abb06726f0 | ||
|
|
d9190e9418 | ||
|
|
9fe3761eca | ||
|
|
51824afba6 | ||
|
|
160a49ad5f | ||
|
|
9d4907d77e | ||
|
|
53d30e0503 | ||
|
|
9609a2fd5d | ||
|
|
66cf0efcb9 | ||
|
|
0129b9df31 | ||
|
|
a7a5521be1 | ||
|
|
f7f7211ca2 | ||
|
|
8c921a6ed5 | ||
|
|
f14ab870f7 | ||
|
|
26e81ffdb6 |
4
.github/workflows/dotnet-desktop.yml
vendored
4
.github/workflows/dotnet-desktop.yml
vendored
@@ -47,8 +47,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref_name }}
|
tag_name: ${{ env.VERSION }}
|
||||||
release_name: Release ${{ github.ref_name }}
|
release_name: Release ${{ env.VERSION }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
|
|||||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -1,19 +1,43 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v0.10.7
|
||||||
|
|
||||||
|
- 增加仅导出选中
|
||||||
|
- 增加模型调试属性
|
||||||
|
|
||||||
|
## v0.10.6
|
||||||
|
|
||||||
|
- 增加文件夹检测
|
||||||
|
- 增加从剪贴板添加(可复制本地文件/文件夹直接打开)
|
||||||
|
- 修复预览图导致的批量添加可能卡死
|
||||||
|
|
||||||
|
## v0.10.5
|
||||||
|
|
||||||
|
- 修复一些问题
|
||||||
|
|
||||||
|
## v0.10.4
|
||||||
|
|
||||||
|
- 修复一些问题
|
||||||
|
|
||||||
|
## v0.10.3
|
||||||
|
|
||||||
|
- 增加自动版本检测
|
||||||
|
- 增加文件拖放打开
|
||||||
|
|
||||||
## v0.10.2
|
## v0.10.2
|
||||||
|
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD>Ҽ<EFBFBD><EFBFBD>˵<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݼ<EFBFBD>
|
- 增加列表右键菜单快捷键
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ԥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
- 增加预览缩略图复制
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD>л<EFBFBD>
|
- 增加列表视图切换
|
||||||
|
|
||||||
## v0.10.1
|
## v0.10.1
|
||||||
|
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>Ԥ<EFBFBD><EFBFBD>ͼ
|
- 增加列表预览图
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>Ԥ<EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
- 增加列表预览图导出
|
||||||
|
|
||||||
## v0.10.0
|
## v0.10.0
|
||||||
|
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ԥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD>Χ<EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD>
|
- 增加了画面和列表的选择联动,并删除了预览画面显示包围盒选项
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˹<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>ʽת<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܣ<EFBFBD>Ŀǰ<EFBFBD><EFBFBD>֧<EFBFBD>ֲ<EFBFBD><EFBFBD>ְ汾<EFBFBD>IJ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
- 增加了骨骼文件格式转换功能,目前仅支持部分版本的不完整功能
|
||||||
- <EFBFBD>Ż<EFBFBD><EFBFBD>˲<EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
- 优化了部分使用体验
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
A simple and user-friendly Spine file viewer and exporter.
|
A simple and user-friendly Spine file viewer and exporter.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
一个简单好用的 Spine 文件查看&导出程序.
|
一个简单好用的 Spine 文件查看&导出程序.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -33,6 +33,9 @@
|
|||||||
| `4.2.x` | :white_check_mark: | |
|
| `4.2.x` | :white_check_mark: | |
|
||||||
| `4.3.x` | | |
|
| `4.3.x` | | |
|
||||||
|
|
||||||
|
- 支持文件拖放/复制到剪贴板打开
|
||||||
|
- 支持自动检测版本
|
||||||
|
- 支持列表缩略图预览
|
||||||
- 支持多骨骼文件动画预览
|
- 支持多骨骼文件动画预览
|
||||||
- 支持每个骨骼独立参数设置
|
- 支持每个骨骼独立参数设置
|
||||||
- 支持动画PNG帧序列导出
|
- 支持动画PNG帧序列导出
|
||||||
@@ -46,6 +49,8 @@
|
|||||||
|
|
||||||
**文件**菜单可以选择**打开**或者**批量打开**进行骨骼文件导入.
|
**文件**菜单可以选择**打开**或者**批量打开**进行骨骼文件导入.
|
||||||
|
|
||||||
|
或者直接把要打开的骨骼文件拖进列表, 这种方式只支持 `.json` 和 `.skel` 后缀的文件, 其他的会被忽略.
|
||||||
|
|
||||||
### 骨骼调整
|
### 骨骼调整
|
||||||
|
|
||||||
在**模型列表**中选择一项或多项, 将会在**模型参数**面板显示可供调节的参数.
|
在**模型列表**中选择一项或多项, 将会在**模型参数**面板显示可供调节的参数.
|
||||||
|
|||||||
@@ -129,8 +129,8 @@ namespace SpineRuntime38 {
|
|||||||
if (skeletonData.hash.Length == 0) skeletonData.hash = null;
|
if (skeletonData.hash.Length == 0) skeletonData.hash = null;
|
||||||
skeletonData.version = input.ReadString();
|
skeletonData.version = input.ReadString();
|
||||||
if (skeletonData.version.Length == 0) skeletonData.version = null;
|
if (skeletonData.version.Length == 0) skeletonData.version = null;
|
||||||
if ("3.8.75" == skeletonData.version)
|
//if ("3.8.75" == skeletonData.version)
|
||||||
throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
|
// throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
|
||||||
skeletonData.x = input.ReadFloat();
|
skeletonData.x = input.ReadFloat();
|
||||||
skeletonData.y = input.ReadFloat();
|
skeletonData.y = input.ReadFloat();
|
||||||
skeletonData.width = input.ReadFloat();
|
skeletonData.width = input.ReadFloat();
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ namespace SpineRuntime38 {
|
|||||||
var skeletonMap = (Dictionary<string, Object>)root["skeleton"];
|
var skeletonMap = (Dictionary<string, Object>)root["skeleton"];
|
||||||
skeletonData.hash = (string)skeletonMap["hash"];
|
skeletonData.hash = (string)skeletonMap["hash"];
|
||||||
skeletonData.version = (string)skeletonMap["spine"];
|
skeletonData.version = (string)skeletonMap["spine"];
|
||||||
if ("3.8.75" == skeletonData.version)
|
//if ("3.8.75" == skeletonData.version)
|
||||||
throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
|
// throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
|
||||||
skeletonData.x = GetFloat(skeletonMap, "x", 0);
|
skeletonData.x = GetFloat(skeletonMap, "x", 0);
|
||||||
skeletonData.y = GetFloat(skeletonMap, "y", 0);
|
skeletonData.y = GetFloat(skeletonMap, "y", 0);
|
||||||
skeletonData.width = GetFloat(skeletonMap, "width", 0);
|
skeletonData.width = GetFloat(skeletonMap, "width", 0);
|
||||||
|
|||||||
67
SpineViewer/Controls/SpineListView.Designer.cs
generated
67
SpineViewer/Controls/SpineListView.Designer.cs
generated
@@ -44,20 +44,22 @@
|
|||||||
toolStripMenuItem_MoveTop = new ToolStripMenuItem();
|
toolStripMenuItem_MoveTop = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_MoveBottom = new ToolStripMenuItem();
|
toolStripMenuItem_MoveBottom = new ToolStripMenuItem();
|
||||||
toolStripSeparator3 = new ToolStripSeparator();
|
toolStripSeparator3 = new ToolStripSeparator();
|
||||||
|
toolStripMenuItem_SelectAll = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_CopyPreview = new ToolStripMenuItem();
|
toolStripMenuItem_CopyPreview = new ToolStripMenuItem();
|
||||||
|
toolStripSeparator4 = new ToolStripSeparator();
|
||||||
toolStripMenuItem_ChangeView = new ToolStripMenuItem();
|
toolStripMenuItem_ChangeView = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_LargeIconView = new ToolStripMenuItem();
|
toolStripMenuItem_LargeIconView = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_SmallIconView = new ToolStripMenuItem();
|
toolStripMenuItem_ListView = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_DetailsView = new ToolStripMenuItem();
|
toolStripMenuItem_DetailsView = new ToolStripMenuItem();
|
||||||
imageList_LargeIcon = new ImageList(components);
|
imageList_LargeIcon = new ImageList(components);
|
||||||
imageList_SmallIcon = new ImageList(components);
|
imageList_SmallIcon = new ImageList(components);
|
||||||
toolStripSeparator4 = new ToolStripSeparator();
|
toolStripMenuItem_AddFromClipboard = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_SelectAll = new ToolStripMenuItem();
|
|
||||||
contextMenuStrip.SuspendLayout();
|
contextMenuStrip.SuspendLayout();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// listView
|
// listView
|
||||||
//
|
//
|
||||||
|
listView.Alignment = ListViewAlignment.Left;
|
||||||
listView.AllowDrop = true;
|
listView.AllowDrop = true;
|
||||||
listView.Columns.AddRange(new ColumnHeader[] { columnHeader_Name });
|
listView.Columns.AddRange(new ColumnHeader[] { columnHeader_Name });
|
||||||
listView.ContextMenuStrip = contextMenuStrip;
|
listView.ContextMenuStrip = contextMenuStrip;
|
||||||
@@ -76,6 +78,7 @@
|
|||||||
listView.ItemDrag += listView_ItemDrag;
|
listView.ItemDrag += listView_ItemDrag;
|
||||||
listView.SelectedIndexChanged += listView_SelectedIndexChanged;
|
listView.SelectedIndexChanged += listView_SelectedIndexChanged;
|
||||||
listView.DragDrop += listView_DragDrop;
|
listView.DragDrop += listView_DragDrop;
|
||||||
|
listView.DragEnter += listView_DragEnter;
|
||||||
listView.DragOver += listView_DragOver;
|
listView.DragOver += listView_DragOver;
|
||||||
//
|
//
|
||||||
// columnHeader_Name
|
// columnHeader_Name
|
||||||
@@ -86,9 +89,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_BatchAdd, toolStripMenuItem_RemoveAll, toolStripSeparator2, toolStripMenuItem_MoveUp, toolStripMenuItem_MoveDown, toolStripMenuItem_MoveTop, toolStripMenuItem_MoveBottom, toolStripSeparator3, toolStripMenuItem_SelectAll, toolStripMenuItem_CopyPreview, toolStripSeparator4, toolStripMenuItem_ChangeView });
|
contextMenuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_Add, toolStripMenuItem_Insert, toolStripMenuItem_Remove, toolStripSeparator1, toolStripMenuItem_BatchAdd, toolStripMenuItem_RemoveAll, toolStripSeparator2, toolStripMenuItem_MoveUp, toolStripMenuItem_MoveDown, toolStripMenuItem_MoveTop, toolStripMenuItem_MoveBottom, toolStripSeparator3, toolStripMenuItem_CopyPreview, toolStripMenuItem_AddFromClipboard, toolStripMenuItem_SelectAll, toolStripSeparator4, toolStripMenuItem_ChangeView });
|
||||||
contextMenuStrip.Name = "contextMenuStrip";
|
contextMenuStrip.Name = "contextMenuStrip";
|
||||||
contextMenuStrip.Size = new Size(329, 421);
|
contextMenuStrip.Size = new Size(329, 451);
|
||||||
contextMenuStrip.Closed += contextMenuStrip_Closed;
|
contextMenuStrip.Closed += contextMenuStrip_Closed;
|
||||||
contextMenuStrip.Opening += contextMenuStrip_Opening;
|
contextMenuStrip.Opening += contextMenuStrip_Opening;
|
||||||
//
|
//
|
||||||
@@ -109,6 +112,7 @@
|
|||||||
// toolStripMenuItem_Remove
|
// toolStripMenuItem_Remove
|
||||||
//
|
//
|
||||||
toolStripMenuItem_Remove.Name = "toolStripMenuItem_Remove";
|
toolStripMenuItem_Remove.Name = "toolStripMenuItem_Remove";
|
||||||
|
toolStripMenuItem_Remove.ShortcutKeys = Keys.Delete;
|
||||||
toolStripMenuItem_Remove.Size = new Size(328, 30);
|
toolStripMenuItem_Remove.Size = new Size(328, 30);
|
||||||
toolStripMenuItem_Remove.Text = "移除";
|
toolStripMenuItem_Remove.Text = "移除";
|
||||||
toolStripMenuItem_Remove.Click += toolStripMenuItem_Remove_Click;
|
toolStripMenuItem_Remove.Click += toolStripMenuItem_Remove_Click;
|
||||||
@@ -174,6 +178,14 @@
|
|||||||
toolStripSeparator3.Name = "toolStripSeparator3";
|
toolStripSeparator3.Name = "toolStripSeparator3";
|
||||||
toolStripSeparator3.Size = new Size(325, 6);
|
toolStripSeparator3.Size = new Size(325, 6);
|
||||||
//
|
//
|
||||||
|
// toolStripMenuItem_SelectAll
|
||||||
|
//
|
||||||
|
toolStripMenuItem_SelectAll.Name = "toolStripMenuItem_SelectAll";
|
||||||
|
toolStripMenuItem_SelectAll.ShortcutKeys = Keys.Control | Keys.A;
|
||||||
|
toolStripMenuItem_SelectAll.Size = new Size(328, 30);
|
||||||
|
toolStripMenuItem_SelectAll.Text = "全选";
|
||||||
|
toolStripMenuItem_SelectAll.Click += toolStripMenuItem_SelectAll_Click;
|
||||||
|
//
|
||||||
// toolStripMenuItem_CopyPreview
|
// toolStripMenuItem_CopyPreview
|
||||||
//
|
//
|
||||||
toolStripMenuItem_CopyPreview.Name = "toolStripMenuItem_CopyPreview";
|
toolStripMenuItem_CopyPreview.Name = "toolStripMenuItem_CopyPreview";
|
||||||
@@ -182,9 +194,14 @@
|
|||||||
toolStripMenuItem_CopyPreview.Text = "复制预览图 (256x256)";
|
toolStripMenuItem_CopyPreview.Text = "复制预览图 (256x256)";
|
||||||
toolStripMenuItem_CopyPreview.Click += toolStripMenuItem_CopyPreview_Click;
|
toolStripMenuItem_CopyPreview.Click += toolStripMenuItem_CopyPreview_Click;
|
||||||
//
|
//
|
||||||
|
// toolStripSeparator4
|
||||||
|
//
|
||||||
|
toolStripSeparator4.Name = "toolStripSeparator4";
|
||||||
|
toolStripSeparator4.Size = new Size(325, 6);
|
||||||
|
//
|
||||||
// toolStripMenuItem_ChangeView
|
// toolStripMenuItem_ChangeView
|
||||||
//
|
//
|
||||||
toolStripMenuItem_ChangeView.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_LargeIconView, toolStripMenuItem_SmallIconView, toolStripMenuItem_DetailsView });
|
toolStripMenuItem_ChangeView.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_LargeIconView, toolStripMenuItem_ListView, toolStripMenuItem_DetailsView });
|
||||||
toolStripMenuItem_ChangeView.Name = "toolStripMenuItem_ChangeView";
|
toolStripMenuItem_ChangeView.Name = "toolStripMenuItem_ChangeView";
|
||||||
toolStripMenuItem_ChangeView.Size = new Size(328, 30);
|
toolStripMenuItem_ChangeView.Size = new Size(328, 30);
|
||||||
toolStripMenuItem_ChangeView.Text = "切换视图";
|
toolStripMenuItem_ChangeView.Text = "切换视图";
|
||||||
@@ -193,24 +210,24 @@
|
|||||||
//
|
//
|
||||||
toolStripMenuItem_LargeIconView.Name = "toolStripMenuItem_LargeIconView";
|
toolStripMenuItem_LargeIconView.Name = "toolStripMenuItem_LargeIconView";
|
||||||
toolStripMenuItem_LargeIconView.ShortcutKeys = Keys.Alt | Keys.D1;
|
toolStripMenuItem_LargeIconView.ShortcutKeys = Keys.Alt | Keys.D1;
|
||||||
toolStripMenuItem_LargeIconView.Size = new Size(270, 34);
|
toolStripMenuItem_LargeIconView.Size = new Size(241, 34);
|
||||||
toolStripMenuItem_LargeIconView.Text = "大图标";
|
toolStripMenuItem_LargeIconView.Text = "大图标";
|
||||||
toolStripMenuItem_LargeIconView.Click += toolStripMenuItem_LargeIconView_Click;
|
toolStripMenuItem_LargeIconView.Click += toolStripMenuItem_LargeIconView_Click;
|
||||||
//
|
//
|
||||||
// toolStripMenuItem_SmallIconView
|
// toolStripMenuItem_ListView
|
||||||
//
|
//
|
||||||
toolStripMenuItem_SmallIconView.Name = "toolStripMenuItem_SmallIconView";
|
toolStripMenuItem_ListView.Name = "toolStripMenuItem_ListView";
|
||||||
toolStripMenuItem_SmallIconView.ShortcutKeys = Keys.Alt | Keys.D2;
|
toolStripMenuItem_ListView.ShortcutKeys = Keys.Alt | Keys.D2;
|
||||||
toolStripMenuItem_SmallIconView.Size = new Size(270, 34);
|
toolStripMenuItem_ListView.Size = new Size(241, 34);
|
||||||
toolStripMenuItem_SmallIconView.Text = "小图标";
|
toolStripMenuItem_ListView.Text = "列表";
|
||||||
toolStripMenuItem_SmallIconView.Click += toolStripMenuItem_SmallIconView_Click;
|
toolStripMenuItem_ListView.Click += toolStripMenuItem_ListView_Click;
|
||||||
//
|
//
|
||||||
// toolStripMenuItem_DetailsView
|
// toolStripMenuItem_DetailsView
|
||||||
//
|
//
|
||||||
toolStripMenuItem_DetailsView.Name = "toolStripMenuItem_DetailsView";
|
toolStripMenuItem_DetailsView.Name = "toolStripMenuItem_DetailsView";
|
||||||
toolStripMenuItem_DetailsView.ShortcutKeys = Keys.Alt | Keys.D3;
|
toolStripMenuItem_DetailsView.ShortcutKeys = Keys.Alt | Keys.D3;
|
||||||
toolStripMenuItem_DetailsView.Size = new Size(270, 34);
|
toolStripMenuItem_DetailsView.Size = new Size(241, 34);
|
||||||
toolStripMenuItem_DetailsView.Text = "列表";
|
toolStripMenuItem_DetailsView.Text = "详细信息";
|
||||||
toolStripMenuItem_DetailsView.Click += toolStripMenuItem_DetailsView_Click;
|
toolStripMenuItem_DetailsView.Click += toolStripMenuItem_DetailsView_Click;
|
||||||
//
|
//
|
||||||
// imageList_LargeIcon
|
// imageList_LargeIcon
|
||||||
@@ -225,18 +242,13 @@
|
|||||||
imageList_SmallIcon.ImageSize = new Size(48, 48);
|
imageList_SmallIcon.ImageSize = new Size(48, 48);
|
||||||
imageList_SmallIcon.TransparentColor = Color.Transparent;
|
imageList_SmallIcon.TransparentColor = Color.Transparent;
|
||||||
//
|
//
|
||||||
// toolStripSeparator4
|
// toolStripMenuItem_AddFromClipboard
|
||||||
//
|
//
|
||||||
toolStripSeparator4.Name = "toolStripSeparator4";
|
toolStripMenuItem_AddFromClipboard.Name = "toolStripMenuItem_AddFromClipboard";
|
||||||
toolStripSeparator4.Size = new Size(325, 6);
|
toolStripMenuItem_AddFromClipboard.ShortcutKeys = Keys.Control | Keys.V;
|
||||||
//
|
toolStripMenuItem_AddFromClipboard.Size = new Size(328, 30);
|
||||||
// toolStripMenuItem_SelectAll
|
toolStripMenuItem_AddFromClipboard.Text = "从剪贴板添加";
|
||||||
//
|
toolStripMenuItem_AddFromClipboard.Click += toolStripMenuItem_AddFromClipboard_Click;
|
||||||
toolStripMenuItem_SelectAll.Name = "toolStripMenuItem_SelectAll";
|
|
||||||
toolStripMenuItem_SelectAll.ShortcutKeys = Keys.Control | Keys.A;
|
|
||||||
toolStripMenuItem_SelectAll.Size = new Size(328, 30);
|
|
||||||
toolStripMenuItem_SelectAll.Text = "全选";
|
|
||||||
toolStripMenuItem_SelectAll.Click += toolStripMenuItem_SelectAll_Click;
|
|
||||||
//
|
//
|
||||||
// SpineListView
|
// SpineListView
|
||||||
//
|
//
|
||||||
@@ -267,12 +279,13 @@
|
|||||||
private ToolStripSeparator toolStripSeparator3;
|
private ToolStripSeparator toolStripSeparator3;
|
||||||
private ToolStripMenuItem toolStripMenuItem_ChangeView;
|
private ToolStripMenuItem toolStripMenuItem_ChangeView;
|
||||||
private ToolStripMenuItem toolStripMenuItem_LargeIconView;
|
private ToolStripMenuItem toolStripMenuItem_LargeIconView;
|
||||||
private ToolStripMenuItem toolStripMenuItem_SmallIconView;
|
private ToolStripMenuItem toolStripMenuItem_ListView;
|
||||||
private ToolStripMenuItem toolStripMenuItem_DetailsView;
|
private ToolStripMenuItem toolStripMenuItem_DetailsView;
|
||||||
private ToolStripMenuItem toolStripMenuItem_MoveTop;
|
private ToolStripMenuItem toolStripMenuItem_MoveTop;
|
||||||
private ToolStripMenuItem toolStripMenuItem_MoveBottom;
|
private ToolStripMenuItem toolStripMenuItem_MoveBottom;
|
||||||
private ToolStripMenuItem toolStripMenuItem_CopyPreview;
|
private ToolStripMenuItem toolStripMenuItem_CopyPreview;
|
||||||
private ToolStripMenuItem toolStripMenuItem_SelectAll;
|
private ToolStripMenuItem toolStripMenuItem_SelectAll;
|
||||||
private ToolStripSeparator toolStripSeparator4;
|
private ToolStripSeparator toolStripSeparator4;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_AddFromClipboard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,19 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
public partial class SpineListView : UserControl
|
public partial class SpineListView : UserControl
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 显示骨骼信息的属性面板
|
||||||
|
/// </summary>
|
||||||
[Category("自定义"), Description("用于显示骨骼属性的属性页")]
|
[Category("自定义"), Description("用于显示骨骼属性的属性页")]
|
||||||
public PropertyGrid? PropertyGrid { get; set; }
|
public PropertyGrid? PropertyGrid { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取数组快照, 访问时必须使用 lock 语句锁定对象本身
|
/// Spine 列表只读视图, 访问时必须使用 lock 语句锁定视图本身
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly ReadOnlyCollection<Spine.Spine> Spines;
|
public readonly ReadOnlyCollection<Spine.Spine> Spines;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Spine 列表, 访问时必须使用 lock 语句锁定 Spines
|
/// Spine 列表, 访问时必须使用 lock 语句锁定只读视图 Spines
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<Spine.Spine> spines = [];
|
private readonly List<Spine.Spine> spines = [];
|
||||||
|
|
||||||
@@ -37,358 +40,34 @@ namespace SpineViewer.Controls
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// listView.SelectedIndices
|
/// 选中的索引
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ListView.SelectedIndexCollection SelectedIndices { get => listView.SelectedIndices; }
|
public ListView.SelectedIndexCollection SelectedIndices => listView.SelectedIndices;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 弹出添加对话框
|
/// 弹出添加对话框在末尾添加
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Add()
|
public void Add() => Insert();
|
||||||
{
|
|
||||||
Insert();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 弹出批量添加对话框
|
/// 弹出添加对话框在指定位置之前插入一项, 如果索引无效则在末尾添加
|
||||||
/// </summary>
|
|
||||||
public void BatchAdd()
|
|
||||||
{
|
|
||||||
var openDialog = new Dialogs.BatchOpenSpineDialog();
|
|
||||||
if (openDialog.ShowDialog() != DialogResult.OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var progressDialog = new Dialogs.ProgressDialog();
|
|
||||||
progressDialog.DoWork += BatchAdd_Work;
|
|
||||||
progressDialog.RunWorkerAsync(openDialog);
|
|
||||||
progressDialog.ShowDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 弹出对话框导出列表预览图
|
|
||||||
/// </summary>
|
|
||||||
public void ExportPreviews()
|
|
||||||
{
|
|
||||||
lock (Spines)
|
|
||||||
{
|
|
||||||
if (spines.Count <= 0)
|
|
||||||
{
|
|
||||||
MessageBox.Show("请至少打开一个骨骼文件", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var saveDialog = new Dialogs.ExportPreviewDialog();
|
|
||||||
if (saveDialog.ShowDialog() != DialogResult.OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var progressDialog = new Dialogs.ProgressDialog();
|
|
||||||
progressDialog.DoWork += ExportPreview_Work;
|
|
||||||
progressDialog.RunWorkerAsync(saveDialog);
|
|
||||||
progressDialog.ShowDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void listView_SelectedIndexChanged(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (PropertyGrid is not null)
|
|
||||||
{
|
|
||||||
lock (Spines)
|
|
||||||
{
|
|
||||||
if (listView.SelectedIndices.Count <= 0)
|
|
||||||
PropertyGrid.SelectedObject = null;
|
|
||||||
else if (listView.SelectedIndices.Count <= 1)
|
|
||||||
PropertyGrid.SelectedObject = spines[listView.SelectedIndices[0]];
|
|
||||||
else
|
|
||||||
PropertyGrid.SelectedObjects = listView.SelectedIndices.Cast<int>().Select(index => spines[index]).ToArray();
|
|
||||||
|
|
||||||
// 标记选中的 Spine
|
|
||||||
for (int i = 0; i < spines.Count; i++)
|
|
||||||
spines[i].IsSelected = listView.SelectedIndices.Contains(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void listView_ItemDrag(object sender, ItemDragEventArgs e)
|
|
||||||
{
|
|
||||||
DoDragDrop(e.Item, DragDropEffects.Move);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void listView_DragOver(object sender, DragEventArgs e)
|
|
||||||
{
|
|
||||||
// 检查拖放目标是否有效
|
|
||||||
e.Effect = DragDropEffects.Move;
|
|
||||||
|
|
||||||
// 获取鼠标位置并确定目标索引
|
|
||||||
var point = listView.PointToClient(new(e.X, e.Y));
|
|
||||||
var targetItem = listView.GetItemAt(point.X, point.Y);
|
|
||||||
|
|
||||||
// 高亮目标项
|
|
||||||
if (targetItem != null)
|
|
||||||
{
|
|
||||||
foreach (ListViewItem item in listView.Items)
|
|
||||||
{
|
|
||||||
item.BackColor = listView.BackColor;
|
|
||||||
}
|
|
||||||
targetItem.BackColor = Color.LightGray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void listView_DragDrop(object sender, DragEventArgs e)
|
|
||||||
{
|
|
||||||
// 获取拖放源项和目标项
|
|
||||||
var draggedItem = (ListViewItem)e.Data.GetData(typeof(ListViewItem));
|
|
||||||
int draggedIndex = draggedItem.Index;
|
|
||||||
var point = listView.PointToClient(new Point(e.X, e.Y));
|
|
||||||
var targetItem = listView.GetItemAt(point.X, point.Y);
|
|
||||||
int targetIndex = targetItem is null ? listView.Items.Count : targetItem.Index;
|
|
||||||
|
|
||||||
if (targetIndex <= draggedIndex)
|
|
||||||
{
|
|
||||||
lock (Spines)
|
|
||||||
{
|
|
||||||
var draggedSpine = spines[draggedIndex];
|
|
||||||
spines.RemoveAt(draggedIndex);
|
|
||||||
spines.Insert(targetIndex, draggedSpine);
|
|
||||||
}
|
|
||||||
listView.Items.RemoveAt(draggedIndex);
|
|
||||||
listView.Items.Insert(targetIndex, draggedItem);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
lock (Spines)
|
|
||||||
{
|
|
||||||
var draggedSpine = spines[draggedIndex];
|
|
||||||
spines.RemoveAt(draggedIndex);
|
|
||||||
spines.Insert(targetIndex - 1, draggedSpine);
|
|
||||||
}
|
|
||||||
listView.Items.RemoveAt(draggedIndex);
|
|
||||||
listView.Items.Insert(targetIndex - 1, draggedItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置背景颜色
|
|
||||||
foreach (ListViewItem item in listView.Items)
|
|
||||||
{
|
|
||||||
item.BackColor = listView.BackColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void contextMenuStrip_Opening(object sender, CancelEventArgs e)
|
|
||||||
{
|
|
||||||
var selectedIndices = listView.SelectedIndices;
|
|
||||||
var selectedCount = selectedIndices.Count;
|
|
||||||
var itemsCount = listView.Items.Count;
|
|
||||||
toolStripMenuItem_Insert.Enabled = selectedCount == 1;
|
|
||||||
toolStripMenuItem_Remove.Enabled = selectedCount >= 1;
|
|
||||||
toolStripMenuItem_MoveTop.Enabled = selectedCount == 1 && selectedIndices[0] != 0;
|
|
||||||
toolStripMenuItem_MoveUp.Enabled = selectedCount == 1 && selectedIndices[0] != 0;
|
|
||||||
toolStripMenuItem_MoveDown.Enabled = selectedCount == 1 && selectedIndices[0] != itemsCount - 1;
|
|
||||||
toolStripMenuItem_MoveBottom.Enabled = selectedCount == 1 && selectedIndices[0] != itemsCount - 1;
|
|
||||||
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 contextMenuStrip_Closed(object sender, ToolStripDropDownClosedEventArgs e)
|
|
||||||
{
|
|
||||||
// 不显示菜单的时候需要把菜单的各项功能启用, 这样才能正常捕获快捷键
|
|
||||||
foreach (var item in contextMenuStrip.Items)
|
|
||||||
if (item is ToolStripMenuItem tsmi)
|
|
||||||
tsmi.Enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toolStripMenuItem_Add_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
Insert();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toolStripMenuItem_BatchAdd_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
BatchAdd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toolStripMenuItem_Insert_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (listView.SelectedIndices.Count == 1)
|
|
||||||
Insert(listView.SelectedIndices[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toolStripMenuItem_Remove_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (listView.SelectedIndices.Count <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (listView.SelectedIndices.Count > 1)
|
|
||||||
{
|
|
||||||
if (MessageBox.Show($"确定移除所选 {listView.SelectedIndices.Count} 项吗?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var i in listView.SelectedIndices.Cast<int>().OrderByDescending(x => x))
|
|
||||||
{
|
|
||||||
lock (Spines)
|
|
||||||
{
|
|
||||||
var spine = spines[i];
|
|
||||||
spines.RemoveAt(i);
|
|
||||||
listView.SmallImageList.Images.RemoveByKey(spine.ID);
|
|
||||||
listView.LargeImageList.Images.RemoveByKey(spine.ID);
|
|
||||||
spine.Dispose();
|
|
||||||
}
|
|
||||||
listView.Items.RemoveAt(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toolStripMenuItem_MoveTop_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (listView.SelectedIndices.Count != 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var index = listView.SelectedIndices[0];
|
|
||||||
if (index > 0)
|
|
||||||
{
|
|
||||||
lock (Spines)
|
|
||||||
{
|
|
||||||
var spine = spines[index];
|
|
||||||
spines.RemoveAt(index);
|
|
||||||
spines.Insert(0, spine);
|
|
||||||
}
|
|
||||||
var item = listView.Items[index];
|
|
||||||
listView.Items.RemoveAt(index);
|
|
||||||
listView.Items.Insert(0, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toolStripMenuItem_MoveUp_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (listView.SelectedIndices.Count != 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var index = listView.SelectedIndices[0];
|
|
||||||
if (index > 0)
|
|
||||||
{
|
|
||||||
lock (Spines) { (spines[index - 1], spines[index]) = (spines[index], spines[index - 1]); }
|
|
||||||
var item = listView.Items[index];
|
|
||||||
listView.Items.RemoveAt(index);
|
|
||||||
listView.Items.Insert(index - 1, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toolStripMenuItem_MoveDown_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (listView.SelectedIndices.Count != 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var index = listView.SelectedIndices[0];
|
|
||||||
if (index < listView.Items.Count - 1)
|
|
||||||
{
|
|
||||||
lock (Spines) { (spines[index], spines[index + 1]) = (spines[index + 1], spines[index]); }
|
|
||||||
var item = listView.Items[index];
|
|
||||||
listView.Items.RemoveAt(index);
|
|
||||||
listView.Items.Insert(index + 1, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toolStripMenuItem_MoveBottom_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (listView.SelectedIndices.Count != 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var index = listView.SelectedIndices[0];
|
|
||||||
if (index < listView.Items.Count - 1)
|
|
||||||
{
|
|
||||||
lock (Spines)
|
|
||||||
{
|
|
||||||
lock (Spines)
|
|
||||||
{
|
|
||||||
var spine = spines[index];
|
|
||||||
spines.RemoveAt(index);
|
|
||||||
spines.Add(spine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var item = listView.Items[index];
|
|
||||||
listView.Items.RemoveAt(index);
|
|
||||||
listView.Items.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toolStripMenuItem_RemoveAll_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (listView.Items.Count <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (MessageBox.Show($"确认移除所有 {listView.Items.Count} 项吗?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock (Spines)
|
|
||||||
{
|
|
||||||
foreach (var spine in spines)
|
|
||||||
spine.Dispose();
|
|
||||||
spines.Clear();
|
|
||||||
listView.SmallImageList.Images.Clear();
|
|
||||||
listView.LargeImageList.Images.Clear();
|
|
||||||
}
|
|
||||||
listView.Items.Clear();
|
|
||||||
if (PropertyGrid is not null)
|
|
||||||
PropertyGrid.SelectedObject = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toolStripMenuItem_SelectAll_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
listView.BeginUpdate();
|
|
||||||
foreach (ListViewItem item in listView.Items)
|
|
||||||
item.Selected = true;
|
|
||||||
listView.EndUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toolStripMenuItem_CopyPreview_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
var fileDropList = new StringCollection();
|
|
||||||
|
|
||||||
lock (Spines)
|
|
||||||
{
|
|
||||||
foreach (int i in listView.SelectedIndices)
|
|
||||||
{
|
|
||||||
var spine = spines[i];
|
|
||||||
var image = spine.Preview;
|
|
||||||
var path = Path.Combine(Program.TempDir, $"{spine.ID}.png");
|
|
||||||
using (var clone = new Bitmap(image))
|
|
||||||
clone.Save(path);
|
|
||||||
fileDropList.Add(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fileDropList.Count > 0)
|
|
||||||
Clipboard.SetFileDropList(fileDropList);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
/// </summary>
|
||||||
private void Insert(int index = -1)
|
private void Insert(int index = -1)
|
||||||
{
|
{
|
||||||
var dialog = new Dialogs.OpenSpineDialog();
|
var dialog = new Dialogs.OpenSpineDialog();
|
||||||
if (dialog.ShowDialog() != DialogResult.OK)
|
if (dialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
Insert(dialog.Result, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从结果在指定位置之前插入一项, 如果索引无效则在末尾添加
|
||||||
|
/// </summary>
|
||||||
|
private void Insert(Dialogs.OpenSpineDialogResult result, int index = -1)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var spine = Spine.Spine.New(dialog.Version, dialog.SkelPath, dialog.AtlasPath);
|
var spine = Spine.Spine.New(result.Version, result.SkelPath, result.AtlasPath);
|
||||||
|
|
||||||
// 如果索引无效则在末尾添加
|
// 如果索引无效则在末尾添加
|
||||||
if (index < 0 || index > listView.Items.Count)
|
if (index < 0 || index > listView.Items.Count)
|
||||||
@@ -410,17 +89,42 @@ namespace SpineViewer.Controls
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Program.Logger.Error(ex.ToString());
|
Program.Logger.Error(ex.ToString());
|
||||||
Program.Logger.Error("Failed to load {} {}", dialog.SkelPath, dialog.AtlasPath);
|
Program.Logger.Error("Failed to load {} {}", result.SkelPath, result.AtlasPath);
|
||||||
MessageBox.Show(ex.ToString(), "骨骼加载失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Error(ex.ToString(), "骨骼加载失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
Program.LogCurrentMemoryUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 弹出批量添加对话框
|
||||||
|
/// </summary>
|
||||||
|
public void BatchAdd()
|
||||||
|
{
|
||||||
|
var openDialog = new Dialogs.BatchOpenSpineDialog();
|
||||||
|
if (openDialog.ShowDialog() != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
BatchAdd(openDialog.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从结果批量添加
|
||||||
|
/// </summary>
|
||||||
|
public void BatchAdd(Dialogs.BatchOpenSpineDialogResult result)
|
||||||
|
{
|
||||||
|
var progressDialog = new Dialogs.ProgressDialog();
|
||||||
|
progressDialog.DoWork += BatchAdd_Work;
|
||||||
|
progressDialog.RunWorkerAsync(result);
|
||||||
|
progressDialog.ShowDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量添加后台任务
|
||||||
|
/// </summary>
|
||||||
private void BatchAdd_Work(object? sender, DoWorkEventArgs e)
|
private void BatchAdd_Work(object? sender, DoWorkEventArgs e)
|
||||||
{
|
{
|
||||||
var worker = sender as BackgroundWorker;
|
var worker = sender as BackgroundWorker;
|
||||||
var arguments = e.Argument as Dialogs.BatchOpenSpineDialog;
|
var arguments = e.Argument as Dialogs.BatchOpenSpineDialogResult;
|
||||||
var skelPaths = arguments.SkelPaths;
|
var skelPaths = arguments.SkelPaths;
|
||||||
var version = arguments.Version;
|
var version = arguments.Version;
|
||||||
|
|
||||||
@@ -471,60 +175,374 @@ namespace SpineViewer.Controls
|
|||||||
Program.Logger.Info("{} skel loaded successfully", success);
|
Program.Logger.Info("{} skel loaded successfully", success);
|
||||||
}
|
}
|
||||||
|
|
||||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
Program.LogCurrentMemoryUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExportPreview_Work(object? sender, DoWorkEventArgs e)
|
/// <summary>
|
||||||
|
/// 从拖放/复制的路径列表添加
|
||||||
|
/// </summary>
|
||||||
|
private void AddFromFileDrop(IEnumerable<string> paths)
|
||||||
{
|
{
|
||||||
var worker = sender as BackgroundWorker;
|
List<string> validPaths = [];
|
||||||
var arguments = e.Argument as Dialogs.ExportPreviewDialog;
|
foreach (var path in paths)
|
||||||
var outputDir = arguments.OutputDir;
|
{
|
||||||
var width = arguments.PreviewWidth;
|
if (File.Exists(path))
|
||||||
var height = arguments.PreviewHeight;
|
{
|
||||||
|
if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(path).ToLower()))
|
||||||
|
validPaths.Add(path);
|
||||||
|
}
|
||||||
|
else if (Directory.Exists(path))
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
|
||||||
|
validPaths.Add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int success = 0;
|
if (validPaths.Count > 1)
|
||||||
int error = 0;
|
{
|
||||||
|
if (validPaths.Count > 100)
|
||||||
|
{
|
||||||
|
if (MessageBox.Quest($"共发现 {validPaths.Count} 个可加载骨骼,数量较多,是否一次性全部加载?") == DialogResult.Cancel)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BatchAdd(new Dialogs.BatchOpenSpineDialogResult(Spine.Version.Auto, validPaths.ToArray()));
|
||||||
|
}
|
||||||
|
else if (validPaths.Count > 0)
|
||||||
|
{
|
||||||
|
Insert(new Dialogs.OpenSpineDialogResult(Spine.Version.Auto, validPaths[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void listView_SelectedIndexChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
lock (Spines)
|
lock (Spines)
|
||||||
{
|
{
|
||||||
int totalCount = spines.Count;
|
if (PropertyGrid is not null)
|
||||||
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
|
||||||
for (int i = 0; i < totalCount; i++)
|
|
||||||
{
|
{
|
||||||
if (worker.CancellationPending)
|
if (listView.SelectedIndices.Count <= 0)
|
||||||
{
|
PropertyGrid.SelectedObject = null;
|
||||||
e.Cancel = true;
|
else if (listView.SelectedIndices.Count <= 1)
|
||||||
break;
|
PropertyGrid.SelectedObject = spines[listView.SelectedIndices[0]];
|
||||||
|
else
|
||||||
|
PropertyGrid.SelectedObjects = listView.SelectedIndices.Cast<int>().Select(index => spines[index]).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var spine = spines[i];
|
// 标记选中的 Spine
|
||||||
try
|
for (int i = 0; i < spines.Count; i++)
|
||||||
{
|
spines[i].IsSelected = listView.SelectedIndices.Contains(i);
|
||||||
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}");
|
// XXX: 图标显示的时候没法自动刷新顺序, 只能切换视图刷新, 不知道什么原理
|
||||||
|
if (listView.View == View.LargeIcon)
|
||||||
|
{
|
||||||
|
listView.BeginUpdate();
|
||||||
|
listView.View = View.List;
|
||||||
|
listView.View = View.LargeIcon;
|
||||||
|
listView.EndUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listView.SelectedItems.Count > 0)
|
||||||
|
listView.SelectedItems[0].EnsureVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void listView_ItemDrag(object sender, ItemDragEventArgs e)
|
||||||
|
{
|
||||||
|
DoDragDrop(e.Item, DragDropEffects.Move);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void listView_DragEnter(object sender, DragEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Data.GetDataPresent(DataFormats.Serializable))
|
||||||
|
e.Effect = DragDropEffects.Move;
|
||||||
|
else if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||||
|
e.Effect = DragDropEffects.Copy;
|
||||||
|
else
|
||||||
|
e.Effect = DragDropEffects.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void listView_DragOver(object sender, DragEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Data.GetDataPresent(DataFormats.Serializable))
|
||||||
|
{
|
||||||
|
// 获取鼠标位置并确定目标索引
|
||||||
|
var point = listView.PointToClient(new(e.X, e.Y));
|
||||||
|
var targetItem = listView.GetItemAt(point.X, point.Y);
|
||||||
|
|
||||||
|
// 高亮目标项
|
||||||
|
if (targetItem != null)
|
||||||
|
{
|
||||||
|
foreach (ListViewItem item in listView.Items)
|
||||||
|
{
|
||||||
|
item.BackColor = listView.BackColor;
|
||||||
|
}
|
||||||
|
targetItem.BackColor = Color.LightGray;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error > 0)
|
private void listView_DragDrop(object sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
Program.Logger.Warn("Preview save {} successfully, {} failed", success, error);
|
if (e.Data.GetDataPresent(DataFormats.Serializable))
|
||||||
|
{
|
||||||
|
// 获取拖放源项和目标项
|
||||||
|
var draggedItem = (ListViewItem)e.Data.GetData(typeof(ListViewItem));
|
||||||
|
int draggedIndex = draggedItem.Index;
|
||||||
|
var point = listView.PointToClient(new Point(e.X, e.Y));
|
||||||
|
var targetItem = listView.GetItemAt(point.X, point.Y);
|
||||||
|
int targetIndex = targetItem is null ? listView.Items.Count : targetItem.Index;
|
||||||
|
|
||||||
|
if (targetIndex <= draggedIndex)
|
||||||
|
{
|
||||||
|
lock (Spines)
|
||||||
|
{
|
||||||
|
var draggedSpine = spines[draggedIndex];
|
||||||
|
spines.RemoveAt(draggedIndex);
|
||||||
|
spines.Insert(targetIndex, draggedSpine);
|
||||||
|
}
|
||||||
|
listView.Items.RemoveAt(draggedIndex);
|
||||||
|
listView.Items.Insert(targetIndex, draggedItem);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Program.Logger.Info("{} preview saved successfully", success);
|
lock (Spines)
|
||||||
|
{
|
||||||
|
var draggedSpine = spines[draggedIndex];
|
||||||
|
spines.RemoveAt(draggedIndex);
|
||||||
|
spines.Insert(targetIndex - 1, draggedSpine);
|
||||||
|
}
|
||||||
|
listView.Items.RemoveAt(draggedIndex);
|
||||||
|
listView.Items.Insert(targetIndex - 1, draggedItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
// 重置背景颜色
|
||||||
|
foreach (ListViewItem item in listView.Items)
|
||||||
|
{
|
||||||
|
item.BackColor = listView.BackColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||||
|
{
|
||||||
|
AddFromFileDrop((string[])e.Data.GetData(DataFormats.FileDrop));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void contextMenuStrip_Opening(object sender, CancelEventArgs e)
|
||||||
|
{
|
||||||
|
var selectedIndices = listView.SelectedIndices;
|
||||||
|
var selectedCount = selectedIndices.Count;
|
||||||
|
var itemsCount = listView.Items.Count;
|
||||||
|
toolStripMenuItem_Insert.Enabled = selectedCount == 1;
|
||||||
|
toolStripMenuItem_Remove.Enabled = selectedCount >= 1;
|
||||||
|
toolStripMenuItem_MoveTop.Enabled = selectedCount == 1 && selectedIndices[0] != 0;
|
||||||
|
toolStripMenuItem_MoveUp.Enabled = selectedCount == 1 && selectedIndices[0] != 0;
|
||||||
|
toolStripMenuItem_MoveDown.Enabled = selectedCount == 1 && selectedIndices[0] != itemsCount - 1;
|
||||||
|
toolStripMenuItem_MoveBottom.Enabled = selectedCount == 1 && selectedIndices[0] != itemsCount - 1;
|
||||||
|
toolStripMenuItem_RemoveAll.Enabled = itemsCount > 0;
|
||||||
|
|
||||||
|
// 视图选项
|
||||||
|
toolStripMenuItem_LargeIconView.Checked = listView.View == View.LargeIcon;
|
||||||
|
toolStripMenuItem_ListView.Checked = listView.View == View.List;
|
||||||
|
toolStripMenuItem_DetailsView.Checked = listView.View == View.Details;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void contextMenuStrip_Closed(object sender, ToolStripDropDownClosedEventArgs e)
|
||||||
|
{
|
||||||
|
// 不显示菜单的时候需要把菜单的各项功能启用, 这样才能正常捕获快捷键
|
||||||
|
foreach (var item in contextMenuStrip.Items)
|
||||||
|
if (item is ToolStripMenuItem tsmi)
|
||||||
|
tsmi.Enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_Add_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Insert();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_BatchAdd_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
BatchAdd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_Insert_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (listView.SelectedIndices.Count == 1)
|
||||||
|
Insert(listView.SelectedIndices[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_Remove_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (listView.SelectedIndices.Count <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (listView.SelectedIndices.Count > 1)
|
||||||
|
{
|
||||||
|
if (MessageBox.Quest($"确定移除所选 {listView.SelectedIndices.Count} 项吗?") != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var i in listView.SelectedIndices.Cast<int>().OrderByDescending(x => x))
|
||||||
|
{
|
||||||
|
listView.Items.RemoveAt(i);
|
||||||
|
lock (Spines)
|
||||||
|
{
|
||||||
|
var spine = spines[i];
|
||||||
|
spines.RemoveAt(i);
|
||||||
|
listView.SmallImageList.Images.RemoveByKey(spine.ID);
|
||||||
|
listView.LargeImageList.Images.RemoveByKey(spine.ID);
|
||||||
|
spine.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_MoveTop_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (listView.SelectedIndices.Count != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var index = listView.SelectedIndices[0];
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
lock (Spines)
|
||||||
|
{
|
||||||
|
var spine = spines[index];
|
||||||
|
spines.RemoveAt(index);
|
||||||
|
spines.Insert(0, spine);
|
||||||
|
}
|
||||||
|
var item = listView.Items[index];
|
||||||
|
listView.Items.RemoveAt(index);
|
||||||
|
listView.Items.Insert(0, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_MoveUp_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (listView.SelectedIndices.Count != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var index = listView.SelectedIndices[0];
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
lock (Spines) { (spines[index - 1], spines[index]) = (spines[index], spines[index - 1]); }
|
||||||
|
var item = listView.Items[index];
|
||||||
|
listView.BeginUpdate();
|
||||||
|
listView.Items.RemoveAt(index);
|
||||||
|
listView.Items.Insert(index - 1, item);
|
||||||
|
listView.EndUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_MoveDown_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (listView.SelectedIndices.Count != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var index = listView.SelectedIndices[0];
|
||||||
|
if (index < listView.Items.Count - 1)
|
||||||
|
{
|
||||||
|
lock (Spines) { (spines[index], spines[index + 1]) = (spines[index + 1], spines[index]); }
|
||||||
|
var item = listView.Items[index];
|
||||||
|
listView.BeginUpdate();
|
||||||
|
listView.Items.RemoveAt(index);
|
||||||
|
listView.Items.Insert(index + 1, item);
|
||||||
|
listView.EndUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_MoveBottom_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (listView.SelectedIndices.Count != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var index = listView.SelectedIndices[0];
|
||||||
|
if (index < listView.Items.Count - 1)
|
||||||
|
{
|
||||||
|
lock (Spines)
|
||||||
|
{
|
||||||
|
var spine = spines[index];
|
||||||
|
spines.RemoveAt(index);
|
||||||
|
spines.Add(spine);
|
||||||
|
}
|
||||||
|
var item = listView.Items[index];
|
||||||
|
listView.Items.RemoveAt(index);
|
||||||
|
listView.Items.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_RemoveAll_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (listView.Items.Count <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (MessageBox.Quest($"确认移除所有 {listView.Items.Count} 项吗?") != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
listView.Items.Clear();
|
||||||
|
lock (Spines)
|
||||||
|
{
|
||||||
|
foreach (var spine in spines) spine.Dispose();
|
||||||
|
spines.Clear();
|
||||||
|
listView.SmallImageList.Images.Clear();
|
||||||
|
listView.LargeImageList.Images.Clear();
|
||||||
|
}
|
||||||
|
if (PropertyGrid is not null)
|
||||||
|
PropertyGrid.SelectedObject = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_CopyPreview_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var fileDropList = new StringCollection();
|
||||||
|
|
||||||
|
lock (Spines)
|
||||||
|
{
|
||||||
|
foreach (int i in listView.SelectedIndices)
|
||||||
|
{
|
||||||
|
var spine = spines[i];
|
||||||
|
var image = spine.Preview;
|
||||||
|
var path = Path.Combine(Program.TempDir, $"{spine.ID}.png");
|
||||||
|
using (var clone = new Bitmap(image))
|
||||||
|
clone.Save(path);
|
||||||
|
fileDropList.Add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fileDropList.Count > 0)
|
||||||
|
Clipboard.SetFileDropList(fileDropList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_AddFromClipboard_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (Clipboard.ContainsFileDropList())
|
||||||
|
{
|
||||||
|
var fileDropList = Clipboard.GetFileDropList();
|
||||||
|
var paths = new string[fileDropList.Count];
|
||||||
|
fileDropList.CopyTo(paths, 0);
|
||||||
|
AddFromFileDrop(paths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_SelectAll_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
listView.BeginUpdate();
|
||||||
|
foreach (ListViewItem item in listView.Items)
|
||||||
|
item.Selected = true;
|
||||||
|
listView.EndUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_LargeIconView_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
listView.View = View.LargeIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_ListView_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
listView.View = View.List;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_DetailsView_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
listView.View = View.Details;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,10 @@ namespace SpineViewer.Controls
|
|||||||
public partial class SpinePreviewer : UserControl
|
public partial class SpinePreviewer : UserControl
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包装类, 用于 PropertyGrid 显示
|
/// 包装类, 用于属性面板显示
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private class PreviewerProperty
|
private class PreviewerProperty(SpinePreviewer previewer)
|
||||||
{
|
{
|
||||||
private readonly SpinePreviewer previewer;
|
|
||||||
|
|
||||||
public PreviewerProperty(SpinePreviewer previewer) { this.previewer = previewer; }
|
|
||||||
|
|
||||||
[TypeConverter(typeof(SizeConverter))]
|
[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; }
|
||||||
@@ -44,6 +40,9 @@ namespace SpineViewer.Controls
|
|||||||
[Category("导出"), DisplayName("垂直翻转")]
|
[Category("导出"), DisplayName("垂直翻转")]
|
||||||
public bool FlipY { get => previewer.FlipY; set => previewer.FlipY = value; }
|
public bool FlipY { get => previewer.FlipY; set => previewer.FlipY = value; }
|
||||||
|
|
||||||
|
[Category("导出"), DisplayName("仅渲染选中")]
|
||||||
|
public bool RenderSelectedOnly { get => previewer.RenderSelectedOnly; set => previewer.RenderSelectedOnly = value; }
|
||||||
|
|
||||||
[Category("预览"), DisplayName("显示坐标轴")]
|
[Category("预览"), DisplayName("显示坐标轴")]
|
||||||
public bool ShowAxis { get => previewer.ShowAxis; set => previewer.ShowAxis = value; }
|
public bool ShowAxis { get => previewer.ShowAxis; set => previewer.ShowAxis = value; }
|
||||||
|
|
||||||
@@ -51,9 +50,15 @@ namespace SpineViewer.Controls
|
|||||||
public uint MaxFps { get => previewer.MaxFps; set => previewer.MaxFps = value; }
|
public uint MaxFps { get => previewer.MaxFps; set => previewer.MaxFps = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 要绑定的 Spine 列表控件
|
||||||
|
/// </summary>
|
||||||
[Category("自定义"), Description("相关联的 SpineListView")]
|
[Category("自定义"), Description("相关联的 SpineListView")]
|
||||||
public SpineListView? SpineListView { get; set; }
|
public SpineListView? SpineListView { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 属性信息面板
|
||||||
|
/// </summary>
|
||||||
[Category("自定义"), Description("用于显示画面属性的属性页")]
|
[Category("自定义"), Description("用于显示画面属性的属性页")]
|
||||||
public PropertyGrid? PropertyGrid
|
public PropertyGrid? PropertyGrid
|
||||||
{
|
{
|
||||||
@@ -67,21 +72,49 @@ namespace SpineViewer.Controls
|
|||||||
}
|
}
|
||||||
private PropertyGrid? propertyGrid;
|
private PropertyGrid? propertyGrid;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 画面缩放最大值
|
||||||
|
/// </summary>
|
||||||
public const float ZOOM_MAX = 1000f;
|
public const float ZOOM_MAX = 1000f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 画面缩放最小值
|
||||||
|
/// </summary>
|
||||||
public const float ZOOM_MIN = 0.001f;
|
public const float ZOOM_MIN = 0.001f;
|
||||||
public const int BACKGROUND_CELL_SIZE = 10;
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预览画面背景色
|
||||||
|
/// </summary>
|
||||||
private static readonly SFML.Graphics.Color BackgroundColor = new(105, 105, 105);
|
private static readonly SFML.Graphics.Color BackgroundColor = new(105, 105, 105);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预览画面坐标轴颜色
|
||||||
|
/// </summary>
|
||||||
private static readonly SFML.Graphics.Color AxisColor = new(220, 220, 220);
|
private static readonly SFML.Graphics.Color AxisColor = new(220, 220, 220);
|
||||||
private static readonly SFML.Graphics.Color BoundsColor = new(120, 200, 0);
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 坐标轴顶点缓冲区
|
||||||
|
/// </summary>
|
||||||
private readonly SFML.Graphics.VertexArray AxisVertex = new(SFML.Graphics.PrimitiveType.Lines, 2);
|
private readonly SFML.Graphics.VertexArray AxisVertex = new(SFML.Graphics.PrimitiveType.Lines, 2);
|
||||||
private readonly SFML.Graphics.VertexArray BoundsRect = new(SFML.Graphics.PrimitiveType.LineStrip, 5);
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 渲染窗口
|
||||||
|
/// </summary>
|
||||||
private readonly SFML.Graphics.RenderWindow RenderWindow;
|
private readonly SFML.Graphics.RenderWindow RenderWindow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 帧间隔计时器
|
||||||
|
/// </summary>
|
||||||
private readonly SFML.System.Clock Clock = new();
|
private readonly SFML.System.Clock Clock = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 画面拖放对象世界坐标源点
|
||||||
|
/// </summary>
|
||||||
private SFML.System.Vector2f? draggingSrc = null;
|
private SFML.System.Vector2f? draggingSrc = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 渲染任务
|
||||||
|
/// </summary>
|
||||||
private Task? task = null;
|
private Task? task = null;
|
||||||
private CancellationTokenSource? cancelToken = null;
|
private CancellationTokenSource? cancelToken = null;
|
||||||
|
|
||||||
@@ -225,6 +258,13 @@ namespace SpineViewer.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 仅渲染选中
|
||||||
|
/// </summary>
|
||||||
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||||
|
[Browsable(false)]
|
||||||
|
public bool RenderSelectedOnly { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 显示坐标轴
|
/// 显示坐标轴
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -240,13 +280,6 @@ namespace SpineViewer.Controls
|
|||||||
public uint MaxFps { get => maxFps; set { RenderWindow.SetFramerateLimit(value); maxFps = value; } }
|
public uint MaxFps { get => maxFps; set { RenderWindow.SetFramerateLimit(value); maxFps = value; } }
|
||||||
private uint maxFps = 60;
|
private uint maxFps = 60;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// RenderWindow.View
|
|
||||||
/// </summary>
|
|
||||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
||||||
[Browsable(false)]
|
|
||||||
public SFML.Graphics.View View { get => RenderWindow.GetView(); }
|
|
||||||
|
|
||||||
public SpinePreviewer()
|
public SpinePreviewer()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -260,6 +293,11 @@ namespace SpineViewer.Controls
|
|||||||
MaxFps = 30;
|
MaxFps = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预览画面帧参数
|
||||||
|
/// </summary>
|
||||||
|
public SpinePreviewerFrameArgs GetFrameArgs() => new(Resolution, RenderWindow.GetView(), RenderSelectedOnly);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开始预览
|
/// 开始预览
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -284,6 +322,67 @@ namespace SpineViewer.Controls
|
|||||||
task = null;
|
task = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 渲染任务
|
||||||
|
/// </summary>
|
||||||
|
private void RenderTask()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RenderWindow.SetActive(true);
|
||||||
|
|
||||||
|
float delta;
|
||||||
|
while (cancelToken is not null && !cancelToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
delta = Clock.ElapsedTime.AsSeconds();
|
||||||
|
Clock.Restart();
|
||||||
|
|
||||||
|
RenderWindow.Clear(BackgroundColor);
|
||||||
|
|
||||||
|
if (ShowAxis)
|
||||||
|
{
|
||||||
|
// 画一个很长的坐标轴, 用 1e9 比较合适
|
||||||
|
AxisVertex[0] = new(new(-1e9f, 0), AxisColor);
|
||||||
|
AxisVertex[1] = new(new(1e9f, 0), AxisColor);
|
||||||
|
RenderWindow.Draw(AxisVertex);
|
||||||
|
AxisVertex[0] = new(new(0, -1e9f), AxisColor);
|
||||||
|
AxisVertex[1] = new(new(0, 1e9f), AxisColor);
|
||||||
|
RenderWindow.Draw(AxisVertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染 Spine
|
||||||
|
if (SpineListView is not null)
|
||||||
|
{
|
||||||
|
lock (SpineListView.Spines)
|
||||||
|
{
|
||||||
|
var spines = SpineListView.Spines;
|
||||||
|
for (int i = spines.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (cancelToken is not null && cancelToken.IsCancellationRequested)
|
||||||
|
break; // 提前中止
|
||||||
|
|
||||||
|
var spine = spines[i];
|
||||||
|
spine.Update(delta);
|
||||||
|
|
||||||
|
if (RenderSelectedOnly && !spine.IsSelected)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
spine.IsDebug = true;
|
||||||
|
RenderWindow.Draw(spine);
|
||||||
|
spine.IsDebug = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderWindow.Display();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
RenderWindow.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SpinePreviewer_SizeChanged(object sender, EventArgs e)
|
private void SpinePreviewer_SizeChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (RenderWindow is null)
|
if (RenderWindow is null)
|
||||||
@@ -326,20 +425,37 @@ namespace SpineViewer.Controls
|
|||||||
draggingSrc = RenderWindow.MapPixelToCoords(new(e.X, e.Y));
|
draggingSrc = RenderWindow.MapPixelToCoords(new(e.X, e.Y));
|
||||||
var src = new PointF(((SFML.System.Vector2f)draggingSrc).X, ((SFML.System.Vector2f)draggingSrc).Y);
|
var src = new PointF(((SFML.System.Vector2f)draggingSrc).X, ((SFML.System.Vector2f)draggingSrc).Y);
|
||||||
|
|
||||||
if (SpineListView is not null)
|
if (SpineListView is null)
|
||||||
{
|
return;
|
||||||
|
|
||||||
lock (SpineListView.Spines)
|
lock (SpineListView.Spines)
|
||||||
{
|
{
|
||||||
var spines = SpineListView.Spines;
|
var spines = SpineListView.Spines;
|
||||||
|
|
||||||
|
// 仅渲染选中模式禁止在画面里选择对象
|
||||||
|
if (RenderSelectedOnly)
|
||||||
|
{
|
||||||
|
bool hit = false;
|
||||||
|
foreach (int i in SpineListView.SelectedIndices)
|
||||||
|
{
|
||||||
|
if (!spines[i].Bounds.Contains(src)) continue;
|
||||||
|
hit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没点到被选中的模型, 则不允许拖动
|
||||||
|
if (!hit) draggingSrc = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// 没有按下 Ctrl 键就只选中点击的那个, 所以先清空选中列表
|
// 没有按下 Ctrl 键就只选中点击的那个, 所以先清空选中列表
|
||||||
if ((ModifierKeys & Keys.Control) == 0)
|
if ((ModifierKeys & Keys.Control) == 0)
|
||||||
{
|
{
|
||||||
bool hit = false;
|
bool hit = false;
|
||||||
for (int i = 0; i < spines.Count; i++)
|
for (int i = 0; i < spines.Count; i++)
|
||||||
{
|
{
|
||||||
if (spines[i].Bounds.Contains(src))
|
if (!spines[i].Bounds.Contains(src)) continue;
|
||||||
{
|
|
||||||
hit = true;
|
hit = true;
|
||||||
|
|
||||||
// 如果点到了没被选中的东西, 则清空原先选中的, 改为只选中这一次点的
|
// 如果点到了没被选中的东西, 则清空原先选中的, 改为只选中这一次点的
|
||||||
@@ -350,18 +466,17 @@ namespace SpineViewer.Controls
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 如果点了空白的地方, 就清空选中列表
|
// 如果点了空白的地方, 就清空选中列表
|
||||||
if (!hit)
|
if (!hit) SpineListView.SelectedIndices.Clear();
|
||||||
SpineListView.SelectedIndices.Clear();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int i = 0; i < spines.Count; i++)
|
for (int i = 0; i < spines.Count; i++)
|
||||||
{
|
{
|
||||||
if (spines[i].Bounds.Contains(src))
|
if (!spines[i].Bounds.Contains(src))
|
||||||
{
|
continue;
|
||||||
|
|
||||||
SpineListView.SelectedIndices.Add(i);
|
SpineListView.SelectedIndices.Add(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -370,7 +485,6 @@ namespace SpineViewer.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void panel_MouseMove(object sender, MouseEventArgs e)
|
private void panel_MouseMove(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -392,11 +506,8 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
lock (SpineListView.Spines)
|
lock (SpineListView.Spines)
|
||||||
{
|
{
|
||||||
foreach (var spine in SpineListView.Spines)
|
foreach (int i in SpineListView.SelectedIndices)
|
||||||
{
|
SpineListView.Spines[i].Position += delta;
|
||||||
if (spine.IsSelected)
|
|
||||||
spine.Position += delta;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
draggingSrc = dst;
|
draggingSrc = dst;
|
||||||
@@ -427,63 +538,27 @@ namespace SpineViewer.Controls
|
|||||||
Zoom *= (e.Delta > 0 ? 1.1f : 0.9f);
|
Zoom *= (e.Delta > 0 ? 1.1f : 0.9f);
|
||||||
PropertyGrid?.Refresh();
|
PropertyGrid?.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderTask()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RenderWindow.SetActive(true);
|
|
||||||
|
|
||||||
float delta;
|
|
||||||
while (cancelToken is not null && !cancelToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
delta = Clock.ElapsedTime.AsSeconds();
|
|
||||||
Clock.Restart();
|
|
||||||
|
|
||||||
RenderWindow.Clear(BackgroundColor);
|
|
||||||
|
|
||||||
if (ShowAxis)
|
|
||||||
{
|
|
||||||
// 画一个很长的坐标轴, 用 1e9 比较合适
|
|
||||||
AxisVertex[0] = new(new(-1e9f, 0), AxisColor);
|
|
||||||
AxisVertex[1] = new(new(1e9f, 0), AxisColor);
|
|
||||||
RenderWindow.Draw(AxisVertex);
|
|
||||||
AxisVertex[0] = new(new(0, -1e9f), AxisColor);
|
|
||||||
AxisVertex[1] = new(new(0, 1e9f), AxisColor);
|
|
||||||
RenderWindow.Draw(AxisVertex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染 Spine
|
/// <summary>
|
||||||
if (SpineListView is not null)
|
/// 预览画面帧参数
|
||||||
|
/// </summary>
|
||||||
|
public class SpinePreviewerFrameArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly)
|
||||||
{
|
{
|
||||||
lock (SpineListView.Spines)
|
/// <summary>
|
||||||
{
|
/// 分辨率
|
||||||
foreach (var spine in SpineListView.Spines.Reverse())
|
/// </summary>
|
||||||
{
|
public Size Resolution => resolution;
|
||||||
spine.Update(delta);
|
|
||||||
RenderWindow.Draw(spine);
|
|
||||||
|
|
||||||
if (spine.IsSelected)
|
/// <summary>
|
||||||
{
|
/// 渲染视窗
|
||||||
var bounds = spine.Bounds;
|
/// </summary>
|
||||||
BoundsRect[0] = BoundsRect[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
public SFML.Graphics.View View => view;
|
||||||
BoundsRect[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
|
||||||
BoundsRect[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
|
||||||
BoundsRect[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
|
||||||
RenderWindow.Draw(BoundsRect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderWindow.Display();
|
/// <summary>
|
||||||
}
|
/// 是否仅渲染/导出选中骨骼
|
||||||
}
|
/// </summary>
|
||||||
finally
|
public bool RenderSelectedOnly => renderSelectedOnly;
|
||||||
{
|
|
||||||
RenderWindow.SetActive(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,12 @@ namespace SpineViewer.Dialogs
|
|||||||
public AboutDialog()
|
public AboutDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
this.label_Version.Text = $"v{InformationalVersion}";
|
Text = $"关于 {Program.Name}";
|
||||||
|
label_Version.Text = $"v{InformationalVersion}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public string InformationalVersion
|
public string InformationalVersion =>
|
||||||
{
|
Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
get
|
|
||||||
{
|
|
||||||
return Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void linkLabel_RepoUrl_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
private void linkLabel_RepoUrl_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -36,7 +32,7 @@ namespace SpineViewer.Dialogs
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Clipboard.SetText(url);
|
Clipboard.SetText(url);
|
||||||
MessageBox.Show(this, "链接已复制到剪贴板,请前往浏览器进行访问", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("链接已复制到剪贴板,请前往浏览器进行访问");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,16 +13,18 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class BatchOpenSpineDialog : Form
|
public partial class BatchOpenSpineDialog : Form
|
||||||
{
|
{
|
||||||
public string[] SkelPaths { get; private set; }
|
/// <summary>
|
||||||
public Spine.Version Version { get; private set; }
|
/// 对话框结果, 取消时为 null
|
||||||
|
/// </summary>
|
||||||
|
public BatchOpenSpineDialogResult Result { get; private set; }
|
||||||
|
|
||||||
public BatchOpenSpineDialog()
|
public BatchOpenSpineDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
comboBox_Version.DataSource = VersionHelper.Versions.ToList();
|
comboBox_Version.DataSource = VersionHelper.Names.ToList();
|
||||||
comboBox_Version.DisplayMember = "Value";
|
comboBox_Version.DisplayMember = "Value";
|
||||||
comboBox_Version.ValueMember = "Key";
|
comboBox_Version.ValueMember = "Key";
|
||||||
comboBox_Version.SelectedValue = Spine.Version.V38;
|
comboBox_Version.SelectedValue = Spine.Version.Auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BatchOpenSpineDialog_Load(object sender, EventArgs e)
|
private void BatchOpenSpineDialog_Load(object sender, EventArgs e)
|
||||||
@@ -47,7 +49,7 @@ namespace SpineViewer.Dialogs
|
|||||||
|
|
||||||
if (listBox_FilePath.Items.Count <= 0)
|
if (listBox_FilePath.Items.Count <= 0)
|
||||||
{
|
{
|
||||||
MessageBox.Show("未选择任何文件", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("未选择任何文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,20 +57,18 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
if (!File.Exists(p))
|
if (!File.Exists(p))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{p}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{p}", "skel文件不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Spine.Spine.ImplementedVersions.Contains(version))
|
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{version.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{version.GetName()} 版本尚未实现(咕咕咕~)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SkelPaths = listBox_FilePath.Items.Cast<string>().ToArray();
|
Result = new(version, listBox_FilePath.Items.Cast<string>().ToArray());
|
||||||
Version = version;
|
|
||||||
|
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,4 +77,20 @@ namespace SpineViewer.Dialogs
|
|||||||
DialogResult = DialogResult.Cancel;
|
DialogResult = DialogResult.Cancel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量打开对话框结果
|
||||||
|
/// </summary>
|
||||||
|
public class BatchOpenSpineDialogResult(Spine.Version version, string[] skelPaths)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 版本
|
||||||
|
/// </summary>
|
||||||
|
public Spine.Version Version => version;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 路径列表
|
||||||
|
/// </summary>
|
||||||
|
public string[] SkelPaths => skelPaths;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class ConvertFileFormatDialog : Form
|
public partial class ConvertFileFormatDialog : Form
|
||||||
{
|
{
|
||||||
|
// TODO: 增加版本转换选项
|
||||||
|
// TODO: 使用结果包装类
|
||||||
public string[] SkelPaths { get; private set; }
|
public string[] SkelPaths { get; private set; }
|
||||||
public Spine.Version SourceVersion { get; private set; }
|
public Spine.Version SourceVersion { get; private set; }
|
||||||
public Spine.Version TargetVersion { get; private set; }
|
public Spine.Version TargetVersion { get; private set; }
|
||||||
@@ -22,7 +24,12 @@ namespace SpineViewer.Dialogs
|
|||||||
public ConvertFileFormatDialog()
|
public ConvertFileFormatDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
comboBox_SourceVersion.DataSource = VersionHelper.Versions.ToList();
|
|
||||||
|
// XXX: 文件格式转换暂时不支持自动检测版本
|
||||||
|
var impVersions = VersionHelper.Names.ToDictionary();
|
||||||
|
impVersions.Remove(Spine.Version.Auto);
|
||||||
|
|
||||||
|
comboBox_SourceVersion.DataSource = impVersions.ToList();
|
||||||
comboBox_SourceVersion.DisplayMember = "Value";
|
comboBox_SourceVersion.DisplayMember = "Value";
|
||||||
comboBox_SourceVersion.ValueMember = "Key";
|
comboBox_SourceVersion.ValueMember = "Key";
|
||||||
comboBox_SourceVersion.SelectedValue = Spine.Version.V38;
|
comboBox_SourceVersion.SelectedValue = Spine.Version.V38;
|
||||||
@@ -57,7 +64,7 @@ namespace SpineViewer.Dialogs
|
|||||||
|
|
||||||
if (listBox_FilePath.Items.Count <= 0)
|
if (listBox_FilePath.Items.Count <= 0)
|
||||||
{
|
{
|
||||||
MessageBox.Show("未选择任何文件", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("未选择任何文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,26 +72,26 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
if (!File.Exists(p))
|
if (!File.Exists(p))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{p}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{p}", "skel文件不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SkeletonConverter.ImplementedVersions.Contains(sourceVersion))
|
if (!SkeletonConverter.ImplementedVersions.Contains(sourceVersion))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{sourceVersion.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{sourceVersion.GetName()} 版本尚未实现(咕咕咕~)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SkeletonConverter.ImplementedVersions.Contains(targetVersion))
|
if (!SkeletonConverter.ImplementedVersions.Contains(targetVersion))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{targetVersion.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{targetVersion.GetName()} 版本尚未实现(咕咕咕~)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jsonSource == jsonTarget && sourceVersion == targetVersion)
|
if (jsonSource == jsonTarget && sourceVersion == targetVersion)
|
||||||
{
|
{
|
||||||
MessageBox.Show($"不需要转换相同的格式和版本", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"不需要转换相同的格式和版本");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,11 +82,11 @@ namespace SpineViewer.Dialogs
|
|||||||
|
|
||||||
private void button_Copy_Click(object sender, EventArgs e)
|
private void button_Copy_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var selectedObject = propertyGrid.SelectedObject as DiagnosticsInformation;
|
var selectedObject = (DiagnosticsInformation)propertyGrid.SelectedObject;
|
||||||
var properties = selectedObject.GetType().GetProperties();
|
var properties = selectedObject.GetType().GetProperties();
|
||||||
var result = string.Join(Environment.NewLine, properties.Select(p => $"{p.Name}\t{p.GetValue(selectedObject)?.ToString()}"));
|
var result = string.Join(Environment.NewLine, properties.Select(p => $"{p.Name}\t{p.GetValue(selectedObject)?.ToString()}"));
|
||||||
Clipboard.SetText(result);
|
Clipboard.SetText(result);
|
||||||
MessageBox.Show(this, "已复制", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("已复制");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class ExportPngDialog : Form
|
public partial class ExportPngDialog : Form
|
||||||
{
|
{
|
||||||
|
// TODO: 该对话框要合并到统一的导出参数对话框
|
||||||
|
// TODO: 使用结果包装类
|
||||||
public string OutputDir { get; private set; }
|
public string OutputDir { get; private set; }
|
||||||
public float Duration { get; private set; }
|
public float Duration { get; private set; }
|
||||||
public uint Fps { get; private set; }
|
public uint Fps { get; private set; }
|
||||||
@@ -40,14 +42,15 @@ namespace SpineViewer.Dialogs
|
|||||||
var outputDir = textBox_OutputDir.Text;
|
var outputDir = textBox_OutputDir.Text;
|
||||||
if (File.Exists(outputDir))
|
if (File.Exists(outputDir))
|
||||||
{
|
{
|
||||||
MessageBox.Show("输出文件夹无效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("输出文件夹无效");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Directory.Exists(outputDir))
|
if (!Directory.Exists(outputDir))
|
||||||
{
|
{
|
||||||
if (MessageBox.Show($"文件夹 {outputDir} 不存在,是否创建?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK)
|
if (MessageBox.Quest($"文件夹 {outputDir} 不存在,是否创建?") != DialogResult.OK)
|
||||||
{
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(outputDir);
|
Directory.CreateDirectory(outputDir);
|
||||||
@@ -55,12 +58,7 @@ namespace SpineViewer.Dialogs
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Program.Logger.Error(ex.ToString());
|
Program.Logger.Error(ex.ToString());
|
||||||
MessageBox.Show(ex.ToString(), "文件夹创建失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Error(ex.ToString(), "文件夹创建失败");
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class ExportPreviewDialog: Form
|
public partial class ExportPreviewDialog: Form
|
||||||
{
|
{
|
||||||
|
// TODO: 用单独的结果包装类
|
||||||
public string OutputDir { get; private set; }
|
public string OutputDir { get; private set; }
|
||||||
public uint PreviewWidth { get; private set; }
|
public uint PreviewWidth { get; private set; }
|
||||||
public uint PreviewHeight { get; private set; }
|
public uint PreviewHeight { get; private set; }
|
||||||
@@ -40,14 +41,15 @@ namespace SpineViewer.Dialogs
|
|||||||
var outputDir = textBox_OutputDir.Text;
|
var outputDir = textBox_OutputDir.Text;
|
||||||
if (File.Exists(outputDir))
|
if (File.Exists(outputDir))
|
||||||
{
|
{
|
||||||
MessageBox.Show("输出文件夹无效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("输出文件夹无效");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Directory.Exists(outputDir))
|
if (!Directory.Exists(outputDir))
|
||||||
{
|
{
|
||||||
if (MessageBox.Show($"文件夹 {outputDir} 不存在,是否创建?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK)
|
if (MessageBox.Quest($"文件夹 {outputDir} 不存在,是否创建?") != DialogResult.OK)
|
||||||
{
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(outputDir);
|
Directory.CreateDirectory(outputDir);
|
||||||
@@ -55,12 +57,7 @@ namespace SpineViewer.Dialogs
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Program.Logger.Error(ex.ToString());
|
Program.Logger.Error(ex.ToString());
|
||||||
MessageBox.Show(ex.ToString(), "文件夹创建失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Error(ex.ToString(), "文件夹创建失败");
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,17 +12,18 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class OpenSpineDialog : Form
|
public partial class OpenSpineDialog : Form
|
||||||
{
|
{
|
||||||
public string SkelPath { get; private set; }
|
/// <summary>
|
||||||
public string? AtlasPath { get; private set; }
|
/// 对话框结果
|
||||||
public Spine.Version Version { get; private set; }
|
/// </summary>
|
||||||
|
public OpenSpineDialogResult Result { get; private set; }
|
||||||
|
|
||||||
public OpenSpineDialog()
|
public OpenSpineDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
comboBox_Version.DataSource = VersionHelper.Versions.ToList();
|
comboBox_Version.DataSource = VersionHelper.Names.ToList();
|
||||||
comboBox_Version.DisplayMember = "Value";
|
comboBox_Version.DisplayMember = "Value";
|
||||||
comboBox_Version.ValueMember = "Key";
|
comboBox_Version.ValueMember = "Key";
|
||||||
comboBox_Version.SelectedValue = Spine.Version.V38;
|
comboBox_Version.SelectedValue = Spine.Version.Auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenSpineDialog_Load(object sender, EventArgs e)
|
private void OpenSpineDialog_Load(object sender, EventArgs e)
|
||||||
@@ -56,7 +57,7 @@ namespace SpineViewer.Dialogs
|
|||||||
|
|
||||||
if (!File.Exists(skelPath))
|
if (!File.Exists(skelPath))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{skelPath}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{skelPath}", "skel文件不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -70,7 +71,7 @@ namespace SpineViewer.Dialogs
|
|||||||
}
|
}
|
||||||
else if (!File.Exists(atlasPath))
|
else if (!File.Exists(atlasPath))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{atlasPath}", "atlas文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{atlasPath}", "atlas文件不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -78,16 +79,13 @@ namespace SpineViewer.Dialogs
|
|||||||
atlasPath = Path.GetFullPath(atlasPath);
|
atlasPath = Path.GetFullPath(atlasPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Spine.Spine.ImplementedVersions.Contains(version))
|
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{version.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{version.GetName()} 版本尚未实现(咕咕咕~)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SkelPath = skelPath;
|
Result = new(version, skelPath, atlasPath);
|
||||||
AtlasPath = atlasPath;
|
|
||||||
Version = version;
|
|
||||||
|
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,4 +94,25 @@ namespace SpineViewer.Dialogs
|
|||||||
DialogResult = DialogResult.Cancel;
|
DialogResult = DialogResult.Cancel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打开骨骼对话框结果
|
||||||
|
/// </summary>
|
||||||
|
public class OpenSpineDialogResult(Spine.Version version, string skelPath, string? atlasPath = null)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 版本
|
||||||
|
/// </summary>
|
||||||
|
public Spine.Version Version => version;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// skel 文件路径
|
||||||
|
/// </summary>
|
||||||
|
public string SkelPath => skelPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// atlas 文件路径
|
||||||
|
/// </summary>
|
||||||
|
public string? AtlasPath => atlasPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,25 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class ProgressDialog : Form
|
public partial class ProgressDialog : Form
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// BackgroundWorker.DoWork 接口暴露
|
||||||
|
/// </summary>
|
||||||
[Category("自定义"), Description("BackgroundWorker 的 DoWork 事件")]
|
[Category("自定义"), Description("BackgroundWorker 的 DoWork 事件")]
|
||||||
public event DoWorkEventHandler? DoWork
|
public event DoWorkEventHandler? DoWork
|
||||||
{
|
{
|
||||||
add { backgroundWorker.DoWork += value; }
|
add => backgroundWorker.DoWork += value;
|
||||||
remove { backgroundWorker.DoWork -= value; }
|
remove => backgroundWorker.DoWork -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunWorkerAsync() { backgroundWorker.RunWorkerAsync(); }
|
/// <summary>
|
||||||
public void RunWorkerAsync(object? argument) { backgroundWorker.RunWorkerAsync(argument); }
|
/// 启动后台执行
|
||||||
|
/// </summary>
|
||||||
|
public void RunWorkerAsync() => backgroundWorker.RunWorkerAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用给定参数启动后台执行
|
||||||
|
/// </summary>
|
||||||
|
public void RunWorkerAsync(object? argument) => backgroundWorker.RunWorkerAsync(argument);
|
||||||
|
|
||||||
public ProgressDialog()
|
public ProgressDialog()
|
||||||
{
|
{
|
||||||
@@ -38,7 +48,7 @@ namespace SpineViewer.Dialogs
|
|||||||
if (e.Error != null)
|
if (e.Error != null)
|
||||||
{
|
{
|
||||||
Program.Logger.Error(e.Error.ToString());
|
Program.Logger.Error(e.Error.ToString());
|
||||||
MessageBox.Show(e.Error.ToString(), "执行出错", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Error(e.Error.ToString(), "执行出错");
|
||||||
DialogResult = DialogResult.Abort;
|
DialogResult = DialogResult.Abort;
|
||||||
}
|
}
|
||||||
else if (e.Cancelled)
|
else if (e.Cancelled)
|
||||||
|
|||||||
44
SpineViewer/MainForm.Designer.cs
generated
44
SpineViewer/MainForm.Designer.cs
generated
@@ -95,7 +95,7 @@
|
|||||||
menuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_File, toolStripMenuItem_Function, toolStripMenuItem_Tool, toolStripMenuItem_Download, 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(1748, 32);
|
||||||
menuStrip.TabIndex = 0;
|
menuStrip.TabIndex = 0;
|
||||||
menuStrip.Text = "菜单";
|
menuStrip.Text = "菜单";
|
||||||
//
|
//
|
||||||
@@ -158,8 +158,8 @@
|
|||||||
//
|
//
|
||||||
toolStripMenuItem_Function.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ResetAnimation });
|
toolStripMenuItem_Function.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ResetAnimation });
|
||||||
toolStripMenuItem_Function.Name = "toolStripMenuItem_Function";
|
toolStripMenuItem_Function.Name = "toolStripMenuItem_Function";
|
||||||
toolStripMenuItem_Function.Size = new Size(84, 28);
|
toolStripMenuItem_Function.Size = new Size(87, 28);
|
||||||
toolStripMenuItem_Function.Text = "功能(&F)";
|
toolStripMenuItem_Function.Text = "功能(&G)";
|
||||||
//
|
//
|
||||||
// toolStripMenuItem_ResetAnimation
|
// toolStripMenuItem_ResetAnimation
|
||||||
//
|
//
|
||||||
@@ -232,7 +232,7 @@
|
|||||||
rtbLog.Margin = new Padding(3, 2, 3, 2);
|
rtbLog.Margin = new Padding(3, 2, 3, 2);
|
||||||
rtbLog.Name = "rtbLog";
|
rtbLog.Name = "rtbLog";
|
||||||
rtbLog.ReadOnly = true;
|
rtbLog.ReadOnly = true;
|
||||||
rtbLog.Size = new Size(1721, 106);
|
rtbLog.Size = new Size(1728, 114);
|
||||||
rtbLog.TabIndex = 0;
|
rtbLog.TabIndex = 0;
|
||||||
rtbLog.Text = "";
|
rtbLog.Text = "";
|
||||||
rtbLog.WordWrap = false;
|
rtbLog.WordWrap = false;
|
||||||
@@ -254,8 +254,8 @@
|
|||||||
//
|
//
|
||||||
splitContainer_MainForm.Panel2.Controls.Add(rtbLog);
|
splitContainer_MainForm.Panel2.Controls.Add(rtbLog);
|
||||||
splitContainer_MainForm.Panel2.Cursor = Cursors.Default;
|
splitContainer_MainForm.Panel2.Cursor = Cursors.Default;
|
||||||
splitContainer_MainForm.Size = new Size(1721, 958);
|
splitContainer_MainForm.Size = new Size(1728, 997);
|
||||||
splitContainer_MainForm.SplitterDistance = 848;
|
splitContainer_MainForm.SplitterDistance = 879;
|
||||||
splitContainer_MainForm.TabIndex = 3;
|
splitContainer_MainForm.TabIndex = 3;
|
||||||
splitContainer_MainForm.TabStop = false;
|
splitContainer_MainForm.TabStop = false;
|
||||||
splitContainer_MainForm.SplitterMoved += splitContainer_SplitterMoved;
|
splitContainer_MainForm.SplitterMoved += splitContainer_SplitterMoved;
|
||||||
@@ -277,8 +277,8 @@
|
|||||||
//
|
//
|
||||||
splitContainer_Functional.Panel2.Controls.Add(groupBox_Preview);
|
splitContainer_Functional.Panel2.Controls.Add(groupBox_Preview);
|
||||||
splitContainer_Functional.Panel2.Cursor = Cursors.Default;
|
splitContainer_Functional.Panel2.Cursor = Cursors.Default;
|
||||||
splitContainer_Functional.Size = new Size(1721, 848);
|
splitContainer_Functional.Size = new Size(1728, 879);
|
||||||
splitContainer_Functional.SplitterDistance = 744;
|
splitContainer_Functional.SplitterDistance = 747;
|
||||||
splitContainer_Functional.TabIndex = 2;
|
splitContainer_Functional.TabIndex = 2;
|
||||||
splitContainer_Functional.TabStop = false;
|
splitContainer_Functional.TabStop = false;
|
||||||
splitContainer_Functional.SplitterMoved += splitContainer_SplitterMoved;
|
splitContainer_Functional.SplitterMoved += splitContainer_SplitterMoved;
|
||||||
@@ -300,8 +300,8 @@
|
|||||||
//
|
//
|
||||||
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(747, 879);
|
||||||
splitContainer_Information.SplitterDistance = 398;
|
splitContainer_Information.SplitterDistance = 399;
|
||||||
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;
|
||||||
@@ -313,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(398, 848);
|
groupBox_SkelList.Size = new Size(399, 879);
|
||||||
groupBox_SkelList.TabIndex = 0;
|
groupBox_SkelList.TabIndex = 0;
|
||||||
groupBox_SkelList.TabStop = false;
|
groupBox_SkelList.TabStop = false;
|
||||||
groupBox_SkelList.Text = "模型列表";
|
groupBox_SkelList.Text = "模型列表";
|
||||||
@@ -324,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(392, 819);
|
spineListView.Size = new Size(393, 850);
|
||||||
spineListView.TabIndex = 0;
|
spineListView.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// propertyGrid_Spine
|
// propertyGrid_Spine
|
||||||
@@ -333,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(336, 470);
|
propertyGrid_Spine.Size = new Size(338, 485);
|
||||||
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;
|
||||||
@@ -355,8 +355,8 @@
|
|||||||
//
|
//
|
||||||
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(342, 848);
|
splitContainer_Config.Size = new Size(344, 879);
|
||||||
splitContainer_Config.SplitterDistance = 499;
|
splitContainer_Config.SplitterDistance = 514;
|
||||||
splitContainer_Config.TabIndex = 0;
|
splitContainer_Config.TabIndex = 0;
|
||||||
splitContainer_Config.TabStop = false;
|
splitContainer_Config.TabStop = false;
|
||||||
splitContainer_Config.SplitterMoved += splitContainer_SplitterMoved;
|
splitContainer_Config.SplitterMoved += splitContainer_SplitterMoved;
|
||||||
@@ -368,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(342, 499);
|
groupBox_SkelConfig.Size = new Size(344, 514);
|
||||||
groupBox_SkelConfig.TabIndex = 0;
|
groupBox_SkelConfig.TabIndex = 0;
|
||||||
groupBox_SkelConfig.TabStop = false;
|
groupBox_SkelConfig.TabStop = false;
|
||||||
groupBox_SkelConfig.Text = "模型参数";
|
groupBox_SkelConfig.Text = "模型参数";
|
||||||
@@ -379,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(342, 345);
|
groupBox_PreviewConfig.Size = new Size(344, 361);
|
||||||
groupBox_PreviewConfig.TabIndex = 1;
|
groupBox_PreviewConfig.TabIndex = 1;
|
||||||
groupBox_PreviewConfig.TabStop = false;
|
groupBox_PreviewConfig.TabStop = false;
|
||||||
groupBox_PreviewConfig.Text = "画面参数";
|
groupBox_PreviewConfig.Text = "画面参数";
|
||||||
@@ -390,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(336, 316);
|
propertyGrid_Previewer.Size = new Size(338, 332);
|
||||||
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;
|
||||||
@@ -401,7 +401,7 @@
|
|||||||
groupBox_Preview.Dock = DockStyle.Fill;
|
groupBox_Preview.Dock = DockStyle.Fill;
|
||||||
groupBox_Preview.Location = new Point(0, 0);
|
groupBox_Preview.Location = new Point(0, 0);
|
||||||
groupBox_Preview.Name = "groupBox_Preview";
|
groupBox_Preview.Name = "groupBox_Preview";
|
||||||
groupBox_Preview.Size = new Size(973, 848);
|
groupBox_Preview.Size = new Size(977, 879);
|
||||||
groupBox_Preview.TabIndex = 1;
|
groupBox_Preview.TabIndex = 1;
|
||||||
groupBox_Preview.TabStop = false;
|
groupBox_Preview.TabStop = false;
|
||||||
groupBox_Preview.Text = "预览画面";
|
groupBox_Preview.Text = "预览画面";
|
||||||
@@ -413,7 +413,7 @@
|
|||||||
spinePreviewer.Location = new Point(3, 26);
|
spinePreviewer.Location = new Point(3, 26);
|
||||||
spinePreviewer.Name = "spinePreviewer";
|
spinePreviewer.Name = "spinePreviewer";
|
||||||
spinePreviewer.PropertyGrid = propertyGrid_Previewer;
|
spinePreviewer.PropertyGrid = propertyGrid_Previewer;
|
||||||
spinePreviewer.Size = new Size(967, 819);
|
spinePreviewer.Size = new Size(971, 850);
|
||||||
spinePreviewer.SpineListView = spineListView;
|
spinePreviewer.SpineListView = spineListView;
|
||||||
spinePreviewer.TabIndex = 0;
|
spinePreviewer.TabIndex = 0;
|
||||||
spinePreviewer.MouseUp += spinePreviewer_MouseUp;
|
spinePreviewer.MouseUp += spinePreviewer_MouseUp;
|
||||||
@@ -425,7 +425,7 @@
|
|||||||
panel_MainForm.Location = new Point(0, 32);
|
panel_MainForm.Location = new Point(0, 32);
|
||||||
panel_MainForm.Name = "panel_MainForm";
|
panel_MainForm.Name = "panel_MainForm";
|
||||||
panel_MainForm.Padding = new Padding(10, 5, 10, 10);
|
panel_MainForm.Padding = new Padding(10, 5, 10, 10);
|
||||||
panel_MainForm.Size = new Size(1741, 973);
|
panel_MainForm.Size = new Size(1748, 1012);
|
||||||
panel_MainForm.TabIndex = 4;
|
panel_MainForm.TabIndex = 4;
|
||||||
//
|
//
|
||||||
// toolTip
|
// toolTip
|
||||||
@@ -436,7 +436,7 @@
|
|||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
ClientSize = new Size(1741, 1005);
|
ClientSize = new Size(1748, 1044);
|
||||||
Controls.Add(panel_MainForm);
|
Controls.Add(panel_MainForm);
|
||||||
Controls.Add(menuStrip);
|
Controls.Add(menuStrip);
|
||||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
using NLog;
|
using FFMpegCore.Pipes;
|
||||||
|
using FFMpegCore;
|
||||||
|
using NLog;
|
||||||
|
using SFML.System;
|
||||||
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;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
namespace SpineViewer
|
namespace SpineViewer
|
||||||
{
|
{
|
||||||
@@ -65,11 +69,12 @@ namespace SpineViewer
|
|||||||
|
|
||||||
private void toolStripMenuItem_Export_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_Export_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
// TODO: 改成统一导出调用
|
||||||
lock (spineListView.Spines)
|
lock (spineListView.Spines)
|
||||||
{
|
{
|
||||||
if (spineListView.Spines.Count <= 0)
|
if (spineListView.Spines.Count <= 0)
|
||||||
{
|
{
|
||||||
MessageBox.Show("请至少打开一个骨骼文件", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("请至少打开一个骨骼文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +91,23 @@ namespace SpineViewer
|
|||||||
|
|
||||||
private void toolStripMenuItem_ExportPreview_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_ExportPreview_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
spineListView.ExportPreviews();
|
lock (spineListView.Spines)
|
||||||
|
{
|
||||||
|
if (spineListView.Spines.Count <= 0)
|
||||||
|
{
|
||||||
|
MessageBox.Info("请至少打开一个骨骼文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var saveDialog = new Dialogs.ExportPreviewDialog();
|
||||||
|
if (saveDialog.ShowDialog() != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var progressDialog = new Dialogs.ProgressDialog();
|
||||||
|
progressDialog.DoWork += ExportPreview_Work;
|
||||||
|
progressDialog.RunWorkerAsync(saveDialog);
|
||||||
|
progressDialog.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toolStripMenuItem_Exit_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_Exit_Click(object sender, EventArgs e)
|
||||||
@@ -115,9 +136,70 @@ namespace SpineViewer
|
|||||||
progressDialog.ShowDialog();
|
progressDialog.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//IEnumerable<IVideoFrame> testExport(int fps)
|
||||||
|
//{
|
||||||
|
// var duration = 2f;
|
||||||
|
// var resolution = spinePreviewer.Resolution;
|
||||||
|
// var delta = 1f / fps;
|
||||||
|
// var frameCount = 1 + (int)(duration / delta); // 零帧开始导出
|
||||||
|
|
||||||
|
// var spinesReverse = spineListView.Spines.Reverse();
|
||||||
|
|
||||||
|
// // 重置动画时间
|
||||||
|
// foreach (var spine in spinesReverse)
|
||||||
|
// spine.CurrentAnimation = spine.CurrentAnimation;
|
||||||
|
|
||||||
|
// // 逐帧导出
|
||||||
|
// var success = 0;
|
||||||
|
// for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
||||||
|
// {
|
||||||
|
// using var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
|
||||||
|
// tex.SetView(spinePreviewer.View);
|
||||||
|
// tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
|
||||||
|
// foreach (var spine in spinesReverse)
|
||||||
|
// {
|
||||||
|
// tex.Draw(spine);
|
||||||
|
// spine.Update(delta);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// tex.Display();
|
||||||
|
// Debug.WriteLine($"ThreadID: {Environment.CurrentManagedThreadId}");
|
||||||
|
// var frame = tex.Texture.CopyToFrame();
|
||||||
|
// tex.Dispose();
|
||||||
|
// yield return frame;
|
||||||
|
|
||||||
|
// success++;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Program.Logger.Info("Exporting done: {}/{}", success, frameCount);
|
||||||
|
//}
|
||||||
|
|
||||||
private void toolStripMenuItem_ManageResource_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_ManageResource_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
//spinePreviewer.StopPreview();
|
||||||
|
|
||||||
|
//lock (spineListView.Spines)
|
||||||
|
//{
|
||||||
|
// //var fps = 24;
|
||||||
|
// ////foreach (var i in testExport(fps))
|
||||||
|
// //// _ = i;
|
||||||
|
// ////var t = testExport(fps).ToArray();
|
||||||
|
// ////var a = testExport(fps).GetEnumerator();
|
||||||
|
// ////while (a.MoveNext());
|
||||||
|
// //var videoFramesSource = new RawVideoPipeSource(testExport(fps)) { FrameRate = fps };
|
||||||
|
// //var outputPath = @"C:\Users\ljh\Desktop\test\a.mov";
|
||||||
|
// //var task = FFMpegArguments
|
||||||
|
// // .FromPipeInput(videoFramesSource)
|
||||||
|
// // .OutputToFile(outputPath, true
|
||||||
|
// // , options => options
|
||||||
|
// // //.WithCustomArgument("-vf \"split[s0][s1];[s0]palettegen=reserve_transparent=1[p];[s1][p]paletteuse=alpha_threshold=128\""))
|
||||||
|
// // .WithCustomArgument("-c:v prores_ks -profile:v 4444 -pix_fmt yuva444p10le"))
|
||||||
|
// // .ProcessAsynchronously();
|
||||||
|
// //task.Wait();
|
||||||
|
//}
|
||||||
|
|
||||||
|
//spinePreviewer.StartPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toolStripMenuItem_About_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_About_Click(object sender, EventArgs e)
|
||||||
@@ -130,13 +212,16 @@ namespace SpineViewer
|
|||||||
(new Dialogs.DiagnosticsDialog()).ShowDialog();
|
(new Dialogs.DiagnosticsDialog()).ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void splitContainer_SplitterMoved(object sender, SplitterEventArgs e) { ActiveControl = null; }
|
private void splitContainer_SplitterMoved(object sender, SplitterEventArgs e) => ActiveControl = null;
|
||||||
|
|
||||||
private void splitContainer_MouseUp(object sender, MouseEventArgs e) { ActiveControl = null; }
|
private void splitContainer_MouseUp(object sender, MouseEventArgs e) => ActiveControl = null;
|
||||||
|
|
||||||
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)
|
private void ExportPng_Work(object? sender, DoWorkEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -147,9 +232,12 @@ namespace SpineViewer
|
|||||||
var fps = arguments.Fps;
|
var fps = arguments.Fps;
|
||||||
var timestamp = DateTime.Now.ToString("yyMMddHHmmss");
|
var timestamp = DateTime.Now.ToString("yyMMddHHmmss");
|
||||||
|
|
||||||
var resolution = spinePreviewer.Resolution;
|
var frameArgs = spinePreviewer.GetFrameArgs();
|
||||||
|
var renderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
||||||
|
|
||||||
|
var resolution = frameArgs.Resolution;
|
||||||
var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
|
var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
|
||||||
tex.SetView(spinePreviewer.View);
|
tex.SetView(frameArgs.View);
|
||||||
var delta = 1f / fps;
|
var delta = 1f / fps;
|
||||||
var frameCount = 1 + (int)(duration / delta); // 零帧开始导出
|
var frameCount = 1 + (int)(duration / delta); // 零帧开始导出
|
||||||
|
|
||||||
@@ -180,6 +268,9 @@ namespace SpineViewer
|
|||||||
|
|
||||||
foreach (var spine in spinesReverse)
|
foreach (var spine in spinesReverse)
|
||||||
{
|
{
|
||||||
|
if (renderSelectedOnly && !spine.IsSelected)
|
||||||
|
continue;
|
||||||
|
|
||||||
tex.Draw(spine);
|
tex.Draw(spine);
|
||||||
spine.Update(delta);
|
spine.Update(delta);
|
||||||
}
|
}
|
||||||
@@ -200,6 +291,77 @@ namespace SpineViewer
|
|||||||
spinePreviewer.StartPreview();
|
spinePreviewer.StartPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
// TODO: 增加填充参数
|
||||||
|
var paddingL = 1u;
|
||||||
|
var paddingR = 1u;
|
||||||
|
var paddingT = 1u;
|
||||||
|
var paddingB = 1u;
|
||||||
|
|
||||||
|
var tex = new SFML.Graphics.RenderTexture(width, height);
|
||||||
|
|
||||||
|
int success = 0;
|
||||||
|
int error = 0;
|
||||||
|
spinePreviewer.StopPreview();
|
||||||
|
lock (spineListView.Spines)
|
||||||
|
{
|
||||||
|
var spines = spineListView.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];
|
||||||
|
var tmp = spine.CurrentAnimation;
|
||||||
|
spine.CurrentAnimation = Spine.Spine.EMPTY_ANIMATION;
|
||||||
|
tex.SetView(spine.GetInitView(width, height, paddingL, paddingR, paddingT, paddingB));
|
||||||
|
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
tex.Draw(spine);
|
||||||
|
tex.Display();
|
||||||
|
spine.CurrentAnimation = tmp;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var img = tex.Texture.CopyToImage())
|
||||||
|
{
|
||||||
|
img.SaveToFile(Path.Combine(outputDir, $"{spine.Name}.png"));
|
||||||
|
}
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spinePreviewer.StartPreview();
|
||||||
|
|
||||||
|
if (error > 0)
|
||||||
|
{
|
||||||
|
Program.Logger.Warn("Preview save {} successfully, {} failed", success, error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Program.Logger.Info("{} preview saved successfully", success);
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.LogCurrentMemoryUsage();
|
||||||
|
}
|
||||||
|
|
||||||
private void ConvertFileFormat_Work(object? sender, DoWorkEventArgs e)
|
private void ConvertFileFormat_Work(object? sender, DoWorkEventArgs e)
|
||||||
{
|
{
|
||||||
var worker = sender as BackgroundWorker;
|
var worker = sender as BackgroundWorker;
|
||||||
|
|||||||
39
SpineViewer/MessageBox.cs
Normal file
39
SpineViewer/MessageBox.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace SpineViewer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 弹窗消息静态类
|
||||||
|
/// </summary>
|
||||||
|
public static class MessageBox
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 提示弹窗
|
||||||
|
/// </summary>
|
||||||
|
public static void Info(string text, string title = "提示信息") =>
|
||||||
|
System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 警告弹窗
|
||||||
|
/// </summary>
|
||||||
|
public static void Warn(string text, string title = "警告信息") =>
|
||||||
|
System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 错误弹窗
|
||||||
|
/// </summary>
|
||||||
|
public static void Error(string text, string title = "错误信息") =>
|
||||||
|
System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作确认弹窗
|
||||||
|
/// </summary>
|
||||||
|
public static DialogResult Quest(string text, string title = "操作确认") =>
|
||||||
|
System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,13 +5,38 @@ namespace SpineViewer
|
|||||||
{
|
{
|
||||||
internal static class Program
|
internal static class Program
|
||||||
{
|
{
|
||||||
public const string Name = "SpineViewer";
|
/// <summary>
|
||||||
|
/// 程序路径
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string FilePath = Environment.ProcessPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序名
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string Name = Path.GetFileNameWithoutExtension(FilePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序目录
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string RootDir = Path.GetDirectoryName(FilePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序临时目录
|
||||||
|
/// </summary>
|
||||||
public static readonly string TempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Name)).FullName;
|
public static readonly string TempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Name)).FullName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序进程
|
||||||
|
/// </summary>
|
||||||
public static readonly Process Process = Process.GetCurrentProcess();
|
public static readonly Process Process = Process.GetCurrentProcess();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序日志器
|
||||||
|
/// </summary>
|
||||||
public static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
public static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main entry point for the application.
|
/// 应用入口点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
static void Main()
|
||||||
@@ -30,7 +55,7 @@ namespace SpineViewer
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Fatal(ex.ToString());
|
Logger.Fatal(ex.ToString());
|
||||||
MessageBox.Show(ex.ToString(), "程序已崩溃", MessageBoxButtons.OK, MessageBoxIcon.Stop);
|
MessageBox.Error(ex.ToString(), "程序已崩溃");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,5 +83,9 @@ namespace SpineViewer
|
|||||||
LogManager.Configuration = config;
|
LogManager.Configuration = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出当前内存使用情况
|
||||||
|
/// </summary>
|
||||||
|
public static void LogCurrentMemoryUsage() => Logger.Info("Current memory usage: {:F2} MB", Process.WorkingSet64 / 1024.0 / 1024.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,13 +8,15 @@ using System.Text.Json;
|
|||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using SpineRuntime38.Attachments;
|
using SpineRuntime38.Attachments;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||||
{
|
{
|
||||||
[SkeletonConverterImplementation(Version.V38)]
|
[SkeletonConverterImplementation(Version.V38)]
|
||||||
class SkeletonConverter38 : SpineViewer.Spine.SkeletonConverter
|
class SkeletonConverter38 : SpineViewer.Spine.SkeletonConverter
|
||||||
{
|
{
|
||||||
private SkeletonReader reader = null;
|
private BinaryReader reader = null;
|
||||||
private JsonObject root = null;
|
private JsonObject root = null;
|
||||||
private bool nonessential = false;
|
private bool nonessential = false;
|
||||||
|
|
||||||
@@ -51,7 +53,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
{
|
{
|
||||||
JsonObject skeleton = [];
|
JsonObject skeleton = [];
|
||||||
skeleton["hash"] = reader.ReadString();
|
skeleton["hash"] = reader.ReadString();
|
||||||
skeleton["spine"] = reader.ReadString();
|
var version = reader.ReadString();
|
||||||
|
if (version == "3.8.75") version = "3.8.76"; // replace 3.8.75 to another version to avoid detection in official runtime
|
||||||
|
skeleton["spine"] = version;
|
||||||
skeleton["x"] = reader.ReadFloat();
|
skeleton["x"] = reader.ReadFloat();
|
||||||
skeleton["y"] = reader.ReadFloat();
|
skeleton["y"] = reader.ReadFloat();
|
||||||
skeleton["width"] = reader.ReadFloat();
|
skeleton["width"] = reader.ReadFloat();
|
||||||
@@ -223,8 +227,8 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
skin["name"] = reader.ReadStringRef();
|
skin["name"] = reader.ReadStringRef();
|
||||||
skin["bones"] = ReadNames(root["bones"].AsArray());
|
skin["bones"] = ReadNames(root["bones"].AsArray());
|
||||||
skin["ik"] = ReadNames(root["ik"].AsArray());
|
skin["ik"] = ReadNames(root["ik"].AsArray());
|
||||||
skin["transform"] = ReadNames(root["transform"].AsArray()); ;
|
skin["transform"] = ReadNames(root["transform"].AsArray());
|
||||||
skin["path"] = ReadNames(root["path"].AsArray()); ;
|
skin["path"] = ReadNames(root["path"].AsArray());
|
||||||
slotCount = reader.ReadVarInt();
|
slotCount = reader.ReadVarInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +336,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
if (nonessential) reader.ReadInt();
|
if (nonessential) reader.ReadInt();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"Invalid attachment type: {type}");
|
throw new ArgumentOutOfRangeException($"Invalid attachment type: {type}");
|
||||||
}
|
}
|
||||||
return attachment;
|
return attachment;
|
||||||
}
|
}
|
||||||
@@ -435,7 +439,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"Invalid slot timeline type: {type}");
|
throw new ArgumentOutOfRangeException($"Invalid slot timeline type: {type}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,7 +519,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"Invalid bone timeline type: {type}");
|
throw new ArgumentOutOfRangeException($"Invalid bone timeline type: {type}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -633,7 +637,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"Invalid path timeline type: {type}");
|
throw new ArgumentOutOfRangeException($"Invalid path timeline type: {type}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -798,11 +802,11 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
obj["c4"] = reader.ReadFloat();
|
obj["c4"] = reader.ReadFloat();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"Invalid curve type: {type}"); ;
|
throw new ArgumentOutOfRangeException($"Invalid curve type: {type}"); ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkeletonWriter writer;
|
private BinaryWriter writer;
|
||||||
private readonly Dictionary<string, int> bone2idx = [];
|
private readonly Dictionary<string, int> bone2idx = [];
|
||||||
private readonly Dictionary<string, int> slot2idx = [];
|
private readonly Dictionary<string, int> slot2idx = [];
|
||||||
private readonly Dictionary<string, int> ik2idx = [];
|
private readonly Dictionary<string, int> ik2idx = [];
|
||||||
@@ -842,7 +846,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
{
|
{
|
||||||
JsonObject skeleton = root["skeleton"].AsObject();
|
JsonObject skeleton = root["skeleton"].AsObject();
|
||||||
writer.WriteString((string)skeleton["hash"]);
|
writer.WriteString((string)skeleton["hash"]);
|
||||||
writer.WriteString((string)skeleton["spine"]);
|
var version = (string)skeleton["spine"];
|
||||||
|
if (version == "3.8.75") version = "3.8.76"; // replace 3.8.75 to another version to avoid detection in official runtime
|
||||||
|
writer.WriteString(version);
|
||||||
if (skeleton.TryGetPropertyValue("x", out var x)) writer.WriteFloat((float)x); else writer.WriteFloat(0);
|
if (skeleton.TryGetPropertyValue("x", out var x)) writer.WriteFloat((float)x); else writer.WriteFloat(0);
|
||||||
if (skeleton.TryGetPropertyValue("y", out var y)) writer.WriteFloat((float)y); else writer.WriteFloat(0);
|
if (skeleton.TryGetPropertyValue("y", out var y)) writer.WriteFloat((float)y); else writer.WriteFloat(0);
|
||||||
if (skeleton.TryGetPropertyValue("width", out var width)) writer.WriteFloat((float)width); else writer.WriteFloat(0);
|
if (skeleton.TryGetPropertyValue("width", out var width)) writer.WriteFloat((float)width); else writer.WriteFloat(0);
|
||||||
@@ -1011,15 +1017,160 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
private void WriteSkins()
|
private void WriteSkins()
|
||||||
{
|
{
|
||||||
if (!root.ContainsKey("skins"))
|
if (!root.ContainsKey("skins"))
|
||||||
|
{
|
||||||
|
writer.WriteVarInt(0); // default 的 slotCount
|
||||||
|
writer.WriteVarInt(0); // 其他皮肤数量
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray skins = root["skins"].AsArray();
|
||||||
|
bool hasDefault = false;
|
||||||
|
foreach (JsonObject skin in skins)
|
||||||
|
{
|
||||||
|
if ((string)skin["name"] == "default")
|
||||||
|
{
|
||||||
|
hasDefault = true;
|
||||||
|
WriteSkin(skin, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasDefault) writer.WriteVarInt(0);
|
||||||
|
|
||||||
|
int skinCount = hasDefault ? skins.Count - 1 : skins.Count;
|
||||||
|
if (skinCount <= 0)
|
||||||
{
|
{
|
||||||
writer.WriteVarInt(0);
|
writer.WriteVarInt(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JsonArray skins = root["skins"].AsArray();
|
|
||||||
writer.WriteVarInt(skins.Count);
|
writer.WriteVarInt(skinCount);
|
||||||
for (int i = 0, n = skins.Count; i < n; i++)
|
foreach (JsonObject skin in skins)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if ((string)skin["name"] != "default")
|
||||||
|
WriteSkin(skin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteSkin(JsonObject skin, bool isDefault = false)
|
||||||
|
{
|
||||||
|
JsonObject skinAttachments = null;
|
||||||
|
if (isDefault)
|
||||||
|
{
|
||||||
|
// 这里固定有一个给 default 的 count 值, 算是占位符, 如果是 0 则表示没有 default 的 skin
|
||||||
|
if (skin.TryGetPropertyValue("attachments", out var attachments)) skinAttachments = attachments.AsObject();
|
||||||
|
writer.WriteVarInt(skinAttachments?.Count ?? 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteStringRef((string)skin["name"]);
|
||||||
|
if (skin.TryGetPropertyValue("bones", out var bones)) WriteNames(bone2idx, bones.AsArray()); else writer.WriteVarInt(0);
|
||||||
|
if (skin.TryGetPropertyValue("ik", out var ik)) WriteNames(ik2idx, ik.AsArray()); else writer.WriteVarInt(0);
|
||||||
|
if (skin.TryGetPropertyValue("transform", out var transform)) WriteNames(transform2idx, transform.AsArray()); else writer.WriteVarInt(0);
|
||||||
|
if (skin.TryGetPropertyValue("path", out var path)) WriteNames(path2idx, path.AsArray()); else writer.WriteVarInt(0);
|
||||||
|
if (skin.TryGetPropertyValue("attachments", out var attachments)) skinAttachments = attachments.AsObject();
|
||||||
|
writer.WriteVarInt(skinAttachments?.Count ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skinAttachments is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var (slotName, _slotAttachments) in skinAttachments)
|
||||||
|
{
|
||||||
|
JsonObject slotAttachments = _slotAttachments.AsObject();
|
||||||
|
writer.WriteVarInt(slot2idx[slotName]);
|
||||||
|
writer.WriteVarInt(slotAttachments.Count);
|
||||||
|
foreach (var (attachmentKey, attachment) in slotAttachments)
|
||||||
|
{
|
||||||
|
writer.WriteStringRef(attachmentKey);
|
||||||
|
WriteAttachment(attachment.AsObject(), attachmentKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteAttachment(JsonObject attachment, string keyName)
|
||||||
|
{
|
||||||
|
int vertexCount;
|
||||||
|
|
||||||
|
string name = keyName;
|
||||||
|
AttachmentType type = AttachmentType.Region;
|
||||||
|
|
||||||
|
if (attachment.TryGetPropertyValue("name", out var _name)) name = (string)_name;
|
||||||
|
if (attachment.TryGetPropertyValue("type", out var _type)) type = Enum.Parse<AttachmentType>((string)_type, true);
|
||||||
|
writer.WriteStringRef(name);
|
||||||
|
writer.WriteByte((byte)type);
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case AttachmentType.Region:
|
||||||
|
if (attachment.TryGetPropertyValue("path", out var path1)) writer.WriteStringRef((string)path1); else writer.WriteStringRef(null);
|
||||||
|
if (attachment.TryGetPropertyValue("rotation", out var rotation1)) writer.WriteFloat((float)rotation1); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("x", out var x1)) writer.WriteFloat((float)x1); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("y", out var y1)) writer.WriteFloat((float)y1); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("scaleX", out var scaleX)) writer.WriteFloat((float)scaleX); else writer.WriteFloat(1);
|
||||||
|
if (attachment.TryGetPropertyValue("scaleY", out var scaleY)) writer.WriteFloat((float)scaleY); else writer.WriteFloat(1);
|
||||||
|
if (attachment.TryGetPropertyValue("width", out var width)) writer.WriteFloat((float)width); else writer.WriteFloat(32);
|
||||||
|
if (attachment.TryGetPropertyValue("height", out var height)) writer.WriteFloat((float)height); else writer.WriteFloat(32);
|
||||||
|
if (attachment.TryGetPropertyValue("color", out var color1)) writer.WriteInt(int.Parse((string)color1, NumberStyles.HexNumber)); else writer.WriteInt(0);
|
||||||
|
break;
|
||||||
|
case AttachmentType.Boundingbox:
|
||||||
|
if (attachment.TryGetPropertyValue("vertexCount", out var _vertexCount1)) vertexCount = (int)_vertexCount1; else vertexCount = 0;
|
||||||
|
writer.WriteVarInt(vertexCount);
|
||||||
|
WriteVertices(attachment["vertices"].AsArray(), vertexCount);
|
||||||
|
if (nonessential) writer.WriteInt(0);
|
||||||
|
break;
|
||||||
|
case AttachmentType.Mesh:
|
||||||
|
if (attachment.TryGetPropertyValue("path", out var path2)) writer.WriteStringRef((string)path2); else writer.WriteStringRef(null);
|
||||||
|
if (attachment.TryGetPropertyValue("color", out var color2)) writer.WriteInt(int.Parse((string)color2, NumberStyles.HexNumber)); else writer.WriteInt(0);
|
||||||
|
if (attachment.TryGetPropertyValue("vertexCount", out var _vertexCount2)) vertexCount = (int)_vertexCount2; else vertexCount = 0;
|
||||||
|
writer.WriteVarInt(vertexCount);
|
||||||
|
WriteFloatArray(attachment["uvs"].AsArray(), vertexCount << 1); // vertexCount = uvs.Length
|
||||||
|
WriteShortArray(attachment["triangles"].AsArray());
|
||||||
|
WriteVertices(attachment["vertices"].AsArray(), vertexCount);
|
||||||
|
if (attachment.TryGetPropertyValue("hull", out var hull)) writer.WriteVarInt((int)hull); else writer.WriteVarInt(0);
|
||||||
|
if (nonessential)
|
||||||
|
{
|
||||||
|
if (attachment.TryGetPropertyValue("edges", out var edges)) WriteShortArray(edges.AsArray()); else writer.WriteVarInt(0);
|
||||||
|
if (attachment.TryGetPropertyValue("width", out var _width)) writer.WriteFloat((float)_width); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("height", out var _height)) writer.WriteFloat((float)_height); else writer.WriteFloat(0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AttachmentType.Linkedmesh:
|
||||||
|
if (attachment.TryGetPropertyValue("path", out var path3)) writer.WriteStringRef((string)path3); else writer.WriteStringRef(null);
|
||||||
|
if (attachment.TryGetPropertyValue("color", out var color3)) writer.WriteInt(int.Parse((string)color3, NumberStyles.HexNumber)); else writer.WriteInt(0);
|
||||||
|
if (attachment.TryGetPropertyValue("skin", out var skin)) writer.WriteStringRef((string)skin); else writer.WriteStringRef(null);
|
||||||
|
if (attachment.TryGetPropertyValue("parent", out var parent)) writer.WriteStringRef((string)parent); else writer.WriteStringRef(null);
|
||||||
|
if (attachment.TryGetPropertyValue("deform", out var deform)) writer.WriteBoolean((bool)deform); else writer.WriteBoolean(true);
|
||||||
|
if (nonessential)
|
||||||
|
{
|
||||||
|
if (attachment.TryGetPropertyValue("width", out var _width)) writer.WriteFloat((float)_width); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("height", out var _height)) writer.WriteFloat((float)_height); else writer.WriteFloat(0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AttachmentType.Path:
|
||||||
|
if (attachment.TryGetPropertyValue("closed", out var closed)) writer.WriteBoolean((bool)closed); else writer.WriteBoolean(false);
|
||||||
|
if (attachment.TryGetPropertyValue("constantSpeed", out var constantSpeed)) writer.WriteBoolean((bool)constantSpeed); else writer.WriteBoolean(true);
|
||||||
|
if (attachment.TryGetPropertyValue("vertexCount", out var _vertexCount3)) vertexCount = (int)_vertexCount3; else vertexCount = 0;
|
||||||
|
writer.WriteVarInt(vertexCount);
|
||||||
|
WriteVertices(attachment["vertices"].AsArray(), vertexCount);
|
||||||
|
WriteFloatArray(attachment["lengths"].AsArray(), vertexCount / 3);
|
||||||
|
if (nonessential) writer.WriteInt(0);
|
||||||
|
break;
|
||||||
|
case AttachmentType.Point:
|
||||||
|
if (attachment.TryGetPropertyValue("rotation", out var rotation2)) writer.WriteFloat((float)rotation2); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("x", out var x2)) writer.WriteFloat((float)x2); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("y", out var y2)) writer.WriteFloat((float)y2); else writer.WriteFloat(0);
|
||||||
|
if (nonessential) writer.WriteInt(0);
|
||||||
|
break;
|
||||||
|
case AttachmentType.Clipping:
|
||||||
|
writer.WriteVarInt(slot2idx[(string)attachment["end"]]);
|
||||||
|
if (attachment.TryGetPropertyValue("vertexCount", out var _vertexCount4)) vertexCount = (int)_vertexCount4; else vertexCount = 0;
|
||||||
|
writer.WriteVarInt(vertexCount);
|
||||||
|
WriteVertices(attachment["vertices"].AsArray(), vertexCount);
|
||||||
|
if (nonessential) writer.WriteInt(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException($"Invalid attachment type: {type}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1072,8 +1223,69 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
private void WriteNames(Dictionary<string, int> name2idx, JsonArray names)
|
private void WriteNames(Dictionary<string, int> name2idx, JsonArray names)
|
||||||
{
|
{
|
||||||
writer.WriteVarInt(names.Count);
|
writer.WriteVarInt(names.Count);
|
||||||
foreach (var name in names)
|
foreach (string name in names)
|
||||||
writer.WriteVarInt(name2idx[(string)name]);
|
writer.WriteVarInt(name2idx[name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteFloatArray(JsonArray array, int n)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
writer.WriteFloat((float)array[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteShortArray(JsonArray array)
|
||||||
|
{
|
||||||
|
writer.WriteVarInt(array.Count);
|
||||||
|
foreach (uint i in array)
|
||||||
|
{
|
||||||
|
writer.WriteByte((byte)(i >> 8));
|
||||||
|
writer.WriteByte((byte)i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteVertices(JsonArray vertices, int vertexCount)
|
||||||
|
{
|
||||||
|
bool hasWeight = vertices.Count != (vertexCount << 1);
|
||||||
|
writer.WriteBoolean(hasWeight);
|
||||||
|
if (!hasWeight)
|
||||||
|
{
|
||||||
|
WriteFloatArray(vertices, vertexCount << 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
for (int i = 0; i < vertexCount; i++)
|
||||||
|
{
|
||||||
|
var bonesCount = (int)vertices[idx++];
|
||||||
|
writer.WriteVarInt(bonesCount);
|
||||||
|
for (int j = 0; j < bonesCount; j++)
|
||||||
|
{
|
||||||
|
writer.WriteVarInt((int)vertices[idx++]);
|
||||||
|
writer.WriteFloat((float)vertices[idx++]);
|
||||||
|
writer.WriteFloat((float)vertices[idx++]);
|
||||||
|
writer.WriteFloat((float)vertices[idx++]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override JsonObject ReadJson(string jsonPath)
|
||||||
|
{
|
||||||
|
// replace 3.8.75 to another version to avoid detection in official runtime
|
||||||
|
var root = base.ReadJson(jsonPath);
|
||||||
|
var skeleton = root["skeleton"].AsObject();
|
||||||
|
var version = (string)skeleton["spine"];
|
||||||
|
if (version == "3.8.75") skeleton["spine"] = "3.8.76";
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteJson(JsonObject root, string jsonPath)
|
||||||
|
{
|
||||||
|
// replace 3.8.75 to another version to avoid detection in official runtime
|
||||||
|
var skeleton = root["skeleton"].AsObject();
|
||||||
|
var version = (string)skeleton["spine"];
|
||||||
|
if (version == "3.8.75") skeleton["spine"] = "3.8.76";
|
||||||
|
base.WriteJson(root, jsonPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override JsonObject ToVersion(JsonObject root, Version version)
|
public override JsonObject ToVersion(JsonObject root, Version version)
|
||||||
@@ -1086,19 +1298,5 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
//public void WriteFloatArray(float[] array)
|
|
||||||
//{
|
|
||||||
// foreach (var i in array)
|
|
||||||
// writer.WriteFloat(i);
|
|
||||||
//}
|
|
||||||
|
|
||||||
//public void WriteShortArray(int[] array)
|
|
||||||
//{
|
|
||||||
// foreach (var i in array)
|
|
||||||
// {
|
|
||||||
// writer.WriteByte((byte)(i >> 8));
|
|
||||||
// writer.WriteByte((byte)i);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// 都不行就报错
|
// 都不行就报错
|
||||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,6 +380,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
//clipping.ClipEnd();
|
//clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// 都不行就报错
|
// 都不行就报错
|
||||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,6 +336,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// 都不行就报错
|
// 都不行就报错
|
||||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,6 +344,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// 都不行就报错
|
// 都不行就报错
|
||||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +347,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// 都不行就报错
|
// 都不行就报错
|
||||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,6 +346,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// 都不行就报错
|
// 都不行就报错
|
||||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,6 +346,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// 都不行就报错
|
// 都不行就报错
|
||||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,6 +346,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,19 +92,19 @@ namespace SpineViewer.Spine
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取 Json 对象
|
/// 读取 Json 对象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public JsonObject ReadJson(string jsonPath)
|
public virtual JsonObject ReadJson(string jsonPath)
|
||||||
{
|
{
|
||||||
using var input = File.OpenRead(jsonPath);
|
using var input = File.OpenRead(jsonPath);
|
||||||
if (JsonNode.Parse(input) is JsonObject root)
|
if (JsonNode.Parse(input) is JsonObject root)
|
||||||
return root;
|
return root;
|
||||||
else
|
else
|
||||||
throw new InvalidOperationException($"{jsonPath} is not a valid json object");
|
throw new InvalidDataException($"{jsonPath} is not a valid json object");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 写入 Json 对象
|
/// 写入 Json 对象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void WriteJson(JsonObject root, string jsonPath)
|
public virtual void WriteJson(JsonObject root, string jsonPath)
|
||||||
{
|
{
|
||||||
using var output = File.Create(jsonPath);
|
using var output = File.Create(jsonPath);
|
||||||
using var writer = new Utf8JsonWriter(output, jsonWriterOptions);
|
using var writer = new Utf8JsonWriter(output, jsonWriterOptions);
|
||||||
@@ -116,14 +116,17 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract JsonObject ToVersion(JsonObject root, Version version);
|
public abstract JsonObject ToVersion(JsonObject root, Version version);
|
||||||
|
|
||||||
protected class SkeletonReader
|
/// <summary>
|
||||||
|
/// 二进制骨骼文件读
|
||||||
|
/// </summary>
|
||||||
|
public class BinaryReader
|
||||||
{
|
{
|
||||||
protected byte[] buffer = new byte[32];
|
protected byte[] buffer = new byte[32];
|
||||||
protected byte[] bytesBigEndian = new byte[8];
|
protected byte[] bytesBigEndian = new byte[8];
|
||||||
public readonly List<string> StringTable = new(32);
|
public readonly List<string> StringTable = new(32);
|
||||||
protected Stream input;
|
protected Stream input;
|
||||||
|
|
||||||
public SkeletonReader(Stream input) { this.input = input; }
|
public BinaryReader(Stream input) { this.input = input; }
|
||||||
public int Read()
|
public int Read()
|
||||||
{
|
{
|
||||||
int val = input.ReadByte();
|
int val = input.ReadByte();
|
||||||
@@ -219,14 +222,17 @@ namespace SpineViewer.Spine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class SkeletonWriter
|
/// <summary>
|
||||||
|
/// 二进制骨骼文件写
|
||||||
|
/// </summary>
|
||||||
|
protected class BinaryWriter
|
||||||
{
|
{
|
||||||
protected byte[] buffer = new byte[32];
|
protected byte[] buffer = new byte[32];
|
||||||
protected byte[] bytesBigEndian = new byte[8];
|
protected byte[] bytesBigEndian = new byte[8];
|
||||||
public readonly List<string> StringTable = new(32);
|
public readonly List<string> StringTable = new(32);
|
||||||
protected Stream output;
|
protected Stream output;
|
||||||
|
|
||||||
public SkeletonWriter(Stream output) { this.output = output; }
|
public BinaryWriter(Stream output) { this.output = output; }
|
||||||
public void Write(int val) => output.WriteByte((byte)val);
|
public void Write(int val) => output.WriteByte((byte)val);
|
||||||
public void WriteByte(byte val) => output.WriteByte(val);
|
public void WriteByte(byte val) => output.WriteByte(val);
|
||||||
public void WriteUByte(byte val) => output.WriteByte(val);
|
public void WriteUByte(byte val) => output.WriteByte(val);
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using SFML.System;
|
|
||||||
using SFML.Window;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
namespace SpineViewer.Spine
|
namespace SpineViewer.Spine
|
||||||
{
|
{
|
||||||
@@ -36,15 +36,25 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Spine : SFML.Graphics.Drawable, IDisposable
|
public abstract class Spine : SFML.Graphics.Drawable, IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 常规骨骼文件后缀集合
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ImmutableHashSet<string> CommonSkelSuffix = [".skel", ".json"];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 空动画标记
|
/// 空动画标记
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string EMPTY_ANIMATION = "<Empty>";
|
public const string EMPTY_ANIMATION = "<Empty>";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 预览图大小
|
/// 预览图宽
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly Size PREVIEW_SIZE = new(256, 256);
|
public const uint PREVIEW_WIDTH = 256;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预览图高
|
||||||
|
/// </summary>
|
||||||
|
public const uint PREVIEW_HEIGHT = 256;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 缩放最小值
|
/// 缩放最小值
|
||||||
@@ -102,15 +112,85 @@ namespace SpineViewer.Spine
|
|||||||
FragmentShader = null;
|
FragmentShader = null;
|
||||||
Program.Logger.Error(ex.ToString());
|
Program.Logger.Error(ex.ToString());
|
||||||
Program.Logger.Error("Failed to load fragment shader");
|
Program.Logger.Error("Failed to load fragment shader");
|
||||||
MessageBox.Show("Fragment shader 加载失败,预乘Alpha通道属性失效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Warn("Fragment shader 加载失败,预乘Alpha通道属性失效");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试检测骨骼文件版本
|
||||||
|
/// </summary>
|
||||||
|
public static Version? GetVersion(string skelPath)
|
||||||
|
{
|
||||||
|
string versionString = null;
|
||||||
|
Version? version = null;
|
||||||
|
using var input = File.OpenRead(skelPath);
|
||||||
|
var reader = new SkeletonConverter.BinaryReader(input);
|
||||||
|
|
||||||
|
// try json format
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (JsonNode.Parse(input) is JsonObject root && root.TryGetPropertyValue("skeleton", out var node) &&
|
||||||
|
node is JsonObject _skeleton && _skeleton.TryGetPropertyValue("spine", out var _version))
|
||||||
|
versionString = (string)_version;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// try v4 binary format
|
||||||
|
if (versionString is null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
input.Position = 0;
|
||||||
|
var hash = reader.ReadLong();
|
||||||
|
var versionPosition = input.Position;
|
||||||
|
var versionByteCount = reader.ReadVarInt();
|
||||||
|
input.Position = versionPosition;
|
||||||
|
if (versionByteCount <= 13)
|
||||||
|
versionString = reader.ReadString();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// try v3 binary format
|
||||||
|
if (versionString is null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
input.Position = 0;
|
||||||
|
var hash = reader.ReadString();
|
||||||
|
versionString = reader.ReadString();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (versionString is not null)
|
||||||
|
{
|
||||||
|
if (versionString.StartsWith("2.1.")) version = Version.V21;
|
||||||
|
else if (versionString.StartsWith("3.6.")) version = Version.V36;
|
||||||
|
else if (versionString.StartsWith("3.7.")) version = Version.V37;
|
||||||
|
else if (versionString.StartsWith("3.8.")) version = Version.V38;
|
||||||
|
else if (versionString.StartsWith("4.0.")) version = Version.V40;
|
||||||
|
else if (versionString.StartsWith("4.1.")) version = Version.V41;
|
||||||
|
else if (versionString.StartsWith("4.2.")) version = Version.V42;
|
||||||
|
else if (versionString.StartsWith("4.3.")) version = Version.V43;
|
||||||
|
else Program.Logger.Error("Unknown verison: {}, {}", versionString, skelPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建特定版本的 Spine
|
/// 创建特定版本的 Spine
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Spine New(Version version, string skelPath, string? atlasPath = null)
|
public static Spine New(Version version, string skelPath, string? atlasPath = null)
|
||||||
{
|
{
|
||||||
|
if (version == Version.Auto)
|
||||||
|
{
|
||||||
|
if (GetVersion(skelPath) is Version detectedVersion)
|
||||||
|
version = detectedVersion;
|
||||||
|
else
|
||||||
|
throw new InvalidDataException($"Auto version detection failed for {skelPath}, try to use a specific version");
|
||||||
|
}
|
||||||
if (!ImplementationTypes.TryGetValue(version, out var spineType))
|
if (!ImplementationTypes.TryGetValue(version, out var spineType))
|
||||||
{
|
{
|
||||||
throw new NotImplementedException($"Not implemented version: {version}");
|
throw new NotImplementedException($"Not implemented version: {version}");
|
||||||
@@ -133,13 +213,14 @@ namespace SpineViewer.Spine
|
|||||||
var attr = type.GetCustomAttribute<SpineImplementationAttribute>();
|
var attr = type.GetCustomAttribute<SpineImplementationAttribute>();
|
||||||
if (attr is null)
|
if (attr is null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Class {type.Name} has no SpineImplementationAttribute.");
|
throw new InvalidOperationException($"Class {type.Name} has no SpineImplementationAttribute");
|
||||||
}
|
}
|
||||||
|
|
||||||
atlasPath ??= Path.ChangeExtension(skelPath, ".atlas");
|
atlasPath ??= Path.ChangeExtension(skelPath, ".atlas");
|
||||||
|
|
||||||
// 设置 Version
|
// 设置 Version
|
||||||
Version = attr.Version;
|
Version = attr.Version;
|
||||||
|
AssetsDir = Directory.GetParent(skelPath).FullName;
|
||||||
SkelPath = Path.GetFullPath(skelPath);
|
SkelPath = Path.GetFullPath(skelPath);
|
||||||
AtlasPath = Path.GetFullPath(atlasPath);
|
AtlasPath = Path.GetFullPath(atlasPath);
|
||||||
Name = Path.GetFileNameWithoutExtension(skelPath);
|
Name = Path.GetFileNameWithoutExtension(skelPath);
|
||||||
@@ -149,6 +230,8 @@ namespace SpineViewer.Spine
|
|||||||
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
|
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
|
||||||
protected virtual void Dispose(bool disposing) { preview?.Dispose(); }
|
protected virtual void Dispose(bool disposing) { preview?.Dispose(); }
|
||||||
|
|
||||||
|
#region 属性 | 基本信息
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取所属版本
|
/// 获取所属版本
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -156,6 +239,12 @@ namespace SpineViewer.Spine
|
|||||||
[Category("基本信息"), DisplayName("运行时版本")]
|
[Category("基本信息"), DisplayName("运行时版本")]
|
||||||
public Version Version { get; }
|
public Version Version { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 资源所在完整目录
|
||||||
|
/// </summary>
|
||||||
|
[Category("基本信息"), DisplayName("资源目录")]
|
||||||
|
public string AssetsDir { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// skel 文件完整路径
|
/// skel 文件完整路径
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -168,6 +257,9 @@ namespace SpineViewer.Spine
|
|||||||
[Category("基本信息"), DisplayName("atlas文件路径")]
|
[Category("基本信息"), DisplayName("atlas文件路径")]
|
||||||
public string AtlasPath { get; }
|
public string AtlasPath { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 名称
|
||||||
|
/// </summary>
|
||||||
[Category("基本信息"), DisplayName("名称")]
|
[Category("基本信息"), DisplayName("名称")]
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
@@ -177,6 +269,10 @@ namespace SpineViewer.Spine
|
|||||||
[Category("基本信息"), DisplayName("文件版本")]
|
[Category("基本信息"), DisplayName("文件版本")]
|
||||||
public abstract string FileVersion { get; }
|
public abstract string FileVersion { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 属性 | 变换
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 缩放比例
|
/// 缩放比例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -202,12 +298,18 @@ namespace SpineViewer.Spine
|
|||||||
[Category("变换"), DisplayName("垂直翻转")]
|
[Category("变换"), DisplayName("垂直翻转")]
|
||||||
public abstract bool FlipY { get; set; }
|
public abstract bool FlipY { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 属性 | 画面
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否使用预乘Alpha
|
/// 是否使用预乘Alpha
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Category("画面"), DisplayName("预乘Alpha通道")]
|
[Category("画面"), DisplayName("预乘Alpha通道")]
|
||||||
public bool UsePremultipliedAlpha { get; set; } = true;
|
public bool UsePremultipliedAlpha { get; set; } = true;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包含的所有动画名称
|
/// 包含的所有动画名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -221,8 +323,10 @@ namespace SpineViewer.Spine
|
|||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public string DefaultAnimationName { get => animationNames.Last(); }
|
public string DefaultAnimationName { get => animationNames.Last(); }
|
||||||
|
|
||||||
|
#region 属性 | 动画
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前动画名称
|
/// 当前动画名称, 如果设置的动画不存在则忽略
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TypeConverter(typeof(AnimationConverter))]
|
[TypeConverter(typeof(AnimationConverter))]
|
||||||
[Category("动画"), DisplayName("当前动画")]
|
[Category("动画"), DisplayName("当前动画")]
|
||||||
@@ -234,6 +338,8 @@ namespace SpineViewer.Spine
|
|||||||
[Category("动画"), DisplayName("当前动画时长")]
|
[Category("动画"), DisplayName("当前动画时长")]
|
||||||
public float CurrentAnimationDuration { get => GetAnimationDuration(CurrentAnimation); }
|
public float CurrentAnimationDuration { get => GetAnimationDuration(CurrentAnimation); }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 骨骼包围盒
|
/// 骨骼包围盒
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -250,7 +356,21 @@ namespace SpineViewer.Spine
|
|||||||
{
|
{
|
||||||
if (preview is null)
|
if (preview is null)
|
||||||
{
|
{
|
||||||
using var img = GetPreview((uint)PREVIEW_SIZE.Width, (uint)PREVIEW_SIZE.Height);
|
// XXX: tex 没办法在这里主动 Dispose
|
||||||
|
// 批量添加在获取预览图的时候极大概率会和预览线程死锁
|
||||||
|
// 虽然两边不会同时调用 Draw, 但是死锁似乎和 Draw 函数有关
|
||||||
|
// 除此之外, 似乎还和 tex 的 Dispose 有关
|
||||||
|
// 如果不对 tex 进行 Dispose, 那么不管是否 Draw 都正常不会死锁
|
||||||
|
var tex = new SFML.Graphics.RenderTexture(PREVIEW_WIDTH, PREVIEW_HEIGHT);
|
||||||
|
tex.SetView(GetInitView(PREVIEW_WIDTH, PREVIEW_HEIGHT));
|
||||||
|
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
var tmp = CurrentAnimation;
|
||||||
|
CurrentAnimation = EMPTY_ANIMATION;
|
||||||
|
tex.Draw(this);
|
||||||
|
CurrentAnimation = tmp;
|
||||||
|
tex.Display();
|
||||||
|
|
||||||
|
using var img = tex.Texture.CopyToImage();
|
||||||
img.SaveToMemory(out var imgBuffer, "bmp");
|
img.SaveToMemory(out var imgBuffer, "bmp");
|
||||||
using var stream = new MemoryStream(imgBuffer);
|
using var stream = new MemoryStream(imgBuffer);
|
||||||
preview = new Bitmap(stream);
|
preview = new Bitmap(stream);
|
||||||
@@ -260,41 +380,6 @@ namespace SpineViewer.Spine
|
|||||||
}
|
}
|
||||||
private Image preview = null;
|
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>
|
||||||
@@ -306,6 +391,89 @@ namespace SpineViewer.Spine
|
|||||||
/// <param name="delta">时间间隔</param>
|
/// <param name="delta">时间间隔</param>
|
||||||
public abstract void Update(float delta);
|
public abstract void Update(float delta);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取初始状态下合适的 View, 参数单位为像素
|
||||||
|
/// </summary>
|
||||||
|
public SFML.Graphics.View GetInitView(Size resolution, Padding padding) =>
|
||||||
|
GetInitView((uint)resolution.Width, (uint)resolution.Height, (uint)padding.Left, (uint)padding.Right, (uint)padding.Top, (uint)padding.Bottom);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取初始状态下合适的 View, 参数单位为像素
|
||||||
|
/// </summary>
|
||||||
|
public SFML.Graphics.View GetInitView(uint width, uint height, Padding padding) =>
|
||||||
|
GetInitView(width, height, (uint)padding.Left, (uint)padding.Right, (uint)padding.Top, (uint)padding.Bottom);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取初始状态下合适的 View, 参数单位为像素
|
||||||
|
/// </summary>
|
||||||
|
public SFML.Graphics.View GetInitView(Size resolution, uint paddingL = 1, uint paddingR = 1, uint paddingT = 1, uint paddingB = 1) =>
|
||||||
|
GetInitView((uint)resolution.Width, (uint)resolution.Height, paddingL, paddingR, paddingT, paddingB);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取初始状态下合适的 View, 参数单位为像素
|
||||||
|
/// </summary>
|
||||||
|
public SFML.Graphics.View GetInitView(uint width, uint height, uint paddingL = 1, uint paddingR = 1, uint paddingT = 1, uint paddingB = 1)
|
||||||
|
{
|
||||||
|
var tmp = CurrentAnimation;
|
||||||
|
CurrentAnimation = EMPTY_ANIMATION;
|
||||||
|
var bounds = Bounds;
|
||||||
|
CurrentAnimation = tmp;
|
||||||
|
|
||||||
|
float sizeX = bounds.Width;
|
||||||
|
float sizeY = bounds.Height;
|
||||||
|
float innerW = width - paddingL - paddingR;
|
||||||
|
float innerH = height - paddingT - paddingB;
|
||||||
|
|
||||||
|
float scale = 1;
|
||||||
|
if ((sizeY / sizeX) < (innerH / innerW))
|
||||||
|
scale = sizeX / innerW; // 相同的 X, 视窗 Y 更大
|
||||||
|
else
|
||||||
|
scale = sizeY / innerH; // 相同的 Y, 视窗 X 更大
|
||||||
|
|
||||||
|
var x = bounds.X + bounds.Width / 2 + ((float)paddingL - (float)paddingR) * scale;
|
||||||
|
var y = bounds.Y + bounds.Height / 2 + ((float)paddingT - (float)paddingB) * scale;
|
||||||
|
var viewX = width * scale;
|
||||||
|
var viewY = height * scale;
|
||||||
|
|
||||||
|
return new(new(x, y), new(viewX, -viewY));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否被选中
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
public bool IsSelected { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 显示调试
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
public bool IsDebug { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包围盒颜色
|
||||||
|
/// </summary>
|
||||||
|
protected static readonly SFML.Graphics.Color BoundsColor = new(120, 200, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包围盒顶点数组
|
||||||
|
/// </summary>
|
||||||
|
protected readonly SFML.Graphics.VertexArray boundsVertices = new(SFML.Graphics.PrimitiveType.LineStrip, 5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 显示包围盒
|
||||||
|
/// </summary>
|
||||||
|
[Category("调试"), DisplayName("显示包围盒")]
|
||||||
|
public bool DebugBounds { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 显示骨骼
|
||||||
|
/// </summary>
|
||||||
|
[Category("调试"), DisplayName("显示骨骼(TODO)")]
|
||||||
|
public bool DebugBones { get; set; } = false;
|
||||||
|
|
||||||
|
#region SFML.Graphics.Drawable 接口实现
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 顶点坐标缓冲区
|
/// 顶点坐标缓冲区
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -314,17 +482,13 @@ namespace SpineViewer.Spine
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 顶点缓冲区
|
/// 顶点缓冲区
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected SFML.Graphics.VertexArray vertexArray = new(SFML.Graphics.PrimitiveType.Triangles);
|
protected readonly SFML.Graphics.VertexArray vertexArray = new(SFML.Graphics.PrimitiveType.Triangles);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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>
|
#endregion
|
||||||
/// 是否被选中
|
|
||||||
/// </summary>
|
|
||||||
[Browsable(false)]
|
|
||||||
public bool IsSelected { get; set; } = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace SpineViewer.Spine
|
|||||||
if (destinationType == typeof(string) && value is Version version)
|
if (destinationType == typeof(string) && value is Version version)
|
||||||
{
|
{
|
||||||
// 调用自定义的 String() 方法
|
// 调用自定义的 String() 方法
|
||||||
return version.String();
|
return version.GetName();
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.ConvertTo(context, culture, value, destinationType);
|
return base.ConvertTo(context, culture, value, destinationType);
|
||||||
@@ -42,7 +42,19 @@ 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)
|
||||||
|
{
|
||||||
return new StandardValuesCollection(obj.AnimationNames);
|
return new StandardValuesCollection(obj.AnimationNames);
|
||||||
|
}
|
||||||
|
else if (context?.Instance is Spine[] spines)
|
||||||
|
{
|
||||||
|
if (spines.Length > 0)
|
||||||
|
{
|
||||||
|
IEnumerable<string> common = spines[0].AnimationNames;
|
||||||
|
foreach (var spine in spines.Skip(1))
|
||||||
|
common = common.Intersect(spine.AnimationNames);
|
||||||
|
return new StandardValuesCollection(common.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
return base.GetStandardValues(context);
|
return base.GetStandardValues(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -8,12 +9,21 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace SpineViewer.Spine
|
namespace SpineViewer.Spine
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Spine 版本静态辅助类
|
||||||
|
/// </summary>
|
||||||
public static class VersionHelper
|
public static class VersionHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 描述缓存
|
/// 版本名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly Dictionary<Version, string> Versions = [];
|
public static readonly ReadOnlyDictionary<Version, string> Names;
|
||||||
|
private static readonly Dictionary<Version, string> names = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runtime 版本字符串
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<Version, string> runtimes = [];
|
||||||
|
|
||||||
static VersionHelper()
|
static VersionHelper()
|
||||||
{
|
{
|
||||||
@@ -22,16 +32,33 @@ namespace SpineViewer.Spine
|
|||||||
{
|
{
|
||||||
var field = typeof(Version).GetField(value.ToString());
|
var field = typeof(Version).GetField(value.ToString());
|
||||||
var attribute = field?.GetCustomAttribute<DescriptionAttribute>();
|
var attribute = field?.GetCustomAttribute<DescriptionAttribute>();
|
||||||
Versions[(Version)value] = attribute?.Description ?? value.ToString();
|
names[(Version)value] = attribute?.Description ?? value.ToString();
|
||||||
}
|
}
|
||||||
|
Names = names.AsReadOnly();
|
||||||
|
|
||||||
|
runtimes[Version.V21] = Assembly.GetAssembly(typeof(SpineRuntime21.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
runtimes[Version.V36] = Assembly.GetAssembly(typeof(SpineRuntime36.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
runtimes[Version.V37] = Assembly.GetAssembly(typeof(SpineRuntime37.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
runtimes[Version.V38] = Assembly.GetAssembly(typeof(SpineRuntime38.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
runtimes[Version.V40] = Assembly.GetAssembly(typeof(SpineRuntime40.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
runtimes[Version.V41] = Assembly.GetAssembly(typeof(SpineRuntime41.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
runtimes[Version.V42] = Assembly.GetAssembly(typeof(SpineRuntime42.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 版本号字符串
|
/// 版本字符串名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string String(this Version version)
|
public static string GetName(this Version version)
|
||||||
{
|
{
|
||||||
return Versions.TryGetValue(version, out var description) ? description : version.ToString();
|
return Names.TryGetValue(version, out var val) ? val : version.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runtime 版本字符串名称
|
||||||
|
/// </summary>
|
||||||
|
public static string GetRuntime(this Version version)
|
||||||
|
{
|
||||||
|
return runtimes.TryGetValue(version, out var val) ? val : GetName(version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +67,7 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum Version
|
public enum Version
|
||||||
{
|
{
|
||||||
|
[Description("<Auto>")] Auto = 0x0000,
|
||||||
[Description("2.1.x")] V21 = 0x0201,
|
[Description("2.1.x")] V21 = 0x0201,
|
||||||
[Description("3.6.x")] V36 = 0x0306,
|
[Description("3.6.x")] V36 = 0x0306,
|
||||||
[Description("3.7.x")] V37 = 0x0307,
|
[Description("3.7.x")] V37 = 0x0307,
|
||||||
|
|||||||
@@ -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.10.2</Version>
|
<Version>0.10.7</Version>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
||||||
<PackageReference Include="NLog.Windows.Forms" Version="5.2.3" />
|
<PackageReference Include="NLog.Windows.Forms" Version="5.2.3" />
|
||||||
<PackageReference Include="SFML.Net" Version="2.6.1" />
|
<PackageReference Include="SFML.Net" Version="2.6.1" />
|
||||||
<PackageReference Include="System.Management" Version="9.0.2" />
|
<PackageReference Include="System.Management" Version="9.0.2" />
|
||||||
|
|||||||
BIN
img/preview.jpg
BIN
img/preview.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 302 KiB |
BIN
img/preview.webp
Normal file
BIN
img/preview.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
Reference in New Issue
Block a user